diff --git a/.changeset/bright-islands-sleep.md b/.changeset/bright-islands-sleep.md new file mode 100644 index 000000000000..4e4e7ffc804c --- /dev/null +++ b/.changeset/bright-islands-sleep.md @@ -0,0 +1,5 @@ +--- +"wrangler": patch +--- + +feat: add telemetry to experimental auto-provisioning diff --git a/packages/wrangler/src/__tests__/provision.test.ts b/packages/wrangler/src/__tests__/provision.test.ts index 85528676f4b9..2ebc1e91cf5e 100644 --- a/packages/wrangler/src/__tests__/provision.test.ts +++ b/packages/wrangler/src/__tests__/provision.test.ts @@ -1,4 +1,5 @@ import { http, HttpResponse } from "msw"; +import { logger } from "../logger"; import { mockAccountId, mockApiToken } from "./helpers/mock-account-id"; import { mockConsoleMethods } from "./helpers/mock-console"; import { clearDialogs, mockPrompt, mockSelect } from "./helpers/mock-dialogs"; @@ -30,6 +31,7 @@ describe("--x-provision", () => { const { setIsTTY } = useMockIsTTY(); beforeEach(() => { + logger.loggerLevel = "debug"; setIsTTY(true); msw.use( ...mswSuccessDeploymentScriptMetadata, @@ -103,6 +105,18 @@ describe("--x-provision", () => { https://test-name.test-sub-domain.workers.dev Current Version ID: Galaxy-Class" `); + + expect(std.debug).toContain( + JSON.stringify( + { + "kv_namespace inherited": 1, + "d1 inherited": 1, + "r2_bucket inherited": 1, + }, + undefined, + 2 + ) + ); expect(std.err).toMatchInlineSnapshot(`""`); expect(std.warn).toMatchInlineSnapshot(`""`); }); @@ -208,6 +222,17 @@ describe("--x-provision", () => { https://test-name.test-sub-domain.workers.dev Current Version ID: Galaxy-Class" `); + expect(std.debug).toContain( + JSON.stringify( + { + "kv_namespace connected": 1, + "d1 connected": 1, + "r2_bucket connected": 1, + }, + undefined, + 2 + ) + ); expect(std.err).toMatchInlineSnapshot(`""`); expect(std.warn).toMatchInlineSnapshot(`""`); }); @@ -327,6 +352,17 @@ describe("--x-provision", () => { https://test-name.test-sub-domain.workers.dev Current Version ID: Galaxy-Class" `); + expect(std.debug).toContain( + JSON.stringify( + { + "kv_namespace connected": 1, + "d1 connected": 1, + "r2_bucket connected": 1, + }, + undefined, + 2 + ) + ); expect(std.err).toMatchInlineSnapshot(`""`); expect(std.warn).toMatchInlineSnapshot(`""`); }); @@ -459,6 +495,17 @@ describe("--x-provision", () => { https://test-name.test-sub-domain.workers.dev Current Version ID: Galaxy-Class" `); + expect(std.debug).toContain( + JSON.stringify( + { + "kv_namespace created": 1, + "d1 created": 1, + "r2_bucket created": 1, + }, + undefined, + 2 + ) + ); expect(std.err).toMatchInlineSnapshot(`""`); expect(std.warn).toMatchInlineSnapshot(`""`); }); @@ -524,6 +571,9 @@ describe("--x-provision", () => { https://test-name.test-sub-domain.workers.dev Current Version ID: Galaxy-Class" `); + expect(std.debug).toContain( + JSON.stringify({ "d1 created": 1 }, undefined, 2) + ); expect(std.err).toMatchInlineSnapshot(`""`); expect(std.warn).toMatchInlineSnapshot(`""`); }); @@ -566,6 +616,9 @@ describe("--x-provision", () => { https://test-name.test-sub-domain.workers.dev Current Version ID: Galaxy-Class" `); + expect(std.debug).toContain( + JSON.stringify({ "d1 inherited": 1 }, undefined, 2) + ); }); it("will not inherit d1 binding when the database name is provided but has changed", async () => { @@ -643,6 +696,9 @@ describe("--x-provision", () => { https://test-name.test-sub-domain.workers.dev Current Version ID: Galaxy-Class" `); + expect(std.debug).toContain( + JSON.stringify({ "d1 created": 1 }, undefined, 2) + ); expect(std.err).toMatchInlineSnapshot(`""`); expect(std.warn).toMatchInlineSnapshot(`""`); }); @@ -716,6 +772,9 @@ describe("--x-provision", () => { https://test-name.test-sub-domain.workers.dev Current Version ID: Galaxy-Class" `); + expect(std.debug).toContain( + JSON.stringify({ "r2_bucket created": 1 }, undefined, 2) + ); expect(std.err).toMatchInlineSnapshot(`""`); expect(std.warn).toMatchInlineSnapshot(`""`); }); @@ -770,6 +829,7 @@ describe("--x-provision", () => { https://test-name.test-sub-domain.workers.dev Current Version ID: Galaxy-Class" `); + expect(std.debug).not.toContain("Provisioning summary"); expect(std.err).toMatchInlineSnapshot(`""`); expect(std.warn).toMatchInlineSnapshot(`""`); }); @@ -814,6 +874,9 @@ describe("--x-provision", () => { https://test-name.test-sub-domain.workers.dev Current Version ID: Galaxy-Class" `); + expect(std.debug).toContain( + JSON.stringify({ "d1 connected": 1 }, undefined, 2) + ); expect(std.err).toMatchInlineSnapshot(`""`); expect(std.warn).toMatchInlineSnapshot(`""`); }); @@ -898,6 +961,9 @@ describe("--x-provision", () => { https://test-name.test-sub-domain.workers.dev Current Version ID: Galaxy-Class" `); + expect(std.debug).toContain( + JSON.stringify({ "r2_bucket created": 1 }, undefined, 2) + ); expect(std.err).toMatchInlineSnapshot(`""`); expect(std.warn).toMatchInlineSnapshot(`""`); }); diff --git a/packages/wrangler/src/deployment-bundle/bindings.ts b/packages/wrangler/src/deployment-bundle/bindings.ts index ddaf785f6e09..7ffa90cd759c 100644 --- a/packages/wrangler/src/deployment-bundle/bindings.ts +++ b/packages/wrangler/src/deployment-bundle/bindings.ts @@ -7,6 +7,7 @@ import { prompt, select } from "../dialogs"; import { UserError } from "../errors"; import { createKVNamespace, listKVNamespaces } from "../kv/helpers"; import { logger } from "../logger"; +import * as metrics from "../metrics"; import { APIError } from "../parse"; import { createR2Bucket, getR2Bucket, listR2Buckets } from "../r2/helpers"; import { isLegacyEnv } from "../utils/isLegacyEnv"; @@ -85,7 +86,9 @@ abstract class ProvisionResourceHandler< public type: T, public binding: B, public idField: keyof B, - public accountId: string + public accountId: string, + /** For telemetry */ + public summary: Record ) {} // Does this resource already exist in the currently deployed version of the Worker? @@ -95,10 +98,16 @@ abstract class ProvisionResourceHandler< ): boolean | Promise; inherit(): void { + this.summary[`${this.type} inherited`] = + (this.summary[`${this.type} inherited`] ?? 0) + 1; // @ts-expect-error idField is a key of this.binding this.binding[this.idField] = INHERIT_SYMBOL; } - connect(id: string): void { + connect(id: string, record: boolean = true): void { + if (record) { + this.summary[`${this.type} connected`] = + (this.summary[`${this.type} connected`] ?? 0) + 1; + } // @ts-expect-error idField is a key of this.binding this.binding[this.idField] = id; } @@ -108,8 +117,10 @@ abstract class ProvisionResourceHandler< abstract get name(): string | undefined; async provision(name: string): Promise { + this.summary[`${this.type} created`] = + (this.summary[`${this.type} created`] ?? 0) + 1; const id = await this.create(name); - this.connect(id); + this.connect(id, false); } // This binding is fully specified and can't/shouldn't be provisioned @@ -164,8 +175,12 @@ class R2Handler extends ProvisionResourceHandler<"r2_bucket", CfR2Bucket> { ); return name; } - constructor(binding: CfR2Bucket, accountId: string) { - super("r2_bucket", binding, "bucket_name", accountId); + constructor( + binding: CfR2Bucket, + accountId: string, + summary: Record + ) { + super("r2_bucket", binding, "bucket_name", accountId, summary); } canInherit(settings: Settings | undefined): boolean { return !!settings?.bindings.find( @@ -212,8 +227,12 @@ class KVHandler extends ProvisionResourceHandler< async create(name: string) { return await createKVNamespace(this.accountId, name); } - constructor(binding: CfKvNamespace, accountId: string) { - super("kv_namespace", binding, "id", accountId); + constructor( + binding: CfKvNamespace, + accountId: string, + summary: Record + ) { + super("kv_namespace", binding, "id", accountId, summary); } canInherit(settings: Settings | undefined): boolean { return !!settings?.bindings.find( @@ -234,8 +253,12 @@ class D1Handler extends ProvisionResourceHandler<"d1", CfD1Database> { const db = await createD1Database(this.accountId, name); return db.uuid; } - constructor(binding: CfD1Database, accountId: string) { - super("d1", binding, "database_id", accountId); + constructor( + binding: CfD1Database, + accountId: string, + summary: Record + ) { + super("d1", binding, "database_id", accountId, summary); } async canInherit(settings: Settings | undefined): Promise { const maybeInherited = settings?.bindings.find( @@ -341,8 +364,13 @@ async function collectPendingResources( accountId: string, scriptName: string, bindings: CfWorkerInit["bindings"] -): Promise { +): Promise<{ + pendingResources: PendingResource[]; + summary: Record; +}> { let settings: Settings | undefined; + // summarises what happens to the resources to send as one telemetry event + const summary: Record = {}; try { settings = await getSettings(accountId, scriptName); @@ -361,7 +389,11 @@ async function collectPendingResources( HANDLERS ) as (keyof typeof HANDLERS)[]) { for (const resource of bindings[resourceType] ?? []) { - const h = new HANDLERS[resourceType].Handler(resource, accountId); + const h = new HANDLERS[resourceType].Handler( + resource, + accountId, + summary + ); if (await h.shouldProvision(settings)) { pendingResources.push({ @@ -373,10 +405,14 @@ async function collectPendingResources( } } - return pendingResources.sort( - (a, b) => HANDLERS[a.resourceType].sort - HANDLERS[b.resourceType].sort - ); + return { + pendingResources: pendingResources.sort( + (a, b) => HANDLERS[a.resourceType].sort - HANDLERS[b.resourceType].sort + ), + summary, + }; } + export async function provisionBindings( bindings: CfWorkerInit["bindings"], accountId: string, @@ -384,7 +420,7 @@ export async function provisionBindings( autoCreate: boolean, config: Config ): Promise { - const pendingResources = await collectPendingResources( + const { pendingResources, summary } = await collectPendingResources( accountId, scriptName, bindings @@ -422,6 +458,12 @@ export async function provisionBindings( logger.log(`🎉 All resources provisioned, continuing with deployment...\n`); } + if (Object.keys(summary).length) { + metrics.sendMetricsEvent("provision resources", summary, { + sendMetrics: config.send_metrics, + }); + logger.debug("Provisioning summary:", JSON.stringify(summary, null, 2)); + } } function getSettings(accountId: string, scriptName: string) { diff --git a/packages/wrangler/src/metrics/send-event.ts b/packages/wrangler/src/metrics/send-event.ts index 47498068b665..473647eaa684 100644 --- a/packages/wrangler/src/metrics/send-event.ts +++ b/packages/wrangler/src/metrics/send-event.ts @@ -81,7 +81,8 @@ export type EventNames = | "list pipelines" | "delete pipeline" | "update pipeline" - | "show pipeline"; + | "show pipeline" + | "provision resources"; /** * Send a metrics event, with no extra properties, to Cloudflare, if usage tracking is enabled.