From 1cd834c8696de9d12a14a5747f5e1a89565814d7 Mon Sep 17 00:00:00 2001 From: Joe Hanley Date: Wed, 21 Jun 2023 14:29:25 -0700 Subject: [PATCH 1/2] Switched most uses of track to GA4 --- src/auth.ts | 8 +- src/command.ts | 22 ++- src/commands/ext-install.ts | 13 +- src/deploy/functions/checkIam.ts | 7 +- src/deploy/hosting/prepare.ts | 6 +- src/deploy/index.ts | 22 ++- src/emulator/controller.ts | 4 +- src/emulator/functionsEmulator.ts | 4 +- src/ensureApiEnabled.ts | 6 +- src/extensions/paramHelper.ts | 5 - src/test/deploy/hosting/prepare.spec.ts | 8 +- src/track.ts | 200 ++++++++++++++++-------- 12 files changed, 200 insertions(+), 105 deletions(-) diff --git a/src/auth.ts b/src/auth.ts index 56e5f31cdd9..4ab586b3c0e 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -19,7 +19,7 @@ import * as scopes from "./scopes"; import { clearCredentials } from "./defaultCredentials"; import { v4 as uuidv4 } from "uuid"; import { randomBytes, createHash } from "crypto"; -import { track } from "./track"; +import { trackGA4 } from "./track"; import { authOrigin, authProxyOrigin, @@ -444,7 +444,7 @@ async function loginRemotely(): Promise { codeVerifier ); - void track("login", "google_remote"); + void trackGA4("login", { method: "google_remote" }); return { user: jwt.decode(tokens.id_token!) as User, @@ -468,7 +468,7 @@ async function loginWithLocalhostGoogle(port: number, userHint?: string): Promis getTokensFromAuthorizationCode ); - void track("login", "google_localhost"); + void trackGA4("login", { method: "google_localhost" }); // getTokensFromAuthoirzationCode doesn't handle the --token case, so we know we'll // always have an id_token. return { @@ -489,7 +489,7 @@ async function loginWithLocalhostGitHub(port: number): Promise { successTemplate, getGithubTokensFromAuthorizationCode ); - void track("login", "google_localhost"); + void trackGA4("login", { method: "github_localhost" }); return tokens; } diff --git a/src/command.ts b/src/command.ts index 86edffbc791..4d55a2d0217 100644 --- a/src/command.ts +++ b/src/command.ts @@ -8,7 +8,7 @@ import { loadRC } from "./rc"; import { Config } from "./config"; import { configstore } from "./configstore"; import { detectProjectRoot } from "./detectProjectRoot"; -import { track, trackEmulator } from "./track"; +import { trackEmulator, trackGA4 } from "./track"; import { selectAccount, setActiveAccount } from "./auth"; import { getFirebaseProject } from "./management/projects"; import { requireAuth } from "./requireAuth"; @@ -202,7 +202,12 @@ export class Command { ); } const duration = Math.floor((process.uptime() - start) * 1000); - const trackSuccess = track(this.name, "success", duration); + const trackSuccess = trackGA4("command_execution", { + command_name: this.name, + result: "success", + duration, + interactive: getInheritedOption(options, "nonInteractive") ? "false" : "true", + }); if (!isEmulator) { await withTimeout(5000, trackSuccess); } else { @@ -236,8 +241,12 @@ export class Command { await withTimeout( 5000, Promise.all([ - track(this.name, "error", duration), - track(err.exit === 1 ? "Error (User)" : "Error (Unexpected)", "", duration), + trackGA4("command_execution", { + command_name: this.name, + result: "error", + duration, + interactive: getInheritedOption(options, "nonInteractive") ? "false" : "true", + }), isEmulator ? trackEmulator("command_error", { command_name: this.name, @@ -400,7 +409,10 @@ export function validateProjectId(project: string): void { if (PROJECT_ID_REGEX.test(project)) { return; } - track("Project ID Check", "invalid"); + trackGA4("error", { + error_type: "Error (User)", + details: "Invalid project ID", + }); const invalidMessage = "Invalid project id: " + clc.bold(project) + "."; if (project.toLowerCase() !== project) { // Attempt to be more helpful in case uppercase letters are used. diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index d4fe999a397..35e55d2d09c 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -27,7 +27,7 @@ import { import { getRandomString } from "../extensions/utils"; import { requirePermissions } from "../requirePermissions"; import * as utils from "../utils"; -import { track } from "../track"; +import { trackGA4 } from "../track"; import { confirm } from "../prompt"; import { Options } from "../options"; import * as manifest from "../extensions/manifest"; @@ -96,11 +96,18 @@ export const command = new Command("ext:install [extensionName]") // Should parse spec locally so we don't need project ID. source = await createSourceFromLocation(needProjectId({ projectId }), extensionName); await displayExtInfo(extensionName, "", source.spec); - void track("Extension Install", "Install by Source", options.interactive ? 1 : 0); + void trackGA4("extension_added_to_manifest", { + published: "local", + interactive: options.nonInteractive ? "false" : "true", + }); } else { - void track("Extension Install", "Install by Extension Ref", options.interactive ? 1 : 0); extensionName = await canonicalizeRefInput(extensionName); extensionVersion = await extensionsApi.getExtensionVersion(extensionName); + + void trackGA4("extension_added_to_manifest", { + published: extensionVersion.listing?.state === "APPROVED" ? "published" : "uploaded", + interactive: options.nonInteractive ? "false" : "true", + }); await infoExtensionVersion({ extensionName, extensionVersion, diff --git a/src/deploy/functions/checkIam.ts b/src/deploy/functions/checkIam.ts index 8b4a0d1d692..20f61599ad2 100644 --- a/src/deploy/functions/checkIam.ts +++ b/src/deploy/functions/checkIam.ts @@ -8,7 +8,7 @@ import { flattenArray } from "../../functional"; import * as iam from "../../gcp/iam"; import * as args from "./args"; import * as backend from "./backend"; -import { track } from "../../track"; +import { trackGA4 } from "../../track"; import * as utils from "../../utils"; import { getIamPolicy, setIamPolicy } from "../../gcp/resourceManager"; @@ -101,7 +101,10 @@ export async function checkHttpIam( } if (!passed) { - void track("Error (User)", "deploy:functions:http_create_missing_iam"); + void trackGA4("error", { + error_type: "Error (User)", + details: "deploy:functions:http_create_missing_iam", + }); throw new FirebaseError( `Missing required permission on project ${bold( context.projectId diff --git a/src/deploy/hosting/prepare.ts b/src/deploy/hosting/prepare.ts index df55b527330..0a6223c39bf 100644 --- a/src/deploy/hosting/prepare.ts +++ b/src/deploy/hosting/prepare.ts @@ -7,7 +7,7 @@ import { Context } from "./context"; import { Options } from "../../options"; import { HostingOptions } from "../../hosting/options"; import { assertExhaustive, zipIn } from "../../functional"; -import { track } from "../../track"; +import { trackGA4 } from "../../track"; import * as utils from "../../utils"; import { HostingSource, RunRewrite } from "../../firebaseConfig"; import * as backend from "../functions/backend"; @@ -137,7 +137,9 @@ export async function prepare(context: Context, options: HostingOptions & Option labels, }; const [, versionName] = await Promise.all([ - track("hosting_deploy", config.webFramework || "classic"), + trackGA4("hosting_version", { + framework: config.webFramework || "classic", + }), api.createVersion(config.site, version), ]); return versionName; diff --git a/src/deploy/index.ts b/src/deploy/index.ts index 54a64b571cb..a34f2b65ce8 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -2,11 +2,11 @@ import * as clc from "colorette"; import { logger } from "../logger"; import { hostingOrigin } from "../api"; import { bold, underline, white } from "colorette"; -import { has, includes, each } from "lodash"; +import { includes, each } from "lodash"; import { needProjectId } from "../projectUtils"; import { logBullet, logSuccess, consoleUrl, addSubdomain } from "../utils"; import { FirebaseError } from "../error"; -import { track } from "../track"; +import { AnalyticsParams, trackGA4 } from "../track"; import { lifecycleHooks } from "./lifecycleHooks"; import * as experiments from "../experiments"; import * as HostingTarget from "./hosting"; @@ -119,12 +119,20 @@ export const deploy = async function ( await chain(releases, context, options, payload); await chain(postdeploys, context, options, payload); - if (has(options, "config.notes.databaseRules")) { - await track("Rules Deploy", options.config.notes.databaseRules); - } - const duration = Date.now() - startTime; - await track("Product Deploy", [...targetNames].sort().join(","), duration); + const analyticsParams: AnalyticsParams = { + duration, + interactive: options.nonInteractive ? "false" : "true", + }; + + Object.keys(TARGETS).reduce((accum, t) => { + accum[t] = "false"; + return accum; + }, analyticsParams); + for (const t of targetNames) { + analyticsParams[t] = "true"; + } + await trackGA4("product_deploy", analyticsParams); logger.info(); logSuccess(bold(underline("Deploy complete!"))); diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index a837d5d2d7d..7505bf9ea81 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -4,7 +4,7 @@ import * as path from "path"; import * as fsConfig from "../firestore/fsConfig"; import { logger } from "../logger"; -import { track, trackEmulator } from "../track"; +import { trackEmulator } from "../track"; import * as utils from "../utils"; import { EmulatorRegistry } from "./registry"; import { @@ -401,7 +401,6 @@ export async function startAll( const name = instance.getName(); // Log the command for analytics - void track("Emulator Run", name); void trackEmulator("emulator_run", { emulator_name: name, is_demo_project: String(isDemoProject), @@ -421,7 +420,6 @@ export async function startAll( // since we originally mistakenly reported emulators:start events // for each emulator, by reporting the "hub" we ensure that our // historical data can still be viewed. - void track("emulators:start", "hub"); await startEmulator(hub); } diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index f350d2d6d33..a3f93206585 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -11,7 +11,7 @@ import { EventEmitter } from "events"; import { Account } from "../types/auth"; import { logger } from "../logger"; -import { track, trackEmulator } from "../track"; +import { trackEmulator } from "../track"; import { Constants } from "./constants"; import { EmulatorInfo, EmulatorInstance, Emulators, FunctionsExecutionMode } from "./types"; import * as chokidar from "chokidar"; @@ -63,7 +63,6 @@ import { resolveBackend } from "../deploy/functions/build"; import { setEnvVarsForEmulators } from "./env"; import { runWithVirtualEnv } from "../functions/python"; -const EVENT_INVOKE = "functions:invoke"; // event name for UA const EVENT_INVOKE_GA4 = "functions_invoke"; // event name GA4 (alphanumertic) /* @@ -1551,7 +1550,6 @@ export class FunctionsEmulator implements EmulatorInstance { } } // For analytics, track the invoked service - void track(EVENT_INVOKE, getFunctionService(trigger)); void trackEmulator(EVENT_INVOKE_GA4, { function_service: getFunctionService(trigger), }); diff --git a/src/ensureApiEnabled.ts b/src/ensureApiEnabled.ts index 48d0044f046..d0da2ba8b21 100644 --- a/src/ensureApiEnabled.ts +++ b/src/ensureApiEnabled.ts @@ -1,6 +1,6 @@ import { bold } from "colorette"; -import { track } from "./track"; +import { trackGA4 } from "./track"; import { serviceUsageOrigin } from "./api"; import { Client } from "./apiv2"; import * as utils from "./utils"; @@ -91,7 +91,9 @@ async function pollCheckEnabled( }); const isEnabled = await check(projectId, apiName, prefix, silent); if (isEnabled) { - void track("api_enabled", apiName); + void trackGA4("api_enabled", { + api_name: apiName, + }); return; } if (!silent) { diff --git a/src/extensions/paramHelper.ts b/src/extensions/paramHelper.ts index 8231d69ebaf..aca46a91d4d 100644 --- a/src/extensions/paramHelper.ts +++ b/src/extensions/paramHelper.ts @@ -7,7 +7,6 @@ import { logger } from "../logger"; import { ExtensionInstance, ExtensionSpec, Param } from "./types"; import { getFirebaseProjectParams, substituteParams } from "./extensionsHelper"; import * as askUserForParam from "./askUserForParam"; -import { track } from "../track"; import * as env from "../functions/env"; import { cloneDeep } from "../utils"; @@ -111,8 +110,6 @@ export async function getParams(args: { reconfiguring: !!args.reconfiguring, }); } - const paramNames = Object.keys(params); - void track("Extension Params", paramNames.length ? "Not Present" : "Present", paramNames.length); return params; } @@ -137,8 +134,6 @@ export async function getParamsForUpdate(args: { instanceId: args.instanceId, }); } - const paramNames = Object.keys(params); - void track("Extension Params", paramNames.length ? "Not Present" : "Present", paramNames.length); return params; } diff --git a/src/test/deploy/hosting/prepare.spec.ts b/src/test/deploy/hosting/prepare.spec.ts index c8041ad9aba..4ddb7b661a0 100644 --- a/src/test/deploy/hosting/prepare.spec.ts +++ b/src/test/deploy/hosting/prepare.spec.ts @@ -104,7 +104,9 @@ describe("hosting prepare", () => { }; await prepare(context, options); - expect(trackingStub.track).to.have.been.calledOnceWith("hosting_deploy", "fake-framework"); + expect(trackingStub.trackGA4).to.have.been.calledOnceWith("hosting_version", { + framework: "fake-framework", + }); expect(hostingStub.createVersion).to.have.been.calledOnce; expect(context.hosting).to.deep.equal({ deploys: [ @@ -138,7 +140,9 @@ describe("hosting prepare", () => { }; await prepare(context, options); - expect(trackingStub.track).to.have.been.calledOnceWith("hosting_deploy", "classic"); + expect(trackingStub.trackGA4).to.have.been.calledOnceWith("hosting_version", { + framework: "classic", + }); expect(hostingStub.createVersion).to.have.been.calledOnce; expect(context.hosting).to.deep.equal({ deploys: [ diff --git a/src/track.ts b/src/track.ts index 65d3bcdd817..c59d5b2e325 100644 --- a/src/track.ts +++ b/src/track.ts @@ -7,11 +7,36 @@ import { configstore } from "./configstore"; import { logger } from "./logger"; const pkg = require("../package.json"); -// The ID identifying the GA4 property for the Emulator Suite only. Should only -// be used in Emulator UI and emulator-related commands (e.g. emulators:start). -export const EMULATOR_GA4_MEASUREMENT_ID = - process.env.FIREBASE_EMULATOR_GA4_MEASUREMENT_ID || "G-KYP2JMPFC0"; - +type cliEventNames = + | "command_execution" + | "product_deploy" + | "error" + | "login" + | "api_enabled" + | "hosting_version" + | "extension_added_to_manifest"; +type GA4Property = "cli" | "emulator"; +interface GA4Info { + measurementId: string; + apiSecret: string; + clientIdKey: string; + currentSession?: AnalyticsSession; +} +export const GA4_PROPERTIES: Record = { + // Info for the GA4 property for the rest of the CLI. + cli: { + measurementId: process.env.FIREBASE_CLI_GA4_MEASUREMENT_ID || "G-PDN0QWHQJR", + apiSecret: process.env.FIREBASE_CLI_GA4_API_SECRET || "LSw5lNxhSFSWeB6aIzJS2w", + clientIdKey: "analytics-uuid", + }, + // Info for the GA4 property for the Emulator Suite only. Should only + // be used in Emulator UI and emulator-related commands (e.g. emulators:start). + emulator: { + measurementId: process.env.FIREBASE_EMULATOR_GA4_MEASUREMENT_ID || "G-KYP2JMPFC0", + apiSecret: process.env.FIREBASE_EMULATOR_GA4_API_SECRET || "2V_zBYc4TdeoppzDaIu0zw", + clientIdKey: "emulator-analytics-clientId", + }, +}; /** * UA is enabled only if: * 1) Entrypoint to the code is Firebase CLI (not require("firebase-tools")). @@ -21,55 +46,9 @@ export function usageEnabled(): boolean { return !!process.env.IS_FIREBASE_CLI && !!configstore.get("usage"); } -// The Tracking ID for the Universal Analytics property for all of the CLI -// including emulator-related commands (double-tracked for historical reasons) -// but excluding Emulator UI. -// TODO: Upgrade to GA4 before July 1, 2023. See: -// https://support.google.com/analytics/answer/11583528 -const FIREBASE_ANALYTICS_UA = process.env.FIREBASE_ANALYTICS_UA || "UA-29174744-3"; - -let visitor: ua.Visitor; - -function ensureUAVisitor(): void { - if (!visitor) { - // Identifier for the client (UUID) in the CLI UA. - let anonId = configstore.get("analytics-uuid") as string; - if (!anonId) { - anonId = uuidV4(); - configstore.set("analytics-uuid", anonId); - } - - visitor = ua(FIREBASE_ANALYTICS_UA, anonId, { - strictCidFormat: false, - https: true, - }); - - visitor.set("cd1", process.platform); // Platform - visitor.set("cd2", process.version); // NodeVersion - visitor.set("cd3", process.env.FIREPIT_VERSION || "none"); // FirepitVersion - } -} - -export function track(action: string, label: string, duration = 0): Promise { - ensureUAVisitor(); - return new Promise((resolve) => { - if (usageEnabled() && configstore.get("tokens")) { - visitor.event("Firebase CLI " + pkg.version, action, label, duration).send(() => { - // we could handle errors here, but we won't - resolve(); - }); - } else { - resolve(); - } - }); -} - -const EMULATOR_GA4_API_SECRET = - process.env.FIREBASE_EMULATOR_GA4_API_SECRET || "2V_zBYc4TdeoppzDaIu0zw"; - // Prop name length must <= 24 and cannot begin with google_/ga_/firebase_. // https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference?client_type=firebase#reserved_parameter_names -const EMULATOR_GA4_USER_PROPS = { +const GA4_USER_PROPS = { node_platform: { value: process.platform, }, @@ -97,6 +76,11 @@ export interface AnalyticsParams { /** The elapsed time in milliseconds (e.g. for command runs) (param for custom metrics) */ duration?: number; + /** The result (success or error) of a command */ + result?: string; + + /** Whether the command was run in interactive or noninteractive mode */ + interactive?: string; /** * One-off params (that may be used for custom params / metrics later). * @@ -110,6 +94,24 @@ export interface AnalyticsParams { [key: string]: string | number | undefined; } +export async function trackGA4( + eventName: cliEventNames, + params: AnalyticsParams, + duration: number = 0 +): Promise { + const session = cliSession(); + if (!session) { + return; + } + return _ga4Track({ + session, + apiSecret: GA4_PROPERTIES.cli.apiSecret, + eventName, + params, + duration, + }); +} + /** * Record an emulator-related event for Analytics. * @@ -134,11 +136,29 @@ export async function trackEmulator(eventName: string, params?: AnalyticsParams) // staring at the terminal and waiting for the command to finish also counts.) const oldTotalEngagementSeconds = session.totalEngagementSeconds; session.totalEngagementSeconds = process.uptime(); + const duration = session.totalEngagementSeconds - oldTotalEngagementSeconds; + return _ga4Track({ + session, + apiSecret: GA4_PROPERTIES.emulator.apiSecret, + eventName, + params, + duration, + }); +} + +async function _ga4Track(args: { + session: AnalyticsSession; + apiSecret: string; + eventName: string; + params?: AnalyticsParams; + duration?: number; +}): Promise { + const { session, apiSecret, eventName, params, duration } = args; // Memorize and set command_name throughout the session. session.commandName = params?.command_name || session.commandName; - const search = `?api_secret=${EMULATOR_GA4_API_SECRET}&measurement_id=${session.measurementId}`; + const search = `?api_secret=${apiSecret}&measurement_id=${session.measurementId}`; const validate = session.validateOnly ? "debug/" : ""; const url = `https://www.google-analytics.com/${validate}mp/collect${search}`; const body = { @@ -147,7 +167,7 @@ export async function trackEmulator(eventName: string, params?: AnalyticsParams) timestamp_micros: `${Date.now()}000`, client_id: session.clientId, user_properties: { - ...EMULATOR_GA4_USER_PROPS, + ...GA4_USER_PROPS, java_major_version: session.javaMajorVersion ? { value: session.javaMajorVersion } : undefined, @@ -165,10 +185,7 @@ export async function trackEmulator(eventName: string, params?: AnalyticsParams) // https://support.google.com/analytics/answer/11109416?hl=en // Additional engagement time since last event, in microseconds. - engagement_time_msec: (session.totalEngagementSeconds - oldTotalEngagementSeconds) - .toFixed(3) - .replace(".", "") - .replace(/^0+/, ""), // trim leading zeros + engagement_time_msec: (duration ?? 0).toFixed(3).replace(".", "").replace(/^0+/, ""), // trim leading zeros // https://support.google.com/analytics/answer/7201382?hl=en // To turn debug mode off, `debug_mode` must be left out not `false`. @@ -240,6 +257,14 @@ export interface AnalyticsSession { } export function emulatorSession(): AnalyticsSession | undefined { + return session("emulator"); +} + +export function cliSession(): AnalyticsSession | undefined { + return session("cli"); +} + +function session(propertyName: GA4Property): AnalyticsSession | undefined { const validateOnly = !!process.env.FIREBASE_CLI_MP_VALIDATE; if (!usageEnabled()) { if (validateOnly) { @@ -247,15 +272,15 @@ export function emulatorSession(): AnalyticsSession | undefined { } return; } - if (!currentEmulatorSession) { - let clientId: string | undefined = configstore.get("emulator-analytics-clientId"); + const property = GA4_PROPERTIES[propertyName]; + if (!property.currentSession) { + let clientId: string | undefined = configstore.get(property.clientIdKey); if (!clientId) { clientId = uuidV4(); - configstore.set("emulator-analytics-clientId", clientId); + configstore.set(property.clientIdKey, clientId); } - - currentEmulatorSession = { - measurementId: EMULATOR_GA4_MEASUREMENT_ID, + property.currentSession = { + measurementId: property.measurementId, clientId, // This must be an int64 string, but only ~50 bits are generated here @@ -268,11 +293,9 @@ export function emulatorSession(): AnalyticsSession | undefined { validateOnly, }; } - return currentEmulatorSession; + return property.currentSession; } -let currentEmulatorSession: AnalyticsSession | undefined = undefined; - function isDebugMode(): boolean { const account = getGlobalDefaultAccount(); if (account?.user.email.endsWith("@google.com")) { @@ -289,3 +312,46 @@ function isDebugMode(): boolean { } return false; } + +// The Tracking ID for the Universal Analytics property for all of the CLI +// including emulator-related commands (double-tracked for historical reasons) +// but excluding Emulator UI. +// TODO: Upgrade to GA4 before July 1, 2023. See: +// https://support.google.com/analytics/answer/11583528 +const FIREBASE_ANALYTICS_UA = process.env.FIREBASE_ANALYTICS_UA || "UA-29174744-3"; + +let visitor: ua.Visitor; + +function ensureUAVisitor(): void { + if (!visitor) { + // Identifier for the client (UUID) in the CLI UA. + let anonId = configstore.get("analytics-uuid") as string; + if (!anonId) { + anonId = uuidV4(); + configstore.set("analytics-uuid", anonId); + } + + visitor = ua(FIREBASE_ANALYTICS_UA, anonId, { + strictCidFormat: false, + https: true, + }); + + visitor.set("cd1", process.platform); // Platform + visitor.set("cd2", process.version); // NodeVersion + visitor.set("cd3", process.env.FIREPIT_VERSION || "none"); // FirepitVersion + } +} + +export function track(action: string, label: string, duration = 0): Promise { + ensureUAVisitor(); + return new Promise((resolve) => { + if (usageEnabled() && configstore.get("tokens")) { + visitor.event("Firebase CLI " + pkg.version, action, label, duration).send(() => { + // we could handle errors here, but we won't + resolve(); + }); + } else { + resolve(); + } + }); +} From 3cebf1b88944cd97a5e838001773909929252347 Mon Sep 17 00:00:00 2001 From: Joe Hanley Date: Thu, 22 Jun 2023 15:02:34 -0700 Subject: [PATCH 2/2] Move duration out of params, and improve debug logging slightly --- src/command.ts | 15 +++++++++------ src/deploy/index.ts | 3 +-- src/track.ts | 8 ++++++-- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/command.ts b/src/command.ts index 4d55a2d0217..1d14255071e 100644 --- a/src/command.ts +++ b/src/command.ts @@ -241,12 +241,15 @@ export class Command { await withTimeout( 5000, Promise.all([ - trackGA4("command_execution", { - command_name: this.name, - result: "error", - duration, - interactive: getInheritedOption(options, "nonInteractive") ? "false" : "true", - }), + trackGA4( + "command_execution", + { + command_name: this.name, + result: "error", + interactive: getInheritedOption(options, "nonInteractive") ? "false" : "true", + }, + duration + ), isEmulator ? trackEmulator("command_error", { command_name: this.name, diff --git a/src/deploy/index.ts b/src/deploy/index.ts index a34f2b65ce8..99d2bb5eccb 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -121,7 +121,6 @@ export const deploy = async function ( const duration = Date.now() - startTime; const analyticsParams: AnalyticsParams = { - duration, interactive: options.nonInteractive ? "false" : "true", }; @@ -132,7 +131,7 @@ export const deploy = async function ( for (const t of targetNames) { analyticsParams[t] = "true"; } - await trackGA4("product_deploy", analyticsParams); + await trackGA4("product_deploy", analyticsParams, duration); logger.info(); logSuccess(bold(underline("Deploy complete!"))); diff --git a/src/track.ts b/src/track.ts index c59d5b2e325..c1aa68eaf33 100644 --- a/src/track.ts +++ b/src/track.ts @@ -97,7 +97,7 @@ export interface AnalyticsParams { export async function trackGA4( eventName: cliEventNames, params: AnalyticsParams, - duration: number = 0 + duration: number = 1 // Default to 1ms duration so that events show up in realtime view. ): Promise { const session = cliSession(); if (!session) { @@ -197,7 +197,11 @@ async function _ga4Track(args: { ], }; if (session.validateOnly) { - logger.info(`Sending Analytics for event ${eventName}`, params, body); + logger.info( + `Sending Analytics for event ${eventName} to property ${session.measurementId}`, + params, + body + ); } try { const response = await fetch(url, {