Browse Source

feat(starfish): API module consistency and continuity updaes (#52347)

Adds 5xx elements to api span summaries. This is to keep consistent data
displayed to users across the module and span summary view.
Sorts the spans table in the module view when zooming in on a
throughput, p95, or error chart.
edwardgou-sentry 1 year ago
parent
commit
0fd919c3a3

+ 11 - 1
static/app/views/starfish/components/chart.tsx

@@ -31,6 +31,7 @@ import {IconWarning} from 'sentry/icons';
 import {DateString} from 'sentry/types';
 import {DateString} from 'sentry/types';
 import {
 import {
   EChartClickHandler,
   EChartClickHandler,
+  EChartDataZoomHandler,
   EChartEventHandler,
   EChartEventHandler,
   EChartHighlightHandler,
   EChartHighlightHandler,
   EChartMouseOutHandler,
   EChartMouseOutHandler,
@@ -88,6 +89,7 @@ type Props = {
   isLineChart?: boolean;
   isLineChart?: boolean;
   log?: boolean;
   log?: boolean;
   onClick?: EChartClickHandler;
   onClick?: EChartClickHandler;
+  onDataZoom?: EChartDataZoomHandler;
   onHighlight?: EChartHighlightHandler;
   onHighlight?: EChartHighlightHandler;
   onLegendSelectChanged?: EChartEventHandler<{
   onLegendSelectChanged?: EChartEventHandler<{
     name: string;
     name: string;
@@ -177,6 +179,7 @@ function Chart({
   tooltipFormatterOptions = {},
   tooltipFormatterOptions = {},
   errored,
   errored,
   onLegendSelectChanged,
   onLegendSelectChanged,
+  onDataZoom,
 }: Props) {
 }: Props) {
   const router = useRouter();
   const router = useRouter();
   const theme = useTheme();
   const theme = useTheme();
@@ -383,7 +386,14 @@ function Chart({
         : series;
         : series;
 
 
     return (
     return (
-      <ChartZoom router={router} period={statsPeriod} start={start} end={end} utc={utc}>
+      <ChartZoom
+        router={router}
+        period={statsPeriod}
+        start={start}
+        end={end}
+        utc={utc}
+        onDataZoom={onDataZoom}
+      >
         {zoomRenderProps => {
         {zoomRenderProps => {
           if (isLineChart) {
           if (isLineChart) {
             return (
             return (

+ 18 - 0
static/app/views/starfish/components/tableCells/countCell.tsx

@@ -0,0 +1,18 @@
+import {NumberContainer} from 'sentry/utils/discover/styles';
+import {formatAbbreviatedNumber} from 'sentry/utils/formatters';
+
+type Props = {
+  count: number;
+  containerProps?: React.DetailedHTMLProps<
+    React.HTMLAttributes<HTMLDivElement>,
+    HTMLDivElement
+  >;
+};
+
+export function CountCell({count, containerProps}: Props) {
+  return (
+    <NumberContainer {...containerProps}>
+      {formatAbbreviatedNumber(count)}
+    </NumberContainer>
+  );
+}

+ 2 - 0
static/app/views/starfish/components/tableCells/renderHeadCell.tsx

@@ -26,6 +26,8 @@ export const SORTABLE_FIELDS = new Set([
   'sps_percent_change()',
   'sps_percent_change()',
   'time_spent_percentage()',
   'time_spent_percentage()',
   'time_spent_percentage(local)',
   'time_spent_percentage(local)',
+  'http_error_count()',
+  'http_error_count_percent_change()',
 ]);
 ]);
 
 
 export const renderHeadCell = ({column, location, sort}: Options) => {
 export const renderHeadCell = ({column, location, sort}: Options) => {

+ 1 - 0
static/app/views/starfish/queries/useSpanMetrics.tsx

@@ -8,6 +8,7 @@ import {useSpansQuery} from 'sentry/views/starfish/utils/useSpansQuery';
 
 
 export type SpanMetrics = {
 export type SpanMetrics = {
   [metric: string]: number | string;
   [metric: string]: number | string;
+  'http_error_count()': number;
   'p95(span.self_time)': number;
   'p95(span.self_time)': number;
   'span.op': string;
   'span.op': string;
   'sps()': number;
   'sps()': number;

+ 31 - 2
static/app/views/starfish/views/spanSummaryPage/index.tsx

@@ -17,12 +17,13 @@ import {
 } from 'sentry/utils/performance/contexts/pageError';
 } from 'sentry/utils/performance/contexts/pageError';
 import useOrganization from 'sentry/utils/useOrganization';
 import useOrganization from 'sentry/utils/useOrganization';
 import {normalizeUrl} from 'sentry/utils/withDomainRequired';
 import {normalizeUrl} from 'sentry/utils/withDomainRequired';
-import {P95_COLOR, THROUGHPUT_COLOR} from 'sentry/views/starfish/colours';
+import {ERRORS_COLOR, P95_COLOR, THROUGHPUT_COLOR} from 'sentry/views/starfish/colours';
 import Chart, {useSynchronizeCharts} from 'sentry/views/starfish/components/chart';
 import Chart, {useSynchronizeCharts} from 'sentry/views/starfish/components/chart';
 import ChartPanel from 'sentry/views/starfish/components/chartPanel';
 import ChartPanel from 'sentry/views/starfish/components/chartPanel';
 import StarfishDatePicker from 'sentry/views/starfish/components/datePicker';
 import StarfishDatePicker from 'sentry/views/starfish/components/datePicker';
 import StarfishPageFilterContainer from 'sentry/views/starfish/components/pageFilterContainer';
 import StarfishPageFilterContainer from 'sentry/views/starfish/components/pageFilterContainer';
 import {SpanDescription} from 'sentry/views/starfish/components/spanDescription';
 import {SpanDescription} from 'sentry/views/starfish/components/spanDescription';
+import {CountCell} from 'sentry/views/starfish/components/tableCells/countCell';
 import DurationCell from 'sentry/views/starfish/components/tableCells/durationCell';
 import DurationCell from 'sentry/views/starfish/components/tableCells/durationCell';
 import ThroughputCell from 'sentry/views/starfish/components/tableCells/throughputCell';
 import ThroughputCell from 'sentry/views/starfish/components/tableCells/throughputCell';
 import {TimeSpentCell} from 'sentry/views/starfish/components/tableCells/timeSpentCell';
 import {TimeSpentCell} from 'sentry/views/starfish/components/tableCells/timeSpentCell';
@@ -79,6 +80,7 @@ function SpanSummaryPage({params, location}: Props) {
       `sum(${SPAN_SELF_TIME})`,
       `sum(${SPAN_SELF_TIME})`,
       `p95(${SPAN_SELF_TIME})`,
       `p95(${SPAN_SELF_TIME})`,
       'time_spent_percentage()',
       'time_spent_percentage()',
+      'http_error_count()',
     ],
     ],
     'span-summary-page-metrics'
     'span-summary-page-metrics'
   );
   );
@@ -89,7 +91,7 @@ function SpanSummaryPage({params, location}: Props) {
     useSpanMetricsSeries(
     useSpanMetricsSeries(
       {group: groupId},
       {group: groupId},
       queryFilter,
       queryFilter,
-      [`p95(${SPAN_SELF_TIME})`, 'sps()'],
+      [`p95(${SPAN_SELF_TIME})`, 'sps()', 'http_error_count()'],
       'span-summary-page-metrics'
       'span-summary-page-metrics'
     );
     );
 
 
@@ -163,6 +165,14 @@ function SpanSummaryPage({params, location}: Props) {
                       milliseconds={spanMetrics?.[`p95(${SPAN_SELF_TIME})`]}
                       milliseconds={spanMetrics?.[`p95(${SPAN_SELF_TIME})`]}
                     />
                     />
                   </Block>
                   </Block>
+                  {span?.['span.op']?.startsWith('http') && (
+                    <Block
+                      title={t('5XX Responses')}
+                      description={t('5XX responses in this span')}
+                    >
+                      <CountCell count={spanMetrics?.[`http_error_count()`]} />
+                    </Block>
+                  )}
                   <Block
                   <Block
                     title={t('Time Spent')}
                     title={t('Time Spent')}
                     description={t(
                     description={t(
@@ -227,6 +237,25 @@ function SpanSummaryPage({params, location}: Props) {
                       />
                       />
                     </ChartPanel>
                     </ChartPanel>
                   </Block>
                   </Block>
+
+                  {span?.['span.op']?.startsWith('http') && (
+                    <Block>
+                      <ChartPanel title={DataTitles.errorCount}>
+                        <Chart
+                          statsPeriod="24h"
+                          height={140}
+                          data={[spanMetricsSeriesData?.[`http_error_count()`]]}
+                          start=""
+                          end=""
+                          loading={areSpanMetricsSeriesLoading}
+                          utc={false}
+                          chartColors={[ERRORS_COLOR]}
+                          isLineChart
+                          definedAxisTicks={4}
+                        />
+                      </ChartPanel>
+                    </Block>
+                  )}
                 </BlockContainer>
                 </BlockContainer>
               )}
               )}
 
 

+ 25 - 0
static/app/views/starfish/views/spans/spanTimeCharts.tsx

@@ -9,6 +9,7 @@ import EventView from 'sentry/utils/discover/eventView';
 import {DiscoverDatasets} from 'sentry/utils/discover/types';
 import {DiscoverDatasets} from 'sentry/utils/discover/types';
 import {useLocation} from 'sentry/utils/useLocation';
 import {useLocation} from 'sentry/utils/useLocation';
 import usePageFilters from 'sentry/utils/usePageFilters';
 import usePageFilters from 'sentry/utils/usePageFilters';
+import useRouter from 'sentry/utils/useRouter';
 import {ERRORS_COLOR, P95_COLOR, THROUGHPUT_COLOR} from 'sentry/views/starfish/colours';
 import {ERRORS_COLOR, P95_COLOR, THROUGHPUT_COLOR} from 'sentry/views/starfish/colours';
 import Chart, {useSynchronizeCharts} from 'sentry/views/starfish/components/chart';
 import Chart, {useSynchronizeCharts} from 'sentry/views/starfish/components/chart';
 import ChartPanel from 'sentry/views/starfish/components/chartPanel';
 import ChartPanel from 'sentry/views/starfish/components/chartPanel';
@@ -140,6 +141,7 @@ function ThroughputChart({moduleName, filters}: ChartProps): JSX.Element {
       tooltipFormatterOptions={{
       tooltipFormatterOptions={{
         valueFormatter: value => formatThroughput(value),
         valueFormatter: value => formatThroughput(value),
       }}
       }}
+      onDataZoom={useSortPageByHandler('-sps()')}
     />
     />
   );
   );
 }
 }
@@ -188,6 +190,7 @@ function DurationChart({moduleName, filters}: ChartProps): JSX.Element {
       stacked
       stacked
       isLineChart
       isLineChart
       chartColors={[P95_COLOR]}
       chartColors={[P95_COLOR]}
+      onDataZoom={useSortPageByHandler('-p95(span.self_time)')}
     />
     />
   );
   );
 }
 }
@@ -225,6 +228,7 @@ function ErrorChart({moduleName, filters}: ChartProps): JSX.Element {
       stacked
       stacked
       isLineChart
       isLineChart
       chartColors={[ERRORS_COLOR]}
       chartColors={[ERRORS_COLOR]}
+      onDataZoom={useSortPageByHandler('-http_error_count()')}
     />
     />
   );
   );
 }
 }
@@ -285,6 +289,27 @@ const buildDiscoverQueryConditions = (
   return result.join(' ');
   return result.join(' ');
 };
 };
 
 
+function useSortPageByHandler(sort: string) {
+  const router = useRouter();
+  const location = useLocation();
+  return (_, chartRef) => {
+    // This is kind of jank but we need to check if the chart is hovered because
+    // onDataZoom is fired for all charts when one chart is zoomed.
+    const hoveredEchartElement = Array.from(document.querySelectorAll(':hover')).find(
+      element => {
+        return element.classList.contains('echarts-for-react');
+      }
+    );
+    const echartElement = document.querySelector(`[_echarts_instance_="${chartRef.id}"]`);
+    if (hoveredEchartElement === echartElement) {
+      router.replace({
+        pathname: location.pathname,
+        query: {...location.query, spansSort: sort},
+      });
+    }
+  };
+}
+
 const ChartsContainer = styled('div')`
 const ChartsContainer = styled('div')`
   display: flex;
   display: flex;
   flex-direction: row;
   flex-direction: row;

+ 2 - 0
static/app/views/starfish/views/spans/spansTable.tsx

@@ -63,6 +63,8 @@ export const SORTABLE_FIELDS = new Set([
   'sps()',
   'sps()',
   'sps_percent_change()',
   'sps_percent_change()',
   'time_spent_percentage()',
   'time_spent_percentage()',
+  'http_error_count()',
+  'http_error_count_percent_change()',
 ]);
 ]);
 
 
 export default function SpansTable({
 export default function SpansTable({