From 86aab8cae0135e016a6772a00ed6e7fe57cc614a Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 15 Apr 2025 12:17:22 -0500 Subject: [PATCH 1/3] ref(insights): Refactor `PerformanceScoreBreakdownChart` Move into its own component and parse location query itself Part of #88560 --- .../charts/performanceScoreBreakdownChart.tsx | 114 +---------------- .../browser/webVitals/views/pageOverview.tsx | 6 +- .../performanceScoreBreakdownChartWidget.tsx | 121 ++++++++++++++++++ .../common/queries/useDiscoverSeries.ts | 6 +- 4 files changed, 129 insertions(+), 118 deletions(-) create mode 100644 static/app/views/insights/common/components/widgets/performanceScoreBreakdownChartWidget.tsx 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 e86489cfe88afa..3b17b14b57170e 100644 --- a/static/app/views/insights/browser/webVitals/components/charts/performanceScoreBreakdownChart.tsx +++ b/static/app/views/insights/browser/webVitals/components/charts/performanceScoreBreakdownChart.tsx @@ -1,29 +1,10 @@ -import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; -import {t} from 'sentry/locale'; import type {Series} from 'sentry/types/echarts'; -import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import {ORDER} from 'sentry/views/insights/browser/webVitals/components/charts/performanceScoreChart'; import type {WebVitalsScoreBreakdown} from 'sentry/views/insights/browser/webVitals/queries/storedScoreQueries/useProjectWebVitalsScoresTimeseriesQuery'; import type {WebVitals} from 'sentry/views/insights/browser/webVitals/types'; -import {getWeights} from 'sentry/views/insights/browser/webVitals/utils/getWeights'; -import type {BrowserType} from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType'; -import {useDefaultWebVitalsQuery} from 'sentry/views/insights/browser/webVitals/utils/useDefaultQuery'; -import {InsightsTimeSeriesWidget} from 'sentry/views/insights/common/components/insightsTimeSeriesWidget'; -import { - type DiscoverSeries, - useMetricsSeries, -} from 'sentry/views/insights/common/queries/useDiscoverSeries'; -import {SpanMetricsField, type SubregionCode} from 'sentry/views/insights/types'; - -import {WebVitalsWeightList} from './webVitalWeightList'; - -type Props = { - browserTypes?: BrowserType[]; - subregions?: SubregionCode[]; - transaction?: string; -}; +import {PerformanceScoreBreakdownChartWidget} from 'sentry/views/insights/common/components/widgets/performanceScoreBreakdownChartWidget'; export function formatTimeSeriesResultsToChartData( data: WebVitalsScoreBreakdown, @@ -44,99 +25,10 @@ export function formatTimeSeriesResultsToChartData( }); } -export function PerformanceScoreBreakdownChart({ - transaction, - browserTypes, - subregions, -}: Props) { - const theme = useTheme(); - const segmentColors = theme.chart.getColorPalette(3).slice(0, 5); - const defaultQuery = useDefaultWebVitalsQuery(); - - const search = new MutableSearch(`${defaultQuery} has:measurements.score.total`); - - if (transaction) { - search.addFilterValue('transaction', transaction); - } - - if (subregions) { - search.addDisjunctionFilterValues(SpanMetricsField.USER_GEO_SUBREGION, subregions); - } - - if (browserTypes) { - search.addDisjunctionFilterValues(SpanMetricsField.BROWSER_NAME, browserTypes); - } - - const { - data: vitalScoresData, - isLoading: areVitalScoresLoading, - error: vitalScoresError, - } = useMetricsSeries( - { - search, - yAxis: [ - 'performance_score(measurements.score.lcp)', - 'performance_score(measurements.score.fcp)', - 'performance_score(measurements.score.cls)', - 'performance_score(measurements.score.inp)', - 'performance_score(measurements.score.ttfb)', - 'count()', - ], - transformAliasToInputFormat: true, - }, - 'api.performance.browser.web-vitals.timeseries-scores2' - ); - - const webVitalsThatHaveData: WebVitals[] = vitalScoresData - ? ORDER.filter(webVital => { - const key = `performance_score(measurements.score.${webVital})` as const; - const series = vitalScoresData[key]; - - return series.data.some(datum => datum.value > 0); - }) - : []; - - const weights = getWeights(webVitalsThatHaveData); - - const allSeries: DiscoverSeries[] = vitalScoresData - ? ORDER.map((webVital, index) => { - const key = `performance_score(measurements.score.${webVital})` as const; - const series = vitalScoresData[key]; - - const scaledSeries: DiscoverSeries = { - ...series, - data: series.data.map(datum => { - return { - ...datum, - value: datum.value * weights[webVital], - }; - }), - color: segmentColors[index], - meta: { - // TODO: The backend doesn't return these score fields with the "score" type yet. Fill this in manually for now. - fields: { - ...series.meta?.fields, - [key]: 'score', - }, - units: series.meta?.units, - }, - }; - - return scaledSeries; - }) - : []; - +export function PerformanceScoreBreakdownChart() { return ( - } - /> + ); } diff --git a/static/app/views/insights/browser/webVitals/views/pageOverview.tsx b/static/app/views/insights/browser/webVitals/views/pageOverview.tsx index d371650f1b8400..c7200b996f0bea 100644 --- a/static/app/views/insights/browser/webVitals/views/pageOverview.tsx +++ b/static/app/views/insights/browser/webVitals/views/pageOverview.tsx @@ -196,11 +196,7 @@ export function PageOverview() { - + {(isPending || isProjectScoresLoading) && } diff --git a/static/app/views/insights/common/components/widgets/performanceScoreBreakdownChartWidget.tsx b/static/app/views/insights/common/components/widgets/performanceScoreBreakdownChartWidget.tsx new file mode 100644 index 00000000000000..79efd55044f2b9 --- /dev/null +++ b/static/app/views/insights/common/components/widgets/performanceScoreBreakdownChartWidget.tsx @@ -0,0 +1,121 @@ +import {useTheme} from '@emotion/react'; + +import {t} from 'sentry/locale'; +import {decodeList} from 'sentry/utils/queryString'; +import {MutableSearch} from 'sentry/utils/tokenizeSearch'; +import useLocationQuery from 'sentry/utils/url/useLocationQuery'; +import {ORDER} from 'sentry/views/insights/browser/webVitals/components/charts/performanceScoreChart'; +import {WebVitalsWeightList} from 'sentry/views/insights/browser/webVitals/components/charts/webVitalWeightList'; +import type {WebVitals} from 'sentry/views/insights/browser/webVitals/types'; +import {getWeights} from 'sentry/views/insights/browser/webVitals/utils/getWeights'; +import decodeBrowserTypes from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType'; +import {useDefaultWebVitalsQuery} from 'sentry/views/insights/browser/webVitals/utils/useDefaultQuery'; +import {InsightsTimeSeriesWidget} from 'sentry/views/insights/common/components/insightsTimeSeriesWidget'; +import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types'; +import { + type DiscoverSeries, + useMetricsSeries, +} from 'sentry/views/insights/common/queries/useDiscoverSeries'; +import {SpanIndexedField, SpanMetricsField} from 'sentry/views/insights/types'; + +export function PerformanceScoreBreakdownChartWidget(props: LoadableChartWidgetProps) { + const { + transaction, + [SpanIndexedField.BROWSER_NAME]: browserTypes, + [SpanIndexedField.USER_GEO_SUBREGION]: subregions, + } = useLocationQuery({ + fields: { + [SpanIndexedField.BROWSER_NAME]: decodeBrowserTypes, + [SpanIndexedField.USER_GEO_SUBREGION]: decodeList, + transaction: decodeList, + }, + }); + const theme = useTheme(); + const segmentColors = theme.chart.getColorPalette(3).slice(0, 5); + const defaultQuery = useDefaultWebVitalsQuery(); + const search = new MutableSearch(`${defaultQuery} has:measurements.score.total`); + + if (transaction.length && transaction[0]) { + search.addFilterValue('transaction', transaction[0]); + } + + if (subregions) { + search.addDisjunctionFilterValues(SpanMetricsField.USER_GEO_SUBREGION, subregions); + } + + if (browserTypes) { + search.addDisjunctionFilterValues(SpanMetricsField.BROWSER_NAME, browserTypes); + } + + const { + data: vitalScoresData, + isLoading: areVitalScoresLoading, + error: vitalScoresError, + } = useMetricsSeries( + { + search, + yAxis: [ + 'performance_score(measurements.score.lcp)', + 'performance_score(measurements.score.fcp)', + 'performance_score(measurements.score.cls)', + 'performance_score(measurements.score.inp)', + 'performance_score(measurements.score.ttfb)', + 'count()', + ], + transformAliasToInputFormat: true, + }, + 'api.performance.browser.web-vitals.timeseries-scores2', + props.pageFilters + ); + + const webVitalsThatHaveData: WebVitals[] = vitalScoresData + ? ORDER.filter(webVital => { + const key = `performance_score(measurements.score.${webVital})` as const; + const series = vitalScoresData[key]; + + return series.data.some(datum => datum.value > 0); + }) + : []; + + const weights = getWeights(webVitalsThatHaveData); + + const allSeries: DiscoverSeries[] = vitalScoresData + ? ORDER.map((webVital, index) => { + const key = `performance_score(measurements.score.${webVital})` as const; + const series = vitalScoresData[key]; + + const scaledSeries: DiscoverSeries = { + ...series, + data: series.data.map(datum => { + return { + ...datum, + value: datum.value * weights[webVital], + }; + }), + color: segmentColors[index], + meta: { + // TODO: The backend doesn't return these score fields with the "score" type yet. Fill this in manually for now. + fields: { + ...series.meta?.fields, + [key]: 'score', + }, + units: series.meta?.units, + }, + }; + + return scaledSeries; + }) + : []; + + return ( + } + /> + ); +} diff --git a/static/app/views/insights/common/queries/useDiscoverSeries.ts b/static/app/views/insights/common/queries/useDiscoverSeries.ts index 0a1613e12efb6f..afefd8e3883c74 100644 --- a/static/app/views/insights/common/queries/useDiscoverSeries.ts +++ b/static/app/views/insights/common/queries/useDiscoverSeries.ts @@ -80,13 +80,15 @@ export const useEAPSeries = < export const useMetricsSeries = ( options: UseMetricsSeriesOptions = {}, - referrer: string + referrer: string, + pageFilters?: PageFilters ) => { const useEap = useInsightsEap(); return useDiscoverSeries( options, useEap ? DiscoverDatasets.SPANS_EAP_RPC : DiscoverDatasets.METRICS, - referrer + referrer, + pageFilters ); }; From d86320c5cddccfc5fe8c579f1aeb101da74d5e17 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 15 Apr 2025 14:21:44 -0500 Subject: [PATCH 2/3] remove extra props --- .../components/charts/performanceScoreChart.tsx | 14 +------------- .../webVitals/views/webVitalsLandingPage.tsx | 2 -- 2 files changed, 1 insertion(+), 15 deletions(-) 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 9cf813c3c386e8..f04e133f93d077 100644 --- a/static/app/views/insights/browser/webVitals/components/charts/performanceScoreChart.tsx +++ b/static/app/views/insights/browser/webVitals/components/charts/performanceScoreChart.tsx @@ -16,15 +16,10 @@ import type { ProjectScore, 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; }; @@ -33,10 +28,7 @@ export const ORDER: WebVitals[] = ['lcp', 'fcp', 'inp', 'cls', 'ttfb']; export function PerformanceScoreChart({ projectScore, webVital, - transaction, isProjectScoreLoading, - browserTypes, - subregions, }: Props) { const theme = useTheme(); const pageFilters = usePageFilters(); @@ -101,11 +93,7 @@ export function PerformanceScoreChart({ )} - + ); } diff --git a/static/app/views/insights/browser/webVitals/views/webVitalsLandingPage.tsx b/static/app/views/insights/browser/webVitals/views/webVitalsLandingPage.tsx index 746248264bc9ea..7f444233694c3a 100644 --- a/static/app/views/insights/browser/webVitals/views/webVitalsLandingPage.tsx +++ b/static/app/views/insights/browser/webVitals/views/webVitalsLandingPage.tsx @@ -95,8 +95,6 @@ export function WebVitalsLandingPage() { projectScore={projectScore} isProjectScoreLoading={isPending || isProjectScoresLoading} webVital={state.webVital} - browserTypes={browserTypes} - subregions={subregions} /> From 7faa6992f3c9e5056e0dd67905524a015bb0046c Mon Sep 17 00:00:00 2001 From: George Gritsouk <989898+gggritso@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:52:11 -0400 Subject: [PATCH 3/3] Move some files around - remove shallow unneeded chart wrapper - rename helper-only file to match the function inside --- ...tsx => formatTimeSeriesResultsToChartData.tsx} | 15 --------------- .../performanceScoreBreakdownChart.spec.tsx | 10 ++++------ .../components/charts/performanceScoreChart.tsx | 10 ++++++++-- .../browser/webVitals/views/pageOverview.tsx | 10 ++++++++-- .../widgets/performanceScoreListWidget.tsx | 2 +- 5 files changed, 21 insertions(+), 26 deletions(-) rename static/app/views/insights/browser/webVitals/components/charts/{performanceScoreBreakdownChart.tsx => formatTimeSeriesResultsToChartData.tsx} (68%) diff --git a/static/app/views/insights/browser/webVitals/components/charts/performanceScoreBreakdownChart.tsx b/static/app/views/insights/browser/webVitals/components/charts/formatTimeSeriesResultsToChartData.tsx similarity index 68% rename from static/app/views/insights/browser/webVitals/components/charts/performanceScoreBreakdownChart.tsx rename to static/app/views/insights/browser/webVitals/components/charts/formatTimeSeriesResultsToChartData.tsx index 3b17b14b57170e..4294c57e6d3aaa 100644 --- a/static/app/views/insights/browser/webVitals/components/charts/performanceScoreBreakdownChart.tsx +++ b/static/app/views/insights/browser/webVitals/components/charts/formatTimeSeriesResultsToChartData.tsx @@ -1,10 +1,7 @@ -import styled from '@emotion/styled'; - import type {Series} from 'sentry/types/echarts'; import {ORDER} from 'sentry/views/insights/browser/webVitals/components/charts/performanceScoreChart'; import type {WebVitalsScoreBreakdown} from 'sentry/views/insights/browser/webVitals/queries/storedScoreQueries/useProjectWebVitalsScoresTimeseriesQuery'; import type {WebVitals} from 'sentry/views/insights/browser/webVitals/types'; -import {PerformanceScoreBreakdownChartWidget} from 'sentry/views/insights/common/components/widgets/performanceScoreBreakdownChartWidget'; export function formatTimeSeriesResultsToChartData( data: WebVitalsScoreBreakdown, @@ -24,15 +21,3 @@ export function formatTimeSeriesResultsToChartData( }; }); } - -export function PerformanceScoreBreakdownChart() { - return ( - - - - ); -} - -const ChartContainer = styled('div')` - flex: 1 1 0%; -`; diff --git a/static/app/views/insights/browser/webVitals/components/charts/performanceScoreBreakdownChart.spec.tsx b/static/app/views/insights/browser/webVitals/components/charts/performanceScoreBreakdownChart.spec.tsx index c2929e8bcb1e66..2777336aa6bab3 100644 --- a/static/app/views/insights/browser/webVitals/components/charts/performanceScoreBreakdownChart.spec.tsx +++ b/static/app/views/insights/browser/webVitals/components/charts/performanceScoreBreakdownChart.spec.tsx @@ -4,15 +4,13 @@ import {render, screen, waitForElementToBeRemoved} from 'sentry-test/reactTestin import {useLocation} from 'sentry/utils/useLocation'; import usePageFilters from 'sentry/utils/usePageFilters'; -import { - formatTimeSeriesResultsToChartData, - PerformanceScoreBreakdownChart, -} from 'sentry/views/insights/browser/webVitals/components/charts/performanceScoreBreakdownChart'; +import {formatTimeSeriesResultsToChartData} from 'sentry/views/insights/browser/webVitals/components/charts/formatTimeSeriesResultsToChartData'; +import {PerformanceScoreBreakdownChartWidget} from 'sentry/views/insights/common/components/widgets/performanceScoreBreakdownChartWidget'; jest.mock('sentry/utils/useLocation'); jest.mock('sentry/utils/usePageFilters'); -describe('PerformanceScoreBreakdownChart', function () { +describe('PerformanceScoreBreakdownChartWidget', function () { const organization = OrganizationFixture(); let eventsStatsMock: jest.Mock; @@ -75,7 +73,7 @@ describe('PerformanceScoreBreakdownChart', function () { action: 'PUSH', key: '', }); - render(, {organization}); + render(, {organization}); await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator')); expect(eventsStatsMock).toHaveBeenCalledWith( 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 f04e133f93d077..6128a8c0a868dc 100644 --- a/static/app/views/insights/browser/webVitals/components/charts/performanceScoreChart.tsx +++ b/static/app/views/insights/browser/webVitals/components/charts/performanceScoreChart.tsx @@ -9,13 +9,13 @@ import {DEFAULT_RELATIVE_PERIODS} from 'sentry/constants'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import usePageFilters from 'sentry/utils/usePageFilters'; -import {PerformanceScoreBreakdownChart} from 'sentry/views/insights/browser/webVitals/components/charts/performanceScoreBreakdownChart'; import PerformanceScoreRingWithTooltips from 'sentry/views/insights/browser/webVitals/components/performanceScoreRingWithTooltips'; import {MODULE_DOC_LINK} from 'sentry/views/insights/browser/webVitals/settings'; import type { ProjectScore, WebVitals, } from 'sentry/views/insights/browser/webVitals/types'; +import {PerformanceScoreBreakdownChartWidget} from 'sentry/views/insights/common/components/widgets/performanceScoreBreakdownChartWidget'; type Props = { isProjectScoreLoading?: boolean; @@ -93,11 +93,17 @@ export function PerformanceScoreChart({ )} - + + + ); } +const ChartContainer = styled('div')` + flex: 1 1 0%; +`; + const Flex = styled('div')` display: flex; flex-direction: row; diff --git a/static/app/views/insights/browser/webVitals/views/pageOverview.tsx b/static/app/views/insights/browser/webVitals/views/pageOverview.tsx index c7200b996f0bea..3b1cd4b09c0987 100644 --- a/static/app/views/insights/browser/webVitals/views/pageOverview.tsx +++ b/static/app/views/insights/browser/webVitals/views/pageOverview.tsx @@ -18,7 +18,6 @@ import useOrganization from 'sentry/utils/useOrganization'; import useProjects from 'sentry/utils/useProjects'; import useRouter from 'sentry/utils/useRouter'; import BrowserTypeSelector from 'sentry/views/insights/browser/webVitals/components/browserTypeSelector'; -import {PerformanceScoreBreakdownChart} from 'sentry/views/insights/browser/webVitals/components/charts/performanceScoreBreakdownChart'; import {PageOverviewSidebar} from 'sentry/views/insights/browser/webVitals/components/pageOverviewSidebar'; import {PageOverviewWebVitalsDetailPanel} from 'sentry/views/insights/browser/webVitals/components/pageOverviewWebVitalsDetailPanel'; import {PageSamplePerformanceTable} from 'sentry/views/insights/browser/webVitals/components/tables/pageSamplePerformanceTable'; @@ -32,6 +31,7 @@ import {WebVitalMetersPlaceholder} from 'sentry/views/insights/browser/webVitals import {ModulePageFilterBar} from 'sentry/views/insights/common/components/modulePageFilterBar'; import {ModulePageProviders} from 'sentry/views/insights/common/components/modulePageProviders'; import {ModuleBodyUpsellHook} from 'sentry/views/insights/common/components/moduleUpsellHookWrapper'; +import {PerformanceScoreBreakdownChartWidget} from 'sentry/views/insights/common/components/widgets/performanceScoreBreakdownChartWidget'; import {useModuleURL} from 'sentry/views/insights/common/utils/useModuleURL'; import {useWebVitalsDrawer} from 'sentry/views/insights/common/utils/useWebVitalsDrawer'; import {FrontendHeader} from 'sentry/views/insights/pages/frontend/frontendPageHeader'; @@ -196,7 +196,9 @@ export function PageOverview() { - + + + {(isPending || isProjectScoresLoading) && } @@ -263,6 +265,10 @@ const Flex = styled('div')` gap: ${space(1)}; `; +const ChartContainer = styled('div')` + flex: 1 1 0%; +`; + const PageSamplePerformanceTableContainer = styled('div')` margin-top: ${space(1)}; `; diff --git a/static/app/views/performance/landing/widgets/widgets/performanceScoreListWidget.tsx b/static/app/views/performance/landing/widgets/widgets/performanceScoreListWidget.tsx index 31683b7aaea178..e6122f7fbadbac 100644 --- a/static/app/views/performance/landing/widgets/widgets/performanceScoreListWidget.tsx +++ b/static/app/views/performance/landing/widgets/widgets/performanceScoreListWidget.tsx @@ -11,7 +11,7 @@ import LoadingIndicator from 'sentry/components/loadingIndicator'; import Truncate from 'sentry/components/truncate'; import {t} from 'sentry/locale'; import {useLocation} from 'sentry/utils/useLocation'; -import {formatTimeSeriesResultsToChartData} from 'sentry/views/insights/browser/webVitals/components/charts/performanceScoreBreakdownChart'; +import {formatTimeSeriesResultsToChartData} from 'sentry/views/insights/browser/webVitals/components/charts/formatTimeSeriesResultsToChartData'; import {ORDER} from 'sentry/views/insights/browser/webVitals/components/charts/performanceScoreChart'; import {PerformanceBadge} from 'sentry/views/insights/browser/webVitals/components/performanceBadge'; import {useProjectWebVitalsScoresQuery} from 'sentry/views/insights/browser/webVitals/queries/storedScoreQueries/useProjectWebVitalsScoresQuery';