ids
This commit is contained in:
parent
cbc52cbac0
commit
02aefb3dc1
@ -1,6 +1,7 @@
|
||||
import {
|
||||
__resetAnalyticsTestState,
|
||||
bucketQueryLength,
|
||||
trackEvent,
|
||||
trackProductEvent,
|
||||
} from "./analytics";
|
||||
|
||||
@ -12,6 +13,7 @@ describe("analytics", () => {
|
||||
vi.setSystemTime(new Date("2026-02-25T12:00:00Z"));
|
||||
track.mockReset();
|
||||
__resetAnalyticsTestState();
|
||||
window.localStorage.clear();
|
||||
Object.defineProperty(window, "umami", {
|
||||
configurable: true,
|
||||
value: { track },
|
||||
@ -34,6 +36,22 @@ describe("analytics", () => {
|
||||
expect(track).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("attaches a stable anonymous analytics user id to every event", () => {
|
||||
trackEvent("star_repo_click", { location: "demo_mode_banner" });
|
||||
trackProductEvent("tracer_drilldown_mode_changed", { mode: "all" });
|
||||
|
||||
expect(track).toHaveBeenCalledTimes(2);
|
||||
|
||||
const firstPayload = track.mock.calls[0][1] as Record<string, unknown>;
|
||||
const secondPayload = track.mock.calls[1][1] as Record<string, unknown>;
|
||||
const storedId = window.localStorage.getItem("jobops.analytics.user_id.v1");
|
||||
|
||||
expect(typeof firstPayload.analytics_user_id).toBe("string");
|
||||
expect(firstPayload.analytics_user_id).toBeTruthy();
|
||||
expect(secondPayload.analytics_user_id).toBe(firstPayload.analytics_user_id);
|
||||
expect(storedId).toBe(firstPayload.analytics_user_id);
|
||||
});
|
||||
|
||||
it("drops disallowed keys and non-primitive payload values", () => {
|
||||
trackProductEvent("jobs_pipeline_run_started", {
|
||||
mode: "automatic",
|
||||
@ -57,6 +75,7 @@ describe("analytics", () => {
|
||||
country: "uk",
|
||||
has_city_locations: true,
|
||||
search_terms_count: 3,
|
||||
analytics_user_id: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -10,7 +10,15 @@ declare global {
|
||||
|
||||
export function trackEvent(event: string, data?: Record<string, unknown>) {
|
||||
if (typeof window === "undefined") return;
|
||||
window.umami?.track(event, data);
|
||||
const analyticsUserId = getAnalyticsUserId();
|
||||
const payload =
|
||||
analyticsUserId === null
|
||||
? data
|
||||
: {
|
||||
...(data ?? {}),
|
||||
analytics_user_id: analyticsUserId,
|
||||
};
|
||||
window.umami?.track(event, payload);
|
||||
}
|
||||
|
||||
type ProductEventMap = {
|
||||
@ -114,8 +122,37 @@ type ProductEventName = keyof ProductEventMap;
|
||||
type Primitive = string | number | boolean | null;
|
||||
type SanitizedPayload = Record<string, Primitive>;
|
||||
|
||||
function generateAnalyticsUserId() {
|
||||
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
return `anon_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
|
||||
}
|
||||
|
||||
function getAnalyticsUserId(): string | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
if (cachedAnalyticsUserId) return cachedAnalyticsUserId;
|
||||
|
||||
try {
|
||||
const existing = window.localStorage.getItem(ANALYTICS_USER_ID_STORAGE_KEY);
|
||||
if (existing) {
|
||||
cachedAnalyticsUserId = existing;
|
||||
return existing;
|
||||
}
|
||||
|
||||
const next = generateAnalyticsUserId();
|
||||
window.localStorage.setItem(ANALYTICS_USER_ID_STORAGE_KEY, next);
|
||||
cachedAnalyticsUserId = next;
|
||||
return next;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const DEDUPE_WINDOW_MS = 3_000;
|
||||
const ANALYTICS_USER_ID_STORAGE_KEY = "jobops.analytics.user_id.v1";
|
||||
const recentEventCache = new Map<string, number>();
|
||||
let cachedAnalyticsUserId: string | null = null;
|
||||
const DISALLOWED_KEY_PARTS = [
|
||||
"query",
|
||||
"url",
|
||||
@ -221,4 +258,5 @@ export function bucketQueryLength(value: string | number): string {
|
||||
|
||||
export function __resetAnalyticsTestState() {
|
||||
recentEventCache.clear();
|
||||
cachedAnalyticsUserId = null;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user