Skip to content

Commit 34d9d4e

Browse files
feat(dataconnect): Add GA metric for Cloud SQL setup (#9315)
* feat(dataconnect): Add GA metric for Cloud SQL setup Adds a new Google Analytics metric, `dataconnect_cloud_sql`, to track the usage and duration of the `setupCloudSql` function. This metric includes the following labels: - source: init, mcp_init, deploy - action: created, created_failed, updated, updated_failed, get - location - enable_google_ml_integration: true, false - result: error, success - free_trial: true, false - database_version: postgres_17 The implementation also tracks the duration of the `setupCloudSql` operation. To differentiate between `init` and `mcp_init` sources, an `isMcp` flag was added to the `Setup` object. * ready * clean * name * nits --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent 247c718 commit 34d9d4e

File tree

8 files changed

+78
-25
lines changed

8 files changed

+78
-25
lines changed

src/dataconnect/provisionCloudSql.ts

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,85 @@
1-
import * as cloudSqlAdminClient from "../gcp/cloudsql/cloudsqladmin";
2-
import * as utils from "../utils";
31
import * as clc from "colorette";
4-
import { grantRolesToCloudSqlServiceAccount } from "./checkIam";
2+
3+
import * as cloudSqlAdminClient from "../gcp/cloudsql/cloudsqladmin";
54
import { Instance } from "../gcp/cloudsql/types";
6-
import { promiseWithSpinner } from "../utils";
75
import { logger } from "../logger";
8-
import { freeTrialTermsLink, checkFreeTrialInstanceUsed } from "./freeTrial";
6+
import { grantRolesToCloudSqlServiceAccount } from "./checkIam";
7+
import { checkFreeTrialInstanceUsed, freeTrialTermsLink } from "./freeTrial";
8+
import { promiseWithSpinner } from "../utils";
9+
import { trackGA4 } from "../track";
10+
import * as utils from "../utils";
911

1012
const GOOGLE_ML_INTEGRATION_ROLE = "roles/aiplatform.user";
1113

14+
type SetupStats = {
15+
action: "get" | "update" | "create";
16+
databaseVersion?: string;
17+
dataconnectLabel?: cloudSqlAdminClient.DataConnectLabel;
18+
};
19+
1220
/** Sets up a Cloud SQL instance, database and its permissions. */
1321
export async function setupCloudSql(args: {
1422
projectId: string;
1523
location: string;
1624
instanceId: string;
1725
databaseId: string;
1826
requireGoogleMlIntegration: boolean;
27+
source: "init" | "mcp_init" | "deploy";
1928
dryRun?: boolean;
2029
}): Promise<void> {
21-
await upsertInstance({ ...args });
2230
const { projectId, instanceId, requireGoogleMlIntegration, dryRun } = args;
31+
32+
const startTime = Date.now();
33+
const stats: SetupStats = { action: "get" };
34+
let success = false;
35+
try {
36+
await upsertInstance(stats, { ...args });
37+
success = true;
38+
} finally {
39+
if (!dryRun) {
40+
await trackGA4(
41+
"dataconnect_cloud_sql",
42+
{
43+
source: args.source,
44+
action: success ? stats.action : `${stats.action}_error`,
45+
location: args.location,
46+
enable_google_ml_integration: args.requireGoogleMlIntegration.toString(),
47+
database_version: stats.databaseVersion?.toLowerCase() || "unknown",
48+
dataconnect_label: stats.dataconnectLabel || "unknown",
49+
},
50+
Date.now() - startTime,
51+
);
52+
}
53+
}
54+
2355
if (requireGoogleMlIntegration && !dryRun) {
2456
await grantRolesToCloudSqlServiceAccount(projectId, instanceId, [GOOGLE_ML_INTEGRATION_ROLE]);
2557
}
2658
}
2759

28-
async function upsertInstance(args: {
29-
projectId: string;
30-
location: string;
31-
instanceId: string;
32-
databaseId: string;
33-
requireGoogleMlIntegration: boolean;
34-
dryRun?: boolean;
35-
}): Promise<void> {
60+
async function upsertInstance(
61+
stats: SetupStats,
62+
args: {
63+
projectId: string;
64+
location: string;
65+
instanceId: string;
66+
databaseId: string;
67+
requireGoogleMlIntegration: boolean;
68+
dryRun?: boolean;
69+
},
70+
): Promise<void> {
3671
const { projectId, instanceId, requireGoogleMlIntegration, dryRun } = args;
3772
try {
3873
const existingInstance = await cloudSqlAdminClient.getInstance(projectId, instanceId);
3974
utils.logLabeledBullet(
4075
"dataconnect",
4176
`Found existing Cloud SQL instance ${clc.bold(instanceId)}.`,
4277
);
78+
stats.databaseVersion = existingInstance.databaseVersion;
79+
stats.dataconnectLabel = existingInstance.settings?.userLabels?.["firebase-data-connect"] as
80+
| cloudSqlAdminClient.DataConnectLabel
81+
| undefined;
82+
4383
const why = getUpdateReason(existingInstance, requireGoogleMlIntegration);
4484
if (why) {
4585
if (dryRun) {
@@ -55,6 +95,7 @@ async function upsertInstance(args: {
5595
`Cloud SQL instance ${clc.bold(instanceId)} settings are not compatible with Firebase Data Connect. ` +
5696
why,
5797
);
98+
stats.action = "update";
5899
await promiseWithSpinner(
59100
() =>
60101
cloudSqlAdminClient.updateInstanceForDataConnect(
@@ -71,7 +112,11 @@ async function upsertInstance(args: {
71112
throw err;
72113
}
73114
// Cloud SQL instance is not found, start its creation.
74-
await createInstance({ ...args });
115+
stats.action = "create";
116+
stats.databaseVersion = cloudSqlAdminClient.DEFAULT_DATABASE_VERSION;
117+
const freeTrialUsed = await checkFreeTrialInstanceUsed(projectId);
118+
stats.dataconnectLabel = freeTrialUsed ? "nt" : "ft";
119+
await createInstance({ ...args, freeTrialLabel: stats.dataconnectLabel });
75120
}
76121
}
77122

@@ -80,10 +125,11 @@ async function createInstance(args: {
80125
location: string;
81126
instanceId: string;
82127
requireGoogleMlIntegration: boolean;
128+
freeTrialLabel: cloudSqlAdminClient.DataConnectLabel;
83129
dryRun?: boolean;
84130
}): Promise<void> {
85-
const { projectId, location, instanceId, requireGoogleMlIntegration, dryRun } = args;
86-
const freeTrialUsed = await checkFreeTrialInstanceUsed(projectId);
131+
const { projectId, location, instanceId, requireGoogleMlIntegration, dryRun, freeTrialLabel } =
132+
args;
87133
if (dryRun) {
88134
utils.logLabeledBullet(
89135
"dataconnect",
@@ -95,11 +141,11 @@ async function createInstance(args: {
95141
location,
96142
instanceId,
97143
enableGoogleMlIntegration: requireGoogleMlIntegration,
98-
freeTrial: !freeTrialUsed,
144+
freeTrialLabel,
99145
});
100146
utils.logLabeledBullet(
101147
"dataconnect",
102-
cloudSQLBeingCreated(projectId, instanceId, !freeTrialUsed),
148+
cloudSQLBeingCreated(projectId, instanceId, freeTrialLabel === "ft"),
103149
);
104150
}
105151
}

src/deploy/dataconnect/deploy.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export default async function (
9696
instanceId,
9797
databaseId,
9898
requireGoogleMlIntegration: requiresVector(s.deploymentMetadata),
99+
source: "deploy",
99100
});
100101
}
101102
}),

src/deploy/dataconnect/prepare.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export default async function (context: any, options: DeployOptions): Promise<vo
8888
databaseId,
8989
requireGoogleMlIntegration: requiresVector(s.deploymentMetadata),
9090
dryRun: true,
91+
source: "deploy",
9192
});
9293
}
9394
}),

