Browse Source

chore(starfish): Clean up spans page, fix metrics ribbon (#58687)

The spans page was using the same
chart component as the screen load landing
page, but we don't want timeseries here
so cleaning that up.

Also, fixes span metrics ribbon and renames
the release selectors.

Updates the tables to use avg_if.
Shruthi 1 year ago
parent
commit
ed157c7a03

+ 6 - 2
static/app/views/starfish/components/releaseSelector.tsx

@@ -127,10 +127,14 @@ export function ReleaseComparisonSelector() {
   const {primaryRelease, secondaryRelease} = useReleaseSelection();
   return (
     <PageFilterBar condensed>
-      <ReleaseSelector selectorKey="primaryRelease" selectorValue={primaryRelease} />
+      <ReleaseSelector
+        selectorKey="primaryRelease"
+        selectorValue={primaryRelease}
+        selectorName={t('Release 1')}
+      />
       <ReleaseSelector
         selectorKey="secondaryRelease"
-        selectorName={t('Compared To')}
+        selectorName={t('Release 2')}
         selectorValue={secondaryRelease}
       />
     </PageFilterBar>

+ 4 - 4
static/app/views/starfish/views/screens/index.tsx

@@ -120,10 +120,10 @@ export function ScreensView({yAxes, additionalFilters, chartHeight}: Props) {
     fields: [
       'transaction',
       SpanMetricsField.PROJECT_ID,
-      'avg(measurements.time_to_initial_display)', // TODO: Update these to avgIf with primary release when available
-      `avg_compare(measurements.time_to_initial_display,release,${primaryRelease},${secondaryRelease})`,
-      'avg(measurements.time_to_full_display)',
-      `avg_compare(measurements.time_to_full_display,release,${primaryRelease},${secondaryRelease})`,
+      `avg_if(measurements.time_to_initial_display,release,${primaryRelease})`,
+      `avg_if(measurements.time_to_initial_display,release,${secondaryRelease})`,
+      `avg_if(measurements.time_to_full_display,release,${primaryRelease})`,
+      `avg_if(measurements.time_to_full_display,release,${secondaryRelease})`,
       'count()',
     ],
     query: queryString,

+ 230 - 0
static/app/views/starfish/views/screens/screenLoadSpans/charts.tsx

@@ -0,0 +1,230 @@
+import {Fragment} from 'react';
+import styled from '@emotion/styled';
+import Color from 'color';
+
+import _EventsRequest from 'sentry/components/charts/eventsRequest';
+import {getInterval} from 'sentry/components/charts/utils';
+import LoadingContainer from 'sentry/components/loading/loadingContainer';
+import {PerformanceLayoutBodyRow} from 'sentry/components/performance/layouts';
+import {CHART_PALETTE} from 'sentry/constants/chartPalette';
+import {space} from 'sentry/styles/space';
+import {Series, SeriesDataUnit} from 'sentry/types/echarts';
+import {defined} from 'sentry/utils';
+import {tooltipFormatterUsingAggregateOutputType} from 'sentry/utils/discover/charts';
+import EventView from 'sentry/utils/discover/eventView';
+import {DiscoverDatasets} from 'sentry/utils/discover/types';
+import {MutableSearch} from 'sentry/utils/tokenizeSearch';
+import usePageFilters from 'sentry/utils/usePageFilters';
+import Chart, {useSynchronizeCharts} from 'sentry/views/starfish/components/chart';
+import MiniChartPanel from 'sentry/views/starfish/components/miniChartPanel';
+import {useReleaseSelection} from 'sentry/views/starfish/queries/useReleases';
+import {STARFISH_CHART_INTERVAL_FIDELITY} from 'sentry/views/starfish/utils/constants';
+import {appendReleaseFilters} from 'sentry/views/starfish/utils/releaseComparison';
+import {useEventsStatsQuery} from 'sentry/views/starfish/utils/useEventsStatsQuery';
+import {
+  CHART_TITLES,
+  OUTPUT_TYPE,
+  YAXIS_COLUMNS,
+} from 'sentry/views/starfish/views/screens';
+
+export enum YAxis {
+  WARM_START,
+  COLD_START,
+  TTID,
+  TTFD,
+  SLOW_FRAME_RATE,
+  FROZEN_FRAME_RATE,
+  THROUGHPUT,
+  COUNT,
+}
+
+const DEVICE_CLASS_BREAKDOWN_INDEX = {
+  high: 0,
+  medium: 1,
+  low: 2,
+};
+
+const EMPTY = '';
+const UNKNOWN = 'unknown';
+type Props = {
+  yAxes: YAxis[];
+  additionalFilters?: string[];
+  chartHeight?: number;
+};
+
+export function ScreenCharts({yAxes, additionalFilters, chartHeight}: Props) {
+  const pageFilter = usePageFilters();
+
+  const yAxisCols = yAxes.map(val => YAXIS_COLUMNS[val]);
+
+  const {
+    primaryRelease,
+    secondaryRelease,
+    isLoading: isReleasesLoading,
+  } = useReleaseSelection();
+
+  const query = new MutableSearch([
+    'event.type:transaction',
+    'transaction.op:ui.load',
+    ...(additionalFilters ?? []),
+  ]);
+  const queryString = appendReleaseFilters(query, primaryRelease, secondaryRelease);
+
+  useSynchronizeCharts();
+  const {
+    isLoading: seriesIsLoading,
+    data: releaseSeries,
+    isError,
+  } = useEventsStatsQuery({
+    eventView: EventView.fromNewQueryWithPageFilters(
+      {
+        name: '',
+        fields: ['release', 'device.class', ...yAxisCols],
+        topEvents: '6',
+        orderby: yAxisCols[0],
+        yAxis: yAxisCols,
+        query: queryString,
+        dataset: DiscoverDatasets.METRICS,
+        version: 2,
+        interval: getInterval(
+          pageFilter.selection.datetime,
+          STARFISH_CHART_INTERVAL_FIDELITY
+        ),
+      },
+      pageFilter.selection
+    ),
+    enabled: !isReleasesLoading,
+    // TODO: Change referrer
+    referrer: 'api.starfish-web-service.span-category-breakdown-timeseries',
+    initialData: {},
+  });
+
+  if (isReleasesLoading) {
+    return <LoadingContainer />;
+  }
+
+  const transformedReleaseSeries: {
+    [yAxisName: string]: {
+      [releaseVersion: string]: {[deviceClass: string]: Series | undefined};
+    };
+  } = {};
+  yAxes.forEach(val => {
+    transformedReleaseSeries[YAXIS_COLUMNS[val]] = {};
+    if (primaryRelease) {
+      transformedReleaseSeries[YAXIS_COLUMNS[val]][primaryRelease] = {};
+    }
+    if (secondaryRelease) {
+      transformedReleaseSeries[YAXIS_COLUMNS[val]][secondaryRelease] = {};
+    }
+  });
+
+  function renderCharts() {
+    if (defined(releaseSeries)) {
+      Object.keys(releaseSeries).forEach(seriesName => {
+        const [deviceClass, ...releaseArray] = seriesName.split(',');
+        const index = DEVICE_CLASS_BREAKDOWN_INDEX[deviceClass] ?? 3;
+        const release = releaseArray.join(',');
+        const isPrimary = release === primaryRelease;
+
+        if (release !== EMPTY) {
+          Object.keys(releaseSeries[seriesName]).forEach(yAxis => {
+            const label = `${deviceClass === EMPTY ? UNKNOWN : deviceClass}, ${release}`;
+            if (yAxis in transformedReleaseSeries) {
+              const data =
+                releaseSeries[seriesName][yAxis]?.data.map(datum => {
+                  return {
+                    name: datum[0] * 1000,
+                    value: datum[1][0].count,
+                  } as SeriesDataUnit;
+                }) ?? [];
+
+              transformedReleaseSeries[yAxis][release][
+                deviceClass === EMPTY ? UNKNOWN : deviceClass
+              ] = {
+                seriesName: label,
+                color: isPrimary
+                  ? CHART_PALETTE[5][index]
+                  : Color(CHART_PALETTE[5][index]).lighten(0.5).string(),
+                data,
+              };
+            }
+          });
+        }
+      });
+    }
+
+    return (
+      <Fragment>
+        {yAxes.map((val, index) => {
+          return (
+            <ChartsContainerItem key={val}>
+              <MiniChartPanel title={CHART_TITLES[val]}>
+                <Chart
+                  height={chartHeight ?? 180}
+                  data={
+                    ['high', 'medium', 'low', UNKNOWN]
+                      .flatMap(deviceClass => {
+                        return [primaryRelease, secondaryRelease].map(r => {
+                          if (r) {
+                            return transformedReleaseSeries[yAxisCols[index]][r][
+                              deviceClass
+                            ];
+                          }
+                          return null;
+                        });
+                      })
+                      .filter(v => defined(v)) as Series[]
+                  }
+                  loading={seriesIsLoading}
+                  utc={false}
+                  grid={{
+                    left: '0',
+                    right: '0',
+                    top: '16px',
+                    bottom: '0',
+                  }}
+                  showLegend
+                  definedAxisTicks={2}
+                  isLineChart
+                  aggregateOutputFormat={OUTPUT_TYPE[val]}
+                  tooltipFormatterOptions={{
+                    valueFormatter: value =>
+                      tooltipFormatterUsingAggregateOutputType(value, OUTPUT_TYPE[val]),
+                  }}
+                  errored={isError}
+                />
+              </MiniChartPanel>
+            </ChartsContainerItem>
+          );
+        })}
+      </Fragment>
+    );
+  }
+
+  return (
+    <div data-test-id="starfish-mobile-view">
+      <StyledRow minSize={200}>
+        <ChartsContainer>{renderCharts()}</ChartsContainer>
+      </StyledRow>
+    </div>
+  );
+}
+
+const StyledRow = styled(PerformanceLayoutBodyRow)`
+  margin-bottom: ${space(2)};
+`;
+
+const ChartsContainer = styled('div')`
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  gap: ${space(2)};
+`;
+
+const ChartsContainerItem = styled('div')`
+  flex: 1;
+`;
+
+export const Spacer = styled('div')`
+  margin-top: ${space(3)};
+`;

+ 8 - 3
static/app/views/starfish/views/screens/screenLoadSpans/index.tsx

@@ -20,7 +20,10 @@ import {ReleaseComparisonSelector} from 'sentry/views/starfish/components/releas
 import {StarfishPageFiltersContainer} from 'sentry/views/starfish/components/starfishPageFiltersContainer';
 import {useRoutingContext} from 'sentry/views/starfish/utils/routingContext';
 import {QueryParameterNames} from 'sentry/views/starfish/views/queryParameters';
-import {ScreensView, YAxis} from 'sentry/views/starfish/views/screens';
+import {
+  ScreenCharts,
+  YAxis,
+} from 'sentry/views/starfish/views/screens/screenLoadSpans/charts';
 import {ScreenLoadSpansTable} from 'sentry/views/starfish/views/screens/screenLoadSpans/table';
 import {ScreenMetricsRibbon} from 'sentry/views/starfish/views/screens/screenMetricsRibbon';
 import {SampleList} from 'sentry/views/starfish/views/spanSummaryPage/sampleList';
@@ -86,10 +89,12 @@ function ScreenLoadSpans() {
                     <DatePageFilter />
                   </PageFilterBar>
                   <ReleaseComparisonSelector />
-                  <ScreenMetricsRibbon />
+                  <ScreenMetricsRibbon
+                    additionalFilters={[`transaction:${transactionName}`]}
+                  />
                 </Container>
               </StarfishPageFiltersContainer>
-              <ScreensView
+              <ScreenCharts
                 yAxes={[YAxis.COUNT, YAxis.TTID, YAxis.TTFD]}
                 additionalFilters={[`transaction:${transactionName}`]}
                 chartHeight={120}

+ 2 - 1
static/app/views/starfish/views/screens/screenMetricsRibbon.tsx

@@ -12,12 +12,13 @@ import {appendReleaseFilters} from 'sentry/views/starfish/utils/releaseCompariso
 import {useTableQuery} from 'sentry/views/starfish/views/screens/screensTable';
 import {Block, BlockContainer} from 'sentry/views/starfish/views/spanSummaryPage/block';
 
-export function ScreenMetricsRibbon() {
+export function ScreenMetricsRibbon({additionalFilters}: {additionalFilters?: string[]}) {
   const {selection} = usePageFilters();
   const location = useLocation();
   const searchQuery = new MutableSearch([
     'event.type:transaction',
     'transaction.op:ui.load',
+    ...(additionalFilters ?? []),
   ]);
 
   const {primaryRelease, isLoading: isReleasesLoading} = useReleaseSelection();

+ 10 - 7
static/app/views/starfish/views/screens/screensTable.tsx

@@ -41,13 +41,15 @@ export function ScreensTable({data, eventView, isLoading, pageLinks}: Props) {
 
   const columnNameMap = {
     transaction: t('Screen'),
-    'avg(measurements.time_to_initial_display)': DataTitles.ttid,
-    'avg(measurements.time_to_full_display)': DataTitles.ttfd,
+    [`avg_if(measurements.time_to_initial_display,release,${primaryRelease})`]:
+      t('TTID (Release 1)'),
+    [`avg_if(measurements.time_to_initial_display,release,${secondaryRelease})`]:
+      t('TTID (Release 2)'),
+    [`avg_if(measurements.time_to_full_display,release,${primaryRelease})`]:
+      t('TTFD (Release 1)'),
+    [`avg_if(measurements.time_to_full_display,release,${secondaryRelease})`]:
+      t('TTFD (Release 2)'),
     'count()': DataTitles.count,
-    [`avg_compare(measurements.time_to_initial_display,release,${primaryRelease},${secondaryRelease})`]:
-      DataTitles.change,
-    [`avg_compare(measurements.time_to_full_display,release,${primaryRelease},${secondaryRelease})`]:
-      DataTitles.change,
   };
 
   function renderBodyCell(column, row): React.ReactNode {
@@ -137,7 +139,8 @@ export function ScreensTable({data, eventView, isLoading, pageLinks}: Props) {
         columnOrder={eventViewColumns
           .filter(
             (col: TableColumn<React.ReactText>) =>
-              col.name !== SpanMetricsField.PROJECT_ID
+              col.name !== SpanMetricsField.PROJECT_ID &&
+              !col.name.startsWith('avg_compare')
           )
           .map((col: TableColumn<React.ReactText>) => {
             return {...col, name: columnNameMap[col.key]};