Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(projects): Show App Hang texts for Apple #87262

Merged
merged 2 commits into from
Mar 19, 2025
Merged
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
6 changes: 3 additions & 3 deletions static/app/views/projectDetail/projectCharts.spec.tsx
Original file line number Diff line number Diff line change
@@ -87,7 +87,7 @@ describe('ProjectDetail > ProjectCharts', () => {
screen.getByRole('button', {name: 'Display Crash Free Sessions'})
);

expect(screen.getByText('ANR Rate')).toBeInTheDocument();
expect(screen.getByText('App Hang Rate')).toBeInTheDocument();
expect(screen.queryByText('Foreground ANR Rate')).not.toBeInTheDocument();
});

@@ -98,7 +98,7 @@ describe('ProjectDetail > ProjectCharts', () => {
screen.getByRole('button', {name: 'Display Crash Free Sessions'})
);

expect(screen.getByText('ANR Rate')).toBeInTheDocument();
expect(screen.getByText('App Hang Rate')).toBeInTheDocument();
expect(screen.queryByText('Foreground ANR Rate')).not.toBeInTheDocument();
});

@@ -109,7 +109,7 @@ describe('ProjectDetail > ProjectCharts', () => {
screen.getByRole('button', {name: 'Display Crash Free Sessions'})
);

expect(screen.queryByText('ANR Rate')).not.toBeInTheDocument();
expect(screen.queryByText('App Hang Rate')).not.toBeInTheDocument();
expect(screen.queryByText('Foreground ANR Rate')).not.toBeInTheDocument();
});

