From 539ad5347c46947494d66800c24659c1d871d74b Mon Sep 17 00:00:00 2001 From: Hubert Sosinski Date: Thu, 29 Jan 2026 11:01:02 -0800 Subject: [PATCH 1/3] Initial code for grouping --- src/CONST/index.ts | 2 ++ src/libs/telemetry/TelemetrySynchronizer.ts | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index a4281c1ad1fae..1f6c9229e626d 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1708,6 +1708,8 @@ const CONST = { CONTEXT_FULLSTORY: 'Fullstory', CONTEXT_POLICIES: 'Policies', TAG_ACTIVE_POLICY: 'active_policy_id', + TAG_POLICIES_COUNT: 'policies', + TAG_REPORTS_COUNT: 'reports', TAG_NUDGE_MIGRATION_COHORT: 'nudge_migration_cohort', TAG_AUTHENTICATION_FUNCTION: 'authentication_function', TAG_AUTHENTICATION_ERROR_TYPE: 'authentication_error_type', diff --git a/src/libs/telemetry/TelemetrySynchronizer.ts b/src/libs/telemetry/TelemetrySynchronizer.ts index a8de074dd5d24..ea87a8e3912e0 100644 --- a/src/libs/telemetry/TelemetrySynchronizer.ts +++ b/src/libs/telemetry/TelemetrySynchronizer.ts @@ -53,6 +53,17 @@ Onyx.connectWithoutView({ }, }); +Onyx.connectWithoutView({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (value) => { + if (!value) { + return; + } + sendReportsCountTag(Object.keys(value).length); + }, +}); + Onyx.connectWithoutView({ key: ONYXKEYS.NVP_TRY_NEW_DOT, callback: (value) => { @@ -67,6 +78,7 @@ function sendPoliciesContext() { } const activePolicies = getActivePolicies(policies, session.email).map((policy) => policy.id); Sentry.setTag(CONST.TELEMETRY.TAG_ACTIVE_POLICY, activePolicyID); + Sentry.setMeasurement(CONST.TELEMETRY.TAG_POLICIES_COUNT, activePolicies.length, 'none') Sentry.setContext(CONST.TELEMETRY.CONTEXT_POLICIES, {activePolicyID, activePolicies}); } @@ -77,3 +89,10 @@ function sendTryNewDotCohortTag() { } Sentry.setTag(CONST.TELEMETRY.TAG_NUDGE_MIGRATION_COHORT, cohort); } + +function sendReportsCountTag(reportsCount: number) { + if (!reportsCount) { + return; + } + Sentry.setMeasurement(CONST.TELEMETRY.TAG_REPORTS_COUNT, reportsCount, 'none') +} \ No newline at end of file From ec667467458828133d958df37728399617fd72c3 Mon Sep 17 00:00:00 2001 From: Hubert Sosinski Date: Fri, 30 Jan 2026 16:12:05 -0800 Subject: [PATCH 2/3] Set cohort tags for reports and policies --- src/CONST/index.ts | 4 +- src/libs/telemetry/TelemetrySynchronizer.ts | 62 +++++++++++++++++-- .../middlewares/copyTagsToChildSpans.ts | 46 ++++++++++++++ src/libs/telemetry/middlewares/index.ts | 3 +- 4 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 src/libs/telemetry/middlewares/copyTagsToChildSpans.ts diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 4612b15712250..d2931dbffc04e 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1717,8 +1717,8 @@ const CONST = { CONTEXT_FULLSTORY: 'Fullstory', CONTEXT_POLICIES: 'Policies', TAG_ACTIVE_POLICY: 'active_policy_id', - TAG_POLICIES_COUNT: 'policies', - TAG_REPORTS_COUNT: 'reports', + TAG_POLICIES_COUNT: 'policies_count', + TAG_REPORTS_COUNT: 'reports_count', TAG_NUDGE_MIGRATION_COHORT: 'nudge_migration_cohort', TAG_AUTHENTICATION_FUNCTION: 'authentication_function', TAG_AUTHENTICATION_ERROR_TYPE: 'authentication_error_type', diff --git a/src/libs/telemetry/TelemetrySynchronizer.ts b/src/libs/telemetry/TelemetrySynchronizer.ts index ea87a8e3912e0..408c7ab38a0b6 100644 --- a/src/libs/telemetry/TelemetrySynchronizer.ts +++ b/src/libs/telemetry/TelemetrySynchronizer.ts @@ -72,13 +72,67 @@ Onyx.connectWithoutView({ }, }); +/** + * Buckets policy count into cohorts for Sentry tagging + */ +function bucketPolicyCount(count: number): string { + if (count <= 1) { + return '0-1'; + } + if (count <= 10) { + return '2-10'; + } + if (count <= 50) { + return '11-50'; + } + if (count <= 100) { + return '51-100'; + } + if (count <= 250) { + return '101-250'; + } + if (count <= 500) { + return '251-500'; + } + if (count <= 1000) { + return '501-1000'; + } + return '1000+'; +} + +/** + * Buckets report count into cohorts for Sentry tagging + */ +function bucketReportCount(count: number): string { + if (count <= 60) { + return '0-60'; + } + if (count <= 300) { + return '61-300'; + } + if (count <= 1000) { + return '301-1000'; + } + if (count <= 2500) { + return '1001-2500'; + } + if (count <= 5000) { + return '2501-5000'; + } + if (count <= 10000) { + return '5001-10000'; + } + return '10000+'; +} + function sendPoliciesContext() { if (!policies || !session?.email || !activePolicyID) { return; } const activePolicies = getActivePolicies(policies, session.email).map((policy) => policy.id); + const policiesCountBucket = bucketPolicyCount(activePolicies.length); Sentry.setTag(CONST.TELEMETRY.TAG_ACTIVE_POLICY, activePolicyID); - Sentry.setMeasurement(CONST.TELEMETRY.TAG_POLICIES_COUNT, activePolicies.length, 'none') + Sentry.setTag(CONST.TELEMETRY.TAG_POLICIES_COUNT, policiesCountBucket); Sentry.setContext(CONST.TELEMETRY.CONTEXT_POLICIES, {activePolicyID, activePolicies}); } @@ -91,8 +145,6 @@ function sendTryNewDotCohortTag() { } function sendReportsCountTag(reportsCount: number) { - if (!reportsCount) { - return; - } - Sentry.setMeasurement(CONST.TELEMETRY.TAG_REPORTS_COUNT, reportsCount, 'none') + const reportsCountBucket = bucketReportCount(reportsCount); + Sentry.setTag(CONST.TELEMETRY.TAG_REPORTS_COUNT, reportsCountBucket); } \ No newline at end of file diff --git a/src/libs/telemetry/middlewares/copyTagsToChildSpans.ts b/src/libs/telemetry/middlewares/copyTagsToChildSpans.ts new file mode 100644 index 0000000000000..79045c78be3ae --- /dev/null +++ b/src/libs/telemetry/middlewares/copyTagsToChildSpans.ts @@ -0,0 +1,46 @@ +import CONST from '@src/CONST'; +import type {TelemetryBeforeSend} from './index'; + +/** + * List of tags that should be copied from the transaction to all child spans + */ +const TAGS_TO_COPY = [ + CONST.TELEMETRY.TAG_POLICIES_COUNT, + CONST.TELEMETRY.TAG_REPORTS_COUNT, + CONST.TELEMETRY.TAG_ACTIVE_POLICY, + CONST.TELEMETRY.TAG_NUDGE_MIGRATION_COHORT, +] as const; + +/** + * Middleware that copies specific tags from the transaction event to all child spans. + * This ensures that child spans inherit important context from the parent transaction. + */ +const copyTagsToChildSpans: TelemetryBeforeSend = (event) => { + if (!event.spans || event.spans.length === 0) { + return event; + } + + if (!event.tags) { + return event; + } + + const spans = event.spans.map((span) => { + const updatedTags: Record = {}; + + for (const tagKey of TAGS_TO_COPY) { + const tagValue = event.tags?.[tagKey]; + if (tagValue !== undefined) { + updatedTags[tagKey] = tagValue; + } + } + + return { + ...span, + tags: updatedTags, + }; + }); + + return {...event, spans}; +}; + +export default copyTagsToChildSpans; diff --git a/src/libs/telemetry/middlewares/index.ts b/src/libs/telemetry/middlewares/index.ts index 290a2855995ef..f6fcf9e54ff33 100644 --- a/src/libs/telemetry/middlewares/index.ts +++ b/src/libs/telemetry/middlewares/index.ts @@ -1,11 +1,12 @@ import type {EventHint, TransactionEvent} from '@sentry/core'; +import copyTagsToChildSpans from './copyTagsToChildSpans'; import emailDomainFilter from './emailDomainFilter'; import firebasePerformanceFilter from './firebasePerformanceFilter'; import minDurationFilter from './minDurationFilter'; type TelemetryBeforeSend = (event: TransactionEvent, hint: EventHint) => TransactionEvent | null | Promise; -const middlewares: TelemetryBeforeSend[] = [emailDomainFilter, firebasePerformanceFilter, minDurationFilter]; +const middlewares: TelemetryBeforeSend[] = [emailDomainFilter, firebasePerformanceFilter, minDurationFilter, copyTagsToChildSpans]; function processBeforeSendTransactions(event: TransactionEvent, hint: EventHint): Promise { return middlewares.reduce( From b802b826fd418544e005f7800904e17578724fa5 Mon Sep 17 00:00:00 2001 From: Hubert Sosinski Date: Fri, 30 Jan 2026 16:17:03 -0800 Subject: [PATCH 3/3] Set cohort tags for reports and policies --- src/libs/telemetry/TelemetrySynchronizer.ts | 2 +- src/libs/telemetry/middlewares/copyTagsToChildSpans.ts | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/libs/telemetry/TelemetrySynchronizer.ts b/src/libs/telemetry/TelemetrySynchronizer.ts index 408c7ab38a0b6..b876ec63e055a 100644 --- a/src/libs/telemetry/TelemetrySynchronizer.ts +++ b/src/libs/telemetry/TelemetrySynchronizer.ts @@ -147,4 +147,4 @@ function sendTryNewDotCohortTag() { function sendReportsCountTag(reportsCount: number) { const reportsCountBucket = bucketReportCount(reportsCount); Sentry.setTag(CONST.TELEMETRY.TAG_REPORTS_COUNT, reportsCountBucket); -} \ No newline at end of file +} diff --git a/src/libs/telemetry/middlewares/copyTagsToChildSpans.ts b/src/libs/telemetry/middlewares/copyTagsToChildSpans.ts index 79045c78be3ae..057c31f654929 100644 --- a/src/libs/telemetry/middlewares/copyTagsToChildSpans.ts +++ b/src/libs/telemetry/middlewares/copyTagsToChildSpans.ts @@ -4,12 +4,7 @@ import type {TelemetryBeforeSend} from './index'; /** * List of tags that should be copied from the transaction to all child spans */ -const TAGS_TO_COPY = [ - CONST.TELEMETRY.TAG_POLICIES_COUNT, - CONST.TELEMETRY.TAG_REPORTS_COUNT, - CONST.TELEMETRY.TAG_ACTIVE_POLICY, - CONST.TELEMETRY.TAG_NUDGE_MIGRATION_COHORT, -] as const; +const TAGS_TO_COPY = [CONST.TELEMETRY.TAG_POLICIES_COUNT, CONST.TELEMETRY.TAG_REPORTS_COUNT, CONST.TELEMETRY.TAG_ACTIVE_POLICY, CONST.TELEMETRY.TAG_NUDGE_MIGRATION_COHORT] as const; /** * Middleware that copies specific tags from the transaction event to all child spans.