Browse Source

feat(perf): Add vital widget for performance landing (#29489)

* feat(perf): Add vital widget for performance landing

This adds a vital widget that will pull the top list of worst vitals
(for LCP at the moment) and list out the other vitals in terms of their
good / meh / poor numbers, by count.
Kev 3 years ago
parent
commit
5db96d0680

+ 9 - 11
static/app/components/charts/eventsRequest.tsx

@@ -40,12 +40,9 @@ type LoadingStatus = {
   errored: boolean;
 };
 
-// Chart format for multiple series.
-type MultiSeriesResults = Series[];
-
 export type RenderProps = LoadingStatus &
   TimeSeriesData & {
-    results?: MultiSeriesResults;
+    results?: Series[]; // Chart with multiple series.
   };
 
 type DefaultProps = {
@@ -483,15 +480,16 @@ class EventsRequest extends React.PureComponent<EventsRequestProps, EventsReques
           ];
         })
         .sort((a, b) => a[0] - b[0]);
-      const results: MultiSeriesResults = sortedTimeseriesData.map(item => {
+      const results: Series[] = sortedTimeseriesData.map(item => {
         return item[1];
       });
-      const previousTimeseriesData: MultiSeriesResults | undefined =
-        sortedTimeseriesData.some(item => item[2] === null)
-          ? undefined
-          : sortedTimeseriesData.map(item => {
-              return item[2] as Series;
-            });
+      const previousTimeseriesData: Series[] | undefined = sortedTimeseriesData.some(
+        item => item[2] === null
+      )
+        ? undefined
+        : sortedTimeseriesData.map(item => {
+            return item[2] as Series;
+          });
 
       return children({
         loading,

+ 9 - 1
static/app/views/performance/landing/views/frontendPageloadView.tsx

@@ -2,7 +2,7 @@ import {usePageError} from 'app/utils/performance/contexts/pageError';
 
 import Table from '../../table';
 import {FRONTEND_PAGELOAD_COLUMN_TITLES} from '../data';
-import {TripleChartRow} from '../widgets/components/widgetChartRow';
+import {DoubleChartRow, TripleChartRow} from '../widgets/components/widgetChartRow';
 import {PerformanceWidgetSetting} from '../widgets/widgetDefinitions';
 
 import {BasePerformanceViewProps} from './types';
@@ -10,6 +10,14 @@ import {BasePerformanceViewProps} from './types';
 export function FrontendPageloadView(props: BasePerformanceViewProps) {
   return (
     <div data-test-id="frontend-pageload-view">
+      <DoubleChartRow
+        {...props}
+        allowedCharts={[
+          PerformanceWidgetSetting.TPM_AREA,
+          PerformanceWidgetSetting.MOST_RELATED_ERRORS,
+          PerformanceWidgetSetting.WORST_LCP_VITALS,
+        ]}
+      />
       <TripleChartRow
         {...props}
         allowedCharts={[

+ 21 - 14
static/app/views/performance/landing/vitalsCards.tsx

@@ -352,6 +352,8 @@ type VitalBarProps = {
   showStates?: boolean;
   showDurationDetail?: boolean;
   showVitalPercentNames?: boolean;
+  showDetail?: boolean;
+  barHeight?: number;
 };
 
 export function VitalBar(props: VitalBarProps) {
@@ -364,6 +366,8 @@ export function VitalBar(props: VitalBarProps) {
     showStates = false,
     showDurationDetail = false,
     showVitalPercentNames = false,
+    showDetail = true,
+    barHeight,
   } = props;
 
   if (isLoading) {
@@ -402,20 +406,23 @@ export function VitalBar(props: VitalBarProps) {
 
   return (
     <React.Fragment>
-      {showBar && <ColorBar colorStops={colorStops} />}
-      <BarDetail>
-        {showDurationDetail && p75 && (
-          <div data-test-id="vital-bar-p75">
-            {t('The p75 for all transactions is ')}
-            <strong>{p75}</strong>
-          </div>
-        )}
-        <VitalPercents
-          vital={vital}
-          percents={percents}
-          showVitalPercentNames={showVitalPercentNames}
-        />
-      </BarDetail>
+      {showBar && <ColorBar barHeight={barHeight} colorStops={colorStops} />}
+      {showDetail && (
+        <BarDetail>
+          {showDurationDetail && p75 && (
+            <div data-test-id="vital-bar-p75">
+              {t('The p75 for all transactions is ')}
+              <strong>{p75}</strong>
+            </div>
+          )}
+
+          <VitalPercents
+            vital={vital}
+            percents={percents}
+            showVitalPercentNames={showVitalPercentNames}
+          />
+        </BarDetail>
+      )}
     </React.Fragment>
   );
 }

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

@@ -14,6 +14,7 @@ import {HistogramWidget} from '../widgets/histogramWidget';
 import {LineChartListWidget} from '../widgets/lineChartListWidget';
 import {SingleFieldAreaWidget} from '../widgets/singleFieldAreaWidget';
 import {TrendsWidget} from '../widgets/trendsWidget';
+import {VitalWidget} from '../widgets/vitalWidget';
 
 import {ChartRowProps} from './widgetChartRow';
 
@@ -94,6 +95,8 @@ const _WidgetContainer = (props: Props) => {
       return <TrendsWidget {...props} {...widgetProps} />;
     case GenericPerformanceWidgetDataType.area:
       return <SingleFieldAreaWidget {...props} {...widgetProps} />;
+    case GenericPerformanceWidgetDataType.vitals:
+      return <VitalWidget {...props} {...widgetProps} />;
     case GenericPerformanceWidgetDataType.line_list:
       return <LineChartListWidget {...props} {...widgetProps} />;
     case GenericPerformanceWidgetDataType.histogram:

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

@@ -13,7 +13,7 @@ import {
 export function WidgetHeader<T extends WidgetDataConstraint>(
   props: GenericPerformanceWidgetProps<T> & WidgetDataProps<T>
 ) {
-  const {title, titleTooltip, subtitle, HeaderActions} = props;
+  const {title, titleTooltip, Subtitle, HeaderActions} = props;
   return (
     <WidgetHeaderContainer>
       <TitleContainer>
@@ -21,7 +21,7 @@ export function WidgetHeader<T extends WidgetDataConstraint>(
           {title}
           <QuestionTooltip position="top" size="sm" title={titleTooltip} />
         </StyledHeaderTitleLegend>
-        <div>{subtitle ? subtitle : null}</div>
+        <div>{Subtitle ? <Subtitle {...props} /> : null}</div>
       </TitleContainer>
 
       {HeaderActions && (

+ 31 - 0
static/app/views/performance/landing/widgets/transforms/transformEventsToVitals.tsx

@@ -0,0 +1,31 @@
+import {RenderProps} from 'app/components/charts/eventsRequest';
+import {getParams} from 'app/components/organizations/globalSelectionHeader/getParams';
+import {defined} from 'app/utils';
+
+import {QueryDefinitionWithKey, WidgetDataConstraint, WidgetPropUnion} from '../types';
+
+export function transformEventsRequestToVitals<T extends WidgetDataConstraint>(
+  widgetProps: WidgetPropUnion<T>,
+  results: RenderProps,
+  _: QueryDefinitionWithKey<T>
+) {
+  const {start, end, utc, interval, statsPeriod} = getParams(widgetProps.location.query);
+
+  const data = results.results ?? [];
+
+  const childData = {
+    ...results,
+    isLoading: results.loading,
+    isErrored: results.errored,
+    hasData: defined(data) && !!data.length && !!data[0].data.length,
+    data,
+
+    utc: utc === 'true',
+    interval,
+    statsPeriod: statsPeriod ?? undefined,
+    start: start ?? '',
+    end: end ?? '',
+  };
+
+  return childData;
+}

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

@@ -94,11 +94,14 @@ type HeaderActions<T> = FunctionComponent<{
   widgetData: T;
 }>;
 
+type Subtitle<T> = FunctionComponent<{
+  widgetData: T;
+}>;
+
 export type GenericPerformanceWidgetProps<T extends WidgetDataConstraint> = {
   // Header;
   title: string;
   titleTooltip: string;
-  subtitle?: JSX.Element;
 
   fields: string[];
   chartHeight: number;
@@ -109,6 +112,7 @@ export type GenericPerformanceWidgetProps<T extends WidgetDataConstraint> = {
   organization: Organization;
 
   // Components
+  Subtitle?: Subtitle<T>;
   HeaderActions?: HeaderActions<T>;
   EmptyComponent?: FunctionComponent<{height?: number}>;
 

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

@@ -51,9 +51,9 @@ export function HistogramWidget(props: Props) {
   return (
     <GenericPerformanceWidget<AreaDataType>
       {...props}
-      subtitle={
+      Subtitle={() => (
         <Subtitle>{t('Compared to last %s ', globalSelection.datetime.period)}</Subtitle>
-      }
+      )}
       HeaderActions={provided => (
         <Fragment>
           <ContainerActions {...provided.widgetData.chart} />

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

@@ -153,7 +153,7 @@ export function LineChartListWidget(props: Props) {
   return (
     <GenericPerformanceWidget<DataType>
       {...props}
-      subtitle={<Subtitle>{t('Suggested transactions')}</Subtitle>}
+      Subtitle={() => <Subtitle>{t('Suggested transactions')}</Subtitle>}
       HeaderActions={provided => (
         <ContainerActions isLoading={provided.widgetData.list?.isLoading} />
       )}

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

@@ -62,9 +62,9 @@ export function SingleFieldAreaWidget(props: Props) {
   return (
     <GenericPerformanceWidget<AreaDataType>
       {...props}
-      subtitle={
+      Subtitle={() => (
         <Subtitle>{t('Compared to last %s ', globalSelection.datetime.period)}</Subtitle>
-      }
+      )}
       HeaderActions={provided => (
         <Fragment>
           <HighlightNumber color={props.chartColor}>

Some files were not shown because too many files changed in this diff