123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- import {Fragment} from 'react';
- import {useTheme} from '@emotion/react';
- import styled from '@emotion/styled';
- import ChartZoom from 'sentry/components/charts/chartZoom';
- import {LineChart, LineChartSeries} from 'sentry/components/charts/lineChart';
- import QuestionTooltip from 'sentry/components/questionTooltip';
- import {t} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import {getDuration} from 'sentry/utils/formatters';
- import usePageFilters from 'sentry/utils/usePageFilters';
- import useRouter from 'sentry/utils/useRouter';
- import {MiniAggregateWaterfall} from 'sentry/views/performance/browser/webVitals/components/miniAggregateWaterfall';
- import {ProjectScore} from 'sentry/views/performance/browser/webVitals/utils/calculatePerformanceScore';
- import {useProjectWebVitalsQuery} from 'sentry/views/performance/browser/webVitals/utils/useProjectWebVitalsQuery';
- import {useProjectWebVitalsValuesTimeseriesQuery} from 'sentry/views/performance/browser/webVitals/utils/useProjectWebVitalsValuesTimeseriesQuery';
- import {SidebarSpacer} from 'sentry/views/performance/transactionSummary/utils';
- const CHART_HEIGHTS = 100;
- type Props = {
- transaction: string;
- projectScore?: ProjectScore;
- };
- export function PageOverviewSidebar({projectScore, transaction}: Props) {
- const theme = useTheme();
- const router = useRouter();
- const pageFilters = usePageFilters();
- const {period, start, end, utc} = pageFilters.selection.datetime;
- const {data: pageData} = useProjectWebVitalsQuery({transaction});
- const {data: seriesData, isLoading: isLoadingSeries} =
- useProjectWebVitalsValuesTimeseriesQuery({transaction});
- const throughtputData: LineChartSeries[] = [
- {
- data: !isLoadingSeries
- ? seriesData.eps.map(({name, value}) => ({
- name,
- value,
- }))
- : [],
- seriesName: 'Throughput',
- },
- ];
- const durationData: LineChartSeries[] = [
- {
- data: !isLoadingSeries
- ? seriesData.duration.map(({name, value}) => ({
- name,
- value,
- }))
- : [],
- seriesName: 'Duration',
- },
- ];
- const errorData: LineChartSeries[] = [
- {
- data: !isLoadingSeries
- ? seriesData.errors.map(({name, value}) => ({
- name,
- value,
- }))
- : [],
- seriesName: '5XX Responses',
- },
- ];
- const epsDiff = !isLoadingSeries
- ? seriesData.eps[seriesData.eps.length - 1].value / seriesData.eps[0].value
- : undefined;
- const initialEps = !isLoadingSeries
- ? `${Math.round(seriesData.eps[0].value * 100) / 100}/s`
- : undefined;
- const currentEps = !isLoadingSeries
- ? `${Math.round(seriesData.eps[seriesData.eps.length - 1].value * 100) / 100}/s`
- : undefined;
- const durationDiff = !isLoadingSeries
- ? seriesData.duration[seriesData.duration.length - 1].value /
- seriesData.duration[0].value
- : undefined;
- const initialDuration = !isLoadingSeries
- ? getDuration(seriesData.duration[0].value / 1000, 2, true)
- : undefined;
- const currentDuration = !isLoadingSeries
- ? getDuration(
- seriesData.duration[seriesData.duration.length - 1].value / 1000,
- 2,
- true
- )
- : undefined;
- const diffToColor = (diff?: number, reverse?: boolean) => {
- if (diff === undefined) {
- return undefined;
- }
- if (diff > 1) {
- if (reverse) {
- return theme.red300;
- }
- return theme.green300;
- }
- if (diff < 1) {
- if (reverse) {
- return theme.green300;
- }
- return theme.red300;
- }
- return undefined;
- };
- return (
- <Fragment>
- <SectionHeading>
- {t('Performance Score')}
- <QuestionTooltip size="sm" title={undefined} />
- </SectionHeading>
- <SidebarPerformanceScoreValue>
- {projectScore?.totalScore ?? '-'}
- </SidebarPerformanceScoreValue>
- <SidebarSpacer />
- <SectionHeading>
- {t('Throughput')}
- {/* TODO: Add a proper tooltip */}
- <QuestionTooltip size="sm" title={undefined} />
- </SectionHeading>
- <ChartValue>{currentEps}</ChartValue>
- <ChartSubText color={diffToColor(epsDiff)}>
- {getChartSubText(epsDiff, initialEps, currentEps)}
- </ChartSubText>
- <ChartZoom router={router} period={period} start={start} end={end} utc={utc}>
- {zoomRenderProps => (
- <LineChart
- {...zoomRenderProps}
- height={CHART_HEIGHTS}
- series={throughtputData}
- xAxis={{show: false}}
- grid={{
- left: 0,
- right: 15,
- top: 10,
- bottom: -10,
- }}
- yAxis={{axisLabel: {formatter: value => `${value}/s`}}}
- tooltip={{valueFormatter: value => `${Math.round(value * 100) / 100}/s`}}
- />
- )}
- </ChartZoom>
- <SidebarSpacer />
- <SectionHeading>
- {t('Duration (P75)')}
- {/* TODO: Add a proper tooltip */}
- <QuestionTooltip size="sm" title={undefined} />
- </SectionHeading>
- <ChartValue>{currentDuration}</ChartValue>
- <ChartSubText color={diffToColor(durationDiff, true)}>
- {getChartSubText(durationDiff, initialDuration, currentDuration)}
- </ChartSubText>
- <ChartZoom router={router} period={period} start={start} end={end} utc={utc}>
- {zoomRenderProps => (
- <LineChart
- {...zoomRenderProps}
- height={CHART_HEIGHTS}
- series={durationData}
- xAxis={{show: false}}
- grid={{
- left: 0,
- right: 15,
- top: 10,
- bottom: -10,
- }}
- yAxis={{axisLabel: {formatter: getAxisLabelFormattedDuration}}}
- tooltip={{valueFormatter: getFormattedDuration}}
- />
- )}
- </ChartZoom>
- <SidebarSpacer />
- <SectionHeading>
- {t('5XX Responses')}
- {/* TODO: Add a proper tooltip */}
- <QuestionTooltip size="sm" title={undefined} />
- </SectionHeading>
- <ChartValue>{pageData?.data[0]['failure_count()']}</ChartValue>
- <ChartZoom router={router} period={period} start={start} end={end} utc={utc}>
- {zoomRenderProps => (
- <LineChart
- {...zoomRenderProps}
- height={CHART_HEIGHTS}
- series={errorData}
- xAxis={{show: false}}
- grid={{
- left: 0,
- right: 15,
- top: 10,
- bottom: -10,
- }}
- />
- )}
- </ChartZoom>
- <SidebarSpacer />
- <SectionHeading>
- {t('Aggregate Spans')}
- {/* TODO: Add a proper tooltip */}
- <QuestionTooltip size="sm" title={undefined} />
- </SectionHeading>
- <MiniAggregateWaterfallContainer>
- <MiniAggregateWaterfall transaction={transaction} />
- </MiniAggregateWaterfallContainer>
- </Fragment>
- );
- }
- const getFormattedDuration = (value: number) => {
- if (value < 1000) {
- return getDuration(value / 1000, 0, true);
- }
- return getDuration(value / 1000, 2, true);
- };
- const getAxisLabelFormattedDuration = (value: number) => {
- return getDuration(value / 1000, 0, true);
- };
- const getChartSubText = (
- diff?: number,
- value?: string | number,
- newValue?: string | number
- ) => {
- if (diff === undefined || value === undefined) {
- return null;
- }
- if (diff > 1) {
- const relativeDiff = Math.round((diff - 1) * 1000) / 10;
- if (relativeDiff === Infinity) {
- return `Up from ${value} to ${newValue}`;
- }
- return `Up ${relativeDiff}% from ${value}`;
- }
- if (diff < 1) {
- const relativeDiff = Math.round((1 - diff) * 1000) / 10;
- return `Down ${relativeDiff}% from ${value}`;
- }
- return t('No Change');
- };
- const SidebarPerformanceScoreValue = styled('div')`
- font-weight: bold;
- font-size: 32px;
- `;
- const ChartValue = styled('div')`
- font-size: ${p => p.theme.fontSizeExtraLarge};
- `;
- const ChartSubText = styled('div')<{color?: string}>`
- font-size: ${p => p.theme.fontSizeMedium};
- color: ${p => p.color ?? p.theme.subText};
- `;
- const SectionHeading = styled('h4')`
- display: inline-grid;
- grid-auto-flow: column;
- gap: ${space(1)};
- align-items: center;
- color: ${p => p.theme.subText};
- font-size: ${p => p.theme.fontSizeMedium};
- margin: 0;
- `;
- const MiniAggregateWaterfallContainer = styled('div')`
- margin-top: ${space(1)};
- margin-bottom: ${space(1)};
- `;
|