diff --git a/static/app/views/insights/browser/common/queries/useResourcesQuery.ts b/static/app/views/insights/browser/common/queries/useResourcesQuery.ts index de95b44228c542..82deeb275623e6 100644 --- a/static/app/views/insights/browser/common/queries/useResourcesQuery.ts +++ b/static/app/views/insights/browser/common/queries/useResourcesQuery.ts @@ -25,6 +25,7 @@ const { HTTP_RESPONSE_CONTENT_LENGTH, PROJECT_ID, FILE_EXTENSION, + USER_GEO_SUBREGION, } = SpanMetricsField; const {TIME_SPENT_PERCENTAGE} = SpanFunction; @@ -49,6 +50,9 @@ export const getResourcesEventViewQuery = ( ...(resourceFilters.transaction ? [`transaction:"${resourceFilters.transaction}"`] : []), + ...(resourceFilters[USER_GEO_SUBREGION] + ? [`user.geo.subregion:[${resourceFilters[USER_GEO_SUBREGION]}]`] + : []), ...getDomainFilter(resourceFilters[SPAN_DOMAIN]), ...(resourceFilters[RESOURCE_RENDER_BLOCKING_STATUS] ? [ diff --git a/static/app/views/insights/browser/resources/components/charts/resourceSummaryCharts.tsx b/static/app/views/insights/browser/resources/components/charts/resourceSummaryCharts.tsx index c5fee05cb2ee67..64aea96d8fde1a 100644 --- a/static/app/views/insights/browser/resources/components/charts/resourceSummaryCharts.tsx +++ b/static/app/views/insights/browser/resources/components/charts/resourceSummaryCharts.tsx @@ -45,6 +45,11 @@ function ResourceSummaryCharts(props: {groupId: string}) { filters[RESOURCE_RENDER_BLOCKING_STATUS], } : {}), + ...(filters[SpanMetricsField.USER_GEO_SUBREGION] + ? { + [SpanMetricsField.USER_GEO_SUBREGION]: `[${filters[SpanMetricsField.USER_GEO_SUBREGION].join(',')}]`, + } + : {}), }), yAxis: [ `spm()`, diff --git a/static/app/views/insights/browser/resources/components/resourceView.tsx b/static/app/views/insights/browser/resources/components/resourceView.tsx index b1eb0ed296e1b1..665fa0a8dac477 100644 --- a/static/app/views/insights/browser/resources/components/resourceView.tsx +++ b/static/app/views/insights/browser/resources/components/resourceView.tsx @@ -36,6 +36,7 @@ const { SPAN_DOMAIN, TRANSACTION, RESOURCE_RENDER_BLOCKING_STATUS, + USER_GEO_SUBREGION, } = BrowserStarfishFields; type Option = { @@ -52,7 +53,12 @@ function ResourceView() { ...(filters[SPAN_DOMAIN] ? {[SPAN_DOMAIN]: filters[SPAN_DOMAIN]} : {}), }; - const extraQuery = getResourceTypeFilter(undefined, DEFAULT_RESOURCE_TYPES); + const extraQuery = [ + ...getResourceTypeFilter(undefined, DEFAULT_RESOURCE_TYPES), + ...(filters[USER_GEO_SUBREGION] + ? [`user.geo.subregion:[${filters[USER_GEO_SUBREGION].join(',')}]`] + : []), + ]; return ( diff --git a/static/app/views/insights/browser/resources/components/tables/resourceSummaryTable.tsx b/static/app/views/insights/browser/resources/components/tables/resourceSummaryTable.tsx index 37bb421abeda82..41cd264d8e2278 100644 --- a/static/app/views/insights/browser/resources/components/tables/resourceSummaryTable.tsx +++ b/static/app/views/insights/browser/resources/components/tables/resourceSummaryTable.tsx @@ -34,6 +34,7 @@ const { SPAN_SELF_TIME, HTTP_RESPONSE_CONTENT_LENGTH, TRANSACTION, + USER_GEO_SUBREGION, } = SpanMetricsField; type Row = { @@ -55,6 +56,7 @@ function ResourceSummaryTable() { const {data, isLoading, pageLinks} = useResourcePagesQuery(groupId, { sort, cursor, + subregions: filters[USER_GEO_SUBREGION], renderBlockingStatus: filters[RESOURCE_RENDER_BLOCKING_STATUS], }); diff --git a/static/app/views/insights/browser/resources/components/tables/resourceTable.tsx b/static/app/views/insights/browser/resources/components/tables/resourceTable.tsx index f9304c712575d7..06e900a5529103 100644 --- a/static/app/views/insights/browser/resources/components/tables/resourceTable.tsx +++ b/static/app/views/insights/browser/resources/components/tables/resourceTable.tsx @@ -25,6 +25,7 @@ import { RESOURCE_THROUGHPUT_UNIT, } from 'sentry/views/insights/browser/resources/settings'; import {ResourceSpanOps} from 'sentry/views/insights/browser/resources/types'; +import {useResourceModuleFilters} from 'sentry/views/insights/browser/resources/utils/useResourceFilters'; import type {ValidSort} from 'sentry/views/insights/browser/resources/utils/useResourceSort'; import {DurationCell} from 'sentry/views/insights/common/components/tableCells/durationCell'; import {renderHeadCell} from 'sentry/views/insights/common/components/tableCells/renderHeadCell'; @@ -79,6 +80,7 @@ function ResourceTable({sort, defaultResourceTypes}: Props) { const location = useLocation(); const organization = useOrganization(); const cursor = decodeScalar(location.query?.[QueryParameterNames.SPANS_CURSOR]); + const filters = useResourceModuleFilters(); const {setPageInfo, pageAlert} = usePageAlert(); const {data, isLoading, pageLinks} = useResourcesQuery({ @@ -130,7 +132,11 @@ function ResourceTable({sort, defaultResourceTypes}: Props) { if (key === SPAN_DESCRIPTION) { const fileExtension = row[SPAN_DESCRIPTION].split('.').pop() || ''; - + const extraLinkQueryParams = {}; + if (filters[SpanMetricsField.USER_GEO_SUBREGION]) { + extraLinkQueryParams[SpanMetricsField.USER_GEO_SUBREGION] = + filters[SpanMetricsField.USER_GEO_SUBREGION]; + } return ( @@ -140,6 +146,7 @@ function ResourceTable({sort, defaultResourceTypes}: Props) { spanOp={row[SPAN_OP]} description={row[SPAN_DESCRIPTION]} group={row[SPAN_GROUP]} + extraLinkQueryParams={extraLinkQueryParams} /> ); diff --git a/static/app/views/insights/browser/resources/queries/useResourcePageQuery.ts b/static/app/views/insights/browser/resources/queries/useResourcePageQuery.ts index d804c77a2eb64e..16b8a143ed4edf 100644 --- a/static/app/views/insights/browser/resources/queries/useResourcePageQuery.ts +++ b/static/app/views/insights/browser/resources/queries/useResourcePageQuery.ts @@ -1,6 +1,6 @@ import type {Sort} from 'sentry/utils/discover/fields'; import {useSpanTransactionMetrics} from 'sentry/views/insights/common/queries/useSpanTransactionMetrics'; -import {SpanMetricsField} from 'sentry/views/insights/types'; +import {SpanMetricsField, type SubregionCode} from 'sentry/views/insights/types'; const {HTTP_RESPONSE_CONTENT_LENGTH, RESOURCE_RENDER_BLOCKING_STATUS} = SpanMetricsField; @@ -9,8 +9,14 @@ export const useResourcePagesQuery = ( { sort, cursor, + subregions, renderBlockingStatus, - }: {sort: Sort; cursor?: string; renderBlockingStatus?: string} + }: { + sort: Sort; + cursor?: string; + renderBlockingStatus?: string; + subregions?: SubregionCode[]; + } ) => { return useSpanTransactionMetrics( { @@ -18,6 +24,9 @@ export const useResourcePagesQuery = ( ...(renderBlockingStatus ? {[RESOURCE_RENDER_BLOCKING_STATUS]: renderBlockingStatus} : {}), + ...(subregions + ? {[SpanMetricsField.USER_GEO_SUBREGION]: `[${subregions.join(',')}]`} + : {}), }, [sort], cursor, diff --git a/static/app/views/insights/browser/resources/utils/useResourceFilters.ts b/static/app/views/insights/browser/resources/utils/useResourceFilters.ts index 293003df739af8..e8af6d8edc9d4f 100644 --- a/static/app/views/insights/browser/resources/utils/useResourceFilters.ts +++ b/static/app/views/insights/browser/resources/utils/useResourceFilters.ts @@ -1,8 +1,11 @@ import pick from 'lodash/pick'; +import {decodeList} from 'sentry/utils/queryString'; import {useLocation} from 'sentry/utils/useLocation'; import type {ResourceSpanOps} from 'sentry/views/insights/browser/resources/types'; +import type {SubregionCode} from 'sentry/views/insights/types'; +// TODO - we should probably just use SpanMetricsField here export enum BrowserStarfishFields { SPAN_OP = 'span.op', TRANSACTION = 'transaction', @@ -10,6 +13,7 @@ export enum BrowserStarfishFields { GROUP_ID = 'groupId', DESCRIPTION = 'description', RESOURCE_RENDER_BLOCKING_STATUS = 'resource.render_blocking_status', + USER_GEO_SUBREGION = 'user.geo.subregion', } export type ModuleFilters = { @@ -22,12 +26,13 @@ export type ModuleFilters = { [BrowserStarfishFields.SPAN_OP]?: ResourceSpanOps; [BrowserStarfishFields.TRANSACTION]?: string; [BrowserStarfishFields.SPAN_DOMAIN]?: string; + [BrowserStarfishFields.USER_GEO_SUBREGION]?: SubregionCode[]; }; export const useResourceModuleFilters = () => { const location = useLocation(); - return pick(location.query, [ + const filters = pick(location.query, [ BrowserStarfishFields.SPAN_DOMAIN, BrowserStarfishFields.SPAN_OP, BrowserStarfishFields.TRANSACTION, @@ -35,4 +40,13 @@ export const useResourceModuleFilters = () => { BrowserStarfishFields.DESCRIPTION, BrowserStarfishFields.RESOURCE_RENDER_BLOCKING_STATUS, ]); + + const subregions = decodeList( + location.query[BrowserStarfishFields.USER_GEO_SUBREGION] + ) as SubregionCode[]; + if (subregions.length) { + filters[BrowserStarfishFields.USER_GEO_SUBREGION] = subregions; + } + + return filters; }; diff --git a/static/app/views/insights/browser/resources/views/resourceSummaryPage.tsx b/static/app/views/insights/browser/resources/views/resourceSummaryPage.tsx index 13ec3955160ed4..731c4e087ea7e5 100644 --- a/static/app/views/insights/browser/resources/views/resourceSummaryPage.tsx +++ b/static/app/views/insights/browser/resources/views/resourceSummaryPage.tsx @@ -29,6 +29,7 @@ import {ToolRibbon} from 'sentry/views/insights/common/components/ribbon'; import {useSpanMetrics} from 'sentry/views/insights/common/queries/useDiscover'; import {useModuleBreadcrumbs} from 'sentry/views/insights/common/utils/useModuleBreadcrumbs'; import {useModuleURL} from 'sentry/views/insights/common/utils/useModuleURL'; +import SubregionSelector from 'sentry/views/insights/common/views/spans/selectors/subregionSelector'; import {SampleList} from 'sentry/views/insights/common/views/spanSummaryPage/sampleList'; import {ModuleName, SpanMetricsField} from 'sentry/views/insights/types'; import {TraceViewSources} from 'sentry/views/performance/newTraceDetails/traceMetadataHeader'; @@ -55,6 +56,11 @@ function ResourceSummary() { { search: MutableSearch.fromQueryObject({ 'span.group': groupId, + ...(filters[SpanMetricsField.USER_GEO_SUBREGION] + ? { + [SpanMetricsField.USER_GEO_SUBREGION]: `[${filters[SpanMetricsField.USER_GEO_SUBREGION].join(',')}]`, + } + : {}), }), fields: [ `avg(${SPAN_SELF_TIME})`, @@ -123,6 +129,7 @@ function ResourceSummary() { + + + + + } /> diff --git a/static/app/views/insights/browser/webVitals/components/charts/performanceScoreBreakdownChart.tsx b/static/app/views/insights/browser/webVitals/components/charts/performanceScoreBreakdownChart.tsx index 556d4a96b12a54..e4a01c34f02080 100644 --- a/static/app/views/insights/browser/webVitals/components/charts/performanceScoreBreakdownChart.tsx +++ b/static/app/views/insights/browser/webVitals/components/charts/performanceScoreBreakdownChart.tsx @@ -15,9 +15,11 @@ import {applyStaticWeightsToTimeseries} from 'sentry/views/insights/browser/webV import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType'; import {PERFORMANCE_SCORE_WEIGHTS} from 'sentry/views/insights/browser/webVitals/utils/scoreThresholds'; import Chart, {ChartType} from 'sentry/views/insights/common/components/chart'; +import type {SubregionCode} from 'sentry/views/insights/types'; type Props = { browserTypes?: BrowserType[]; + subregions?: SubregionCode[]; transaction?: string; }; @@ -43,14 +45,18 @@ export const formatTimeSeriesResultsToChartData = ( }); }; -export function PerformanceScoreBreakdownChart({transaction, browserTypes}: Props) { +export function PerformanceScoreBreakdownChart({ + transaction, + browserTypes, + subregions, +}: Props) { const theme = useTheme(); const segmentColors = [...theme.charts.getColorPalette(3).slice(0, 5)]; const pageFilters = usePageFilters(); const {data: timeseriesData, isLoading: isTimeseriesLoading} = - useProjectWebVitalsScoresTimeseriesQuery({transaction, browserTypes}); + useProjectWebVitalsScoresTimeseriesQuery({transaction, browserTypes, subregions}); const period = pageFilters.selection.datetime.period; const performanceScoreSubtext = (period && DEFAULT_RELATIVE_PERIODS[period]) ?? ''; diff --git a/static/app/views/insights/browser/webVitals/components/charts/performanceScoreChart.tsx b/static/app/views/insights/browser/webVitals/components/charts/performanceScoreChart.tsx index 1cd529869a35cf..2786d92b71ecd5 100644 --- a/static/app/views/insights/browser/webVitals/components/charts/performanceScoreChart.tsx +++ b/static/app/views/insights/browser/webVitals/components/charts/performanceScoreChart.tsx @@ -17,11 +17,13 @@ import type { WebVitals, } from 'sentry/views/insights/browser/webVitals/types'; import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType'; +import type {SubregionCode} from 'sentry/views/insights/types'; type Props = { browserTypes?: BrowserType[]; isProjectScoreLoading?: boolean; projectScore?: ProjectScore; + subregions?: SubregionCode[]; transaction?: string; webVital?: WebVitals | null; }; @@ -34,6 +36,7 @@ export function PerformanceScoreChart({ transaction, isProjectScoreLoading, browserTypes, + subregions, }: Props) { const theme = useTheme(); const pageFilters = usePageFilters(); @@ -100,6 +103,7 @@ export function PerformanceScoreChart({ ); diff --git a/static/app/views/insights/browser/webVitals/components/pageOverviewSidebar.tsx b/static/app/views/insights/browser/webVitals/components/pageOverviewSidebar.tsx index 73fa3a0465c1b3..5b46dedc1d470d 100644 --- a/static/app/views/insights/browser/webVitals/components/pageOverviewSidebar.tsx +++ b/static/app/views/insights/browser/webVitals/components/pageOverviewSidebar.tsx @@ -22,6 +22,7 @@ import {useProjectRawWebVitalsValuesTimeseriesQuery} from 'sentry/views/insights import {MODULE_DOC_LINK} from 'sentry/views/insights/browser/webVitals/settings'; import type {ProjectScore} from 'sentry/views/insights/browser/webVitals/types'; import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType'; +import type {SubregionCode} from 'sentry/views/insights/types'; import {SidebarSpacer} from 'sentry/views/performance/transactionSummary/utils'; const CHART_HEIGHTS = 100; @@ -32,6 +33,7 @@ type Props = { projectScore?: ProjectScore; projectScoreIsLoading?: boolean; search?: string; + subregions?: SubregionCode[]; }; export function PageOverviewSidebar({ @@ -39,6 +41,7 @@ export function PageOverviewSidebar({ transaction, projectScoreIsLoading, browserTypes, + subregions, }: Props) { const theme = useTheme(); const router = useRouter(); @@ -62,6 +65,7 @@ export function PageOverviewSidebar({ transaction, datetime: doubledDatetime, browserTypes, + subregions, }); const {countDiff, currentSeries, currentCount, initialCount} = processSeriesData( diff --git a/static/app/views/insights/browser/webVitals/components/pageOverviewWebVitalsDetailPanel.tsx b/static/app/views/insights/browser/webVitals/components/pageOverviewWebVitalsDetailPanel.tsx index e01eff778827c7..a4606aa25c3726 100644 --- a/static/app/views/insights/browser/webVitals/components/pageOverviewWebVitalsDetailPanel.tsx +++ b/static/app/views/insights/browser/webVitals/components/pageOverviewWebVitalsDetailPanel.tsx @@ -39,7 +39,7 @@ import type { import decodeBrowserTypes from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType'; import useProfileExists from 'sentry/views/insights/browser/webVitals/utils/useProfileExists'; import DetailPanel from 'sentry/views/insights/common/components/detailPanel'; -import {SpanIndexedField} from 'sentry/views/insights/types'; +import {SpanIndexedField, type SubregionCode} from 'sentry/views/insights/types'; import {TraceViewSources} from 'sentry/views/performance/newTraceDetails/traceMetadataHeader'; import {generateReplayLink} from 'sentry/views/performance/transactionSummary/utils'; @@ -89,6 +89,9 @@ export function PageOverviewWebVitalsDetailPanel({ const {replayExists} = useReplayExists(); const browserTypes = decodeBrowserTypes(location.query[SpanIndexedField.BROWSER_NAME]); + const subregions = location.query[ + SpanIndexedField.USER_GEO_SUBREGION + ] as SubregionCode[]; const isInp = webVital === 'inp'; const replayLinkGenerator = generateReplayLink(routes); @@ -104,11 +107,16 @@ export function PageOverviewWebVitalsDetailPanel({ : location.query.transaction : undefined; - const {data: projectData} = useProjectRawWebVitalsQuery({transaction, browserTypes}); + const {data: projectData} = useProjectRawWebVitalsQuery({ + transaction, + browserTypes, + subregions, + }); const {data: projectScoresData} = useProjectWebVitalsScoresQuery({ weightWebVital: webVital ?? 'total', transaction, browserTypes, + subregions, }); const projectScore = calculatePerformanceScoreFromStoredTableDataRow( @@ -121,6 +129,7 @@ export function PageOverviewWebVitalsDetailPanel({ webVital, enabled: Boolean(webVital) && !isInp, browserTypes, + subregions, }); const {data: inpTableData, isLoading: isInteractionsLoading} = @@ -128,6 +137,7 @@ export function PageOverviewWebVitalsDetailPanel({ transaction: transaction ?? '', enabled: Boolean(webVital) && isInp, browserTypes, + subregions, }); const {profileExists} = useProfileExists( @@ -135,7 +145,7 @@ export function PageOverviewWebVitalsDetailPanel({ ); const {data: timeseriesData, isLoading: isTimeseriesLoading} = - useProjectRawWebVitalsValuesTimeseriesQuery({transaction, browserTypes}); + useProjectRawWebVitalsValuesTimeseriesQuery({transaction, browserTypes, subregions}); const webVitalData: LineChartSeries = { data: diff --git a/static/app/views/insights/browser/webVitals/components/tables/pagePerformanceTable.tsx b/static/app/views/insights/browser/webVitals/components/tables/pagePerformanceTable.tsx index 458d16f3c34189..f9c4961e60dd8c 100644 --- a/static/app/views/insights/browser/webVitals/components/tables/pagePerformanceTable.tsx +++ b/static/app/views/insights/browser/webVitals/components/tables/pagePerformanceTable.tsx @@ -20,7 +20,7 @@ import type {Sort} from 'sentry/utils/discover/fields'; import {parseFunction} from 'sentry/utils/discover/fields'; import getDuration from 'sentry/utils/duration/getDuration'; import {formatAbbreviatedNumber} from 'sentry/utils/formatters'; -import {decodeScalar} from 'sentry/utils/queryString'; +import {decodeList, decodeScalar} from 'sentry/utils/queryString'; import {escapeFilterValue} from 'sentry/utils/tokenizeSearch'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; @@ -31,7 +31,11 @@ import type {RowWithScoreAndOpportunity} from 'sentry/views/insights/browser/web import {SORTABLE_FIELDS} from 'sentry/views/insights/browser/webVitals/types'; import decodeBrowserTypes from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType'; import {useWebVitalsSort} from 'sentry/views/insights/browser/webVitals/utils/useWebVitalsSort'; -import {ModuleName, SpanIndexedField} from 'sentry/views/insights/types'; +import { + ModuleName, + SpanIndexedField, + type SubregionCode, +} from 'sentry/views/insights/types'; type Column = GridColumnHeader; @@ -67,6 +71,9 @@ export function PagePerformanceTable() { const query = decodeScalar(location.query.query, ''); const browserTypes = decodeBrowserTypes(location.query[SpanIndexedField.BROWSER_NAME]); + const subregions = decodeList( + location.query[SpanIndexedField.USER_GEO_SUBREGION] + ) as SubregionCode[]; const sort = useWebVitalsSort({defaultSort: DEFAULT_SORT}); @@ -81,6 +88,7 @@ export function PagePerformanceTable() { defaultSort: DEFAULT_SORT, shouldEscapeFilters: false, browserTypes, + subregions, }); const tableData: RowWithScoreAndOpportunity[] = data.map(row => ({ diff --git a/static/app/views/insights/browser/webVitals/components/tables/pageSamplePerformanceTable.tsx b/static/app/views/insights/browser/webVitals/components/tables/pageSamplePerformanceTable.tsx index d1518740d1c39a..1fa66003400a67 100644 --- a/static/app/views/insights/browser/webVitals/components/tables/pageSamplePerformanceTable.tsx +++ b/static/app/views/insights/browser/webVitals/components/tables/pageSamplePerformanceTable.tsx @@ -23,7 +23,7 @@ import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls'; import getDuration from 'sentry/utils/duration/getDuration'; import {getShortEventId} from 'sentry/utils/events'; import {generateProfileFlamechartRoute} from 'sentry/utils/profiling/routes'; -import {decodeScalar} from 'sentry/utils/queryString'; +import {decodeList, decodeScalar} from 'sentry/utils/queryString'; import useReplayExists from 'sentry/utils/replayCount/useReplayExists'; import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import {useLocation} from 'sentry/utils/useLocation'; @@ -46,7 +46,11 @@ import { import decodeBrowserTypes from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType'; import useProfileExists from 'sentry/views/insights/browser/webVitals/utils/useProfileExists'; import {useWebVitalsSort} from 'sentry/views/insights/browser/webVitals/utils/useWebVitalsSort'; -import {SpanIndexedField} from 'sentry/views/insights/types'; +import { + SpanIndexedField, + SpanMetricsField, + type SubregionCode, +} from 'sentry/views/insights/types'; import {TraceViewSources} from 'sentry/views/performance/newTraceDetails/traceMetadataHeader'; import {generateReplayLink} from 'sentry/views/performance/transactionSummary/utils'; @@ -102,6 +106,10 @@ export function PageSamplePerformanceTable({transaction, search, limit = 9}: Pro const router = useRouter(); const browserTypes = decodeBrowserTypes(location.query[SpanIndexedField.BROWSER_NAME]); + const subregions = decodeList( + location.query[SpanMetricsField.USER_GEO_SUBREGION] + ) as SubregionCode[]; + let datatype = Datatype.PAGELOADS; switch (decodeScalar(location.query[DATATYPE_KEY], 'pageloads')) { case 'interactions': @@ -138,6 +146,7 @@ export function PageSamplePerformanceTable({transaction, search, limit = 9}: Pro withProfiles: true, enabled: datatype === Datatype.PAGELOADS, browserTypes, + subregions, }); const { @@ -150,6 +159,7 @@ export function PageSamplePerformanceTable({transaction, search, limit = 9}: Pro limit, filters: new MutableSearch(query ?? '').filters, browserTypes, + subregions, }); const {profileExists} = useProfileExists( diff --git a/static/app/views/insights/browser/webVitals/components/webVitalsDetailPanel.tsx b/static/app/views/insights/browser/webVitals/components/webVitalsDetailPanel.tsx index 04d175fdd35c2e..9178d20a13e23a 100644 --- a/static/app/views/insights/browser/webVitals/components/webVitalsDetailPanel.tsx +++ b/static/app/views/insights/browser/webVitals/components/webVitalsDetailPanel.tsx @@ -16,6 +16,7 @@ import {trackAnalytics} from 'sentry/utils/analytics'; import getDuration from 'sentry/utils/duration/getDuration'; import {formatAbbreviatedNumber} from 'sentry/utils/formatters'; import {PageAlert, PageAlertProvider} from 'sentry/utils/performance/contexts/pageAlert'; +import {decodeList} from 'sentry/utils/queryString'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import {WebVitalStatusLineChart} from 'sentry/views/insights/browser/webVitals/components/charts/webVitalStatusLineChart'; @@ -34,7 +35,7 @@ import type { } from 'sentry/views/insights/browser/webVitals/types'; import decodeBrowserTypes from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType'; import DetailPanel from 'sentry/views/insights/common/components/detailPanel'; -import {SpanIndexedField} from 'sentry/views/insights/types'; +import {SpanIndexedField, type SubregionCode} from 'sentry/views/insights/types'; type Column = GridColumnHeader; @@ -60,11 +61,15 @@ export function WebVitalsDetailPanel({ const location = useLocation(); const organization = useOrganization(); const browserTypes = decodeBrowserTypes(location.query[SpanIndexedField.BROWSER_NAME]); + const subregions = decodeList( + location.query[SpanIndexedField.USER_GEO_SUBREGION] + ) as SubregionCode[]; - const {data: projectData} = useProjectRawWebVitalsQuery({browserTypes}); + const {data: projectData} = useProjectRawWebVitalsQuery({browserTypes, subregions}); const {data: projectScoresData} = useProjectWebVitalsScoresQuery({ weightWebVital: webVital ?? 'total', browserTypes, + subregions, }); const projectScore = calculatePerformanceScoreFromStoredTableDataRow( @@ -85,6 +90,7 @@ export function WebVitalsDetailPanel({ enabled: webVital !== null, sortName: 'webVitalsDetailPanelSort', browserTypes, + subregions, }); const dataByOpportunity = useMemo(() => { @@ -116,7 +122,7 @@ export function WebVitalsDetailPanel({ }, [data, projectScoresData?.data, webVital]); const {data: timeseriesData, isLoading: isTimeseriesLoading} = - useProjectRawWebVitalsValuesTimeseriesQuery({browserTypes}); + useProjectRawWebVitalsValuesTimeseriesQuery({browserTypes, subregions}); const webVitalData: LineChartSeries = { data: diff --git a/static/app/views/insights/browser/webVitals/queries/rawWebVitalsQueries/useProjectRawWebVitalsQuery.tsx b/static/app/views/insights/browser/webVitals/queries/rawWebVitalsQueries/useProjectRawWebVitalsQuery.tsx index e314e1573dc001..c4d8bce8e6f87f 100644 --- a/static/app/views/insights/browser/webVitals/queries/rawWebVitalsQueries/useProjectRawWebVitalsQuery.tsx +++ b/static/app/views/insights/browser/webVitals/queries/rawWebVitalsQueries/useProjectRawWebVitalsQuery.tsx @@ -8,11 +8,12 @@ import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import {DEFAULT_QUERY_FILTER} from 'sentry/views/insights/browser/webVitals/settings'; import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType'; -import {SpanIndexedField} from 'sentry/views/insights/types'; +import {SpanMetricsField, type SubregionCode} from 'sentry/views/insights/types'; type Props = { browserTypes?: BrowserType[]; dataset?: DiscoverDatasets; + subregions?: SubregionCode[]; tag?: Tag; transaction?: string; }; @@ -22,6 +23,7 @@ export const useProjectRawWebVitalsQuery = ({ tag, dataset, browserTypes, + subregions, }: Props = {}) => { const organization = useOrganization(); const pageFilters = usePageFilters(); @@ -33,8 +35,11 @@ export const useProjectRawWebVitalsQuery = ({ if (tag) { search.addFilterValue(tag.key, tag.name); } + if (subregions) { + search.addDisjunctionFilterValues(SpanMetricsField.USER_GEO_SUBREGION, subregions); + } if (browserTypes) { - search.addDisjunctionFilterValues(SpanIndexedField.BROWSER_NAME, browserTypes); + search.addDisjunctionFilterValues(SpanMetricsField.BROWSER_NAME, browserTypes); } const projectEventView = EventView.fromNewQueryWithPageFilters( diff --git a/static/app/views/insights/browser/webVitals/queries/rawWebVitalsQueries/useProjectRawWebVitalsValuesTimeseriesQuery.tsx b/static/app/views/insights/browser/webVitals/queries/rawWebVitalsQueries/useProjectRawWebVitalsValuesTimeseriesQuery.tsx index 818b737a9ff98d..5d7e5bd4187cbd 100644 --- a/static/app/views/insights/browser/webVitals/queries/rawWebVitalsQueries/useProjectRawWebVitalsValuesTimeseriesQuery.tsx +++ b/static/app/views/insights/browser/webVitals/queries/rawWebVitalsQueries/useProjectRawWebVitalsValuesTimeseriesQuery.tsx @@ -12,11 +12,12 @@ import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import {DEFAULT_QUERY_FILTER} from 'sentry/views/insights/browser/webVitals/settings'; import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType'; -import {SpanIndexedField} from 'sentry/views/insights/types'; +import {SpanIndexedField, type SubregionCode} from 'sentry/views/insights/types'; type Props = { browserTypes?: BrowserType[]; datetime?: PageFilters['datetime']; + subregions?: SubregionCode[]; transaction?: string | null; }; @@ -24,6 +25,7 @@ export const useProjectRawWebVitalsValuesTimeseriesQuery = ({ transaction, datetime, browserTypes, + subregions, }: Props) => { const pageFilters = usePageFilters(); const location = useLocation(); @@ -35,6 +37,9 @@ export const useProjectRawWebVitalsValuesTimeseriesQuery = ({ if (browserTypes) { search.addDisjunctionFilterValues(SpanIndexedField.BROWSER_NAME, browserTypes); } + if (subregions) { + search.addDisjunctionFilterValues(SpanIndexedField.USER_GEO_SUBREGION, subregions); + } const projectTimeSeriesEventView = EventView.fromNewQueryWithPageFilters( { yAxis: [ diff --git a/static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useProjectWebVitalsScoresQuery.tsx b/static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useProjectWebVitalsScoresQuery.tsx index a6581316185e00..f9230d8b4dee81 100644 --- a/static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useProjectWebVitalsScoresQuery.tsx +++ b/static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useProjectWebVitalsScoresQuery.tsx @@ -9,12 +9,13 @@ import usePageFilters from 'sentry/utils/usePageFilters'; import {DEFAULT_QUERY_FILTER} from 'sentry/views/insights/browser/webVitals/settings'; import type {WebVitals} from 'sentry/views/insights/browser/webVitals/types'; import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType'; -import {SpanIndexedField} from 'sentry/views/insights/types'; +import {SpanIndexedField, type SubregionCode} from 'sentry/views/insights/types'; type Props = { browserTypes?: BrowserType[]; dataset?: DiscoverDatasets; enabled?: boolean; + subregions?: SubregionCode[]; tag?: Tag; transaction?: string; weightWebVital?: WebVitals | 'total'; @@ -27,6 +28,7 @@ export const useProjectWebVitalsScoresQuery = ({ enabled = true, weightWebVital = 'total', browserTypes, + subregions, }: Props = {}) => { const organization = useOrganization(); const pageFilters = usePageFilters(); @@ -42,6 +44,9 @@ export const useProjectWebVitalsScoresQuery = ({ if (browserTypes) { search.addDisjunctionFilterValues(SpanIndexedField.BROWSER_NAME, browserTypes); } + if (subregions) { + search.addDisjunctionFilterValues(SpanIndexedField.USER_GEO_SUBREGION, subregions); + } const projectEventView = EventView.fromNewQueryWithPageFilters( { diff --git a/static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useProjectWebVitalsScoresTimeseriesQuery.tsx b/static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useProjectWebVitalsScoresTimeseriesQuery.tsx index f01985d8035811..ef739b0917c0a3 100644 --- a/static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useProjectWebVitalsScoresTimeseriesQuery.tsx +++ b/static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useProjectWebVitalsScoresTimeseriesQuery.tsx @@ -12,11 +12,12 @@ import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import {DEFAULT_QUERY_FILTER} from 'sentry/views/insights/browser/webVitals/settings'; import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType'; -import {SpanIndexedField} from 'sentry/views/insights/types'; +import {SpanMetricsField, type SubregionCode} from 'sentry/views/insights/types'; type Props = { browserTypes?: BrowserType[]; enabled?: boolean; + subregions?: SubregionCode[]; tag?: Tag; transaction?: string | null; weighted?: boolean; @@ -44,6 +45,7 @@ export const useProjectWebVitalsScoresTimeseriesQuery = ({ tag, enabled = true, browserTypes, + subregions, }: Props) => { const pageFilters = usePageFilters(); const location = useLocation(); @@ -55,8 +57,11 @@ export const useProjectWebVitalsScoresTimeseriesQuery = ({ if (transaction) { search.addFilterValue('transaction', transaction); } + if (subregions) { + search.addDisjunctionFilterValues(SpanMetricsField.USER_GEO_SUBREGION, subregions); + } if (browserTypes) { - search.addDisjunctionFilterValues(SpanIndexedField.BROWSER_NAME, browserTypes); + search.addDisjunctionFilterValues(SpanMetricsField.BROWSER_NAME, browserTypes); } const projectTimeSeriesEventView = EventView.fromNewQueryWithPageFilters( { diff --git a/static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useTransactionSamplesWebVitalsScoresQuery.tsx b/static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useTransactionSamplesWebVitalsScoresQuery.tsx index 2596843796fdfc..0ffb4c01f0e211 100644 --- a/static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useTransactionSamplesWebVitalsScoresQuery.tsx +++ b/static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useTransactionSamplesWebVitalsScoresQuery.tsx @@ -17,7 +17,7 @@ import { import {mapWebVitalToOrderBy} from 'sentry/views/insights/browser/webVitals/utils/mapWebVitalToOrderBy'; import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType'; import {useWebVitalsSort} from 'sentry/views/insights/browser/webVitals/utils/useWebVitalsSort'; -import {SpanIndexedField} from 'sentry/views/insights/types'; +import {SpanIndexedField, type SubregionCode} from 'sentry/views/insights/types'; type Props = { transaction: string; @@ -27,6 +27,7 @@ type Props = { orderBy?: WebVitals | null; query?: string; sortName?: string; + subregions?: SubregionCode[]; webVital?: WebVitals; withProfiles?: boolean; }; @@ -41,6 +42,7 @@ export const useTransactionSamplesWebVitalsScoresQuery = ({ sortName, webVital, browserTypes, + subregions, }: Props) => { const organization = useOrganization(); const pageFilters = usePageFilters(); @@ -64,6 +66,12 @@ export const useTransactionSamplesWebVitalsScoresQuery = ({ if (browserTypes) { mutableSearch.addDisjunctionFilterValues(SpanIndexedField.BROWSER_NAME, browserTypes); } + if (subregions) { + mutableSearch.addDisjunctionFilterValues( + SpanIndexedField.USER_GEO_SUBREGION, + subregions + ); + } const eventView = EventView.fromNewQueryWithPageFilters( { diff --git a/static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useTransactionWebVitalsScoresQuery.tsx b/static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useTransactionWebVitalsScoresQuery.tsx index 1f49591c469722..ae5c468d3bbef4 100644 --- a/static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useTransactionWebVitalsScoresQuery.tsx +++ b/static/app/views/insights/browser/webVitals/queries/storedScoreQueries/useTransactionWebVitalsScoresQuery.tsx @@ -14,7 +14,7 @@ import type { } from 'sentry/views/insights/browser/webVitals/types'; import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType'; import {useWebVitalsSort} from 'sentry/views/insights/browser/webVitals/utils/useWebVitalsSort'; -import {SpanIndexedField} from 'sentry/views/insights/types'; +import {SpanIndexedField, type SubregionCode} from 'sentry/views/insights/types'; type Props = { browserTypes?: BrowserType[]; @@ -24,6 +24,7 @@ type Props = { query?: string; shouldEscapeFilters?: boolean; sortName?: string; + subregions?: SubregionCode[]; transaction?: string | null; webVital?: WebVitals | 'total'; }; @@ -38,6 +39,7 @@ export const useTransactionWebVitalsScoresQuery = ({ query, shouldEscapeFilters = true, browserTypes, + subregions, }: Props) => { const organization = useOrganization(); const pageFilters = usePageFilters(); @@ -63,6 +65,10 @@ export const useTransactionWebVitalsScoresQuery = ({ if (browserTypes) { search.addDisjunctionFilterValues(SpanIndexedField.BROWSER_NAME, browserTypes); } + if (subregions) { + search.addDisjunctionFilterValues(SpanIndexedField.USER_GEO_SUBREGION, subregions); + } + const eventView = EventView.fromNewQueryWithPageFilters( { fields: [ diff --git a/static/app/views/insights/browser/webVitals/queries/useInpSpanSamplesWebVitalsQuery.tsx b/static/app/views/insights/browser/webVitals/queries/useInpSpanSamplesWebVitalsQuery.tsx index e4b3dfe2f29054..70e2ddc6c260b7 100644 --- a/static/app/views/insights/browser/webVitals/queries/useInpSpanSamplesWebVitalsQuery.tsx +++ b/static/app/views/insights/browser/webVitals/queries/useInpSpanSamplesWebVitalsQuery.tsx @@ -7,7 +7,7 @@ import { import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType'; import {useWebVitalsSort} from 'sentry/views/insights/browser/webVitals/utils/useWebVitalsSort'; import {useSpansIndexed} from 'sentry/views/insights/common/queries/useDiscover'; -import {SpanIndexedField} from 'sentry/views/insights/types'; +import {SpanIndexedField, type SubregionCode} from 'sentry/views/insights/types'; export function useInpSpanSamplesWebVitalsQuery({ transaction, @@ -16,12 +16,14 @@ export function useInpSpanSamplesWebVitalsQuery({ filters = {}, sortName, browserTypes, + subregions, }: { limit: number; browserTypes?: BrowserType[]; enabled?: boolean; filters?: {[key: string]: string[] | string | number | undefined}; sortName?: string; + subregions?: SubregionCode[]; transaction?: string; }) { const filteredSortableFields = SORTABLE_INDEXED_INTERACTION_FIELDS; @@ -44,6 +46,12 @@ export function useInpSpanSamplesWebVitalsQuery({ if (browserTypes) { mutableSearch.addDisjunctionFilterValues(SpanIndexedField.BROWSER_NAME, browserTypes); } + if (subregions) { + mutableSearch.addDisjunctionFilterValues( + SpanIndexedField.USER_GEO_SUBREGION, + subregions + ); + } const {data, isLoading, ...rest} = useSpansIndexed( { diff --git a/static/app/views/insights/browser/webVitals/queries/useInteractionsCategorizedSamplesQuery.tsx b/static/app/views/insights/browser/webVitals/queries/useInteractionsCategorizedSamplesQuery.tsx index f15242aafd330e..7218d954fe680d 100644 --- a/static/app/views/insights/browser/webVitals/queries/useInteractionsCategorizedSamplesQuery.tsx +++ b/static/app/views/insights/browser/webVitals/queries/useInteractionsCategorizedSamplesQuery.tsx @@ -5,10 +5,12 @@ import { PERFORMANCE_SCORE_MEDIANS, PERFORMANCE_SCORE_P90S, } from 'sentry/views/insights/browser/webVitals/utils/scoreThresholds'; +import type {SubregionCode} from 'sentry/views/insights/types'; type Props = { browserTypes: BrowserType[]; enabled: boolean; + subregions: SubregionCode[]; transaction: string; }; @@ -16,6 +18,7 @@ export function useInteractionsCategorizedSamplesQuery({ transaction, enabled, browserTypes, + subregions, }: Props) { const {data: goodData, isFetching: isGoodDataLoading} = useInpSpanSamplesWebVitalsQuery( { @@ -26,6 +29,7 @@ export function useInteractionsCategorizedSamplesQuery({ 'measurements.inp': `<${PERFORMANCE_SCORE_P90S.inp}`, }, browserTypes, + subregions, } ); const {data: mehData, isFetching: isMehDataLoading} = useInpSpanSamplesWebVitalsQuery({ @@ -39,6 +43,7 @@ export function useInteractionsCategorizedSamplesQuery({ ], }, browserTypes, + subregions, }); const {data: poorData, isFetching: isBadDataLoading} = useInpSpanSamplesWebVitalsQuery({ transaction, @@ -48,6 +53,7 @@ export function useInteractionsCategorizedSamplesQuery({ 'measurements.inp': `>=${PERFORMANCE_SCORE_MEDIANS.inp}`, }, browserTypes, + subregions, }); const data = [...goodData, ...mehData, ...poorData]; diff --git a/static/app/views/insights/browser/webVitals/queries/useTransactionsCategorizedSamplesQuery.tsx b/static/app/views/insights/browser/webVitals/queries/useTransactionsCategorizedSamplesQuery.tsx index 96d4be3cc26d18..1de8fa94159734 100644 --- a/static/app/views/insights/browser/webVitals/queries/useTransactionsCategorizedSamplesQuery.tsx +++ b/static/app/views/insights/browser/webVitals/queries/useTransactionsCategorizedSamplesQuery.tsx @@ -8,10 +8,12 @@ import { PERFORMANCE_SCORE_MEDIANS, PERFORMANCE_SCORE_P90S, } from 'sentry/views/insights/browser/webVitals/utils/scoreThresholds'; +import type {SubregionCode} from 'sentry/views/insights/types'; type Props = { browserTypes: BrowserType[]; enabled: boolean; + subregions: SubregionCode[]; transaction: string; webVital: WebVitals | null; }; @@ -21,6 +23,7 @@ export function useTransactionsCategorizedSamplesQuery({ webVital, enabled, browserTypes, + subregions, }: Props) { const {data: goodData, isLoading: isGoodTransactionWebVitalsQueryLoading} = useTransactionSamplesWebVitalsScoresQuery({ @@ -34,6 +37,7 @@ export function useTransactionsCategorizedSamplesQuery({ sortName: 'webVitalSort', webVital: webVital ?? undefined, browserTypes, + subregions, }); const {data: mehData, isLoading: isMehTransactionWebVitalsQueryLoading} = @@ -48,6 +52,7 @@ export function useTransactionsCategorizedSamplesQuery({ sortName: 'webVitalSort', webVital: webVital ?? undefined, browserTypes, + subregions, }); const {data: poorData, isLoading: isPoorTransactionWebVitalsQueryLoading} = @@ -62,6 +67,7 @@ export function useTransactionsCategorizedSamplesQuery({ sortName: 'webVitalSort', webVital: webVital ?? undefined, browserTypes, + subregions, }); const data = [...goodData, ...mehData, ...poorData]; diff --git a/static/app/views/insights/browser/webVitals/views/pageOverview.tsx b/static/app/views/insights/browser/webVitals/views/pageOverview.tsx index fefdc0ade738ec..55bfe0d83c8739 100644 --- a/static/app/views/insights/browser/webVitals/views/pageOverview.tsx +++ b/static/app/views/insights/browser/webVitals/views/pageOverview.tsx @@ -19,7 +19,7 @@ import {space} from 'sentry/styles/space'; import {defined} from 'sentry/utils'; import {trackAnalytics} from 'sentry/utils/analytics'; import {browserHistory} from 'sentry/utils/browserHistory'; -import {decodeScalar} from 'sentry/utils/queryString'; +import {decodeList, decodeScalar} from 'sentry/utils/queryString'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import useProjects from 'sentry/utils/useProjects'; @@ -38,7 +38,8 @@ import decodeBrowserTypes from 'sentry/views/insights/browser/webVitals/utils/qu import {ModulePageProviders} from 'sentry/views/insights/common/components/modulePageProviders'; import {useModuleBreadcrumbs} from 'sentry/views/insights/common/utils/useModuleBreadcrumbs'; import {useModuleURL} from 'sentry/views/insights/common/utils/useModuleURL'; -import {SpanIndexedField} from 'sentry/views/insights/types'; +import SubregionSelector from 'sentry/views/insights/common/views/spans/selectors/subregionSelector'; +import {SpanIndexedField, type SubregionCode} from 'sentry/views/insights/types'; import {transactionSummaryRouteWithQuery} from 'sentry/views/performance/transactionSummary/utils'; export enum LandingDisplayField { @@ -94,13 +95,17 @@ export function PageOverview() { const query = decodeScalar(location.query.query); const browserTypes = decodeBrowserTypes(location.query[SpanIndexedField.BROWSER_NAME]); + const subregions = decodeList( + location.query[SpanIndexedField.USER_GEO_SUBREGION] + ) as SubregionCode[]; const {data: pageData, isLoading} = useProjectRawWebVitalsQuery({ transaction, browserTypes, + subregions, }); const {data: projectScores, isLoading: isProjectScoresLoading} = - useProjectWebVitalsScoresQuery({transaction, browserTypes}); + useProjectWebVitalsScoresQuery({transaction, browserTypes, subregions}); if (transaction === undefined) { // redirect user to webvitals landing page @@ -192,6 +197,7 @@ export function PageOverview() { + @@ -225,6 +231,7 @@ export function PageOverview() { transaction={transaction} projectScoreIsLoading={isLoading} browserTypes={browserTypes} + subregions={subregions} /> diff --git a/static/app/views/insights/browser/webVitals/views/webVitalsLandingPage.tsx b/static/app/views/insights/browser/webVitals/views/webVitalsLandingPage.tsx index b27ee751253748..4503787bf9d808 100644 --- a/static/app/views/insights/browser/webVitals/views/webVitalsLandingPage.tsx +++ b/static/app/views/insights/browser/webVitals/views/webVitalsLandingPage.tsx @@ -13,6 +13,7 @@ import {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionT import {Tooltip} from 'sentry/components/tooltip'; import {t, tct} from 'sentry/locale'; import {space} from 'sentry/styles/space'; +import {decodeList} from 'sentry/utils/queryString'; import {useLocation} from 'sentry/utils/useLocation'; import useRouter from 'sentry/utils/useRouter'; import BrowserTypeSelector from 'sentry/views/insights/browser/webVitals/components/browserTypeSelector'; @@ -35,7 +36,11 @@ import {ModulePageProviders} from 'sentry/views/insights/common/components/modul import {ModulesOnboarding} from 'sentry/views/insights/common/components/modulesOnboarding'; import {useModuleBreadcrumbs} from 'sentry/views/insights/common/utils/useModuleBreadcrumbs'; import SubregionSelector from 'sentry/views/insights/common/views/spans/selectors/subregionSelector'; -import {ModuleName, SpanIndexedField} from 'sentry/views/insights/types'; +import { + ModuleName, + SpanMetricsField, + type SubregionCode, +} from 'sentry/views/insights/types'; export function WebVitalsLandingPage() { const location = useLocation(); @@ -46,11 +51,17 @@ export function WebVitalsLandingPage() { webVital: (location.query.webVital as WebVitals) ?? null, }); - const browserTypes = decodeBrowserTypes(location.query[SpanIndexedField.BROWSER_NAME]); + const browserTypes = decodeBrowserTypes(location.query[SpanMetricsField.BROWSER_NAME]); + const subregions = decodeList( + location.query[SpanMetricsField.USER_GEO_SUBREGION] + ) as SubregionCode[]; - const {data: projectData, isLoading} = useProjectRawWebVitalsQuery({browserTypes}); + const {data: projectData, isLoading} = useProjectRawWebVitalsQuery({ + browserTypes, + subregions, + }); const {data: projectScores, isLoading: isProjectScoresLoading} = - useProjectWebVitalsScoresQuery({browserTypes}); + useProjectWebVitalsScoresQuery({browserTypes, subregions}); const projectScore = isProjectScoresLoading || isLoading @@ -101,6 +112,7 @@ export function WebVitalsLandingPage() { isProjectScoreLoading={isLoading || isProjectScoresLoading} webVital={state.webVital} browserTypes={browserTypes} + subregions={subregions} /> diff --git a/static/app/views/insights/common/components/spanGroupDetailsLink.tsx b/static/app/views/insights/common/components/spanGroupDetailsLink.tsx index a5aa3dfd8e69ae..de279bdd41a11e 100644 --- a/static/app/views/insights/common/components/spanGroupDetailsLink.tsx +++ b/static/app/views/insights/common/components/spanGroupDetailsLink.tsx @@ -11,8 +11,10 @@ const {SPAN_OP} = SpanMetricsField; interface Props { description: React.ReactNode; + // extra query params to add to the link moduleName: ModuleName.DB | ModuleName.RESOURCE; projectId: number; + extraLinkQueryParams?: Record; group?: string; spanOp?: string; } @@ -23,6 +25,7 @@ export function SpanGroupDetailsLink({ projectId, spanOp, description, + extraLinkQueryParams, }: Props) { const location = useLocation(); @@ -32,6 +35,7 @@ export function SpanGroupDetailsLink({ ...location.query, project: projectId, ...(spanOp ? {[SPAN_OP]: spanOp} : {}), + ...(extraLinkQueryParams ? extraLinkQueryParams : {}), }; return ( diff --git a/static/app/views/insights/common/components/tableCells/spanDescriptionCell.tsx b/static/app/views/insights/common/components/tableCells/spanDescriptionCell.tsx index da5c81914fbf51..d0d60c07754faa 100644 --- a/static/app/views/insights/common/components/tableCells/spanDescriptionCell.tsx +++ b/static/app/views/insights/common/components/tableCells/spanDescriptionCell.tsx @@ -17,6 +17,7 @@ interface Props { description: string; moduleName: ModuleName.DB | ModuleName.RESOURCE; projectId: number; + extraLinkQueryParams?: Record; // extra query params to add to the link group?: string; spanOp?: string; } @@ -27,6 +28,7 @@ export function SpanDescriptionCell({ moduleName, spanOp, projectId, + extraLinkQueryParams, }: Props) { const formatterDescription = useMemo(() => { if (moduleName !== ModuleName.DB) { @@ -47,6 +49,7 @@ export function SpanDescriptionCell({ projectId={projectId} spanOp={spanOp} description={formatterDescription} + extraLinkQueryParams={extraLinkQueryParams} /> ); diff --git a/static/app/views/insights/common/queries/useSpanSamples.tsx b/static/app/views/insights/common/queries/useSpanSamples.tsx index 50b8bbb2d3c646..79aa6b36f98173 100644 --- a/static/app/views/insights/common/queries/useSpanSamples.tsx +++ b/static/app/views/insights/common/queries/useSpanSamples.tsx @@ -14,8 +14,9 @@ import {getDateConditions} from 'sentry/views/insights/common/utils/getDateCondi import type { SpanIndexedFieldTypes, SpanMetricsQueryFilters, + SubregionCode, } from 'sentry/views/insights/types'; -import {SpanIndexedField} from 'sentry/views/insights/types'; +import {SpanIndexedField, SpanMetricsField} from 'sentry/views/insights/types'; const {SPAN_SELF_TIME, SPAN_GROUP} = SpanIndexedField; @@ -25,6 +26,7 @@ type Options = { additionalFields?: string[]; release?: string; spanSearch?: MutableSearch; + subregions?: SubregionCode[]; transactionMethod?: string; }; @@ -52,6 +54,7 @@ export const useSpanSamples = (options: Options) => { release, spanSearch, additionalFields, + subregions, } = options; const location = useLocation(); @@ -73,6 +76,11 @@ export const useSpanSamples = (options: Options) => { filters.release = release; } + if (subregions) { + query.addDisjunctionFilterValues(SpanMetricsField.USER_GEO_SUBREGION, subregions); + filters[SpanMetricsField.USER_GEO_SUBREGION] = `[${subregions.join(',')}]`; + } + const dateCondtions = getDateConditions(pageFilter.selection); const {isLoading: isLoadingSeries, data: spanMetricsSeriesData} = useSpanMetricsSeries( @@ -103,6 +111,7 @@ export const useSpanSamples = (options: Options) => { dateCondtions.start, dateCondtions.end, queryString, + subregions?.join(','), additionalFields?.join(','), ], queryFn: async () => { diff --git a/static/app/views/insights/common/views/spanSummaryPage/sampleList/durationChart/index.tsx b/static/app/views/insights/common/views/spanSummaryPage/sampleList/durationChart/index.tsx index 6150796205960d..53963136921444 100644 --- a/static/app/views/insights/common/views/spanSummaryPage/sampleList/durationChart/index.tsx +++ b/static/app/views/insights/common/views/spanSummaryPage/sampleList/durationChart/index.tsx @@ -16,7 +16,7 @@ import type {SpanSample} from 'sentry/views/insights/common/queries/useSpanSampl import {useSpanSamples} from 'sentry/views/insights/common/queries/useSpanSamples'; import {AverageValueMarkLine} from 'sentry/views/insights/common/utils/averageValueMarkLine'; import {useSampleScatterPlotSeries} from 'sentry/views/insights/common/views/spanSummaryPage/sampleList/durationChart/useSampleScatterPlotSeries'; -import type {SpanMetricsQueryFilters} from 'sentry/views/insights/types'; +import type {SpanMetricsQueryFilters, SubregionCode} from 'sentry/views/insights/types'; import {SpanMetricsField} from 'sentry/views/insights/types'; const {SPAN_SELF_TIME, SPAN_OP} = SpanMetricsField; @@ -34,6 +34,7 @@ type Props = { release?: string; spanDescription?: string; spanSearch?: MutableSearch; + subregions?: SubregionCode[]; transactionMethod?: string; }; @@ -49,6 +50,7 @@ function DurationChart({ release, spanSearch, platform, + subregions, additionalFilters, }: Props) { const {setPageError} = usePageAlert(); @@ -67,6 +69,10 @@ function DurationChart({ filters.release = release; } + if (subregions) { + filters[SpanMetricsField.USER_GEO_SUBREGION] = `[${subregions.join(',')}]`; + } + if (platform) { filters['os.name'] = platform; } diff --git a/static/app/views/insights/common/views/spanSummaryPage/sampleList/index.tsx b/static/app/views/insights/common/views/spanSummaryPage/sampleList/index.tsx index 2a251c8075498a..eab55f44306304 100644 --- a/static/app/views/insights/common/views/spanSummaryPage/sampleList/index.tsx +++ b/static/app/views/insights/common/views/spanSummaryPage/sampleList/index.tsx @@ -33,6 +33,7 @@ import { ModuleName, SpanIndexedField, SpanMetricsField, + type SubregionCode, } from 'sentry/views/insights/types'; import {useSpanFieldSupportedTags} from 'sentry/views/performance/utils/useSpanFieldSupportedTags'; @@ -44,6 +45,7 @@ type Props = { transactionName: string; onClose?: () => void; referrer?: string; + subregions?: SubregionCode[]; transactionMethod?: string; transactionRoute?: string; }; @@ -53,6 +55,7 @@ export function SampleList({ moduleName, transactionName, transactionMethod, + subregions, onClose, transactionRoute = '/performance/summary/', referrer, @@ -193,12 +196,14 @@ export function SampleList({ groupId={groupId} transactionName={transactionName} transactionMethod={transactionMethod} + subregions={subregions} /> { router.push( @@ -240,6 +245,7 @@ export function SampleList({ groupId={groupId} moduleName={moduleName} transactionName={transactionName} + subregions={subregions} spanSearch={spanSearch} columnOrder={columnOrder} additionalFields={additionalFields} diff --git a/static/app/views/insights/common/views/spanSummaryPage/sampleList/sampleInfo/index.tsx b/static/app/views/insights/common/views/spanSummaryPage/sampleList/sampleInfo/index.tsx index 82c7eea277fd1c..49f876e7941cba 100644 --- a/static/app/views/insights/common/views/spanSummaryPage/sampleList/sampleInfo/index.tsx +++ b/static/app/views/insights/common/views/spanSummaryPage/sampleList/sampleInfo/index.tsx @@ -12,18 +12,19 @@ import { DataTitles, getThroughputTitle, } from 'sentry/views/insights/common/views/spans/types'; -import type {SpanMetricsQueryFilters} from 'sentry/views/insights/types'; +import type {SpanMetricsQueryFilters, SubregionCode} from 'sentry/views/insights/types'; import {SpanMetricsField} from 'sentry/views/insights/types'; type Props = { groupId: string; transactionName: string; displayedMetrics?: string[]; + subregions?: SubregionCode[]; transactionMethod?: string; }; function SampleInfo(props: Props) { - const {groupId, transactionName, transactionMethod} = props; + const {groupId, transactionName, transactionMethod, subregions} = props; const {setPageError} = usePageAlert(); const ribbonFilters: SpanMetricsQueryFilters = { @@ -35,6 +36,10 @@ function SampleInfo(props: Props) { ribbonFilters['transaction.method'] = transactionMethod; } + if (subregions) { + ribbonFilters[SpanMetricsField.USER_GEO_SUBREGION] = `[${subregions.join(',')}]`; + } + const {data, error, isLoading} = useSpanMetrics( { search: MutableSearch.fromQueryObject(ribbonFilters), diff --git a/static/app/views/insights/common/views/spanSummaryPage/sampleList/sampleTable/sampleTable.tsx b/static/app/views/insights/common/views/spanSummaryPage/sampleList/sampleTable/sampleTable.tsx index c354e482d77ab9..d345de1c33d402 100644 --- a/static/app/views/insights/common/views/spanSummaryPage/sampleList/sampleTable/sampleTable.tsx +++ b/static/app/views/insights/common/views/spanSummaryPage/sampleList/sampleTable/sampleTable.tsx @@ -15,7 +15,11 @@ import {useSpanMetrics} from 'sentry/views/insights/common/queries/useDiscover'; import type {SpanSample} from 'sentry/views/insights/common/queries/useSpanSamples'; import {useSpanSamples} from 'sentry/views/insights/common/queries/useSpanSamples'; import {useTransactions} from 'sentry/views/insights/common/queries/useTransactions'; -import type {ModuleName, SpanMetricsQueryFilters} from 'sentry/views/insights/types'; +import type { + ModuleName, + SpanMetricsQueryFilters, + SubregionCode, +} from 'sentry/views/insights/types'; import {SpanMetricsField} from 'sentry/views/insights/types'; const {SPAN_SELF_TIME, SPAN_OP} = SpanMetricsField; @@ -35,6 +39,7 @@ type Props = { referrer?: string; release?: string; spanSearch?: MutableSearch; + subregions?: SubregionCode[]; transactionMethod?: string; }; @@ -51,6 +56,7 @@ function SampleTable({ spanSearch, additionalFields, additionalFilters, + subregions, referrer, }: Props) { const filters: SpanMetricsQueryFilters = { @@ -66,6 +72,10 @@ function SampleTable({ filters.release = release; } + if (subregions) { + filters[SpanMetricsField.USER_GEO_SUBREGION] = `[${subregions.join(',')}]`; + } + const {data, isFetching: isFetchingSpanMetrics} = useSpanMetrics( { search: MutableSearch.fromQueryObject({...filters, ...additionalFilters}), @@ -93,6 +103,7 @@ function SampleTable({ groupId, transactionName, transactionMethod, + subregions, release, spanSearch, additionalFields, diff --git a/static/app/views/insights/common/views/spans/selectors/subregionSelector.tsx b/static/app/views/insights/common/views/spans/selectors/subregionSelector.tsx index ad8a108d729ca2..ab94110e4ff52d 100644 --- a/static/app/views/insights/common/views/spans/selectors/subregionSelector.tsx +++ b/static/app/views/insights/common/views/spans/selectors/subregionSelector.tsx @@ -11,6 +11,7 @@ import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {trackAnalytics} from 'sentry/utils/analytics'; import {decodeList} from 'sentry/utils/queryString'; +import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import {useLocation} from 'sentry/utils/useLocation'; import {useNavigate} from 'sentry/utils/useNavigate'; import useOrganization from 'sentry/utils/useOrganization'; @@ -25,7 +26,12 @@ export default function SubregionSelector() { const value = decodeList(location.query[SpanMetricsField.USER_GEO_SUBREGION]); const {data, isLoading} = useSpanMetrics( - {fields: [SpanMetricsField.USER_GEO_SUBREGION], enabled: hasGeoSelectorFeature}, + { + fields: [SpanMetricsField.USER_GEO_SUBREGION, 'count()'], + search: new MutableSearch('has:user.geo.subregion'), + enabled: hasGeoSelectorFeature, + sorts: [{field: 'count()', kind: 'desc'}], + }, 'api.insights.user-geo-subregion-selector' ); @@ -48,6 +54,7 @@ export default function SubregionSelector() { return ( diff --git a/static/app/views/insights/common/views/spans/useModuleFilters.ts b/static/app/views/insights/common/views/spans/useModuleFilters.ts index 287006f63e6fb8..74fdfd4ba8c040 100644 --- a/static/app/views/insights/common/views/spans/useModuleFilters.ts +++ b/static/app/views/insights/common/views/spans/useModuleFilters.ts @@ -1,22 +1,33 @@ import pick from 'lodash/pick'; +import {decodeList} from 'sentry/utils/queryString'; import {useLocation} from 'sentry/utils/useLocation'; -import {SpanMetricsField} from 'sentry/views/insights/types'; +import {SpanMetricsField, type SubregionCode} from 'sentry/views/insights/types'; export type ModuleFilters = { [SpanMetricsField.SPAN_ACTION]?: string; [SpanMetricsField.SPAN_DOMAIN]?: string; [SpanMetricsField.SPAN_GROUP]?: string; [SpanMetricsField.SPAN_OP]?: string; + [SpanMetricsField.USER_GEO_SUBREGION]?: SubregionCode[]; }; export const useModuleFilters = () => { const location = useLocation(); - return pick(location.query, [ + const filters = pick(location.query, [ SpanMetricsField.SPAN_ACTION, SpanMetricsField.SPAN_DOMAIN, SpanMetricsField.SPAN_OP, SpanMetricsField.SPAN_GROUP, ]); + + const subregions = decodeList( + location.query[SpanMetricsField.USER_GEO_SUBREGION] + ) as SubregionCode[]; + if (subregions.length) { + filters[SpanMetricsField.USER_GEO_SUBREGION] = subregions; + } + + return filters; }; diff --git a/static/app/views/insights/types.tsx b/static/app/views/insights/types.tsx index 1b0505e3f457cf..3387eb2fb682c8 100644 --- a/static/app/views/insights/types.tsx +++ b/static/app/views/insights/types.tsx @@ -56,6 +56,7 @@ export enum SpanMetricsField { URL_FULL = 'url.full', USER_AGENT_ORIGINAL = 'user_agent.original', CLIENT_ADDRESS = 'client.address', + BROWSER_NAME = 'browser.name', USER_GEO_SUBREGION = 'user.geo.subregion', } @@ -84,8 +85,7 @@ export type SpanStringFields = | 'span.status_code' | 'span.ai.pipeline.group' | 'project' - | 'messaging.destination.name' - | SpanMetricsField.USER_GEO_SUBREGION; + | 'messaging.destination.name'; export type SpanMetricsQueryFilters = { [Field in SpanStringFields]?: string; @@ -166,6 +166,8 @@ export type SpanMetricsResponse = { | `${Property}(${string})` | `${Property}(${string},${string})` | `${Property}(${string},${string},${string})`]: number; +} & { + [SpanMetricsField.USER_GEO_SUBREGION]: SubregionCode; }; export type MetricsFilters = { @@ -367,3 +369,5 @@ export const subregionCodeToName = { '61': 'Polynesia', '53': 'Australia and New Zealand', }; + +export type SubregionCode = keyof typeof subregionCodeToName;