From 15be14244ef0020a7f5b04c8b1a9260cdcf9ce08 Mon Sep 17 00:00:00 2001 From: Kosty Maleyev Date: Fri, 23 Oct 2026 17:24:06 -0700 Subject: [PATCH 1/2] feat(stats): Show paused data as Paused Show data that is dropped because of a project category pause or an organization pause as Paused on the Stats page, instead of Internal. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Gvaif9ogukYjqzpAPLX4Bd --- .../app/views/organizationStats/getReasonGroupName.spec.ts | 4 ++++ static/app/views/organizationStats/getReasonGroupName.ts | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/static/app/views/organizationStats/getReasonGroupName.spec.ts b/static/app/views/organizationStats/getReasonGroupName.spec.ts index af7a02c232f4..9056164e8813 100644 --- a/static/app/views/organizationStats/getReasonGroupName.spec.ts +++ b/static/app/views/organizationStats/getReasonGroupName.spec.ts @@ -88,6 +88,10 @@ describe('getReasonGroupName', () => { expect(getReasonGroupName(Outcome.RATE_LIMITED, 'key_quota')).toBe('DSN limit'); expect(getReasonGroupName(Outcome.RATE_LIMITED, 'org_quota')).toBe('global limit'); + expect(getReasonGroupName(Outcome.RATE_LIMITED, 'project_category_pause')).toBe( + 'paused' + ); + expect(getReasonGroupName(Outcome.RATE_LIMITED, 'organization_pause')).toBe('paused'); expect(getReasonGroupName(Outcome.FILTERED, 'browser-extensions')).toBe( 'Browser Extensions' diff --git a/static/app/views/organizationStats/getReasonGroupName.ts b/static/app/views/organizationStats/getReasonGroupName.ts index 75a37c347ec8..2600873ac7ee 100644 --- a/static/app/views/organizationStats/getReasonGroupName.ts +++ b/static/app/views/organizationStats/getReasonGroupName.ts @@ -97,6 +97,8 @@ enum RateLimitedReason { PROJECT_ABUSE_LIMIT = 'project_abuse_limit', ORG_ABUSE_LIMIT = 'org_abuse_limit', GLOBAL_ABUSE_LIMIT = 'global_abuse_limit', + PROJECT_CATEGORY_PAUSE = 'project_category_pause', + ORGANIZATION_PAUSE = 'organization_pause', } // Invalid reasons should not be exposed directly, but instead in the following groups: @@ -225,6 +227,9 @@ function getRateLimitedReasonGroupName(reason: RateLimitedReason | string): stri case RateLimitedReason.SPIKE_PROTECTION: case RateLimitedReason.SMART_RATE_LIMIT: return 'spike protection'; + case RateLimitedReason.PROJECT_CATEGORY_PAUSE: + case RateLimitedReason.ORGANIZATION_PAUSE: + return 'paused'; case RateLimitedReason.PROJECT_ABUSE_LIMIT: return 'project abuse limit'; case RateLimitedReason.ORG_ABUSE_LIMIT: From 8bbd5e2217e6405e877702dd6142c7cd46509cf0 Mon Sep 17 00:00:00 2001 From: Kosty Maleyev Date: Fri, 23 Oct 2026 17:33:40 -0700 Subject: [PATCH 2/2] feat(stats): Name the pause reasons after solutions.sentry.io Show project category pauses as Pause (solutions.sentry.io) and organization pauses as Org Pause (solutions.sentry.io), without title case. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Gvaif9ogukYjqzpAPLX4Bd --- .../getReasonGroupName.spec.ts | 6 ++-- .../organizationStats/getReasonGroupName.ts | 12 ++++++- .../mapSeriesToChart.spec.ts | 31 +++++++++++++++++++ .../organizationStats/mapSeriesToChart.ts | 6 ++-- 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/static/app/views/organizationStats/getReasonGroupName.spec.ts b/static/app/views/organizationStats/getReasonGroupName.spec.ts index 9056164e8813..58c75531502c 100644 --- a/static/app/views/organizationStats/getReasonGroupName.spec.ts +++ b/static/app/views/organizationStats/getReasonGroupName.spec.ts @@ -89,9 +89,11 @@ describe('getReasonGroupName', () => { expect(getReasonGroupName(Outcome.RATE_LIMITED, 'key_quota')).toBe('DSN limit'); expect(getReasonGroupName(Outcome.RATE_LIMITED, 'org_quota')).toBe('global limit'); expect(getReasonGroupName(Outcome.RATE_LIMITED, 'project_category_pause')).toBe( - 'paused' + 'Pause (solutions.sentry.io)' + ); + expect(getReasonGroupName(Outcome.RATE_LIMITED, 'organization_pause')).toBe( + 'Org Pause (solutions.sentry.io)' ); - expect(getReasonGroupName(Outcome.RATE_LIMITED, 'organization_pause')).toBe('paused'); expect(getReasonGroupName(Outcome.FILTERED, 'browser-extensions')).toBe( 'Browser Extensions' diff --git a/static/app/views/organizationStats/getReasonGroupName.ts b/static/app/views/organizationStats/getReasonGroupName.ts index 2600873ac7ee..d21ef7367eb4 100644 --- a/static/app/views/organizationStats/getReasonGroupName.ts +++ b/static/app/views/organizationStats/getReasonGroupName.ts @@ -101,6 +101,15 @@ enum RateLimitedReason { ORGANIZATION_PAUSE = 'organization_pause', } +const PROJECT_CATEGORY_PAUSE_NAME = 'Pause (solutions.sentry.io)'; +const ORGANIZATION_PAUSE_NAME = 'Org Pause (solutions.sentry.io)'; + +// Group names that are shown as written, without title case. +export const VERBATIM_REASON_GROUP_NAMES = new Set([ + PROJECT_CATEGORY_PAUSE_NAME, + ORGANIZATION_PAUSE_NAME, +]); + // Invalid reasons should not be exposed directly, but instead in the following groups: const invalidReasonsGroup: Record = { duplicate: [DiscardReason.DUPLICATE], @@ -228,8 +237,9 @@ function getRateLimitedReasonGroupName(reason: RateLimitedReason | string): stri case RateLimitedReason.SMART_RATE_LIMIT: return 'spike protection'; case RateLimitedReason.PROJECT_CATEGORY_PAUSE: + return PROJECT_CATEGORY_PAUSE_NAME; case RateLimitedReason.ORGANIZATION_PAUSE: - return 'paused'; + return ORGANIZATION_PAUSE_NAME; case RateLimitedReason.PROJECT_ABUSE_LIMIT: return 'project abuse limit'; case RateLimitedReason.ORG_ABUSE_LIMIT: diff --git a/static/app/views/organizationStats/mapSeriesToChart.spec.ts b/static/app/views/organizationStats/mapSeriesToChart.spec.ts index f7dc9dbe1c4b..fcf0847beb95 100644 --- a/static/app/views/organizationStats/mapSeriesToChart.spec.ts +++ b/static/app/views/organizationStats/mapSeriesToChart.spec.ts @@ -220,6 +220,37 @@ describe('mapSeriesToChart func', () => { expect(mappedSeries.cardStats.rateLimited).toBe('11'); }); + it('shows pause reasons as written', () => { + const mappedSeries = mapSeriesToChart({ + orgStats: { + start: '2021-01-01T00:00:00Z', + end: '2021-01-07T00:00:00Z', + intervals: ['2021-01-01T00:00:00Z'], + groups: [ + { + by: {outcome: 'rate_limited', reason: 'project_category_pause'}, + totals: {'sum(quantity)': 1}, + series: {'sum(quantity)': [1]}, + }, + { + by: {outcome: 'rate_limited', reason: 'organization_pause'}, + totals: {'sum(quantity)': 2}, + series: {'sum(quantity)': [2]}, + }, + ], + }, + chartDateInterval: '1h', + chartDateUtc: true, + dataCategory: DataCategory.ERRORS, + endpointQuery: {}, + }); + + expect(mappedSeries.chartSubLabels.map(subLabel => subLabel.label)).toEqual([ + 'Pause (solutions.sentry.io)', + 'Org Pause (solutions.sentry.io)', + ]); + }); + it('excludes no_parent_span from client discard data', () => { const mappedSeries = mapSeriesToChart({ orgStats: { diff --git a/static/app/views/organizationStats/mapSeriesToChart.ts b/static/app/views/organizationStats/mapSeriesToChart.ts index b3c6118c4917..d36ba7bfba94 100644 --- a/static/app/views/organizationStats/mapSeriesToChart.ts +++ b/static/app/views/organizationStats/mapSeriesToChart.ts @@ -7,7 +7,7 @@ import type {DataCategory, IntervalPeriod} from 'sentry/types/core'; import {Outcome} from 'sentry/types/core'; import {getDateFromMoment} from './usageChart/utils'; -import {getReasonGroupName} from './getReasonGroupName'; +import {getReasonGroupName, VERBATIM_REASON_GROUP_NAMES} from './getReasonGroupName'; import type {UsageSeries, UsageStat} from './types'; import type {ChartStats} from './usageChart'; import {SeriesTypes} from './usageChart'; @@ -127,7 +127,9 @@ export function mapSeriesToChart({ // Function to handle chart sub-label updates const updateChartSubLabels = ( parentLabel: SeriesTypes, - label = startCase(reason.replace(/-|_/g, ' ')) + label = VERBATIM_REASON_GROUP_NAMES.has(reason) + ? reason + : startCase(reason.replace(/-|_/g, ' ')) ) => { const existingSubLabel = chartSubLabels.find( subLabel => subLabel.label === label && subLabel.parentLabel === parentLabel