Browse Source

feat(starfish): updates sparklines for span explorer (#49404)

Adds and updates sparklines in the span explorer to utilize horizontal
markers
edwardgou-sentry 1 year ago
parent
commit
eda1d289f7

+ 65 - 1
static/app/views/starfish/components/sparkline.tsx

@@ -1,9 +1,12 @@
+import {Theme} from '@emotion/react';
 import {LineChart} from 'echarts/charts';
 import * as echarts from 'echarts/core';
 import {SVGRenderer} from 'echarts/renderers';
 import ReactEChartsCore from 'echarts-for-react/lib/core';
+import moment from 'moment';
 
-import {Series} from 'sentry/types/echarts';
+import MarkLine from 'sentry/components/charts/components/markLine';
+import {Series, SeriesDataUnit} from 'sentry/types/echarts';
 
 type SparklineProps = {
   series: Series;
@@ -124,3 +127,64 @@ export function MultiSparkline({
     />
   );
 }
+
+export function generateMarkLine(
+  title: string,
+  position: string,
+  data: SeriesDataUnit[],
+  theme: Theme
+) {
+  const index = data.findIndex(item => {
+    return (
+      Math.abs(moment.duration(moment(item.name).diff(moment(position))).asSeconds()) <
+      86400
+    );
+  });
+  return {
+    seriesName: title,
+    type: 'line',
+    color: theme.blue300,
+    data: [],
+    xAxisIndex: 0,
+    yAxisIndex: 0,
+    markLine: MarkLine({
+      silent: true,
+      animation: false,
+      lineStyle: {color: theme.blue300, type: 'dotted'},
+      data: [
+        {
+          xAxis: index,
+        },
+      ],
+      label: {
+        show: false,
+      },
+    }),
+  };
+}
+
+export function generateHorizontalLine(title: string, position: number, theme: Theme) {
+  return {
+    seriesName: title,
+    type: 'line',
+    color: theme.blue300,
+    data: [],
+    xAxisIndex: 0,
+    yAxisIndex: 0,
+    markLine: MarkLine({
+      silent: true,
+      animation: false,
+      lineStyle: {color: theme.blue300, type: 'dotted'},
+      data: [
+        {
+          yAxis: position,
+        },
+      ],
+      label: {
+        show: true,
+        position: 'insideStart',
+        formatter: title,
+      },
+    }),
+  };
+}

+ 3 - 2
static/app/views/starfish/modules/databaseModule/databaseTableView.tsx

@@ -9,10 +9,11 @@ import Link from 'sentry/components/links/link';
 import {CHART_PALETTE} from 'sentry/constants/chartPalette';
 import {space} from 'sentry/styles/space';
 import {getDuration} from 'sentry/utils/formatters';
-import Sparkline from 'sentry/views/starfish/components/sparkline';
+import Sparkline, {
+  generateHorizontalLine,
+} from 'sentry/views/starfish/components/sparkline';
 import {Sort} from 'sentry/views/starfish/modules/databaseModule';
 import {SortableHeader} from 'sentry/views/starfish/modules/databaseModule/panel/queryTransactionTable';
-import {generateHorizontalLine} from 'sentry/views/starfish/modules/databaseModule/utils';
 
 type Props = {
   isDataLoading: boolean;

+ 2 - 4
static/app/views/starfish/modules/databaseModule/panel/index.tsx

@@ -19,6 +19,7 @@ import usePageFilters from 'sentry/utils/usePageFilters';
 import Chart from 'sentry/views/starfish/components/chart';
 import Detail from 'sentry/views/starfish/components/detailPanel';
 import {FormattedCode} from 'sentry/views/starfish/components/formattedCode';
+import {generateMarkLine} from 'sentry/views/starfish/components/sparkline';
 import ProfileView from 'sentry/views/starfish/modules/databaseModule/panel/profileView';
 import QueryTransactionTable, {
   PanelSort,
@@ -33,10 +34,7 @@ import {
   useQueryPanelTable,
   useQueryTransactionByTPMAndDuration,
 } from 'sentry/views/starfish/modules/databaseModule/queries';
-import {
-  generateMarkLine,
-  queryToSeries,
-} from 'sentry/views/starfish/modules/databaseModule/utils';
+import {queryToSeries} from 'sentry/views/starfish/modules/databaseModule/utils';
 import {getDateFilters} from 'sentry/views/starfish/utils/dates';
 import {zeroFillSeries} from 'sentry/views/starfish/utils/zeroFillSeries';
 

+ 1 - 64
static/app/views/starfish/modules/databaseModule/utils.ts

@@ -1,8 +1,6 @@
-import {Theme} from '@emotion/react';
 import moment from 'moment';
 
-import MarkLine from 'sentry/components/charts/components/markLine';
-import {Series, SeriesDataUnit} from 'sentry/types/echarts';
+import {Series} from 'sentry/types/echarts';
 import {zeroFillSeries} from 'sentry/views/starfish/utils/zeroFillSeries';
 
 export const queryToSeries = (
@@ -41,64 +39,3 @@ export const queryToSeries = (
     )
   );
 };
-
-export function generateMarkLine(
-  title: string,
-  position: string,
-  data: SeriesDataUnit[],
-  theme: Theme
-) {
-  const index = data.findIndex(item => {
-    return (
-      Math.abs(moment.duration(moment(item.name).diff(moment(position))).asSeconds()) <
-      86400
-    );
-  });
-  return {
-    seriesName: title,
-    type: 'line',
-    color: theme.blue300,
-    data: [],
-    xAxisIndex: 0,
-    yAxisIndex: 0,
-    markLine: MarkLine({
-      silent: true,
-      animation: false,
-      lineStyle: {color: theme.blue300, type: 'dotted'},
-      data: [
-        {
-          xAxis: index,
-        },
-      ],
-      label: {
-        show: false,
-      },
-    }),
-  };
-}
-
-export function generateHorizontalLine(title: string, position: number, theme: Theme) {
-  return {
-    seriesName: title,
-    type: 'line',
-    color: theme.blue300,
-    data: [],
-    xAxisIndex: 0,
-    yAxisIndex: 0,
-    markLine: MarkLine({
-      silent: true,
-      animation: false,
-      lineStyle: {color: theme.blue300, type: 'dotted'},
-      data: [
-        {
-          yAxis: position,
-        },
-      ],
-      label: {
-        show: true,
-        position: 'insideStart',
-        formatter: title,
-      },
-    }),
-  };
-}

+ 3 - 1
static/app/views/starfish/views/spans/queries.tsx

@@ -65,7 +65,9 @@ export const getSpansTrendsQuery = (
     SELECT
     group_id, span_operation,
     toStartOfInterval(start_timestamp, INTERVAL 1 DAY) as interval,
-    quantile(0.50)(exclusive_time) as percentile_value
+    quantile(0.50)(exclusive_time) as p50_trend,
+    quantile(0.95)(exclusive_time) as p95_trend,
+    divide(count(), multiply(24, 60)) as throughput
     FROM spans_experimental_starfish
     WHERE greaterOrEquals(start_timestamp, '${start_timestamp}')
     ${end_timestamp ? `AND lessOrEquals(start_timestamp, '${end_timestamp}')` : ''}

+ 101 - 31
static/app/views/starfish/views/spans/spansTable.tsx

@@ -1,3 +1,4 @@
+import {Theme, useTheme} from '@emotion/react';
 import styled from '@emotion/styled';
 import {Location} from 'history';
 import moment from 'moment';
@@ -12,9 +13,12 @@ import SortLink from 'sentry/components/gridEditable/sortLink';
 import Link from 'sentry/components/links/link';
 import {CHART_PALETTE} from 'sentry/constants/chartPalette';
 import {Series} from 'sentry/types/echarts';
+import {getDuration} from 'sentry/utils/formatters';
 import {TableColumnSort} from 'sentry/views/discover/table/types';
 import {FormattedCode} from 'sentry/views/starfish/components/formattedCode';
-import Sparkline from 'sentry/views/starfish/components/sparkline';
+import Sparkline, {
+  generateHorizontalLine,
+} from 'sentry/views/starfish/components/sparkline';
 import {DataRow} from 'sentry/views/starfish/modules/databaseModule/databaseTableView';
 import {zeroFillSeries} from 'sentry/views/starfish/utils/zeroFillSeries';
 
@@ -33,6 +37,7 @@ export type SpanDataRow = {
   count: number;
   description: string;
   domain: string;
+  epm: number;
   group_id: string;
   p50: number;
   p95: number;
@@ -57,37 +62,58 @@ export default function SpansTable({
   isLoading,
   onSelect,
 }: Props) {
-  const spansTrendsGrouped = {};
+  const theme = useTheme();
+  const spansTrendsGrouped = {p50_trend: {}, p95_trend: {}, throughput: {}};
 
-  spansTrendsData?.forEach(({group_id, span_operation, interval, percentile_value}) => {
-    if (span_operation in spansTrendsGrouped) {
-      if (group_id in spansTrendsGrouped[span_operation]) {
-        return spansTrendsGrouped[span_operation][group_id].push({
-          name: interval,
-          value: percentile_value,
-        });
+  spansTrendsData?.forEach(({group_id, span_operation, interval, ...rest}) => {
+    ['p50_trend', 'p95_trend', 'throughput'].forEach(trend => {
+      if (span_operation in spansTrendsGrouped[trend]) {
+        if (group_id in spansTrendsGrouped[trend][span_operation]) {
+          return spansTrendsGrouped[trend][span_operation][group_id].push({
+            name: interval,
+            value: rest[trend],
+          });
+        }
+        return (spansTrendsGrouped[trend][span_operation][group_id] = [
+          {name: interval, value: rest[trend]},
+        ]);
       }
-      return (spansTrendsGrouped[span_operation][group_id] = [
-        {name: interval, value: percentile_value},
-      ]);
-    }
-    return (spansTrendsGrouped[span_operation] = {
-      [group_id]: [{name: interval, value: percentile_value}],
+      return (spansTrendsGrouped[trend][span_operation] = {
+        [group_id]: [{name: interval, value: rest[trend]}],
+      });
     });
   });
 
   const combinedSpansData = spansData?.map(spanData => {
     const {group_id, span_operation} = spanData;
-    if (spansTrendsGrouped[span_operation] === undefined) {
+    if (spansTrendsGrouped.p50_trend?.[span_operation] === undefined) {
       return spanData;
     }
-    const percentile_trend: Series = {
-      seriesName: 'percentile_trend',
-      data: spansTrendsGrouped[span_operation][group_id],
+    const p50_trend: Series = {
+      seriesName: 'p50_trend',
+      data: spansTrendsGrouped.p50_trend[span_operation][group_id],
+    };
+    const p95_trend: Series = {
+      seriesName: 'p95_trend',
+      data: spansTrendsGrouped.p95_trend[span_operation][group_id],
+    };
+    const throughput_trend: Series = {
+      seriesName: 'throughput_trend',
+      data: spansTrendsGrouped.throughput[span_operation][group_id],
     };
 
-    const zeroFilled = zeroFillSeries(percentile_trend, moment.duration(1, 'day'));
-    return {...spanData, percentile_trend: zeroFilled};
+    const zeroFilledP50 = zeroFillSeries(p50_trend, moment.duration(1, 'day'));
+    const zeroFilledP95 = zeroFillSeries(p95_trend, moment.duration(1, 'day'));
+    const zeroFilledThroughput = zeroFillSeries(
+      throughput_trend,
+      moment.duration(1, 'day')
+    );
+    return {
+      ...spanData,
+      p50_trend: zeroFilledP50,
+      p95_trend: zeroFilledP95,
+      throughput_trend: zeroFilledThroughput,
+    };
   });
 
   return (
@@ -100,7 +126,7 @@ export default function SpansTable({
       }
       grid={{
         renderHeadCell: getRenderHeadCell(orderBy, onSetOrderBy),
-        renderBodyCell: (column, row) => renderBodyCell(column, row, onSelect),
+        renderBodyCell: (column, row) => renderBodyCell(column, row, theme, onSelect),
       }}
       location={location}
     />
@@ -112,7 +138,7 @@ function getRenderHeadCell(orderBy: string, onSetOrderBy: (orderBy: string) => v
     return (
       <SortLink
         align="left"
-        canSort={column.key !== 'percentile_trend'}
+        canSort
         direction={orderBy === column.key ? 'desc' : undefined}
         onClick={() => {
           onSetOrderBy(`${column.key}`);
@@ -133,14 +159,53 @@ function getRenderHeadCell(orderBy: string, onSetOrderBy: (orderBy: string) => v
 function renderBodyCell(
   column: GridColumnHeader,
   row: SpanDataRow,
+  theme: Theme,
   onSelect?: (row: SpanDataRow) => void
 ): React.ReactNode {
-  if (column.key === 'percentile_trend' && row[column.key]) {
+  if (column.key === 'throughput_trend' && row[column.key]) {
+    const horizontalLine = generateHorizontalLine(
+      `${row.epm.toFixed(2)}`,
+      row.epm,
+      theme
+    );
     return (
       <Sparkline
         color={CHART_PALETTE[3][0]}
         series={row[column.key]}
         width={column.width ? column.width - column.width / 5 : undefined}
+        markLine={horizontalLine}
+      />
+    );
+  }
+
+  if (column.key === 'p50_trend' && row[column.key]) {
+    const horizontalLine = generateHorizontalLine(
+      `${getDuration(row.p50 / 1000, 2, true)}`,
+      row.p50,
+      theme
+    );
+    return (
+      <Sparkline
+        color={CHART_PALETTE[3][1]}
+        series={row[column.key]}
+        width={column.width ? column.width - column.width / 5 : undefined}
+        markLine={horizontalLine}
+      />
+    );
+  }
+
+  if (column.key === 'p95_trend' && row[column.key]) {
+    const horizontalLine = generateHorizontalLine(
+      `${getDuration(row.p95 / 1000, 2, true)}`,
+      row.p95,
+      theme
+    );
+    return (
+      <Sparkline
+        color={CHART_PALETTE[3][2]}
+        series={row[column.key]}
+        width={column.width ? column.width - column.width / 5 : undefined}
+        markLine={horizontalLine}
       />
     );
   }
@@ -243,7 +308,7 @@ function getColumns(queryConditions: string[]): GridColumnOrder[] {
     {
       key: 'total_exclusive_time',
       name: 'Total Time',
-      width: 250,
+      width: COL_WIDTH_UNDEFINED,
     },
     {
       key: 'transactions',
@@ -251,14 +316,19 @@ function getColumns(queryConditions: string[]): GridColumnOrder[] {
       width: COL_WIDTH_UNDEFINED,
     },
     {
-      key: 'p50',
-      name: 'p50',
-      width: COL_WIDTH_UNDEFINED,
+      key: 'throughput_trend',
+      name: 'throughput (spm)',
+      width: 175,
+    },
+    {
+      key: 'p50_trend',
+      name: 'p50 trend',
+      width: 175,
     },
     {
-      key: 'percentile_trend',
-      name: 'p50 Trend',
-      width: 250,
+      key: 'p95_trend',
+      name: 'p95 trend',
+      width: 175,
     },
   ];