src/gcp/cloudsql/cloudsqladmin.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ describe("cloudsqladmin", () => {
9393
location: "us-central",
9494
instanceId: INSTANCE_ID,
9595
enableGoogleMlIntegration: false,
96-
freeTrial: false,
96+
freeTrialLabel: "nt",
9797
}),
9898
).to.be.rejectedWith("Cloud SQL free trial instances are not yet available in us-central");
9999
expect(nock.isDone()).to.be.true;
@@ -160,7 +160,7 @@ describe("cloudsqladmin", () => {
160160
location: "us-central",
161161
instanceId: INSTANCE_ID,
162162
enableGoogleMlIntegration: false,
163-
freeTrial: false,
163+
freeTrialLabel: "nt",
164164
});
165165

166166
expect(nock.isDone()).to.be.true;

src/gcp/cloudsql/cloudsqladmin.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,15 @@ export function instanceConsoleLink(projectId: string, instanceId: string) {
5959
return `https://console.cloud.google.com/sql/instances/${instanceId}/overview?project=${projectId}`;
6060
}
6161

62+
export type DataConnectLabel = "ft" | "nt";
63+
export const DEFAULT_DATABASE_VERSION = "POSTGRES_15";
64+
6265
export async function createInstance(args: {
6366
projectId: string;
6467
location: string;
6568
instanceId: string;
6669
enableGoogleMlIntegration: boolean;
67-
freeTrial: boolean;
70+
freeTrialLabel: DataConnectLabel;
6871
}): Promise<void> {
6972
const databaseFlags = [{ name: "cloudsql.iam_authentication", value: "on" }];
7073
if (args.enableGoogleMlIntegration) {
@@ -74,7 +77,7 @@ export async function createInstance(args: {
7477
await client.post<Partial<Instance>, Operation>(`projects/${args.projectId}/instances`, {
7578
name: args.instanceId,
7679
region: args.location,
77-
databaseVersion: "POSTGRES_15",
80+
databaseVersion: DEFAULT_DATABASE_VERSION,
7881
settings: {
7982
tier: "db-f1-micro",
8083
edition: "ENTERPRISE",
@@ -84,7 +87,7 @@ export async function createInstance(args: {
8487
enableGoogleMlIntegration: args.enableGoogleMlIntegration,
8588
databaseFlags,
8689
storageAutoResize: false,
87-
userLabels: { "firebase-data-connect": args.freeTrial ? "ft" : "nt" },
90+
userLabels: { "firebase-data-connect": args.freeTrialLabel },
8891
insightsConfig: {
8992
queryInsightsEnabled: true,
9093
queryPlansPerMinute: 5, // Match the default settings

src/init/features/dataconnect/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ async function actuateWithInfo(
220220
instanceId: info.cloudSqlInstanceId,
221221
databaseId: info.cloudSqlDatabase,
222222
requireGoogleMlIntegration: false,
223+
source: info.analyticsFlow.startsWith("mcp") ? "mcp_init" : "init",
223224
});
224225
}
225226

src/init/features/dataconnect/sdk.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ export async function actuate(setup: Setup, config: Config) {
165165
} finally {
166166
let flow = "no_app";
167167
if (sdkInfo.apps.length) {
168-
const platforms = sdkInfo.apps.map(appDescription).sort();
168+
const platforms = sdkInfo.apps.map((a) => a.platform.toLowerCase()).sort();
169169
flow = `${platforms.join("_")}_app`;
170170
}
171171
if (fdcInfo) {

src/track.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type cliEventNames =
2424
| "product_init"
2525
| "product_init_mcp"
2626
| "dataconnect_init"
27+
| "dataconnect_cloud_sql"
2728
| "error"
2829
| "login"
2930
| "api_enabled"

0 commit comments

Comments
 (0)