Skip to content

Commit a3d9526

Browse files
authored
ref(insights): use useSpanMetrics in mobile vitals landing (#89060)
use `useSpanMetrics` and `useMetrics` in mobile vitals landing as these hooks have the conditional to query eap or the original dataset and are completely typesafe. Closes DAIN-186
1 parent 463ab74 commit a3d9526

File tree

3 files changed

+87
-109
lines changed

3 files changed

+87
-109
lines changed

static/app/views/insights/mobile/screens/utils.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {DURATION_UNITS} from 'sentry/utils/discover/fieldRenderers';
22
import type {DiscoverDatasets} from 'sentry/utils/discover/types';
33
import getDuration from 'sentry/utils/duration/getDuration';
44
import {formatPercentage} from 'sentry/utils/number/formatPercentage';
5+
import type {MetricsProperty, SpanMetricsProperty} from 'sentry/views/insights/types';
56
import {VitalState} from 'sentry/views/performance/vitalDetail/utils';
67

78
const formatMetricValue = (metric: MetricValue, field?: string | undefined): string => {
@@ -49,18 +50,24 @@ export type VitalStatus = {
4950
value: MetricValue | undefined;
5051
};
5152

52-
export type VitalItem = {
53-
dataset: DiscoverDatasets;
53+
export type GenericVitalItem<
54+
T extends DiscoverDatasets.SPANS_METRICS | DiscoverDatasets.METRICS,
55+
> = {
56+
dataset: T;
5457
description: string;
5558
docs: React.ReactNode;
56-
field: string;
59+
field: T extends DiscoverDatasets.SPANS_METRICS ? SpanMetricsProperty : MetricsProperty;
5760
getStatus: (value: MetricValue, field?: string | undefined) => VitalStatus;
5861
platformDocLinks: Record<string, string>;
5962
sdkDocLinks: Record<string, string>;
6063
setup: React.ReactNode | undefined;
6164
title: string;
6265
};
6366

67+
export type VitalItem =
68+
| GenericVitalItem<DiscoverDatasets.METRICS>
69+
| GenericVitalItem<DiscoverDatasets.SPANS_METRICS>;
70+
6471
export type MetricValue = {
6572
// the field type if defined, e.g. duration
6673
type: string | undefined;

static/app/views/insights/mobile/screens/views/screensLandingPage.tsx

Lines changed: 45 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,17 @@ import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilt
1212
import {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionTooltip';
1313
import {t} from 'sentry/locale';
1414
import {space} from 'sentry/styles/space';
15-
import type {NewQuery} from 'sentry/types/organization';
16-
import {useDiscoverQuery} from 'sentry/utils/discover/discoverQuery';
17-
import EventView from 'sentry/utils/discover/eventView';
1815
import {DiscoverDatasets} from 'sentry/utils/discover/types';
1916
import {PageAlert, PageAlertProvider} from 'sentry/utils/performance/contexts/pageAlert';
2017
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
2118
import {useLocation} from 'sentry/utils/useLocation';
2219
import {useNavigate} from 'sentry/utils/useNavigate';
23-
import useOrganization from 'sentry/utils/useOrganization';
24-
import usePageFilters from 'sentry/utils/usePageFilters';
2520
import {ModulePageProviders} from 'sentry/views/insights/common/components/modulePageProviders';
2621
import {ModuleBodyUpsellHook} from 'sentry/views/insights/common/components/moduleUpsellHookWrapper';
22+
import {
23+
useMetrics,
24+
useSpanMetrics,
25+
} from 'sentry/views/insights/common/queries/useDiscover';
2726
import {useMobileVitalsDrawer} from 'sentry/views/insights/common/utils/useMobileVitalsDrawer';
2827
import useCrossPlatformProject from 'sentry/views/insights/mobile/common/queries/useCrossPlatformProject';
2928
import {PlatformSelector} from 'sentry/views/insights/mobile/screenload/components/platformSelector';
@@ -53,7 +52,6 @@ function ScreensLandingPage() {
5352
const moduleName = ModuleName.MOBILE_VITALS;
5453
const navigate = useNavigate();
5554
const location = useLocation();
56-
const organization = useOrganization();
5755
const {isProjectCrossPlatform, selectedPlatform} = useCrossPlatformProject();
5856

5957
const handleProjectChange = useCallback(() => {
@@ -67,9 +65,8 @@ function ScreensLandingPage() {
6765
{replace: true}
6866
);
6967
}, [location, navigate]);
70-
const {selection} = usePageFilters();
7168

72-
const vitalItems: VitalItem[] = [
69+
const vitalItems = [
7370
{
7471
title: t('Avg. Cold App Start'),
7572
description: t('Average Cold App Start duration'),
@@ -86,7 +83,7 @@ function ScreensLandingPage() {
8683
'https://docs.sentry.io/platforms/android/tracing/instrumentation/automatic-instrumentation/#app-start-instrumentation',
8784
iOS: 'https://docs.sentry.io/platforms/apple/guides/ios/tracing/instrumentation/automatic-instrumentation/#app-start-tracing',
8885
},
89-
field: 'avg(measurements.app_start_cold)',
86+
field: 'avg(measurements.app_start_cold)' as const,
9087
dataset: DiscoverDatasets.METRICS,
9188
getStatus: getColdAppStartPerformance,
9289
},
@@ -106,7 +103,7 @@ function ScreensLandingPage() {
106103
'https://docs.sentry.io/platforms/android/tracing/instrumentation/automatic-instrumentation/#app-start-instrumentation',
107104
iOS: 'https://docs.sentry.io/platforms/apple/guides/ios/tracing/instrumentation/automatic-instrumentation/#app-start-tracing',
108105
},
109-
field: 'avg(measurements.app_start_warm)',
106+
field: 'avg(measurements.app_start_warm)' as const,
110107
dataset: DiscoverDatasets.METRICS,
111108
getStatus: getWarmAppStartPerformance,
112109
},
@@ -123,7 +120,7 @@ function ScreensLandingPage() {
123120
'https://docs.sentry.io/platforms/android/tracing/instrumentation/automatic-instrumentation/#slow-and-frozen-frames',
124121
iOS: 'https://docs.sentry.io/platforms/apple/guides/ios/tracing/instrumentation/automatic-instrumentation/#slow-and-frozen-frames',
125122
},
126-
field: `division(mobile.slow_frames,mobile.total_frames)`,
123+
field: `division(mobile.slow_frames,mobile.total_frames)` as const,
127124
dataset: DiscoverDatasets.SPANS_METRICS,
128125
getStatus: getDefaultMetricPerformance,
129126
},
@@ -140,7 +137,7 @@ function ScreensLandingPage() {
140137
'https://docs.sentry.io/platforms/android/tracing/instrumentation/automatic-instrumentation/#slow-and-frozen-frames',
141138
iOS: 'https://docs.sentry.io/platforms/apple/guides/ios/tracing/instrumentation/automatic-instrumentation/#slow-and-frozen-frames',
142139
},
143-
field: `division(mobile.frozen_frames,mobile.total_frames)`,
140+
field: `division(mobile.frozen_frames,mobile.total_frames)` as const,
144141
dataset: DiscoverDatasets.SPANS_METRICS,
145142
getStatus: getDefaultMetricPerformance,
146143
},
@@ -159,7 +156,7 @@ function ScreensLandingPage() {
159156
'https://docs.sentry.io/platforms/android/tracing/instrumentation/automatic-instrumentation/#slow-and-frozen-frames',
160157
iOS: 'https://docs.sentry.io/platforms/apple/guides/ios/tracing/instrumentation/automatic-instrumentation/#slow-and-frozen-frames',
161158
},
162-
field: `avg(mobile.frames_delay)`,
159+
field: `avg(mobile.frames_delay)` as const,
163160
dataset: DiscoverDatasets.SPANS_METRICS,
164161
getStatus: getDefaultMetricPerformance,
165162
},
@@ -177,7 +174,7 @@ function ScreensLandingPage() {
177174
'https://docs.sentry.io/platforms/android/tracing/instrumentation/automatic-instrumentation/#time-to-initial-display',
178175
iOS: 'https://docs.sentry.io/platforms/apple/features/experimental-features/',
179176
},
180-
field: `avg(measurements.time_to_initial_display)`,
177+
field: `avg(measurements.time_to_initial_display)` as const,
181178
dataset: DiscoverDatasets.METRICS,
182179
getStatus: getDefaultMetricPerformance,
183180
},
@@ -195,98 +192,51 @@ function ScreensLandingPage() {
195192
'https://docs.sentry.io/platforms/android/tracing/instrumentation/automatic-instrumentation/#time-to-full-display',
196193
iOS: 'https://docs.sentry.io/platforms/apple/features/experimental-features/',
197194
},
198-
field: `avg(measurements.time_to_full_display)`,
195+
field: `avg(measurements.time_to_full_display)` as const,
199196
dataset: DiscoverDatasets.METRICS,
200197
getStatus: getDefaultMetricPerformance,
201198
},
202-
];
199+
] satisfies VitalItem[];
200+
201+
const metricsFields = vitalItems
202+
.filter(item => item.dataset === DiscoverDatasets.METRICS)
203+
.map(item => item.field);
204+
205+
const spanMetricsFields = vitalItems
206+
.filter(item => item.dataset === DiscoverDatasets.SPANS_METRICS)
207+
.map(item => item.field);
203208

