Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/report/reportAnalyticsConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ export interface ReportAnalyticsConfig {
// Mirrors the CLI's telemetry opt-out at generation time. When true the frontend never
// initializes PostHog. The viewer's own browser opt-out is checked separately at runtime.
readonly telemetryDisabled: boolean;
// Per-run id; the frontend uses it as the PostHog distinct id so every view of one report
// groups together and joins back to the CLI run that produced it.
// Per-run id, sent as the pw_report_id property (not the identity) so report views join back to
// the CLI run that produced them.
readonly reportId: string;
// Anonymous id of the machine that generated the report (empty when telemetry was disabled).
// Anon id of the generating machine (empty when telemetry was disabled). Used as the PostHog
// distinct id so the report shares one person with its CLI run.
readonly generatedByAnonId: string;
readonly version: string;
}
10 changes: 8 additions & 2 deletions src/report/web/analytics/postHogAnalytics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,18 @@ describe('createPostHogReportAnalytics', () => {
maskAllInputs: true,
blockSelector: 'script[type="application/json"]',
},
bootstrap: { distinctID: 'report-123' },
bootstrap: { distinctID: 'anon-456' },
});
expect(fake.init[0]?.config['before_send']).toBeDefined();
});

test('falls back to the report id as the distinct id when no anon id was embedded', () => {
const fake = createFakeClient();
createPostHogReportAnalytics(postHogReportAnalyticsOptions.build({ client: fake.client, generatedByAnonId: '' }));

expect(fake.init[0]?.config['bootstrap']).toEqual({ distinctID: 'report-123' });
});

test('registers the pw_* super-properties', () => {
const fake = createFakeClient();
createPostHogReportAnalytics(postHogReportAnalyticsOptions.build({ client: fake.client }));
Expand All @@ -63,7 +70,6 @@ describe('createPostHogReportAnalytics', () => {
pw_surface: 'report',
pw_version: '1.2.3',
pw_report_id: 'report-123',
pw_generated_by: 'anon-456',
});
});

Expand Down
11 changes: 7 additions & 4 deletions src/report/web/analytics/postHogAnalytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,22 @@ export interface CreatePostHogReportAnalyticsOptions {
export function createPostHogReportAnalytics(options: CreatePostHogReportAnalyticsOptions): Analytics {
const { buildInfo, reportId, generatedByAnonId, version, client = posthog } = options;

// Identify by the generating machine's anon id (the CLI's distinct id too) so a run's CLI events
// and report views are one PostHog person. Fall back to the report id for reports with no anon id.
const distinctId = generatedByAnonId || reportId;

const superProperties: Record<string, unknown> = {
pw_surface: 'report',
pw_version: version,
pw_report_id: reportId,
pw_generated_by: generatedByAnonId,
};

client.init(buildInfo.postHogKey, {
api_host: buildInfo.postHogHost,
// The report runs from a file:// page with no reliable storage, so keep identity in memory
// and seed the distinct id from the report id rather than persisting one per viewer.
// The report runs from a file:// page with no reliable storage, so keep identity in memory and
// seed the distinct id via bootstrap rather than persisting one per viewer.
persistence: 'memory',
bootstrap: { distinctID: reportId },
bootstrap: { distinctID: distinctId },
// Report copy lives in clickable elements; autocapture would leak it via $elements_text.
autocapture: false,
capture_pageview: true,
Expand Down