Skip to content

Commit 182a789

Browse files
feat(projects): Show ANR rate for Apple projects
Show the ANR rate for Apple projects and hide the foreground ANR rate. It's acceptable to still call it ANR rate, because the feature is hidden behind a feature flag. We will update the descriptions in an upcoming PR to rename ANR rate to App Hang rate, see GH-86666. Fixes GH-86665
1 parent d36d037 commit 182a789

File tree

5 files changed

+141
-31
lines changed

5 files changed

+141
-31
lines changed

src/sentry/features/temporary.py

+4
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,10 @@ def register_temporary_features(manager: FeatureManager):
522522
manager.add("projects:plugins", ProjectPluginFeature, FeatureHandlerStrategy.INTERNAL, default=True, api_expose=True)
523523

524524
manager.add("projects:profiling-ingest-unsampled-profiles", ProjectFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
525+
526+
# Enable apple app hang rate
527+
manager.add("projects:project-detail-apple-app-hang-rate", ProjectFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
528+
525529
# fmt: on
526530

527531
# Partner oauth

static/app/views/projectDetail/projectCharts.spec.tsx

+45-9
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrar
77
import type {PlatformKey} from 'sentry/types/project';
88
import ProjectCharts from 'sentry/views/projectDetail/projectCharts';
99

10-
function renderProjectCharts(platform?: PlatformKey, chartDisplay?: string) {
10+
function renderProjectCharts(
11+
platform?: PlatformKey,
12+
chartDisplay?: string,
13+
features?: [string]
14+
) {
1115
const {organization, router, project} = initializeOrg({
1216
organization: OrganizationFixture(),
13-
projects: [{platform}],
17+
projects: [{platform, features}],
1418
router: {
1519
params: {orgId: 'org-slug', projectId: 'project-slug'},
1620
location: {
@@ -76,6 +80,43 @@ describe('ProjectDetail > ProjectCharts', () => {
7680
expect(screen.getByText('ANR Rate')).toBeInTheDocument();
7781
});
7882

83+
it('renders App Hang options for apple projects when the feature flag is enabled', async () => {
84+
renderProjectCharts('apple', undefined, [
85+
'projects:project-detail-apple-app-hang-rate',
86+
]);
87+
88+
await userEvent.click(
89+
screen.getByRole('button', {name: 'Display Crash Free Sessions'})
90+
);
91+
92+
expect(screen.getByText('ANR Rate')).toBeInTheDocument();
93+
expect(screen.queryByText('Foreground ANR Rate')).not.toBeInTheDocument();
94+
});
95+
96+
it('renders App Hang options for apple-ios projects when the feature flag is enabled', async () => {
97+
renderProjectCharts('apple-ios', undefined, [
98+
'projects:project-detail-apple-app-hang-rate',
99+
]);
100+
101+
await userEvent.click(
102+
screen.getByRole('button', {name: 'Display Crash Free Sessions'})
103+
);
104+
105+
expect(screen.getByText('ANR Rate')).toBeInTheDocument();
106+
expect(screen.queryByText('Foreground ANR Rate')).not.toBeInTheDocument();
107+
});
108+
109+
it('does not render App Hang options for apple-ios projects when the feature flag is disabled', async () => {
110+
renderProjectCharts('apple-ios', undefined);
111+
112+
await userEvent.click(
113+
screen.getByRole('button', {name: 'Display Crash Free Sessions'})
114+
);
115+
116+
expect(screen.queryByText('ANR Rate')).not.toBeInTheDocument();
117+
expect(screen.queryByText('Foreground ANR Rate')).not.toBeInTheDocument();
118+
});
119+
79120
it('does not render ANR options for non-compatible platforms', async () => {
80121
renderProjectCharts('python');
81122

@@ -109,10 +150,7 @@ describe('ProjectDetail > ProjectCharts', () => {
109150
groups: [
110151
{
111152
by: {},
112-
totals: {
113-
'anr_rate()': 492,
114-
'count_unique(user)': 3,
115-
},
153+
totals: {'anr_rate()': 492, 'count_unique(user)': 3},
116154
series: {
117155
'anr_rate()': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 490],
118156
'count_unique(user)': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1],
@@ -132,9 +170,7 @@ describe('ProjectDetail > ProjectCharts', () => {
132170
expect(mockSessions).toHaveBeenCalledWith(
133171
'/organizations/org-slug/sessions/',
134172
expect.objectContaining({
135-
query: expect.objectContaining({
136-
field: ['anr_rate()', 'count_unique(user)'],
137-
}),
173+
query: expect.objectContaining({field: ['anr_rate()', 'count_unique(user)']}),
138174
})
139175
)
140176
);

static/app/views/projectDetail/projectCharts.tsx

+38-21
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ import {DiscoverDatasets} from 'sentry/utils/discover/types';
3737
import {decodeScalar} from 'sentry/utils/queryString';
3838
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
3939
import withApi from 'sentry/utils/withApi';
40-
import {isPlatformANRCompatible} from 'sentry/views/projectDetail/utils';
40+
import {
41+
isPlatformANRCompatible,
42+
isPlatformForegroundANRCompatible,
43+
} from 'sentry/views/projectDetail/utils';
4144
import {
4245
getSessionTermDescription,
4346
SessionTerm,
@@ -95,7 +98,7 @@ class ProjectCharts extends Component<Props, State> {
9598
}
9699

97100
if (hasSessions && !hasTransactions) {
98-
if (isPlatformANRCompatible(project?.platform)) {
101+
if (isPlatformANRCompatible(project?.platform, project?.features)) {
99102
return [DisplayModes.STABILITY, DisplayModes.ANR_RATE];
100103
}
101104
return [DisplayModes.STABILITY, DisplayModes.ERRORS];
@@ -105,7 +108,7 @@ class ProjectCharts extends Component<Props, State> {
105108
return [DisplayModes.FAILURE_RATE, DisplayModes.APDEX];
106109
}
107110

108-
if (isPlatformANRCompatible(project?.platform)) {
111+
if (isPlatformANRCompatible(project?.platform, project?.features)) {
109112
return [DisplayModes.STABILITY, DisplayModes.ANR_RATE];
110113
}
111114

@@ -218,25 +221,29 @@ class ProjectCharts extends Component<Props, State> {
218221
},
219222
];
220223

221-
if (isPlatformANRCompatible(project?.platform)) {
222-
return [
224+
if (isPlatformANRCompatible(project?.platform, project?.features)) {
225+
const anrRateOptions = [
223226
{
224227
value: DisplayModes.ANR_RATE,
225228
label: t('ANR Rate'),
226229
disabled:
227230
this.otherActiveDisplayModes.includes(DisplayModes.ANR_RATE) || !hasSessions,
228231
tooltip: !hasSessions ? noHealthTooltip : undefined,
229232
},
230-
{
233+
];
234+
235+
if (isPlatformForegroundANRCompatible(project?.platform)) {
236+
anrRateOptions.push({
231237
value: DisplayModes.FOREGROUND_ANR_RATE,
232238
label: t('Foreground ANR Rate'),
233239
disabled:
234240
this.otherActiveDisplayModes.includes(DisplayModes.FOREGROUND_ANR_RATE) ||
235241
!hasSessions,
236242
tooltip: !hasSessions ? noHealthTooltip : undefined,
237-
},
238-
...options,
239-
];
243+
});
244+
}
245+
246+
return [...anrRateOptions, ...options];
240247
}
241248

242249
return options;
@@ -319,7 +326,13 @@ class ProjectCharts extends Component<Props, State> {
319326
const {totalValues} = this.state;
320327
const hasDiscover = organization.features.includes('discover-basic');
321328
const displayMode = this.displayMode;
322-
const hasAnrRateFeature = isPlatformANRCompatible(project?.platform);
329+
const hasAnrRateFeature = isPlatformANRCompatible(
330+
project?.platform,
331+
project?.features
332+
);
333+
const hasAnrForegroundRateFeature = isPlatformForegroundANRCompatible(
334+
project?.platform
335+
);
323336

324337
return (
325338
<Panel>
@@ -447,17 +460,21 @@ class ProjectCharts extends Component<Props, State> {
447460
query={query}
448461
/>
449462
)}
450-
{hasAnrRateFeature && displayMode === DisplayModes.FOREGROUND_ANR_RATE && (
451-
<ProjectBaseSessionsChart
452-
title={t('Foreground ANR Rate')}
453-
help={getSessionTermDescription(SessionTerm.FOREGROUND_ANR_RATE, null)}
454-
api={api}
455-
organization={organization}
456-
onTotalValuesChange={this.handleTotalValuesChange}
457-
displayMode={displayMode}
458-
query={query}
459-
/>
460-
)}
463+
{hasAnrForegroundRateFeature &&
464+
displayMode === DisplayModes.FOREGROUND_ANR_RATE && (
465+
<ProjectBaseSessionsChart
466+
title={t('Foreground ANR Rate')}
467+
help={getSessionTermDescription(
468+
SessionTerm.FOREGROUND_ANR_RATE,
469+
null
470+
)}
471+
api={api}
472+
organization={organization}
473+
onTotalValuesChange={this.handleTotalValuesChange}
474+
displayMode={displayMode}
475+
query={query}
476+
/>
477+
)}
461478
{displayMode === DisplayModes.STABILITY_USERS && (
462479
<ProjectBaseSessionsChart
463480
title={t('Crash Free Users')}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {isPlatformANRCompatible, isPlatformForegroundANRCompatible} from './utils';
2+
3+
describe('ProjectDetail Utils', function () {
4+
describe('isPlatformANRCompatible', function () {
5+
it('returns true for compatible platforms', function () {
6+
expect(isPlatformANRCompatible('javascript-electron')).toBe(true);
7+
expect(isPlatformANRCompatible('android')).toBe(true);
8+
expect(
9+
isPlatformANRCompatible('apple', ['projects:project-detail-apple-app-hang-rate'])
10+
).toBe(true);
11+
expect(
12+
isPlatformANRCompatible('apple-ios', [
13+
'projects:project-detail-apple-app-hang-rate',
14+
])
15+
).toBe(true);
16+
});
17+
18+
it('returns false for incompatible platforms', function () {
19+
expect(isPlatformANRCompatible('python')).toBe(false);
20+
expect(isPlatformANRCompatible('node')).toBe(false);
21+
expect(isPlatformANRCompatible(undefined)).toBe(false);
22+
expect(isPlatformANRCompatible('apple-macos')).toBe(false);
23+
expect(isPlatformANRCompatible('apple-ios')).toBe(false);
24+
});
25+
});
26+
27+
describe('isPlatformForegroundANRCompatible', function () {
28+
it('returns true for compatible platforms', function () {
29+
expect(isPlatformForegroundANRCompatible('javascript-electron')).toBe(true);
30+
expect(isPlatformForegroundANRCompatible('android')).toBe(true);
31+
});
32+
33+
it('returns false for incompatible platforms', function () {
34+
expect(isPlatformForegroundANRCompatible('apple')).toBe(false);
35+
expect(isPlatformForegroundANRCompatible('apple-ios')).toBe(false);
36+
expect(isPlatformForegroundANRCompatible('apple-macos')).toBe(false);
37+
expect(isPlatformForegroundANRCompatible('python')).toBe(false);
38+
expect(isPlatformForegroundANRCompatible(undefined)).toBe(false);
39+
});
40+
});
41+
});

static/app/views/projectDetail/utils.tsx

+13-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@ export function didProjectOrEnvironmentChange(location1: Location, location2: Lo
99
);
1010
}
1111

12-
export function isPlatformANRCompatible(platform?: PlatformKey) {
12+
export function isPlatformANRCompatible(platform?: PlatformKey, features?: string[]) {
13+
if (isPlatformForegroundANRCompatible(platform)) {
14+
return true;
15+
}
16+
if (platform === 'apple' || platform === 'apple-ios') {
17+
if (features?.includes('projects:project-detail-apple-app-hang-rate')) {
18+
return true;
19+
}
20+
}
21+
return false;
22+
}
23+
24+
export function isPlatformForegroundANRCompatible(platform?: PlatformKey) {
1325
return platform === 'javascript-electron' || platform === 'android';
1426
}

0 commit comments

Comments
 (0)