204-
const metricsFields: string[] = [];
205-
const spanMetricsFields: string[] = [];
206209
const [state, setState] = useState<{
207210
status: VitalStatus | undefined;
208211
vital: VitalItem | undefined;
209212
}>({status: undefined, vital: undefined});
210213

211-
vitalItems.forEach(element => {
212-
if (element.dataset === DiscoverDatasets.METRICS) {
213-
metricsFields.push(element.field);
214-
} else if (element.dataset === DiscoverDatasets.SPANS_METRICS) {
215-
spanMetricsFields.push(element.field);
216-
}
217-
});
218-
219214
const query = new MutableSearch(['transaction.op:ui.load']);
220215
if (isProjectCrossPlatform) {
221216
query.addFilterValue('os.name', selectedPlatform);
222217
}
223-
const metricsQuery: NewQuery = {
224-
name: '',
225-
fields: metricsFields,
226-
query: query.formatString(),
227-
dataset: DiscoverDatasets.METRICS,
228-
version: 2,
229-
projects: selection.projects,
230-
};
231-
const metricsQueryView: EventView = EventView.fromNewQueryWithLocation(
232-
metricsQuery,
233-
location
234-
);
235-
236-
const metricsResult = useDiscoverQuery({
237-
eventView: metricsQueryView,
238-
location,
239-
cursor: '',
240-
orgSlug: organization.slug,
241-
limit: 25,
242-
referrer: Referrer.SCREENS_METRICS,
243-
});
244218

