Skip to content

Commit 3465d84

Browse files
authoredAug 15, 2024··
feat(insights): implement geo region selector in web vitals and assets (#76185)
Work toward #75230 Adds geo selector in the web vitals and resource module and ensure all queries gets the filter applied NOTE: not all the metrics are properly tagged yet, so web vitals doesn't work entierely yet, and the resource module doesn't have geo data for resource size metrics. Its all behind a feature flag, so this not an issue that customers can see.
1 parent 108d1df commit 3465d84

37 files changed

+299
-53
lines changed
 

‎static/app/views/insights/browser/common/queries/useResourcesQuery.ts

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const {
2525
HTTP_RESPONSE_CONTENT_LENGTH,
2626
PROJECT_ID,
2727
FILE_EXTENSION,
28+
USER_GEO_SUBREGION,
2829
} = SpanMetricsField;
2930

3031
const {TIME_SPENT_PERCENTAGE} = SpanFunction;
@@ -49,6 +50,9 @@ export const getResourcesEventViewQuery = (
4950
...(resourceFilters.transaction
5051
? [`transaction:"${resourceFilters.transaction}"`]
5152
: []),
53+
...(resourceFilters[USER_GEO_SUBREGION]
54+
? [`user.geo.subregion:[${resourceFilters[USER_GEO_SUBREGION]}]`]
55+
: []),
5256
...getDomainFilter(resourceFilters[SPAN_DOMAIN]),
5357
...(resourceFilters[RESOURCE_RENDER_BLOCKING_STATUS]
5458
? [

‎static/app/views/insights/browser/resources/components/charts/resourceSummaryCharts.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ function ResourceSummaryCharts(props: {groupId: string}) {
4545
filters[RESOURCE_RENDER_BLOCKING_STATUS],
4646
}
4747
: {}),
48+
...(filters[SpanMetricsField.USER_GEO_SUBREGION]
49+
? {
50+
[SpanMetricsField.USER_GEO_SUBREGION]: `[${filters[SpanMetricsField.USER_GEO_SUBREGION].join(',')}]`,
51+
}
52+
: {}),
4853
}),
4954
yAxis: [
5055
`spm()`,

‎static/app/views/insights/browser/resources/components/resourceView.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const {
3636
SPAN_DOMAIN,
3737
TRANSACTION,
3838
RESOURCE_RENDER_BLOCKING_STATUS,
39+
USER_GEO_SUBREGION,
3940
} = BrowserStarfishFields;
4041

4142
type Option = {
@@ -52,7 +53,12 @@ function ResourceView() {
5253
...(filters[SPAN_DOMAIN] ? {[SPAN_DOMAIN]: filters[SPAN_DOMAIN]} : {}),
5354
};
5455

55-
const extraQuery = getResourceTypeFilter(undefined, DEFAULT_RESOURCE_TYPES);
56+
const extraQuery = [
57+
...getResourceTypeFilter(undefined, DEFAULT_RESOURCE_TYPES),
58+
...(filters[USER_GEO_SUBREGION]
59+
? [`user.geo.subregion:[${filters[USER_GEO_SUBREGION].join(',')}]`]
60+
: []),
61+
];
5662

5763
return (
5864
<Fragment>

‎static/app/views/insights/browser/resources/components/tables/resourceSummaryTable.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const {
3434
SPAN_SELF_TIME,
3535
HTTP_RESPONSE_CONTENT_LENGTH,
3636
TRANSACTION,
37+
USER_GEO_SUBREGION,
3738
} = SpanMetricsField;
3839

3940
type Row = {
@@ -55,6 +56,7 @@ function ResourceSummaryTable() {
5556
const {data, isLoading, pageLinks} = useResourcePagesQuery(groupId, {
5657
sort,
5758
cursor,
59+
subregions: filters[USER_GEO_SUBREGION],
5860
renderBlockingStatus: filters[RESOURCE_RENDER_BLOCKING_STATUS],
5961
});
6062

‎static/app/views/insights/browser/resources/components/tables/resourceTable.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
RESOURCE_THROUGHPUT_UNIT,
2626
} from 'sentry/views/insights/browser/resources/settings';
2727
import {ResourceSpanOps} from 'sentry/views/insights/browser/resources/types';
28+
import {useResourceModuleFilters} from 'sentry/views/insights/browser/resources/utils/useResourceFilters';
2829
import type {ValidSort} from 'sentry/views/insights/browser/resources/utils/useResourceSort';
2930
import {DurationCell} from 'sentry/views/insights/common/components/tableCells/durationCell';
3031
import {renderHeadCell} from 'sentry/views/insights/common/components/tableCells/renderHeadCell';
@@ -79,6 +80,7 @@ function ResourceTable({sort, defaultResourceTypes}: Props) {
7980
const location = useLocation();
8081
const organization = useOrganization();
8182
const cursor = decodeScalar(location.query?.[QueryParameterNames.SPANS_CURSOR]);
83+
const filters = useResourceModuleFilters();
8284
const {setPageInfo, pageAlert} = usePageAlert();
8385

8486
const {data, isLoading, pageLinks} = useResourcesQuery({
@@ -130,7 +132,11 @@ function ResourceTable({sort, defaultResourceTypes}: Props) {
130132

131133
if (key === SPAN_DESCRIPTION) {
132134
const fileExtension = row[SPAN_DESCRIPTION].split('.').pop() || '';
133-
135+
const extraLinkQueryParams = {};
136+
if (filters[SpanMetricsField.USER_GEO_SUBREGION]) {
137+
extraLinkQueryParams[SpanMetricsField.USER_GEO_SUBREGION] =
138+
filters[SpanMetricsField.USER_GEO_SUBREGION];
139+
}
134140
return (
135141
<DescriptionWrapper>
136142
<ResourceIcon fileExtension={fileExtension} spanOp={row[SPAN_OP]} />
@@ -140,6 +146,7 @@ function ResourceTable({sort, defaultResourceTypes}: Props) {
140146
spanOp={row[SPAN_OP]}
141147
description={row[SPAN_DESCRIPTION]}
142148
group={row[SPAN_GROUP]}
149+
extraLinkQueryParams={extraLinkQueryParams}
143150
/>
144151
</DescriptionWrapper>
145152
);

‎static/app/views/insights/browser/resources/queries/useResourcePageQuery.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type {Sort} from 'sentry/utils/discover/fields';
22
import {useSpanTransactionMetrics} from 'sentry/views/insights/common/queries/useSpanTransactionMetrics';
3-
import {SpanMetricsField} from 'sentry/views/insights/types';
3+
import {SpanMetricsField, type SubregionCode} from 'sentry/views/insights/types';
44

55
const {HTTP_RESPONSE_CONTENT_LENGTH, RESOURCE_RENDER_BLOCKING_STATUS} = SpanMetricsField;
66

@@ -9,15 +9,24 @@ export const useResourcePagesQuery = (
99
{
1010
sort,
1111
cursor,
12+
subregions,
1213
renderBlockingStatus,
13-
}: {sort: Sort; cursor?: string; renderBlockingStatus?: string}
14+
}: {
15+
sort: Sort;
16+
cursor?: string;
17+
renderBlockingStatus?: string;
18+
subregions?: SubregionCode[];
19+
}
1420
) => {
1521
return useSpanTransactionMetrics(
1622
{
1723
'span.group': groupId,
1824
...(renderBlockingStatus
1925
? {[RESOURCE_RENDER_BLOCKING_STATUS]: renderBlockingStatus}
2026
: {}),
27+
...(subregions
28+
? {[SpanMetricsField.USER_GEO_SUBREGION]: `[${subregions.join(',')}]`}
29+
: {}),
2130
},
2231
[sort],
2332
cursor,

‎static/app/views/insights/browser/resources/utils/useResourceFilters.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import pick from 'lodash/pick';
22

3+
import {decodeList} from 'sentry/utils/queryString';
34
import {useLocation} from 'sentry/utils/useLocation';
45
import type {ResourceSpanOps} from 'sentry/views/insights/browser/resources/types';
6+
import type {SubregionCode} from 'sentry/views/insights/types';
57

8+
// TODO - we should probably just use SpanMetricsField here
69
export enum BrowserStarfishFields {
710
SPAN_OP = 'span.op',
811
TRANSACTION = 'transaction',
912
SPAN_DOMAIN = 'span.domain',
1013
GROUP_ID = 'groupId',
1114
DESCRIPTION = 'description',
1215
RESOURCE_RENDER_BLOCKING_STATUS = 'resource.render_blocking_status',
16+
USER_GEO_SUBREGION = 'user.geo.subregion',
1317
}
1418

1519
export type ModuleFilters = {
@@ -22,17 +26,27 @@ export type ModuleFilters = {
2226
[BrowserStarfishFields.SPAN_OP]?: ResourceSpanOps;
2327
[BrowserStarfishFields.TRANSACTION]?: string;
2428
[BrowserStarfishFields.SPAN_DOMAIN]?: string;
29+
[BrowserStarfishFields.USER_GEO_SUBREGION]?: SubregionCode[];
2530
};
2631

2732
export const useResourceModuleFilters = () => {
2833
const location = useLocation<ModuleFilters>();
2934

30-
return pick(location.query, [
35+
const filters = pick(location.query, [
3136
BrowserStarfishFields.SPAN_DOMAIN,
3237
BrowserStarfishFields.SPAN_OP,
3338
BrowserStarfishFields.TRANSACTION,
3439
BrowserStarfishFields.GROUP_ID,
3540
BrowserStarfishFields.DESCRIPTION,
3641
BrowserStarfishFields.RESOURCE_RENDER_BLOCKING_STATUS,
3742
]);
43+
44+
const subregions = decodeList(
45+
location.query[BrowserStarfishFields.USER_GEO_SUBREGION]
46+
) as SubregionCode[];
47+
if (subregions.length) {
48+
filters[BrowserStarfishFields.USER_GEO_SUBREGION] = subregions;
49+
}
50+
51+
return filters;
3852
};

‎static/app/views/insights/browser/resources/views/resourceSummaryPage.tsx

+8
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {ToolRibbon} from 'sentry/views/insights/common/components/ribbon';
2929
import {useSpanMetrics} from 'sentry/views/insights/common/queries/useDiscover';
3030
import {useModuleBreadcrumbs} from 'sentry/views/insights/common/utils/useModuleBreadcrumbs';
3131
import {useModuleURL} from 'sentry/views/insights/common/utils/useModuleURL';
32+
import SubregionSelector from 'sentry/views/insights/common/views/spans/selectors/subregionSelector';
3233
import {SampleList} from 'sentry/views/insights/common/views/spanSummaryPage/sampleList';
3334
import {ModuleName, SpanMetricsField} from 'sentry/views/insights/types';
3435
import {TraceViewSources} from 'sentry/views/performance/newTraceDetails/traceMetadataHeader';
@@ -55,6 +56,11 @@ function ResourceSummary() {
5556
{
5657
search: MutableSearch.fromQueryObject({
5758
'span.group': groupId,
59+
...(filters[SpanMetricsField.USER_GEO_SUBREGION]
60+
? {
61+
[SpanMetricsField.USER_GEO_SUBREGION]: `[${filters[SpanMetricsField.USER_GEO_SUBREGION].join(',')}]`,
62+
}
63+
: {}),
5864
}),
5965
fields: [
6066
`avg(${SPAN_SELF_TIME})`,
@@ -123,6 +129,7 @@ function ResourceSummary() {
123129
<RenderBlockingSelector
124130
value={filters[RESOURCE_RENDER_BLOCKING_STATUS] || ''}
125131
/>
132+
<SubregionSelector />
126133
</ToolRibbon>
127134
<ResourceInfo
128135
isLoading={isLoading}
@@ -154,6 +161,7 @@ function ResourceSummary() {
154161
<ModuleLayout.Full>
155162
<SampleList
156163
transactionRoute={webVitalsModuleURL}
164+
subregions={filters[SpanMetricsField.USER_GEO_SUBREGION]}
157165
groupId={groupId}
158166
moduleName={ModuleName.RESOURCE}
159167
transactionName={transaction as string}

‎static/app/views/insights/browser/resources/views/resourcesLandingPage.tsx

+14-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, {Fragment} from 'react';
22
import styled from '@emotion/styled';
33

44
import {Breadcrumbs} from 'sentry/components/breadcrumbs';
@@ -27,6 +27,7 @@ import {ModulesOnboarding} from 'sentry/views/insights/common/components/modules
2727
import {ToolRibbon} from 'sentry/views/insights/common/components/ribbon';
2828
import {useModuleBreadcrumbs} from 'sentry/views/insights/common/utils/useModuleBreadcrumbs';
2929
import {DomainSelector} from 'sentry/views/insights/common/views/spans/selectors/domainSelector';
30+
import SubregionSelector from 'sentry/views/insights/common/views/spans/selectors/subregionSelector';
3031
import {ModuleName} from 'sentry/views/insights/types';
3132

3233
const {SPAN_OP, SPAN_DOMAIN} = BrowserStarfishFields;
@@ -64,15 +65,18 @@ function ResourcesLandingPage() {
6465
<ModulePageFilterBar
6566
moduleName={ModuleName.RESOURCE}
6667
extraFilters={
67-
<DomainSelector
68-
moduleName={ModuleName.RESOURCE}
69-
emptyOptionLocation="top"
70-
value={filters[SPAN_DOMAIN] || ''}
71-
additionalQuery={[
72-
...DEFAULT_RESOURCE_FILTERS,
73-
`${SPAN_OP}:[${DEFAULT_RESOURCE_TYPES.join(',')}]`,
74-
]}
75-
/>
68+
<Fragment>
69+
<DomainSelector
70+
moduleName={ModuleName.RESOURCE}
71+
emptyOptionLocation="top"
72+
value={filters[SPAN_DOMAIN] || ''}
73+
additionalQuery={[
74+
...DEFAULT_RESOURCE_FILTERS,
75+
`${SPAN_OP}:[${DEFAULT_RESOURCE_TYPES.join(',')}]`,
76+
]}
77+
/>
78+
<SubregionSelector />
79+
</Fragment>
7680
}
7781
/>
7882
</ToolRibbon>

‎static/app/views/insights/browser/webVitals/components/charts/performanceScoreBreakdownChart.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ import {applyStaticWeightsToTimeseries} from 'sentry/views/insights/browser/webV
1515
import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType';
1616
import {PERFORMANCE_SCORE_WEIGHTS} from 'sentry/views/insights/browser/webVitals/utils/scoreThresholds';
1717
import Chart, {ChartType} from 'sentry/views/insights/common/components/chart';
18+
import type {SubregionCode} from 'sentry/views/insights/types';
1819

1920
type Props = {
2021
browserTypes?: BrowserType[];
22+
subregions?: SubregionCode[];
2123
transaction?: string;
2224
};
2325

@@ -43,14 +45,18 @@ export const formatTimeSeriesResultsToChartData = (
4345
});
4446
};
4547

46-
export function PerformanceScoreBreakdownChart({transaction, browserTypes}: Props) {
48+
export function PerformanceScoreBreakdownChart({
49+
transaction,
50+
browserTypes,
51+
subregions,
52+
}: Props) {
4753
const theme = useTheme();
4854
const segmentColors = [...theme.charts.getColorPalette(3).slice(0, 5)];
4955

5056
const pageFilters = usePageFilters();
5157

5258
const {data: timeseriesData, isLoading: isTimeseriesLoading} =
53-
useProjectWebVitalsScoresTimeseriesQuery({transaction, browserTypes});
59+
useProjectWebVitalsScoresTimeseriesQuery({transaction, browserTypes, subregions});
5460

5561
const period = pageFilters.selection.datetime.period;
5662
const performanceScoreSubtext = (period && DEFAULT_RELATIVE_PERIODS[period]) ?? '';

‎static/app/views/insights/browser/webVitals/components/charts/performanceScoreChart.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ import type {
1717
WebVitals,
1818
} from 'sentry/views/insights/browser/webVitals/types';
1919
import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType';
20+
import type {SubregionCode} from 'sentry/views/insights/types';
2021

2122
type Props = {
2223
browserTypes?: BrowserType[];
2324
isProjectScoreLoading?: boolean;
2425
projectScore?: ProjectScore;
26+
subregions?: SubregionCode[];
2527
transaction?: string;
2628
webVital?: WebVitals | null;
2729
};
@@ -34,6 +36,7 @@ export function PerformanceScoreChart({
3436
transaction,
3537
isProjectScoreLoading,
3638
browserTypes,
39+
subregions,
3740
}: Props) {
3841
const theme = useTheme();
3942
const pageFilters = usePageFilters();
@@ -100,6 +103,7 @@ export function PerformanceScoreChart({
100103
<PerformanceScoreBreakdownChart
101104
transaction={transaction}
102105
browserTypes={browserTypes}
106+
subregions={subregions}
103107
/>
104108
</Flex>
105109
);

‎static/app/views/insights/browser/webVitals/components/pageOverviewSidebar.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {useProjectRawWebVitalsValuesTimeseriesQuery} from 'sentry/views/insights
2222
import {MODULE_DOC_LINK} from 'sentry/views/insights/browser/webVitals/settings';
2323
import type {ProjectScore} from 'sentry/views/insights/browser/webVitals/types';
2424
import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType';
25+
import type {SubregionCode} from 'sentry/views/insights/types';
2526
import {SidebarSpacer} from 'sentry/views/performance/transactionSummary/utils';
2627

2728
const CHART_HEIGHTS = 100;
@@ -32,13 +33,15 @@ type Props = {
3233
projectScore?: ProjectScore;
3334
projectScoreIsLoading?: boolean;
3435
search?: string;
36+
subregions?: SubregionCode[];
3537
};
3638

3739
export function PageOverviewSidebar({
3840
projectScore,
3941
transaction,
4042
projectScoreIsLoading,
4143
browserTypes,
44+
subregions,
4245
}: Props) {
4346
const theme = useTheme();
4447
const router = useRouter();
@@ -62,6 +65,7 @@ export function PageOverviewSidebar({
6265
transaction,
6366
datetime: doubledDatetime,
6467
browserTypes,
68+
subregions,
6569
});
6670

6771
const {countDiff, currentSeries, currentCount, initialCount} = processSeriesData(

‎static/app/views/insights/browser/webVitals/components/pageOverviewWebVitalsDetailPanel.tsx

+13-3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import type {
3939
import decodeBrowserTypes from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType';
4040
import useProfileExists from 'sentry/views/insights/browser/webVitals/utils/useProfileExists';
4141
import DetailPanel from 'sentry/views/insights/common/components/detailPanel';
42-
import {SpanIndexedField} from 'sentry/views/insights/types';
42+
import {SpanIndexedField, type SubregionCode} from 'sentry/views/insights/types';
4343
import {TraceViewSources} from 'sentry/views/performance/newTraceDetails/traceMetadataHeader';
4444
import {generateReplayLink} from 'sentry/views/performance/transactionSummary/utils';
4545

@@ -89,6 +89,9 @@ export function PageOverviewWebVitalsDetailPanel({
8989
const {replayExists} = useReplayExists();
9090

9191
const browserTypes = decodeBrowserTypes(location.query[SpanIndexedField.BROWSER_NAME]);
92+
const subregions = location.query[
93+
SpanIndexedField.USER_GEO_SUBREGION
94+
] as SubregionCode[];
9295
const isInp = webVital === 'inp';
9396

9497
const replayLinkGenerator = generateReplayLink(routes);
@@ -104,11 +107,16 @@ export function PageOverviewWebVitalsDetailPanel({
104107
: location.query.transaction
105108
: undefined;
106109

107-
const {data: projectData} = useProjectRawWebVitalsQuery({transaction, browserTypes});
110+
const {data: projectData} = useProjectRawWebVitalsQuery({
111+
transaction,
112+
browserTypes,
113+
subregions,
114+
});
108115
const {data: projectScoresData} = useProjectWebVitalsScoresQuery({
109116
weightWebVital: webVital ?? 'total',
110117
transaction,
111118
browserTypes,
119+
subregions,
112120
});
113121

114122
const projectScore = calculatePerformanceScoreFromStoredTableDataRow(
@@ -121,21 +129,23 @@ export function PageOverviewWebVitalsDetailPanel({
121129
webVital,
122130
enabled: Boolean(webVital) && !isInp,
123131
browserTypes,
132+
subregions,
124133
});
125134

126135
const {data: inpTableData, isLoading: isInteractionsLoading} =
127136
useInteractionsCategorizedSamplesQuery({
128137
transaction: transaction ?? '',
129138
enabled: Boolean(webVital) && isInp,
130139
browserTypes,
140+
subregions,
131141
});
132142

133143
const {profileExists} = useProfileExists(
134144
inpTableData.filter(row => row['profile.id']).map(row => row['profile.id'])
135145
);
136146

137147
const {data: timeseriesData, isLoading: isTimeseriesLoading} =
138-
useProjectRawWebVitalsValuesTimeseriesQuery({transaction, browserTypes});
148+
useProjectRawWebVitalsValuesTimeseriesQuery({transaction, browserTypes, subregions});
139149

140150
const webVitalData: LineChartSeries = {
141151
data:

‎static/app/views/insights/browser/webVitals/components/tables/pagePerformanceTable.tsx

+10-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import type {Sort} from 'sentry/utils/discover/fields';
2020
import {parseFunction} from 'sentry/utils/discover/fields';
2121
import getDuration from 'sentry/utils/duration/getDuration';
2222
import {formatAbbreviatedNumber} from 'sentry/utils/formatters';
23-
import {decodeScalar} from 'sentry/utils/queryString';
23+
import {decodeList, decodeScalar} from 'sentry/utils/queryString';
2424
import {escapeFilterValue} from 'sentry/utils/tokenizeSearch';
2525
import {useLocation} from 'sentry/utils/useLocation';
2626
import useOrganization from 'sentry/utils/useOrganization';
@@ -31,7 +31,11 @@ import type {RowWithScoreAndOpportunity} from 'sentry/views/insights/browser/web
3131
import {SORTABLE_FIELDS} from 'sentry/views/insights/browser/webVitals/types';
3232
import decodeBrowserTypes from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType';
3333
import {useWebVitalsSort} from 'sentry/views/insights/browser/webVitals/utils/useWebVitalsSort';
34-
import {ModuleName, SpanIndexedField} from 'sentry/views/insights/types';
34+
import {
35+
ModuleName,
36+
SpanIndexedField,
37+
type SubregionCode,
38+
} from 'sentry/views/insights/types';
3539

3640
type Column = GridColumnHeader<keyof RowWithScoreAndOpportunity>;
3741

@@ -67,6 +71,9 @@ export function PagePerformanceTable() {
6771

6872
const query = decodeScalar(location.query.query, '');
6973
const browserTypes = decodeBrowserTypes(location.query[SpanIndexedField.BROWSER_NAME]);
74+
const subregions = decodeList(
75+
location.query[SpanIndexedField.USER_GEO_SUBREGION]
76+
) as SubregionCode[];
7077

7178
const sort = useWebVitalsSort({defaultSort: DEFAULT_SORT});
7279

@@ -81,6 +88,7 @@ export function PagePerformanceTable() {
8188
defaultSort: DEFAULT_SORT,
8289
shouldEscapeFilters: false,
8390
browserTypes,
91+
subregions,
8492
});
8593

8694
const tableData: RowWithScoreAndOpportunity[] = data.map(row => ({

‎static/app/views/insights/browser/webVitals/components/tables/pageSamplePerformanceTable.tsx

+12-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls';
2323
import getDuration from 'sentry/utils/duration/getDuration';
2424
import {getShortEventId} from 'sentry/utils/events';
2525
import {generateProfileFlamechartRoute} from 'sentry/utils/profiling/routes';
26-
import {decodeScalar} from 'sentry/utils/queryString';
26+
import {decodeList, decodeScalar} from 'sentry/utils/queryString';
2727
import useReplayExists from 'sentry/utils/replayCount/useReplayExists';
2828
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
2929
import {useLocation} from 'sentry/utils/useLocation';
@@ -46,7 +46,11 @@ import {
4646
import decodeBrowserTypes from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType';
4747
import useProfileExists from 'sentry/views/insights/browser/webVitals/utils/useProfileExists';
4848
import {useWebVitalsSort} from 'sentry/views/insights/browser/webVitals/utils/useWebVitalsSort';
49-
import {SpanIndexedField} from 'sentry/views/insights/types';
49+
import {
50+
SpanIndexedField,
51+
SpanMetricsField,
52+
type SubregionCode,
53+
} from 'sentry/views/insights/types';
5054
import {TraceViewSources} from 'sentry/views/performance/newTraceDetails/traceMetadataHeader';
5155
import {generateReplayLink} from 'sentry/views/performance/transactionSummary/utils';
5256

@@ -102,6 +106,10 @@ export function PageSamplePerformanceTable({transaction, search, limit = 9}: Pro
102106
const router = useRouter();
103107

104108
const browserTypes = decodeBrowserTypes(location.query[SpanIndexedField.BROWSER_NAME]);
109+
const subregions = decodeList(
110+
location.query[SpanMetricsField.USER_GEO_SUBREGION]
111+
) as SubregionCode[];
112+
105113
let datatype = Datatype.PAGELOADS;
106114
switch (decodeScalar(location.query[DATATYPE_KEY], 'pageloads')) {
107115
case 'interactions':
@@ -138,6 +146,7 @@ export function PageSamplePerformanceTable({transaction, search, limit = 9}: Pro
138146
withProfiles: true,
139147
enabled: datatype === Datatype.PAGELOADS,
140148
browserTypes,
149+
subregions,
141150
});
142151

143152
const {
@@ -150,6 +159,7 @@ export function PageSamplePerformanceTable({transaction, search, limit = 9}: Pro
150159
limit,
151160
filters: new MutableSearch(query ?? '').filters,
152161
browserTypes,
162+
subregions,
153163
});
154164

155165
const {profileExists} = useProfileExists(

‎static/app/views/insights/browser/webVitals/components/webVitalsDetailPanel.tsx

+9-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {trackAnalytics} from 'sentry/utils/analytics';
1616
import getDuration from 'sentry/utils/duration/getDuration';
1717
import {formatAbbreviatedNumber} from 'sentry/utils/formatters';
1818
import {PageAlert, PageAlertProvider} from 'sentry/utils/performance/contexts/pageAlert';
19+
import {decodeList} from 'sentry/utils/queryString';
1920
import {useLocation} from 'sentry/utils/useLocation';
2021
import useOrganization from 'sentry/utils/useOrganization';
2122
import {WebVitalStatusLineChart} from 'sentry/views/insights/browser/webVitals/components/charts/webVitalStatusLineChart';
@@ -34,7 +35,7 @@ import type {
3435
} from 'sentry/views/insights/browser/webVitals/types';
3536
import decodeBrowserTypes from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType';
3637
import DetailPanel from 'sentry/views/insights/common/components/detailPanel';
37-
import {SpanIndexedField} from 'sentry/views/insights/types';
38+
import {SpanIndexedField, type SubregionCode} from 'sentry/views/insights/types';
3839

3940
type Column = GridColumnHeader;
4041

@@ -60,11 +61,15 @@ export function WebVitalsDetailPanel({
6061
const location = useLocation();
6162
const organization = useOrganization();
6263
const browserTypes = decodeBrowserTypes(location.query[SpanIndexedField.BROWSER_NAME]);
64+
const subregions = decodeList(
65+
location.query[SpanIndexedField.USER_GEO_SUBREGION]
66+
) as SubregionCode[];
6367

64-
const {data: projectData} = useProjectRawWebVitalsQuery({browserTypes});
68+
const {data: projectData} = useProjectRawWebVitalsQuery({browserTypes, subregions});
6569
const {data: projectScoresData} = useProjectWebVitalsScoresQuery({
6670
weightWebVital: webVital ?? 'total',
6771
browserTypes,
72+
subregions,
6873
});
6974

7075
const projectScore = calculatePerformanceScoreFromStoredTableDataRow(
@@ -85,6 +90,7 @@ export function WebVitalsDetailPanel({
8590
enabled: webVital !== null,
8691
sortName: 'webVitalsDetailPanelSort',
8792
browserTypes,
93+
subregions,
8894
});
8995

9096
const dataByOpportunity = useMemo(() => {
@@ -116,7 +122,7 @@ export function WebVitalsDetailPanel({
116122
}, [data, projectScoresData?.data, webVital]);
117123

118124
const {data: timeseriesData, isLoading: isTimeseriesLoading} =
119-
useProjectRawWebVitalsValuesTimeseriesQuery({browserTypes});
125+
useProjectRawWebVitalsValuesTimeseriesQuery({browserTypes, subregions});
120126

121127
const webVitalData: LineChartSeries = {
122128
data:

‎static/app/views/insights/browser/webVitals/queries/rawWebVitalsQueries/useProjectRawWebVitalsQuery.tsx

+7-2
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ import useOrganization from 'sentry/utils/useOrganization';
88
import usePageFilters from 'sentry/utils/usePageFilters';
99
import {DEFAULT_QUERY_FILTER} from 'sentry/views/insights/browser/webVitals/settings';
1010
import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType';
11-
import {SpanIndexedField} from 'sentry/views/insights/types';
11+
import {SpanMetricsField, type SubregionCode} from 'sentry/views/insights/types';
1212

1313
type Props = {
1414
browserTypes?: BrowserType[];
1515
dataset?: DiscoverDatasets;
16+
subregions?: SubregionCode[];
1617
tag?: Tag;
1718
transaction?: string;
1819
};
@@ -22,6 +23,7 @@ export const useProjectRawWebVitalsQuery = ({
2223
tag,
2324
dataset,
2425
browserTypes,
26+
subregions,
2527
}: Props = {}) => {
2628
const organization = useOrganization();
2729
const pageFilters = usePageFilters();
@@ -33,8 +35,11 @@ export const useProjectRawWebVitalsQuery = ({
3335
if (tag) {
3436
search.addFilterValue(tag.key, tag.name);
3537
}
38+
if (subregions) {
39+
search.addDisjunctionFilterValues(SpanMetricsField.USER_GEO_SUBREGION, subregions);
40+
}
3641
if (browserTypes) {
37-
search.addDisjunctionFilterValues(SpanIndexedField.BROWSER_NAME, browserTypes);
42+
search.addDisjunctionFilterValues(SpanMetricsField.BROWSER_NAME, browserTypes);
3843
}
3944

4045
const projectEventView = EventView.fromNewQueryWithPageFilters(

‎static/app/views/insights/browser/webVitals/queries/rawWebVitalsQueries/useProjectRawWebVitalsValuesTimeseriesQuery.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,20 @@ import useOrganization from 'sentry/utils/useOrganization';
1212
import usePageFilters from 'sentry/utils/usePageFilters';
1313
import {DEFAULT_QUERY_FILTER} from 'sentry/views/insights/browser/webVitals/settings';
1414
import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType';
15-
import {SpanIndexedField} from 'sentry/views/insights/types';
15+
import {SpanIndexedField, type SubregionCode} from 'sentry/views/insights/types';
1616

1717
type Props = {
1818
browserTypes?: BrowserType[];
1919
datetime?: PageFilters['datetime'];
20+
subregions?: SubregionCode[];
2021
transaction?: string | null;
2122
};
2223

2324
export const useProjectRawWebVitalsValuesTimeseriesQuery = ({
2425
transaction,
2526
datetime,
2627
browserTypes,
28+
subregions,
2729
}: Props) => {
2830
const pageFilters = usePageFilters();
2931
const location = useLocation();
@@ -35,6 +37,9 @@ export const useProjectRawWebVitalsValuesTimeseriesQuery = ({
3537
if (browserTypes) {
3638
search.addDisjunctionFilterValues(SpanIndexedField.BROWSER_NAME, browserTypes);
3739
}
40+
if (subregions) {
41+
search.addDisjunctionFilterValues(SpanIndexedField.USER_GEO_SUBREGION, subregions);
42+
}
3843
const projectTimeSeriesEventView = EventView.fromNewQueryWithPageFilters(
3944
{
4045
yAxis: [

‎static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useProjectWebVitalsScoresQuery.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ import usePageFilters from 'sentry/utils/usePageFilters';
99
import {DEFAULT_QUERY_FILTER} from 'sentry/views/insights/browser/webVitals/settings';
1010
import type {WebVitals} from 'sentry/views/insights/browser/webVitals/types';
1111
import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType';
12-
import {SpanIndexedField} from 'sentry/views/insights/types';
12+
import {SpanIndexedField, type SubregionCode} from 'sentry/views/insights/types';
1313

1414
type Props = {
1515
browserTypes?: BrowserType[];
1616
dataset?: DiscoverDatasets;
1717
enabled?: boolean;
18+
subregions?: SubregionCode[];
1819
tag?: Tag;
1920
transaction?: string;
2021
weightWebVital?: WebVitals | 'total';
@@ -27,6 +28,7 @@ export const useProjectWebVitalsScoresQuery = ({
2728
enabled = true,
2829
weightWebVital = 'total',
2930
browserTypes,
31+
subregions,
3032
}: Props = {}) => {
3133
const organization = useOrganization();
3234
const pageFilters = usePageFilters();
@@ -42,6 +44,9 @@ export const useProjectWebVitalsScoresQuery = ({
4244
if (browserTypes) {
4345
search.addDisjunctionFilterValues(SpanIndexedField.BROWSER_NAME, browserTypes);
4446
}
47+
if (subregions) {
48+
search.addDisjunctionFilterValues(SpanIndexedField.USER_GEO_SUBREGION, subregions);
49+
}
4550

4651
const projectEventView = EventView.fromNewQueryWithPageFilters(
4752
{

‎static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useProjectWebVitalsScoresTimeseriesQuery.tsx

+7-2
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ import useOrganization from 'sentry/utils/useOrganization';
1212
import usePageFilters from 'sentry/utils/usePageFilters';
1313
import {DEFAULT_QUERY_FILTER} from 'sentry/views/insights/browser/webVitals/settings';
1414
import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType';
15-
import {SpanIndexedField} from 'sentry/views/insights/types';
15+
import {SpanMetricsField, type SubregionCode} from 'sentry/views/insights/types';
1616

1717
type Props = {
1818
browserTypes?: BrowserType[];
1919
enabled?: boolean;
20+
subregions?: SubregionCode[];
2021
tag?: Tag;
2122
transaction?: string | null;
2223
weighted?: boolean;
@@ -44,6 +45,7 @@ export const useProjectWebVitalsScoresTimeseriesQuery = ({
4445
tag,
4546
enabled = true,
4647
browserTypes,
48+
subregions,
4749
}: Props) => {
4850
const pageFilters = usePageFilters();
4951
const location = useLocation();
@@ -55,8 +57,11 @@ export const useProjectWebVitalsScoresTimeseriesQuery = ({
5557
if (transaction) {
5658
search.addFilterValue('transaction', transaction);
5759
}
60+
if (subregions) {
61+
search.addDisjunctionFilterValues(SpanMetricsField.USER_GEO_SUBREGION, subregions);
62+
}
5863
if (browserTypes) {
59-
search.addDisjunctionFilterValues(SpanIndexedField.BROWSER_NAME, browserTypes);
64+
search.addDisjunctionFilterValues(SpanMetricsField.BROWSER_NAME, browserTypes);
6065
}
6166
const projectTimeSeriesEventView = EventView.fromNewQueryWithPageFilters(
6267
{

‎static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useTransactionSamplesWebVitalsScoresQuery.tsx

+9-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
import {mapWebVitalToOrderBy} from 'sentry/views/insights/browser/webVitals/utils/mapWebVitalToOrderBy';
1818
import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType';
1919
import {useWebVitalsSort} from 'sentry/views/insights/browser/webVitals/utils/useWebVitalsSort';
20-
import {SpanIndexedField} from 'sentry/views/insights/types';
20+
import {SpanIndexedField, type SubregionCode} from 'sentry/views/insights/types';
2121

2222
type Props = {
2323
transaction: string;
@@ -27,6 +27,7 @@ type Props = {
2727
orderBy?: WebVitals | null;
2828
query?: string;
2929
sortName?: string;
30+
subregions?: SubregionCode[];
3031
webVital?: WebVitals;
3132
withProfiles?: boolean;
3233
};
@@ -41,6 +42,7 @@ export const useTransactionSamplesWebVitalsScoresQuery = ({
4142
sortName,
4243
webVital,
4344
browserTypes,
45+
subregions,
4446
}: Props) => {
4547
const organization = useOrganization();
4648
const pageFilters = usePageFilters();
@@ -64,6 +66,12 @@ export const useTransactionSamplesWebVitalsScoresQuery = ({
6466
if (browserTypes) {
6567
mutableSearch.addDisjunctionFilterValues(SpanIndexedField.BROWSER_NAME, browserTypes);
6668
}
69+
if (subregions) {
70+
mutableSearch.addDisjunctionFilterValues(
71+
SpanIndexedField.USER_GEO_SUBREGION,
72+
subregions
73+
);
74+
}
6775

6876
const eventView = EventView.fromNewQueryWithPageFilters(
6977
{

‎static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useTransactionWebVitalsScoresQuery.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type {
1414
} from 'sentry/views/insights/browser/webVitals/types';
1515
import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType';
1616
import {useWebVitalsSort} from 'sentry/views/insights/browser/webVitals/utils/useWebVitalsSort';
17-
import {SpanIndexedField} from 'sentry/views/insights/types';
17+
import {SpanIndexedField, type SubregionCode} from 'sentry/views/insights/types';
1818

1919
type Props = {
2020
browserTypes?: BrowserType[];
@@ -24,6 +24,7 @@ type Props = {
2424
query?: string;
2525
shouldEscapeFilters?: boolean;
2626
sortName?: string;
27+
subregions?: SubregionCode[];
2728
transaction?: string | null;
2829
webVital?: WebVitals | 'total';
2930
};
@@ -38,6 +39,7 @@ export const useTransactionWebVitalsScoresQuery = ({
3839
query,
3940
shouldEscapeFilters = true,
4041
browserTypes,
42+
subregions,
4143
}: Props) => {
4244
const organization = useOrganization();
4345
const pageFilters = usePageFilters();
@@ -63,6 +65,10 @@ export const useTransactionWebVitalsScoresQuery = ({
6365
if (browserTypes) {
6466
search.addDisjunctionFilterValues(SpanIndexedField.BROWSER_NAME, browserTypes);
6567
}
68+
if (subregions) {
69+
search.addDisjunctionFilterValues(SpanIndexedField.USER_GEO_SUBREGION, subregions);
70+
}
71+
6672
const eventView = EventView.fromNewQueryWithPageFilters(
6773
{
6874
fields: [

‎static/app/views/insights/browser/webVitals/queries/useInpSpanSamplesWebVitalsQuery.tsx

+9-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType';
88
import {useWebVitalsSort} from 'sentry/views/insights/browser/webVitals/utils/useWebVitalsSort';
99
import {useSpansIndexed} from 'sentry/views/insights/common/queries/useDiscover';
10-
import {SpanIndexedField} from 'sentry/views/insights/types';
10+
import {SpanIndexedField, type SubregionCode} from 'sentry/views/insights/types';
1111

1212
export function useInpSpanSamplesWebVitalsQuery({
1313
transaction,
@@ -16,12 +16,14 @@ export function useInpSpanSamplesWebVitalsQuery({
1616
filters = {},
1717
sortName,
1818
browserTypes,
19+
subregions,
1920
}: {
2021
limit: number;
2122
browserTypes?: BrowserType[];
2223
enabled?: boolean;
2324
filters?: {[key: string]: string[] | string | number | undefined};
2425
sortName?: string;
26+
subregions?: SubregionCode[];
2527
transaction?: string;
2628
}) {
2729
const filteredSortableFields = SORTABLE_INDEXED_INTERACTION_FIELDS;
@@ -44,6 +46,12 @@ export function useInpSpanSamplesWebVitalsQuery({
4446
if (browserTypes) {
4547
mutableSearch.addDisjunctionFilterValues(SpanIndexedField.BROWSER_NAME, browserTypes);
4648
}
49+
if (subregions) {
50+
mutableSearch.addDisjunctionFilterValues(
51+
SpanIndexedField.USER_GEO_SUBREGION,
52+
subregions
53+
);
54+
}
4755

4856
const {data, isLoading, ...rest} = useSpansIndexed(
4957
{

‎static/app/views/insights/browser/webVitals/queries/useInteractionsCategorizedSamplesQuery.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,20 @@ import {
55
PERFORMANCE_SCORE_MEDIANS,
66
PERFORMANCE_SCORE_P90S,
77
} from 'sentry/views/insights/browser/webVitals/utils/scoreThresholds';
8+
import type {SubregionCode} from 'sentry/views/insights/types';
89

910
type Props = {
1011
browserTypes: BrowserType[];
1112
enabled: boolean;
13+
subregions: SubregionCode[];
1214
transaction: string;
1315
};
1416

1517
export function useInteractionsCategorizedSamplesQuery({
1618
transaction,
1719
enabled,
1820
browserTypes,
21+
subregions,
1922
}: Props) {
2023
const {data: goodData, isFetching: isGoodDataLoading} = useInpSpanSamplesWebVitalsQuery(
2124
{
@@ -26,6 +29,7 @@ export function useInteractionsCategorizedSamplesQuery({
2629
'measurements.inp': `<${PERFORMANCE_SCORE_P90S.inp}`,
2730
},
2831
browserTypes,
32+
subregions,
2933
}
3034
);
3135
const {data: mehData, isFetching: isMehDataLoading} = useInpSpanSamplesWebVitalsQuery({
@@ -39,6 +43,7 @@ export function useInteractionsCategorizedSamplesQuery({
3943
],
4044
},
4145
browserTypes,
46+
subregions,
4247
});
4348
const {data: poorData, isFetching: isBadDataLoading} = useInpSpanSamplesWebVitalsQuery({
4449
transaction,
@@ -48,6 +53,7 @@ export function useInteractionsCategorizedSamplesQuery({
4853
'measurements.inp': `>=${PERFORMANCE_SCORE_MEDIANS.inp}`,
4954
},
5055
browserTypes,
56+
subregions,
5157
});
5258

5359
const data = [...goodData, ...mehData, ...poorData];

‎static/app/views/insights/browser/webVitals/queries/useTransactionsCategorizedSamplesQuery.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import {
88
PERFORMANCE_SCORE_MEDIANS,
99
PERFORMANCE_SCORE_P90S,
1010
} from 'sentry/views/insights/browser/webVitals/utils/scoreThresholds';
11+
import type {SubregionCode} from 'sentry/views/insights/types';
1112

1213
type Props = {
1314
browserTypes: BrowserType[];
1415
enabled: boolean;
16+
subregions: SubregionCode[];
1517
transaction: string;
1618
webVital: WebVitals | null;
1719
};
@@ -21,6 +23,7 @@ export function useTransactionsCategorizedSamplesQuery({
2123
webVital,
2224
enabled,
2325
browserTypes,
26+
subregions,
2427
}: Props) {
2528
const {data: goodData, isLoading: isGoodTransactionWebVitalsQueryLoading} =
2629
useTransactionSamplesWebVitalsScoresQuery({
@@ -34,6 +37,7 @@ export function useTransactionsCategorizedSamplesQuery({
3437
sortName: 'webVitalSort',
3538
webVital: webVital ?? undefined,
3639
browserTypes,
40+
subregions,
3741
});
3842

3943
const {data: mehData, isLoading: isMehTransactionWebVitalsQueryLoading} =
@@ -48,6 +52,7 @@ export function useTransactionsCategorizedSamplesQuery({
4852
sortName: 'webVitalSort',
4953
webVital: webVital ?? undefined,
5054
browserTypes,
55+
subregions,
5156
});
5257

5358
const {data: poorData, isLoading: isPoorTransactionWebVitalsQueryLoading} =
@@ -62,6 +67,7 @@ export function useTransactionsCategorizedSamplesQuery({
6267
sortName: 'webVitalSort',
6368
webVital: webVital ?? undefined,
6469
browserTypes,
70+
subregions,
6571
});
6672

6773
const data = [...goodData, ...mehData, ...poorData];

‎static/app/views/insights/browser/webVitals/views/pageOverview.tsx

+10-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {space} from 'sentry/styles/space';
1919
import {defined} from 'sentry/utils';
2020
import {trackAnalytics} from 'sentry/utils/analytics';
2121
import {browserHistory} from 'sentry/utils/browserHistory';
22-
import {decodeScalar} from 'sentry/utils/queryString';
22+
import {decodeList, decodeScalar} from 'sentry/utils/queryString';
2323
import {useLocation} from 'sentry/utils/useLocation';
2424
import useOrganization from 'sentry/utils/useOrganization';
2525
import useProjects from 'sentry/utils/useProjects';
@@ -38,7 +38,8 @@ import decodeBrowserTypes from 'sentry/views/insights/browser/webVitals/utils/qu
3838
import {ModulePageProviders} from 'sentry/views/insights/common/components/modulePageProviders';
3939
import {useModuleBreadcrumbs} from 'sentry/views/insights/common/utils/useModuleBreadcrumbs';
4040
import {useModuleURL} from 'sentry/views/insights/common/utils/useModuleURL';
41-
import {SpanIndexedField} from 'sentry/views/insights/types';
41+
import SubregionSelector from 'sentry/views/insights/common/views/spans/selectors/subregionSelector';
42+
import {SpanIndexedField, type SubregionCode} from 'sentry/views/insights/types';
4243
import {transactionSummaryRouteWithQuery} from 'sentry/views/performance/transactionSummary/utils';
4344

4445
export enum LandingDisplayField {
@@ -94,13 +95,17 @@ export function PageOverview() {
9495

9596
const query = decodeScalar(location.query.query);
9697
const browserTypes = decodeBrowserTypes(location.query[SpanIndexedField.BROWSER_NAME]);
98+
const subregions = decodeList(
99+
location.query[SpanIndexedField.USER_GEO_SUBREGION]
100+
) as SubregionCode[];
97101

98102
const {data: pageData, isLoading} = useProjectRawWebVitalsQuery({
99103
transaction,
100104
browserTypes,
105+
subregions,
101106
});
102107
const {data: projectScores, isLoading: isProjectScoresLoading} =
103-
useProjectWebVitalsScoresQuery({transaction, browserTypes});
108+
useProjectWebVitalsScoresQuery({transaction, browserTypes, subregions});
104109

105110
if (transaction === undefined) {
106111
// redirect user to webvitals landing page
@@ -192,6 +197,7 @@ export function PageOverview() {
192197
<DatePageFilter />
193198
</PageFilterBar>
194199
<BrowserTypeSelector />
200+
<SubregionSelector />
195201
</TopMenuContainer>
196202
<Flex>
197203
<PerformanceScoreBreakdownChart transaction={transaction} />
@@ -225,6 +231,7 @@ export function PageOverview() {
225231
transaction={transaction}
226232
projectScoreIsLoading={isLoading}
227233
browserTypes={browserTypes}
234+
subregions={subregions}
228235
/>
229236
</Layout.Side>
230237
</Layout.Body>

‎static/app/views/insights/browser/webVitals/views/webVitalsLandingPage.tsx

+16-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionT
1313
import {Tooltip} from 'sentry/components/tooltip';
1414
import {t, tct} from 'sentry/locale';
1515
import {space} from 'sentry/styles/space';
16+
import {decodeList} from 'sentry/utils/queryString';
1617
import {useLocation} from 'sentry/utils/useLocation';
1718
import useRouter from 'sentry/utils/useRouter';
1819
import BrowserTypeSelector from 'sentry/views/insights/browser/webVitals/components/browserTypeSelector';
@@ -35,7 +36,11 @@ import {ModulePageProviders} from 'sentry/views/insights/common/components/modul
3536
import {ModulesOnboarding} from 'sentry/views/insights/common/components/modulesOnboarding';
3637
import {useModuleBreadcrumbs} from 'sentry/views/insights/common/utils/useModuleBreadcrumbs';
3738
import SubregionSelector from 'sentry/views/insights/common/views/spans/selectors/subregionSelector';
38-
import {ModuleName, SpanIndexedField} from 'sentry/views/insights/types';
39+
import {
40+
ModuleName,
41+
SpanMetricsField,
42+
type SubregionCode,
43+
} from 'sentry/views/insights/types';
3944

4045
export function WebVitalsLandingPage() {
4146
const location = useLocation();
@@ -46,11 +51,17 @@ export function WebVitalsLandingPage() {
4651
webVital: (location.query.webVital as WebVitals) ?? null,
4752
});
4853

49-
const browserTypes = decodeBrowserTypes(location.query[SpanIndexedField.BROWSER_NAME]);
54+
const browserTypes = decodeBrowserTypes(location.query[SpanMetricsField.BROWSER_NAME]);
55+
const subregions = decodeList(
56+
location.query[SpanMetricsField.USER_GEO_SUBREGION]
57+
) as SubregionCode[];
5058

51-
const {data: projectData, isLoading} = useProjectRawWebVitalsQuery({browserTypes});
59+
const {data: projectData, isLoading} = useProjectRawWebVitalsQuery({
60+
browserTypes,
61+
subregions,
62+
});
5263
const {data: projectScores, isLoading: isProjectScoresLoading} =
53-
useProjectWebVitalsScoresQuery({browserTypes});
64+
useProjectWebVitalsScoresQuery({browserTypes, subregions});
5465

5566
const projectScore =
5667
isProjectScoresLoading || isLoading
@@ -101,6 +112,7 @@ export function WebVitalsLandingPage() {
101112
isProjectScoreLoading={isLoading || isProjectScoresLoading}
102113
webVital={state.webVital}
103114
browserTypes={browserTypes}
115+
subregions={subregions}
104116
/>
105117
</PerformanceScoreChartContainer>
106118
<WebVitalMetersContainer>

‎static/app/views/insights/common/components/spanGroupDetailsLink.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ const {SPAN_OP} = SpanMetricsField;
1111

1212
interface Props {
1313
description: React.ReactNode;
14+
// extra query params to add to the link
1415
moduleName: ModuleName.DB | ModuleName.RESOURCE;
1516
projectId: number;
17+
extraLinkQueryParams?: Record<string, string>;
1618
group?: string;
1719
spanOp?: string;
1820
}
@@ -23,6 +25,7 @@ export function SpanGroupDetailsLink({
2325
projectId,
2426
spanOp,
2527
description,
28+
extraLinkQueryParams,
2629
}: Props) {
2730
const location = useLocation();
2831

@@ -32,6 +35,7 @@ export function SpanGroupDetailsLink({
3235
...location.query,
3336
project: projectId,
3437
...(spanOp ? {[SPAN_OP]: spanOp} : {}),
38+
...(extraLinkQueryParams ? extraLinkQueryParams : {}),
3539
};
3640

3741
return (

‎static/app/views/insights/common/components/tableCells/spanDescriptionCell.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ interface Props {
1717
description: string;
1818
moduleName: ModuleName.DB | ModuleName.RESOURCE;
1919
projectId: number;
20+
extraLinkQueryParams?: Record<string, string>; // extra query params to add to the link
2021
group?: string;
2122
spanOp?: string;
2223
}
@@ -27,6 +28,7 @@ export function SpanDescriptionCell({
2728
moduleName,
2829
spanOp,
2930
projectId,
31+
extraLinkQueryParams,
3032
}: Props) {
3133
const formatterDescription = useMemo(() => {
3234
if (moduleName !== ModuleName.DB) {
@@ -47,6 +49,7 @@ export function SpanDescriptionCell({
4749
projectId={projectId}
4850
spanOp={spanOp}
4951
description={formatterDescription}
52+
extraLinkQueryParams={extraLinkQueryParams}
5053
/>
5154
);
5255

‎static/app/views/insights/common/queries/useSpanSamples.tsx

+10-1
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import {getDateConditions} from 'sentry/views/insights/common/utils/getDateCondi
1414
import type {
1515
SpanIndexedFieldTypes,
1616
SpanMetricsQueryFilters,
17+
SubregionCode,
1718
} from 'sentry/views/insights/types';
18-
import {SpanIndexedField} from 'sentry/views/insights/types';
19+
import {SpanIndexedField, SpanMetricsField} from 'sentry/views/insights/types';
1920

2021
const {SPAN_SELF_TIME, SPAN_GROUP} = SpanIndexedField;
2122

@@ -25,6 +26,7 @@ type Options = {
2526
additionalFields?: string[];
2627
release?: string;
2728
spanSearch?: MutableSearch;
29+
subregions?: SubregionCode[];
2830
transactionMethod?: string;
2931
};
3032

@@ -52,6 +54,7 @@ export const useSpanSamples = (options: Options) => {
5254
release,
5355
spanSearch,
5456
additionalFields,
57+
subregions,
5558
} = options;
5659
const location = useLocation();
5760

@@ -73,6 +76,11 @@ export const useSpanSamples = (options: Options) => {
7376
filters.release = release;
7477
}
7578

79+
if (subregions) {
80+
query.addDisjunctionFilterValues(SpanMetricsField.USER_GEO_SUBREGION, subregions);
81+
filters[SpanMetricsField.USER_GEO_SUBREGION] = `[${subregions.join(',')}]`;
82+
}
83+
7684
const dateCondtions = getDateConditions(pageFilter.selection);
7785

7886
const {isLoading: isLoadingSeries, data: spanMetricsSeriesData} = useSpanMetricsSeries(
@@ -103,6 +111,7 @@ export const useSpanSamples = (options: Options) => {
103111
dateCondtions.start,
104112
dateCondtions.end,
105113
queryString,
114+
subregions?.join(','),
106115
additionalFields?.join(','),
107116
],
108117
queryFn: async () => {

‎static/app/views/insights/common/views/spanSummaryPage/sampleList/durationChart/index.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import type {SpanSample} from 'sentry/views/insights/common/queries/useSpanSampl
1616
import {useSpanSamples} from 'sentry/views/insights/common/queries/useSpanSamples';
1717
import {AverageValueMarkLine} from 'sentry/views/insights/common/utils/averageValueMarkLine';
1818
import {useSampleScatterPlotSeries} from 'sentry/views/insights/common/views/spanSummaryPage/sampleList/durationChart/useSampleScatterPlotSeries';
19-
import type {SpanMetricsQueryFilters} from 'sentry/views/insights/types';
19+
import type {SpanMetricsQueryFilters, SubregionCode} from 'sentry/views/insights/types';
2020
import {SpanMetricsField} from 'sentry/views/insights/types';
2121

2222
const {SPAN_SELF_TIME, SPAN_OP} = SpanMetricsField;
@@ -34,6 +34,7 @@ type Props = {
3434
release?: string;
3535
spanDescription?: string;
3636
spanSearch?: MutableSearch;
37+
subregions?: SubregionCode[];
3738
transactionMethod?: string;
3839
};
3940

@@ -49,6 +50,7 @@ function DurationChart({
4950
release,
5051
spanSearch,
5152
platform,
53+
subregions,
5254
additionalFilters,
5355
}: Props) {
5456
const {setPageError} = usePageAlert();
@@ -67,6 +69,10 @@ function DurationChart({
6769
filters.release = release;
6870
}
6971

72+
if (subregions) {
73+
filters[SpanMetricsField.USER_GEO_SUBREGION] = `[${subregions.join(',')}]`;
74+
}
75+
7076
if (platform) {
7177
filters['os.name'] = platform;
7278
}

‎static/app/views/insights/common/views/spanSummaryPage/sampleList/index.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
ModuleName,
3333
SpanIndexedField,
3434
SpanMetricsField,
35+
type SubregionCode,
3536
} from 'sentry/views/insights/types';
3637
import {useSpanFieldSupportedTags} from 'sentry/views/performance/utils/useSpanFieldSupportedTags';
3738

@@ -43,6 +44,7 @@ type Props = {
4344
transactionName: string;
4445
onClose?: () => void;
4546
referrer?: string;
47+
subregions?: SubregionCode[];
4648
transactionMethod?: string;
4749
transactionRoute?: string;
4850
};
@@ -52,6 +54,7 @@ export function SampleList({
5254
moduleName,
5355
transactionName,
5456
transactionMethod,
57+
subregions,
5558
onClose,
5659
transactionRoute = '/performance/summary/',
5760
referrer,
@@ -192,12 +195,14 @@ export function SampleList({
192195
groupId={groupId}
193196
transactionName={transactionName}
194197
transactionMethod={transactionMethod}
198+
subregions={subregions}
195199
/>
196200

197201
<DurationChart
198202
groupId={groupId}
199203
transactionName={transactionName}
200204
transactionMethod={transactionMethod}
205+
subregions={subregions}
201206
additionalFields={additionalFields}
202207
onClickSample={span => {
203208
router.push(
@@ -237,6 +242,7 @@ export function SampleList({
237242
groupId={groupId}
238243
moduleName={moduleName}
239244
transactionName={transactionName}
245+
subregions={subregions}
240246
spanSearch={spanSearch}
241247
columnOrder={columnOrder}
242248
additionalFields={additionalFields}

‎static/app/views/insights/common/views/spanSummaryPage/sampleList/sampleInfo/index.tsx

+7-2
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,19 @@ import {
1212
DataTitles,
1313
getThroughputTitle,
1414
} from 'sentry/views/insights/common/views/spans/types';
15-
import type {SpanMetricsQueryFilters} from 'sentry/views/insights/types';
15+
import type {SpanMetricsQueryFilters, SubregionCode} from 'sentry/views/insights/types';
1616
import {SpanMetricsField} from 'sentry/views/insights/types';
1717

1818
type Props = {
1919
groupId: string;
2020
transactionName: string;
2121
displayedMetrics?: string[];
22+
subregions?: SubregionCode[];
2223
transactionMethod?: string;
2324
};
2425

2526
function SampleInfo(props: Props) {
26-
const {groupId, transactionName, transactionMethod} = props;
27+
const {groupId, transactionName, transactionMethod, subregions} = props;
2728
const {setPageError} = usePageAlert();
2829

2930
const ribbonFilters: SpanMetricsQueryFilters = {
@@ -35,6 +36,10 @@ function SampleInfo(props: Props) {
3536
ribbonFilters['transaction.method'] = transactionMethod;
3637
}
3738

39+
if (subregions) {
40+
ribbonFilters[SpanMetricsField.USER_GEO_SUBREGION] = `[${subregions.join(',')}]`;
41+
}
42+
3843
const {data, error, isLoading} = useSpanMetrics(
3944
{
4045
search: MutableSearch.fromQueryObject(ribbonFilters),

‎static/app/views/insights/common/views/spanSummaryPage/sampleList/sampleTable/sampleTable.tsx

+12-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ import {useSpanMetrics} from 'sentry/views/insights/common/queries/useDiscover';
1515
import type {SpanSample} from 'sentry/views/insights/common/queries/useSpanSamples';
1616
import {useSpanSamples} from 'sentry/views/insights/common/queries/useSpanSamples';
1717
import {useTransactions} from 'sentry/views/insights/common/queries/useTransactions';
18-
import type {ModuleName, SpanMetricsQueryFilters} from 'sentry/views/insights/types';
18+
import type {
19+
ModuleName,
20+
SpanMetricsQueryFilters,
21+
SubregionCode,
22+
} from 'sentry/views/insights/types';
1923
import {SpanMetricsField} from 'sentry/views/insights/types';
2024

2125
const {SPAN_SELF_TIME, SPAN_OP} = SpanMetricsField;
@@ -35,6 +39,7 @@ type Props = {
3539
referrer?: string;
3640
release?: string;
3741
spanSearch?: MutableSearch;
42+
subregions?: SubregionCode[];
3843
transactionMethod?: string;
3944
};
4045

@@ -51,6 +56,7 @@ function SampleTable({
5156
spanSearch,
5257
additionalFields,
5358
additionalFilters,
59+
subregions,
5460
referrer,
5561
}: Props) {
5662
const filters: SpanMetricsQueryFilters = {
@@ -66,6 +72,10 @@ function SampleTable({
6672
filters.release = release;
6773
}
6874

75+
if (subregions) {
76+
filters[SpanMetricsField.USER_GEO_SUBREGION] = `[${subregions.join(',')}]`;
77+
}
78+
6979
const {data, isFetching: isFetchingSpanMetrics} = useSpanMetrics(
7080
{
7181
search: MutableSearch.fromQueryObject({...filters, ...additionalFilters}),
@@ -93,6 +103,7 @@ function SampleTable({
93103
groupId,
94104
transactionName,
95105
transactionMethod,
106+
subregions,
96107
release,
97108
spanSearch,
98109
additionalFields,

‎static/app/views/insights/common/views/spans/selectors/subregionSelector.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {t} from 'sentry/locale';
1111
import {space} from 'sentry/styles/space';
1212
import {trackAnalytics} from 'sentry/utils/analytics';
1313
import {decodeList} from 'sentry/utils/queryString';
14+
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
1415
import {useLocation} from 'sentry/utils/useLocation';
1516
import {useNavigate} from 'sentry/utils/useNavigate';
1617
import useOrganization from 'sentry/utils/useOrganization';
@@ -25,7 +26,12 @@ export default function SubregionSelector() {
2526

2627
const value = decodeList(location.query[SpanMetricsField.USER_GEO_SUBREGION]);
2728
const {data, isLoading} = useSpanMetrics(
28-
{fields: [SpanMetricsField.USER_GEO_SUBREGION], enabled: hasGeoSelectorFeature},
29+
{
30+
fields: [SpanMetricsField.USER_GEO_SUBREGION, 'count()'],
31+
search: new MutableSearch('has:user.geo.subregion'),
32+
enabled: hasGeoSelectorFeature,
33+
sorts: [{field: 'count()', kind: 'desc'}],
34+
},
2935
'api.insights.user-geo-subregion-selector'
3036
);
3137

@@ -48,6 +54,7 @@ export default function SubregionSelector() {
4854

4955
return (
5056
<CompactSelect
57+
searchable
5158
triggerProps={{
5259
prefix: (
5360
<Fragment>
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,33 @@
11
import pick from 'lodash/pick';
22

3+
import {decodeList} from 'sentry/utils/queryString';
34
import {useLocation} from 'sentry/utils/useLocation';
4-
import {SpanMetricsField} from 'sentry/views/insights/types';
5+
import {SpanMetricsField, type SubregionCode} from 'sentry/views/insights/types';
56

67
export type ModuleFilters = {
78
[SpanMetricsField.SPAN_ACTION]?: string;
89
[SpanMetricsField.SPAN_DOMAIN]?: string;
910
[SpanMetricsField.SPAN_GROUP]?: string;
1011
[SpanMetricsField.SPAN_OP]?: string;
12+
[SpanMetricsField.USER_GEO_SUBREGION]?: SubregionCode[];
1113
};
1214

1315
export const useModuleFilters = () => {
1416
const location = useLocation<ModuleFilters>();
1517

16-
return pick(location.query, [
18+
const filters = pick(location.query, [
1719
SpanMetricsField.SPAN_ACTION,
1820
SpanMetricsField.SPAN_DOMAIN,
1921
SpanMetricsField.SPAN_OP,
2022
SpanMetricsField.SPAN_GROUP,
2123
]);
24+
25+
const subregions = decodeList(
26+
location.query[SpanMetricsField.USER_GEO_SUBREGION]
27+
) as SubregionCode[];
28+
if (subregions.length) {
29+
filters[SpanMetricsField.USER_GEO_SUBREGION] = subregions;
30+
}
31+
32+
return filters;
2233
};

‎static/app/views/insights/types.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export enum SpanMetricsField {
5656
URL_FULL = 'url.full',
5757
USER_AGENT_ORIGINAL = 'user_agent.original',
5858
CLIENT_ADDRESS = 'client.address',
59+
BROWSER_NAME = 'browser.name',
5960
USER_GEO_SUBREGION = 'user.geo.subregion',
6061
}
6162

@@ -84,8 +85,7 @@ export type SpanStringFields =
8485
| 'span.status_code'
8586
| 'span.ai.pipeline.group'
8687
| 'project'
87-
| 'messaging.destination.name'
88-
| SpanMetricsField.USER_GEO_SUBREGION;
88+
| 'messaging.destination.name';
8989

9090
export type SpanMetricsQueryFilters = {
9191
[Field in SpanStringFields]?: string;
@@ -166,6 +166,8 @@ export type SpanMetricsResponse = {
166166
| `${Property}(${string})`
167167
| `${Property}(${string},${string})`
168168
| `${Property}(${string},${string},${string})`]: number;
169+
} & {
170+
[SpanMetricsField.USER_GEO_SUBREGION]: SubregionCode;
169171
};
170172

171173
export type MetricsFilters = {
@@ -367,3 +369,5 @@ export const subregionCodeToName = {
367369
'61': 'Polynesia',
368370
'53': 'Australia and New Zealand',
369371
};
372+
373+
export type SubregionCode = keyof typeof subregionCodeToName;

0 commit comments

Comments
 (0)
Please sign in to comment.