10 changes: 7 additions & 3 deletions static/app/views/projectDetail/projectCharts.tsx
Original file line number Diff line number Diff line change
@@ -38,6 +38,7 @@ import {decodeScalar} from 'sentry/utils/queryString';
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
import withApi from 'sentry/utils/withApi';
import {
getANRRateText,
isPlatformANRCompatible,
isPlatformForegroundANRCompatible,
} from 'sentry/views/projectDetail/utils';
@@ -225,7 +226,7 @@ class ProjectCharts extends Component<Props, State> {
const anrRateOptions = [
{
value: DisplayModes.ANR_RATE,
label: t('ANR Rate'),
label: getANRRateText(project?.platform),
disabled:
this.otherActiveDisplayModes.includes(DisplayModes.ANR_RATE) || !hasSessions,
tooltip: hasSessions ? undefined : noHealthTooltip,
@@ -449,8 +450,11 @@ class ProjectCharts extends Component<Props, State> {
)}
{hasAnrRateFeature && displayMode === DisplayModes.ANR_RATE && (
<ProjectBaseSessionsChart
title={t('ANR Rate')}
help={getSessionTermDescription(SessionTerm.ANR_RATE, null)}
title={getANRRateText(project?.platform)}
help={getSessionTermDescription(
SessionTerm.ANR_RATE,
project?.platform || null
)}
api={api}
organization={organization}
onTotalValuesChange={this.handleTotalValuesChange}
Original file line number Diff line number Diff line change
@@ -11,12 +11,14 @@ import {URL_PARAM} from 'sentry/constants/pageFilters';
import {t} from 'sentry/locale';
import type {PageFilters} from 'sentry/types/core';
import type {Organization, SessionApiResponse} from 'sentry/types/organization';
import type {PlatformKey} from 'sentry/types/project';
import {defined} from 'sentry/utils';
import {trackAnalytics} from 'sentry/utils/analytics';
import {getPeriod} from 'sentry/utils/duration/getPeriod';
import useApi from 'sentry/utils/useApi';
import {BigNumberWidgetVisualization} from 'sentry/views/dashboards/widgets/bigNumberWidget/bigNumberWidgetVisualization';
import {Widget} from 'sentry/views/dashboards/widgets/widget/widget';
import {getANRIssueQueryText, getANRRateText} from 'sentry/views/projectDetail/utils';
import {
getSessionTermDescription,
SessionTerm,
@@ -27,6 +29,7 @@ type Props = {
location: Location;
organization: Organization;
selection: PageFilters;
platform?: PlatformKey;
query?: string;
};

@@ -36,6 +39,7 @@ export function ProjectAnrScoreCard({
selection,
location,
query,
platform,
}: Props) {
const {environments, projects, datetime} = selection;
const {start, end, period} = datetime;
@@ -128,7 +132,7 @@ export function ProjectAnrScoreCard({

const endpointPath = `/organizations/${organization.slug}/issues/`;

const issueQuery = ['mechanism:[ANR,AppExitInfo]', query].join(' ').trim();
const issueQuery = [getANRIssueQueryText(platform), query].join(' ').trim();

const queryParams = {
...normalizeDateTimeParams(pick(location.query, [...Object.values(URL_PARAM)])),
@@ -141,9 +145,8 @@ export function ProjectAnrScoreCard({
query: queryParams,
};

const cardTitle = t('ANR Rate');

const cardHelp = getSessionTermDescription(SessionTerm.ANR_RATE, null);
const cardTitle = getANRRateText(platform);
const cardHelp = getSessionTermDescription(SessionTerm.ANR_RATE, platform || null);

const Title = <Widget.WidgetTitle title={cardTitle} />;

Original file line number Diff line number Diff line change
@@ -68,6 +68,7 @@ function ProjectScoreCards({
isProjectStabilized={isProjectStabilized}
query={query}
location={location}
platform={project?.platform}
/>
) : (
<ProjectApdexScoreCard
42 changes: 41 additions & 1 deletion static/app/views/projectDetail/utils.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import {isPlatformANRCompatible, isPlatformForegroundANRCompatible} from './utils';
import {
getANRIssueQueryText,
getANRRateText,
isPlatformANRCompatible,
isPlatformForegroundANRCompatible,
} from './utils';

describe('ProjectDetail Utils', function () {
describe('isPlatformANRCompatible', function () {
@@ -50,4 +55,39 @@ describe('ProjectDetail Utils', function () {
expect(isPlatformForegroundANRCompatible(undefined)).toBe(false);
});
});

describe('getANRRateText', function () {
it('returns "App Hang Rate" for apple platforms', function () {
expect(getANRRateText('apple')).toBe('App Hang Rate');
expect(getANRRateText('apple-ios')).toBe('App Hang Rate');
});

it('returns "ANR Rate" for other platforms', function () {
expect(getANRRateText('apple-macos')).toBe('ANR Rate');
expect(getANRRateText('android')).toBe('ANR Rate');
expect(getANRRateText('javascript-electron')).toBe('ANR Rate');
expect(getANRRateText('python')).toBe('ANR Rate');
expect(getANRRateText(undefined)).toBe('ANR Rate');
});
});

describe('getANRIssueQueryText', () => {
it('returns correct query text for apple platforms', () => {
expect(getANRIssueQueryText('apple')).toBe(
'error.type:["Fatal App Hang Fully Blocked","Fatal App Hang Non Fully Blocked"]'
);
expect(getANRIssueQueryText('apple-ios')).toBe(
'error.type:["Fatal App Hang Fully Blocked","Fatal App Hang Non Fully Blocked"]'
);
});

it('returns correct query text for android platform', () => {
expect(getANRIssueQueryText('android')).toBe('mechanism:[ANR,AppExitInfo]');
});

it('returns correct query text for other platforms', () => {
expect(getANRIssueQueryText('windows')).toBe('mechanism:[ANR,AppExitInfo]');
expect(getANRIssueQueryText()).toBe('mechanism:[ANR,AppExitInfo]');
});
});
});
31 changes: 27 additions & 4 deletions static/app/views/projectDetail/utils.tsx
Original file line number Diff line number Diff line change
@@ -13,14 +13,37 @@ export function isPlatformANRCompatible(platform?: PlatformKey, features?: strin
if (isPlatformForegroundANRCompatible(platform)) {
return true;
}
if (platform === 'apple' || platform === 'apple-ios') {
if (features?.includes('project-detail-apple-app-hang-rate')) {
return true;
}
if (
isAppHangPlatform(platform) &&
features?.includes('project-detail-apple-app-hang-rate')
) {
return true;
}
return false;
}

export function isPlatformForegroundANRCompatible(platform?: PlatformKey) {
return platform === 'javascript-electron' || platform === 'android';
}

export function getANRRateText(platform?: PlatformKey) {
if (isAppHangPlatform(platform)) {
return 'App Hang Rate';
}

return 'ANR Rate';
}

export function getANRIssueQueryText(platform?: PlatformKey) {
if (isAppHangPlatform(platform)) {
return 'error.type:["Fatal App Hang Fully Blocked","Fatal App Hang Non Fully Blocked"]';
}

return 'mechanism:[ANR,AppExitInfo]';
}

// Don't include apple-macos because the ANR rate requires app hangs V2,
// which is not available on macos, when writing this comment on 2025-03-17.
const isAppHangPlatform = (platform?: PlatformKey): boolean => {
return platform === 'apple' || platform === 'apple-ios';
};
9 changes: 9 additions & 0 deletions static/app/views/releases/utils/sessionTerm.spec.tsx
Original file line number Diff line number Diff line change
@@ -470,6 +470,15 @@ describe('Release Health Session Term', function () {
'apple-macos'
);
expect(unhandledSessionTerm).toEqual(mobileTermsDescription.unhandled);

// ANR Rate
const anrRateSessionTerm = getSessionTermDescription(
SessionTerm.ANR_RATE,
'apple-ios'
);
expect(anrRateSessionTerm).toBe(
'Percentage of unique users that experienced a fatal App Hang.'
);
});

it('node-express terms', function () {
3 changes: 3 additions & 0 deletions static/app/views/releases/utils/sessionTerm.tsx
Original file line number Diff line number Diff line change
@@ -123,6 +123,9 @@ function getTermDescriptions(platform: PlatformKey | null) {
...commonTermsDescription,
...mobileTermsDescription,
[SessionTerm.CRASHED]: t('An error that resulted in the application crashing'),
[SessionTerm.ANR_RATE]: t(
'Percentage of unique users that experienced a fatal App Hang.'
),
};
}
case 'node':