245-
const spanMetricsQuery: NewQuery = {
246-
name: '',
247-
fields: spanMetricsFields,
248-
query: query.formatString(),
249-
dataset: DiscoverDatasets.SPANS_METRICS,
250-
version: 2,
251-
projects: selection.projects,
252-
};
253-
254-
const spanMetricsQueryView = EventView.fromNewQueryWithLocation(
255-
spanMetricsQuery,
256-
location
219+
const metricsResult = useMetrics(
220+
{
221+
search: query,
222+
limit: 25,
223+
fields: metricsFields,
224+
},
225+
Referrer.SCREENS_METRICS
257226
);
258227

259-
const spanMetricsResult = useDiscoverQuery({
260-
eventView: spanMetricsQueryView,
261-
location,
262-
cursor: '',
263-
orgSlug: organization.slug,
264-
limit: 25,
265-
referrer: Referrer.SCREENS_METRICS,
266-
});
267-
268-
const metricValueFor = (item: VitalItem): MetricValue | undefined => {
269-
const dataset =
270-
item.dataset === DiscoverDatasets.METRICS ? metricsResult : spanMetricsResult;
271-
272-
if (dataset.data) {
273-
const row = dataset.data.data[0]!;
274-
const units = dataset.data.meta?.units;
275-
const fieldTypes = dataset.data.meta?.fields;
276-
277-
const value = row?.[item.field];
278-
const unit = units?.[item.field];
279-
const fieldType = fieldTypes?.[item.field];
280-
281-
return {
282-
type: fieldType,
283-
unit,
284-
value,
285-
};
286-
}
228+
const spanMetricsResult = useSpanMetrics(
229+
{
230+
search: query,
231+
limit: 25,
232+
fields: spanMetricsFields,
233+
},
234+
Referrer.SCREENS_METRICS
235+
);
287236

288-
return undefined;
289-
};
237+
const metricsData = {...metricsResult.data[0], ...spanMetricsResult.data[0]};
238+
const metaUnits = {...metricsResult.meta?.units, ...spanMetricsResult.meta?.units};
239+
const metaFields = {...metricsResult.meta?.fields, ...spanMetricsResult.meta?.fields};
290240

291241
const {openVitalsDrawer} = useMobileVitalsDrawer({
292242
Component: <VitalDetailPanel vital={state.vital} status={state.status} />,
@@ -334,7 +284,12 @@ function ScreensLandingPage() {
334284
<Container>
335285
<Flex data-test-id="mobile-vitals-top-metrics">
336286
{vitalItems.map(item => {
337-
const metricValue = metricValueFor(item);
287+
const metricValue: MetricValue = {
288+
type: metaFields?.[item.field],
289+
value: metricsData?.[item.field],
290+
unit: metaUnits?.[item.field],
291+
};
292+
338293
const status =
339294
(metricValue && item.getStatus(metricValue, item.field)) ??
340295
STATUS_UNKNOWN;

static/app/views/insights/types.tsx

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ export enum SpanMetricsField {
6666
USER_GEO_SUBREGION = 'user.geo.subregion',
6767
PRECISE_START_TS = 'precise.start_ts',
6868
PRECISE_FINISH_TS = 'precise.finish_ts',
69+
MOBILE_FRAMES_DELAY = 'mobile.frames_delay',
70+
MOBILE_FROZEN_FRAMES = 'mobile.frozen_frames',
71+
MOBILE_TOTAL_FRAMES = 'mobile.total_frames',
72+
MOBILE_SLOW_FRAMES = 'mobile.slow_frames',
6973
}
7074

7175
export type SpanNumberFields =
@@ -80,6 +84,10 @@ export type SpanNumberFields =
8084
| SpanMetricsField.CACHE_ITEM_SIZE
8185
| SpanMetricsField.PRECISE_START_TS
8286
| SpanMetricsField.PRECISE_FINISH_TS
87+
| SpanMetricsField.MOBILE_FRAMES_DELAY
88+
| SpanMetricsField.MOBILE_FROZEN_FRAMES
89+
| SpanMetricsField.MOBILE_TOTAL_FRAMES
90+
| SpanMetricsField.MOBILE_SLOW_FRAMES
8391
| DiscoverNumberFields;
8492

8593
export type SpanStringFields =
@@ -175,20 +183,21 @@ export type SpanMetricsResponse = {
175183
[Property in SpanStringFields as `${Property}`]: string;
176184
} & {
177185
[Property in SpanStringArrayFields as `${Property}`]: string[];
178-
} & {
179-
// TODO: This should include all valid HTTP codes or just all integers
180-
'http_response_count(2)': number;
181-
'http_response_count(3)': number;
182-
'http_response_count(4)': number;
183-
'http_response_count(5)': number;
184-
'http_response_rate(2)': number;
185-
'http_response_rate(3)': number;
186-
'http_response_rate(4)': number;
187-
'http_response_rate(5)': number;
188-
} & {
189-
['project']: string;
190-
['project.id']: number;
191-
} & Record<RegressionFunctions, number> &
186+
// TODO: We should allow a nicer way to define functions with multiple arguments and different arg types
187+
} & Record<`division(${SpanNumberFields},${SpanNumberFields})`, number> & {
188+
// TODO: This should include all valid HTTP codes or just all integers
189+
'http_response_count(2)': number;
190+
'http_response_count(3)': number;
191+
'http_response_count(4)': number;
192+
'http_response_count(5)': number;
193+
'http_response_rate(2)': number;
194+
'http_response_rate(3)': number;
195+
'http_response_rate(4)': number;
196+
'http_response_rate(5)': number;
197+
} & {
198+
['project']: string;
199+
['project.id']: number;
200+
} & Record<RegressionFunctions, number> &
192201
Record<SpanAnyFunction, string> & {
193202
[Property in ConditionalAggregate as
194203
| `${Property}(${string})`
@@ -197,7 +206,6 @@ export type SpanMetricsResponse = {
197206
} & {
198207
[SpanMetricsField.USER_GEO_SUBREGION]: SubregionCode;
199208
};
200-
201209
export type MetricsFilters = {
202210
[Property in SpanStringFields as `${Property}`]?: string | string[];
203211
};
@@ -458,6 +466,10 @@ export enum MetricsFields {
458466
REPLAY_ID = 'replayId',
459467
TIMESTAMP = 'timestamp',
460468
PROFILE_ID = 'profile.id',
469+
APP_START_COLD = 'measurements.app_start_cold',
470+
APP_START_WARM = 'measurements.app_start_warm',
471+
TIME_TO_INITIAL_DISPLAY = 'measurements.time_to_initial_display',
472+
TIME_TO_FULL_DISPLAY = 'measurements.time_to_full_display',
461473
}
462474

463475
export type MetricsNumberFields =
@@ -479,7 +491,11 @@ export type MetricsNumberFields =
479491
| MetricsFields.FCP
480492
| MetricsFields.INP
481493
| MetricsFields.CLS
482-
| MetricsFields.TTFB;
494+
| MetricsFields.TTFB
495+
| MetricsFields.APP_START_COLD
496+
| MetricsFields.APP_START_WARM
497+
| MetricsFields.TIME_TO_INITIAL_DISPLAY
498+
| MetricsFields.TIME_TO_FULL_DISPLAY;
483499

484500
export type MetricsStringFields =
485501
| MetricsFields.TRANSACTION

0 commit comments

Comments
 (0)