diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 55eaf64d919f1..d2931dbffc04e 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1717,6 +1717,8 @@ const CONST = { CONTEXT_FULLSTORY: 'Fullstory', CONTEXT_POLICIES: 'Policies', TAG_ACTIVE_POLICY: 'active_policy_id', + 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 a8de074dd5d24..b876ec63e055a 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) => { @@ -61,12 +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.setTag(CONST.TELEMETRY.TAG_POLICIES_COUNT, policiesCountBucket); Sentry.setContext(CONST.TELEMETRY.CONTEXT_POLICIES, {activePolicyID, activePolicies}); } @@ -77,3 +143,8 @@ function sendTryNewDotCohortTag() { } Sentry.setTag(CONST.TELEMETRY.TAG_NUDGE_MIGRATION_COHORT, cohort); } + +function sendReportsCountTag(reportsCount: number) { + const reportsCountBucket = bucketReportCount(reportsCount); + Sentry.setTag(CONST.TELEMETRY.TAG_REPORTS_COUNT, reportsCountBucket); +} diff --git a/src/libs/telemetry/middlewares/copyTagsToChildSpans.ts b/src/libs/telemetry/middlewares/copyTagsToChildSpans.ts new file mode 100644 index 0000000000000..057c31f654929 --- /dev/null +++ b/src/libs/telemetry/middlewares/copyTagsToChildSpans.ts @@ -0,0 +1,41 @@ +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(