Browse Source

feat(perf): Show stacked area in the span op widget (#44159)

Stacked bars isn't the conventional way to display duration, so I'm
swapping it with stacked area. Additionally, there're units now on the
y-axis to make it clear what metric is displayed. Also the title has
been updated.

Before:
<img width="638" alt="Screen Shot 2023-02-06 at 2 20 05 PM"
src="https://user-images.githubusercontent.com/23648387/216981898-94ef2f56-ca6f-4a45-8520-b843d593fe83.png">

After:
<img width="419" alt="Screen Shot 2023-02-06 at 2 19 31 PM"
src="https://user-images.githubusercontent.com/23648387/216981891-a46b2700-a59a-4ebf-95ab-43aa7991d62f.png">

Also this PR handles a case when there's only a single op and so that
the backend response is different.
<img width="417" alt="image"
src="https://user-images.githubusercontent.com/23648387/216985759-15aace7e-7706-4715-9922-cd61cbafdc96.png">

Fixes PERF-1944, PERF-1948
Dameli Ushbayeva 2 years ago
parent
commit
a502dadfe6

+ 1 - 0
static/app/components/charts/eventsRequest.tsx

@@ -41,6 +41,7 @@ export type TimeSeriesData = {
   timeseriesData?: Series[];
   timeseriesResultsTypes?: Record<string, AggregationOutputType>;
   timeseriesTotals?: {count: number};
+  yAxis?: string | string[];
 };
 
 type LoadingStatus = {

+ 3 - 3
static/app/views/performance/landing/widgets/components/widgetContainer.tsx

@@ -32,7 +32,7 @@ import {
 import {HistogramWidget} from '../widgets/histogramWidget';
 import {LineChartListWidget} from '../widgets/lineChartListWidget';
 import {SingleFieldAreaWidget} from '../widgets/singleFieldAreaWidget';
-import {StackedBarsChartListWidget} from '../widgets/stackedBarsChartListWidget';
+import {StackedAreaChartListWidget} from '../widgets/stackedAreaChartListWidget';
 import {TrendsWidget} from '../widgets/trendsWidget';
 import {VitalWidget} from '../widgets/vitalWidget';
 
@@ -196,8 +196,8 @@ const _WidgetContainer = (props: Props) => {
       return (
         <HistogramWidget {...passedProps} {...widgetProps} titleTooltip={titleTooltip} />
       );
-    case GenericPerformanceWidgetDataType.stacked_bars:
-      return <StackedBarsChartListWidget {...passedProps} {...widgetProps} />;
+    case GenericPerformanceWidgetDataType.stacked_area:
+      return <StackedAreaChartListWidget {...passedProps} {...widgetProps} />;
     default:
       throw new Error(`Widget type "${widgetProps.dataType}" has no implementation.`);
   }

+ 7 - 2
static/app/views/performance/landing/widgets/transforms/transformEventsToStackedBars.tsx

@@ -4,7 +4,7 @@ import {defined} from 'sentry/utils';
 
 import {QueryDefinitionWithKey, WidgetDataConstraint, WidgetPropUnion} from '../types';
 
-export function transformEventsRequestToStackedBars<T extends WidgetDataConstraint>(
+export function transformEventsRequestToStackedArea<T extends WidgetDataConstraint>(
   widgetProps: WidgetPropUnion<T>,
   results: RenderProps,
   _: QueryDefinitionWithKey<T>
@@ -13,7 +13,12 @@ export function transformEventsRequestToStackedBars<T extends WidgetDataConstrai
     widgetProps.location.query
   );
 
-  const data = results.results ?? [];
+  let data;
+  if (Array.isArray(results.yAxis) && results.yAxis.length > 1) {
+    data = results.results ?? [];
+  } else {
+    data = results.timeseriesData;
+  }
 
   const childData = {
     ...results,

+ 1 - 1
static/app/views/performance/landing/widgets/types.tsx

@@ -22,7 +22,7 @@ export enum GenericPerformanceWidgetDataType {
   vitals = 'vitals',
   line_list = 'line_list',
   trends = 'trends',
-  stacked_bars = 'stacked_bars',
+  stacked_area = 'stacked_area',
 }
 
 export type PerformanceWidgetProps = {

+ 2 - 2
static/app/views/performance/landing/widgets/widgetDefinitions.tsx

@@ -315,9 +315,9 @@ export const WIDGET_DEFINITIONS: ({
     dataType: GenericPerformanceWidgetDataType.trends,
   },
   [PerformanceWidgetSetting.SPAN_OPERATIONS]: {
-    title: t('Span Operations'),
+    title: t('Span Operations Breakdown'),
     titleTooltip: '',
     fields: SPAN_OP_BREAKDOWN_FIELDS.map(spanOp => `p75(${spanOp})`),
-    dataType: GenericPerformanceWidgetDataType.stacked_bars,
+    dataType: GenericPerformanceWidgetDataType.stacked_area,
   },
 });

+ 31 - 22
static/app/views/performance/landing/widgets/widgets/stackedBarsChartListWidget.tsx → static/app/views/performance/landing/widgets/widgets/stackedAreaChartListWidget.tsx

@@ -2,14 +2,19 @@ import {Fragment, useMemo, useState} from 'react';
 import {useTheme} from '@emotion/react';
 import pick from 'lodash/pick';
 
-import {BarChart} from 'sentry/components/charts/barChart';
 import _EventsRequest from 'sentry/components/charts/eventsRequest';
+import StackedAreaChart from 'sentry/components/charts/stackedAreaChart';
 import {getInterval} from 'sentry/components/charts/utils';
 import Count from 'sentry/components/count';
 import Truncate from 'sentry/components/truncate';
 import {t} from 'sentry/locale';
-import {tooltipFormatter} from 'sentry/utils/discover/charts';
+import {
+  axisLabelFormatter,
+  getDurationUnit,
+  tooltipFormatter,
+} from 'sentry/utils/discover/charts';
 import DiscoverQuery from 'sentry/utils/discover/discoverQuery';
+import {aggregateOutputType} from 'sentry/utils/discover/fields';
 import {
   canUseMetricsData,
   useMEPSettingContext,
@@ -33,16 +38,16 @@ import {
   WidgetEmptyStateWarning,
 } from '../components/selectableList';
 import {transformDiscoverToList} from '../transforms/transformDiscoverToList';
-import {transformEventsRequestToStackedBars} from '../transforms/transformEventsToStackedBars';
+import {transformEventsRequestToStackedArea} from '../transforms/transformEventsToStackedBars';
 import {PerformanceWidgetProps, QueryDefinition, WidgetDataResult} from '../types';
 import {eventsRequestQueryProps, getMEPParamsIfApplicable} from '../utils';
 
 type DataType = {
-  chart: WidgetDataResult & ReturnType<typeof transformEventsRequestToStackedBars>;
+  chart: WidgetDataResult & ReturnType<typeof transformEventsRequestToStackedArea>;
   list: WidgetDataResult & ReturnType<typeof transformDiscoverToList>;
 };
 
-export function StackedBarsChartListWidget(props: PerformanceWidgetProps) {
+export function StackedAreaChartListWidget(props: PerformanceWidgetProps) {
   const location = useLocation();
   const mepSetting = useMEPSettingContext();
   const [selectedListIndex, setSelectListIndex] = useState<number>(0);
@@ -152,7 +157,7 @@ export function StackedBarsChartListWidget(props: PerformanceWidgetProps) {
             />
           );
         },
-        transform: transformEventsRequestToStackedBars,
+        transform: transformEventsRequestToStackedArea,
       };
     },
     // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -165,19 +170,32 @@ export function StackedBarsChartListWidget(props: PerformanceWidgetProps) {
   };
 
   const assembleAccordionItems = provided =>
-    getHeaders(provided).map(header => ({header, content: getChart(provided)}));
+    getHeaders(provided).map(header => ({header, content: getAreaChart(provided)}));
 
-  const getChart = provided => () =>
-    (
-      <BarChart
+  const getAreaChart = provided => () => {
+    const durationUnit = getDurationUnit(provided.widgetData.chart.data);
+    return (
+      <StackedAreaChart
         {...provided.widgetData.chart}
         {...provided}
         colors={colors}
         series={provided.widgetData.chart.data}
-        stacked
         animation
         isGroupedByDate
         showTimeInTooltip
+        yAxis={{
+          minInterval: durationUnit,
+          axisLabel: {
+            formatter(value: number) {
+              return axisLabelFormatter(
+                value,
+                aggregateOutputType(provided.widgetData.chart.data[0].seriesName),
+                undefined,
+                durationUnit
+              );
+            },
+          },
+        }}
         xAxis={{
           show: false,
           axisLabel: {show: true, margin: 8},
@@ -186,18 +204,9 @@ export function StackedBarsChartListWidget(props: PerformanceWidgetProps) {
         tooltip={{
           valueFormatter: value => tooltipFormatter(value, 'duration'),
         }}
-        start={
-          provided.widgetData.chart.start
-            ? new Date(provided.widgetData.chart.start)
-            : undefined
-        }
-        end={
-          provided.widgetData.chart.end
-            ? new Date(provided.widgetData.chart.end)
-            : undefined
-        }
       />
     );
+  };
 
   const getHeaders = provided =>
     provided.widgetData.list.data.map(listItem => () => {
@@ -236,7 +245,7 @@ export function StackedBarsChartListWidget(props: PerformanceWidgetProps) {
     <GenericPerformanceWidget<DataType>
       {...props}
       location={location}
-      Subtitle={() => <Subtitle>{t('Top transactions in count')}</Subtitle>}
+      Subtitle={() => <Subtitle>{t('P75 in Top Transactions')}</Subtitle>}
       HeaderActions={provided =>
         ContainerActions && (
           <ContainerActions isLoading={provided.widgetData.list?.isLoading} />