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 {
   EChartClickHandler,
+  EChartDataZoomHandler,
   EChartEventHandler,
   EChartHighlightHandler,
   EChartMouseOutHandler,
@@ -88,6 +89,7 @@ type Props = {
   isLineChart?: boolean;
   log?: boolean;
   onClick?: EChartClickHandler;
+  onDataZoom?: EChartDataZoomHandler;
   onHighlight?: EChartHighlightHandler;
   onLegendSelectChanged?: EChartEventHandler<{
     name: string;
@@ -177,6 +179,7 @@ function Chart({
   tooltipFormatterOptions = {},
   errored,
   onLegendSelectChanged,
+  onDataZoom,
 }: Props) {
   const router = useRouter();
   const theme = useTheme();
@@ -383,7 +386,14 @@ function Chart({
         : series;
 
     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 => {
           if (isLineChart) {
             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()',
   'time_spent_percentage()',
   'time_spent_percentage(local)',
+  'http_error_count()',
+  'http_error_count_percent_change()',
 ]);
 
 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 = {
   [metric: string]: number | string;
+  'http_error_count()': number;
   'p95(span.self_time)': number;
   'span.op': string;
   'sps()': number;

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

@@ -17,12 +17,13 @@ import {
 } from 'sentry/utils/performance/contexts/pageError';
 import useOrganization from 'sentry/utils/useOrganization';
 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 ChartPanel from 'sentry/views/starfish/components/chartPanel';
 import StarfishDatePicker from 'sentry/views/starfish/components/datePicker';
 import StarfishPageFilterContainer from 'sentry/views/starfish/components/pageFilterContainer';
 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 ThroughputCell from 'sentry/views/starfish/components/tableCells/throughputCell';
 import {TimeSpentCell} from 'sentry/views/starfish/components/tableCells/timeSpentCell';
@@ -79,6 +80,7 @@ function SpanSummaryPage({params, location}: Props) {
       `sum(${SPAN_SELF_TIME})`,
       `p95(${SPAN_SELF_TIME})`,
       'time_spent_percentage()',
+      'http_error_count()',
     ],
     'span-summary-page-metrics'
   );
@@ -89,7 +91,7 @@ function SpanSummaryPage({params, location}: Props) {
     useSpanMetricsSeries(
       {group: groupId},
       queryFilter,
-      [`p95(${SPAN_SELF_TIME})`, 'sps()'],
+      [`p95(${SPAN_SELF_TIME})`, 'sps()', 'http_error_count()'],
       'span-summary-page-metrics'
     );
 
@@ -163,6 +165,14 @@ function SpanSummaryPage({params, location}: Props) {
                       milliseconds={spanMetrics?.[`p95(${SPAN_SELF_TIME})`]}
                     />
                   </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
                     title={t('Time Spent')}
                     description={t(
@@ -227,6 +237,25 @@ function SpanSummaryPage({params, location}: Props) {
                       />
                     </ChartPanel>
                   </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>
               )}
 

+ 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 {useLocation} from 'sentry/utils/useLocation';
 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 Chart, {useSynchronizeCharts} from 'sentry/views/starfish/components/chart';
 import ChartPanel from 'sentry/views/starfish/components/chartPanel';
@@ -140,6 +141,7 @@ function ThroughputChart({moduleName, filters}: ChartProps): JSX.Element {
       tooltipFormatterOptions={{
         valueFormatter: value => formatThroughput(value),
       }}
+      onDataZoom={useSortPageByHandler('-sps()')}
     />
   );
 }
@@ -188,6 +190,7 @@ function DurationChart({moduleName, filters}: ChartProps): JSX.Element {
       stacked
       isLineChart
       chartColors={[P95_COLOR]}
+      onDataZoom={useSortPageByHandler('-p95(span.self_time)')}
     />
   );
 }
@@ -225,6 +228,7 @@ function ErrorChart({moduleName, filters}: ChartProps): JSX.Element {
       stacked
       isLineChart
       chartColors={[ERRORS_COLOR]}
+      onDataZoom={useSortPageByHandler('-http_error_count()')}
     />
   );
 }
@@ -285,6 +289,27 @@ const buildDiscoverQueryConditions = (
   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')`
   display: flex;
   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_percent_change()',
   'time_spent_percentage()',
+  'http_error_count()',
+  'http_error_count_percent_change()',
 ]);
 
 export default function SpansTable({