diff --git a/orchestrator/src/lib/analytics.test.ts b/orchestrator/src/lib/analytics.test.ts index 372567b..3e8b2c4 100644 --- a/orchestrator/src/lib/analytics.test.ts +++ b/orchestrator/src/lib/analytics.test.ts @@ -14,6 +14,7 @@ describe("analytics", () => { track.mockReset(); __resetAnalyticsTestState(); window.localStorage.clear(); + (globalThis as any).__APP_VERSION__ = "abc1234-dev"; Object.defineProperty(window, "umami", { configurable: true, value: { track }, @@ -36,7 +37,7 @@ describe("analytics", () => { expect(track).toHaveBeenCalledTimes(2); }); - it("attaches a stable anonymous analytics user id to every event", () => { + it("attaches stable analytics metadata to every event", () => { trackEvent("star_repo_click", { location: "demo_mode_banner" }); trackProductEvent("tracer_drilldown_mode_changed", { mode: "all" }); @@ -50,6 +51,8 @@ describe("analytics", () => { expect(firstPayload.analytics_user_id).toBeTruthy(); expect(secondPayload.analytics_user_id).toBe(firstPayload.analytics_user_id); expect(storedId).toBe(firstPayload.analytics_user_id); + expect(firstPayload.app_version).toBe("abc1234-dev"); + expect(secondPayload.app_version).toBe("abc1234-dev"); }); it("drops disallowed keys and non-primitive payload values", () => { @@ -76,6 +79,7 @@ describe("analytics", () => { has_city_locations: true, search_terms_count: 3, analytics_user_id: expect.any(String), + app_version: "abc1234-dev", }); }); diff --git a/orchestrator/src/lib/analytics.ts b/orchestrator/src/lib/analytics.ts index 0c0627f..0fb7797 100644 --- a/orchestrator/src/lib/analytics.ts +++ b/orchestrator/src/lib/analytics.ts @@ -1,3 +1,5 @@ +declare const __APP_VERSION__: string; + type UmamiTracker = { track: (event: string, data?: Record) => void; }; @@ -11,12 +13,14 @@ declare global { export function trackEvent(event: string, data?: Record) { if (typeof window === "undefined") return; const analyticsUserId = getAnalyticsUserId(); + const appVersion = getAnalyticsAppVersion(); const payload = - analyticsUserId === null + analyticsUserId === null && appVersion === null ? data : { ...(data ?? {}), - analytics_user_id: analyticsUserId, + ...(analyticsUserId ? { analytics_user_id: analyticsUserId } : {}), + ...(appVersion ? { app_version: appVersion } : {}), }; window.umami?.track(event, payload); } @@ -149,6 +153,16 @@ function getAnalyticsUserId(): string | null { } } +function getAnalyticsAppVersion(): string | null { + try { + return typeof __APP_VERSION__ !== "undefined" && __APP_VERSION__.trim() + ? __APP_VERSION__ + : null; + } catch { + return null; + } +} + const DEDUPE_WINDOW_MS = 3_000; const ANALYTICS_USER_ID_STORAGE_KEY = "jobops.analytics.user_id.v1"; const recentEventCache = new Map();