Browse Source

feat(starfish): Plot multiple releases on mobile service view (#53335)

![Screenshot 2023-07-21 at 10 50 47
AM](https://github.com/getsentry/sentry/assets/63818634/d97dde14-2f3c-41eb-9f3e-17e753c0f101)
Shruthi 1 year ago
parent
commit
446ea5748e

+ 3 - 1
static/app/views/starfish/utils/useEventsStatsQuery.tsx

@@ -16,9 +16,11 @@ export function useEventsStatsQuery({
   enabled,
   referrer,
   initialData,
+  excludeOther = false,
 }: {
   eventView: EventView;
   enabled?: boolean;
+  excludeOther?: boolean;
   initialData?: MultiSeriesEventsStats;
   referrer?: string;
 }) {
@@ -33,7 +35,7 @@ export function useEventsStatsQuery({
       ...eventView.getEventsAPIPayload(location),
       yAxis: eventView.yAxis,
       topEvents: eventView.topEvents,
-      excludeOther: 0,
+      excludeOther: excludeOther === true ? 1 : 0,
       partial: 1,
       orderby: eventView.sorts?.[0] ? encodeSort(eventView.sorts?.[0]) : undefined,
       interval: eventView.interval,

+ 309 - 245
static/app/views/starfish/views/mobileServiceView/index.tsx

@@ -5,297 +5,355 @@ 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 {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
-import {Series} from 'sentry/types/echarts';
+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 {decodeScalar} from 'sentry/utils/queryString';
 import {MutableSearch} from 'sentry/utils/tokenizeSearch';
 import {useLocation} from 'sentry/utils/useLocation';
 import usePageFilters from 'sentry/utils/usePageFilters';
-import withApi from 'sentry/utils/withApi';
-import {P95_COLOR, THROUGHPUT_COLOR} from 'sentry/views/starfish/colours';
 import Chart, {useSynchronizeCharts} from 'sentry/views/starfish/components/chart';
 import MiniChartPanel from 'sentry/views/starfish/components/miniChartPanel';
 import {useReleases} from 'sentry/views/starfish/queries/useReleases';
 import {STARFISH_CHART_INTERVAL_FIDELITY} from 'sentry/views/starfish/utils/constants';
+import {useEventsStatsQuery} from 'sentry/views/starfish/utils/useEventsStatsQuery';
 import {ViewsList} from 'sentry/views/starfish/views/mobileServiceView/viewsList';
 import {BaseStarfishViewProps} from 'sentry/views/starfish/views/webServiceView/starfishLanding';
 
-const EventsRequest = withApi(_EventsRequest);
+const READABLE_YAXIS_LABELS = {
+  'avg(measurements.app_start_cold)': 'avg(app_start_cold)',
+  'avg(measurements.app_start_warm)': 'avg(app_start_warm)',
+  'avg(measurements.time_to_initial_display)': 'avg(time_to_initial_display)',
+  'avg(measurements.time_to_full_display)': 'avg(time_to_full_display)',
+  'avg(measurements.frames_slow_rate)': 'avg(frames_slow_rate)',
+  'avg(measurements.frames_frozen_rate)': 'avg(frames_frozen_rate)',
+};
 
 export function MobileStarfishView(props: BaseStarfishViewProps) {
-  const {eventView, organization} = props;
+  const {eventView} = props;
   const pageFilter = usePageFilters();
   const location = useLocation();
   const {data: releases, isLoading: isReleasesLoading} = useReleases();
 
-  useSynchronizeCharts();
-  if (isReleasesLoading) {
-    return <LoadingContainer />;
-  }
-
   const release1 =
     decodeScalar(location.query.release1) ?? releases?.[0]?.version ?? undefined;
 
   const release2 =
     decodeScalar(location.query.release2) ?? releases?.[0]?.version ?? undefined;
 
-  const releaseFilter: string[] = [];
-  if (defined(release1) && release1 !== '') {
-    releaseFilter.push(release1);
-  }
-
-  if (defined(release2) && release2 !== '' && release1 !== release2) {
-    releaseFilter.push(release2);
-  }
-
-  function renderCharts() {
-    const query = new MutableSearch(['event.type:transaction', 'transaction.op:ui.load']);
-
-    if (releaseFilter.length > 0) {
-      query.addStringFilter(`release:[${releaseFilter.join(',')}]`);
-    }
+  const query = new MutableSearch(['event.type:transaction', 'transaction.op:ui.load']);
 
-    return (
-      <EventsRequest
-        query={query.formatString()}
-        includePrevious={false}
-        partial
-        interval={getInterval(
+  useSynchronizeCharts();
+  const {
+    isLoading: seriesIsLoading,
+    data: firstReleaseSeries,
+    isError,
+  } = useEventsStatsQuery({
+    eventView: EventView.fromNewQueryWithPageFilters(
+      {
+        name: '',
+        fields: [],
+        yAxis: [
+          'avg(measurements.app_start_cold)',
+          'avg(measurements.app_start_warm)',
+          'avg(measurements.time_to_initial_display)',
+          'avg(measurements.time_to_full_display)',
+          'avg(measurements.frames_slow_rate)',
+          'avg(measurements.frames_frozen_rate)',
+        ],
+        query:
+          defined(release1) && release1 !== ''
+            ? query.copy().addStringFilter(`release:${release1}`).formatString()
+            : query.formatString(),
+        dataset: DiscoverDatasets.METRICS,
+        version: 2,
+        interval: getInterval(
           pageFilter.selection.datetime,
           STARFISH_CHART_INTERVAL_FIDELITY
-        )}
-        includeTransformedData
-        limit={1}
-        environment={eventView.environment}
-        project={eventView.project}
-        period={eventView.statsPeriod}
-        referrer="api.starfish-mobile-service.homepage-charts"
-        start={eventView.start}
-        end={eventView.end}
-        organization={organization}
-        yAxis={[
+        ),
+      },
+      pageFilter.selection
+    ),
+    enabled: !isReleasesLoading,
+    referrer: 'api.starfish-web-service.span-category-breakdown-timeseries',
+    initialData: {},
+  });
+
+  const {data: secondReleaseSeries} = useEventsStatsQuery({
+    eventView: EventView.fromNewQueryWithPageFilters(
+      {
+        name: '',
+        fields: [],
+        yAxis: [
           'avg(measurements.app_start_cold)',
           'avg(measurements.app_start_warm)',
           'avg(measurements.time_to_initial_display)',
           'avg(measurements.time_to_full_display)',
           'avg(measurements.frames_slow_rate)',
           'avg(measurements.frames_frozen_rate)',
-        ]}
-        dataset={DiscoverDatasets.METRICS}
-      >
-        {({loading, results}) => {
-          if (!results || !results[0] || !results[1]) {
-            return null;
-          }
-
-          const coldStart: Series = {
-            seriesName: 'avg(app_start_cold)',
-            data: results[0].data,
-          };
-
-          const warmStart: Series = {
-            seriesName: 'avg(app_start_warm)',
-            data: results[1].data,
-          };
-
-          const initialDisplay: Series = {
-            seriesName: 'avg(time_to_initial_display)',
-            data: results[2].data,
-          };
-
-          const fullDisplay: Series = {
-            seriesName: 'avg(time_to_full_display)',
-            data: results[3].data,
-          };
+        ],
+        query:
+          defined(release2) && release2 !== ''
+            ? query.copy().addStringFilter(`release:${release2}`).formatString()
+            : query.formatString(),
+        dataset: DiscoverDatasets.METRICS,
+        version: 2,
+        interval: getInterval(
+          pageFilter.selection.datetime,
+          STARFISH_CHART_INTERVAL_FIDELITY
+        ),
+      },
+      pageFilter.selection
+    ),
+    enabled: !isReleasesLoading && release1 !== release2,
+    referrer: 'api.starfish-web-service.span-category-breakdown-timeseries',
+    initialData: {},
+  });
 
-          const slowFrames: Series = {
-            seriesName: 'avg(slow_frames_rate)',
-            data: results[4].data,
-          };
+  if (isReleasesLoading) {
+    return <LoadingContainer />;
+  }
 
-          const frozenFrames: Series = {
-            seriesName: 'avg(frozen_frames_rate)',
-            data: results[5].data,
-          };
+  function renderCharts() {
+    const transformedSeries: {[yAxisName: string]: Series[]} = {
+      'avg(measurements.app_start_cold)': [],
+      'avg(measurements.app_start_warm)': [],
+      'avg(measurements.time_to_initial_display)': [],
+      'avg(measurements.time_to_full_display)': [],
+      'avg(measurements.frames_slow_rate)': [],
+      'avg(measurements.frames_frozen_rate)': [],
+    };
 
-          return (
-            <Fragment>
-              <ChartsContainerItem>
-                <MiniChartPanel title={t('App Initialization')}>
-                  <Chart
-                    statsPeriod={eventView.statsPeriod}
-                    height={125}
-                    data={[coldStart]}
-                    start={eventView.start as string}
-                    end={eventView.end as string}
-                    loading={loading}
-                    utc={false}
-                    grid={{
-                      left: '0',
-                      right: '0',
-                      top: '16px',
-                      bottom: '0',
-                    }}
-                    showLegend
-                    definedAxisTicks={2}
-                    isLineChart
-                    chartColors={[P95_COLOR]}
-                    aggregateOutputFormat="duration"
-                    tooltipFormatterOptions={{
-                      valueFormatter: value =>
-                        tooltipFormatterUsingAggregateOutputType(value, 'duration'),
-                    }}
-                  />
+    if (defined(firstReleaseSeries)) {
+      Object.keys(firstReleaseSeries).forEach(yAxis => {
+        const label = `${release1}`;
+        if (yAxis in transformedSeries) {
+          transformedSeries[yAxis].push({
+            seriesName: label,
+            color: CHART_PALETTE[1][0],
+            data:
+              firstReleaseSeries[yAxis]?.data.map(datum => {
+                return {
+                  name: datum[0] * 1000,
+                  value: datum[1][0].count,
+                } as SeriesDataUnit;
+              }) ?? [],
+          });
+        }
+      });
+    }
 
-                  <Spacer />
+    if (defined(secondReleaseSeries)) {
+      Object.keys(secondReleaseSeries).forEach(yAxis => {
+        const label = `${release2}`;
+        if (yAxis in transformedSeries) {
+          transformedSeries[yAxis].push({
+            seriesName: label,
+            color: CHART_PALETTE[1][1],
+            data:
+              secondReleaseSeries[yAxis]?.data.map(datum => {
+                return {
+                  name: datum[0] * 1000,
+                  value: datum[1][0].count,
+                } as SeriesDataUnit;
+              }) ?? [],
+          });
+        }
+      });
+    }
 
-                  <Chart
-                    statsPeriod={eventView.statsPeriod}
-                    height={125}
-                    data={[warmStart]}
-                    start=""
-                    end=""
-                    loading={loading}
-                    showLegend
-                    utc={false}
-                    grid={{
-                      left: '0',
-                      right: '0',
-                      top: '16px',
-                      bottom: '0',
-                    }}
-                    aggregateOutputFormat="duration"
-                    definedAxisTicks={2}
-                    stacked
-                    isLineChart
-                    chartColors={[THROUGHPUT_COLOR]}
-                    tooltipFormatterOptions={{
-                      valueFormatter: value =>
-                        tooltipFormatterUsingAggregateOutputType(value, 'duration'),
-                    }}
-                  />
-                </MiniChartPanel>
-              </ChartsContainerItem>
-              <ChartsContainerItem>
-                <MiniChartPanel title={t('Perceived Page Load')}>
-                  <Chart
-                    statsPeriod={eventView.statsPeriod}
-                    height={125}
-                    data={[initialDisplay]}
-                    start={eventView.start as string}
-                    end={eventView.end as string}
-                    loading={loading}
-                    utc={false}
-                    grid={{
-                      left: '0',
-                      right: '0',
-                      top: '16px',
-                      bottom: '0',
-                    }}
-                    showLegend
-                    definedAxisTicks={2}
-                    isLineChart
-                    chartColors={[P95_COLOR]}
-                    aggregateOutputFormat="duration"
-                    tooltipFormatterOptions={{
-                      valueFormatter: value =>
-                        tooltipFormatterUsingAggregateOutputType(value, 'duration'),
-                    }}
-                  />
+    return (
+      <Fragment>
+        <ChartsContainerItem>
+          <MiniChartPanel title={t('App Initialization')}>
+            <SubTitle>
+              {READABLE_YAXIS_LABELS['avg(measurements.app_start_cold)']}
+            </SubTitle>
+            <Chart
+              statsPeriod={eventView.statsPeriod}
+              height={125}
+              data={transformedSeries['avg(measurements.app_start_cold)']}
+              start={eventView.start as string}
+              end={eventView.end as string}
+              loading={seriesIsLoading}
+              utc={false}
+              grid={{
+                left: '0',
+                right: '0',
+                top: '16px',
+                bottom: '0',
+              }}
+              showLegend
+              definedAxisTicks={2}
+              isLineChart
+              aggregateOutputFormat="duration"
+              tooltipFormatterOptions={{
+                valueFormatter: value =>
+                  tooltipFormatterUsingAggregateOutputType(value, 'duration'),
+              }}
+              errored={isError}
+            />
 
-                  <Spacer />
+            <Spacer />
 
-                  <Chart
-                    statsPeriod={eventView.statsPeriod}
-                    height={125}
-                    data={[fullDisplay]}
-                    start=""
-                    end=""
-                    loading={loading}
-                    showLegend
-                    utc={false}
-                    grid={{
-                      left: '0',
-                      right: '0',
-                      top: '16px',
-                      bottom: '0',
-                    }}
-                    aggregateOutputFormat="duration"
-                    definedAxisTicks={2}
-                    stacked
-                    isLineChart
-                    chartColors={[THROUGHPUT_COLOR]}
-                    tooltipFormatterOptions={{
-                      valueFormatter: value =>
-                        tooltipFormatterUsingAggregateOutputType(value, 'duration'),
-                    }}
-                  />
-                </MiniChartPanel>
-              </ChartsContainerItem>
-              <ChartsContainerItem>
-                <MiniChartPanel title={t('Responsiveness')}>
-                  <Chart
-                    statsPeriod={eventView.statsPeriod}
-                    height={125}
-                    data={[slowFrames]}
-                    start={eventView.start as string}
-                    end={eventView.end as string}
-                    loading={loading}
-                    utc={false}
-                    grid={{
-                      left: '0',
-                      right: '0',
-                      top: '16px',
-                      bottom: '0',
-                    }}
-                    showLegend
-                    definedAxisTicks={2}
-                    isLineChart
-                    chartColors={[P95_COLOR]}
-                    aggregateOutputFormat="percentage"
-                    tooltipFormatterOptions={{
-                      valueFormatter: value =>
-                        tooltipFormatterUsingAggregateOutputType(value, 'percentage'),
-                    }}
-                  />
+            <SubTitle>
+              {READABLE_YAXIS_LABELS['avg(measurements.app_start_warm)']}
+            </SubTitle>
+            <Chart
+              statsPeriod={eventView.statsPeriod}
+              height={125}
+              data={transformedSeries['avg(measurements.app_start_warm)']}
+              start=""
+              end=""
+              loading={seriesIsLoading}
+              showLegend
+              utc={false}
+              grid={{
+                left: '0',
+                right: '0',
+                top: '16px',
+                bottom: '0',
+              }}
+              aggregateOutputFormat="duration"
+              definedAxisTicks={2}
+              stacked
+              isLineChart
+              tooltipFormatterOptions={{
+                valueFormatter: value =>
+                  tooltipFormatterUsingAggregateOutputType(value, 'duration'),
+              }}
+              errored={isError}
+            />
+          </MiniChartPanel>
+        </ChartsContainerItem>
+        <ChartsContainerItem>
+          <MiniChartPanel title={t('Perceived Screen Load')}>
+            <SubTitle>
+              {READABLE_YAXIS_LABELS['avg(measurements.time_to_initial_display)']}
+            </SubTitle>
+            <Chart
+              statsPeriod={eventView.statsPeriod}
+              height={125}
+              data={transformedSeries['avg(measurements.time_to_initial_display)']}
+              start={eventView.start as string}
+              end={eventView.end as string}
+              loading={seriesIsLoading}
+              utc={false}
+              grid={{
+                left: '0',
+                right: '0',
+                top: '16px',
+                bottom: '0',
+              }}
+              showLegend
+              definedAxisTicks={2}
+              isLineChart
+              aggregateOutputFormat="duration"
+              tooltipFormatterOptions={{
+                valueFormatter: value =>
+                  tooltipFormatterUsingAggregateOutputType(value, 'duration'),
+              }}
+              errored={isError}
+            />
 
-                  <Spacer />
+            <Spacer />
+            <SubTitle>
+              {READABLE_YAXIS_LABELS['avg(measurements.time_to_full_display)']}
+            </SubTitle>
+            <Chart
+              statsPeriod={eventView.statsPeriod}
+              height={125}
+              data={transformedSeries['avg(measurements.time_to_full_display)']}
+              start=""
+              end=""
+              loading={seriesIsLoading}
+              showLegend
+              utc={false}
+              grid={{
+                left: '0',
+                right: '0',
+                top: '16px',
+                bottom: '0',
+              }}
+              aggregateOutputFormat="duration"
+              definedAxisTicks={2}
+              stacked
+              isLineChart
+              tooltipFormatterOptions={{
+                valueFormatter: value =>
+                  tooltipFormatterUsingAggregateOutputType(value, 'duration'),
+              }}
+              errored={isError}
+            />
+          </MiniChartPanel>
+        </ChartsContainerItem>
+        <ChartsContainerItem>
+          <MiniChartPanel title={t('Responsiveness')}>
+            <SubTitle>
+              {READABLE_YAXIS_LABELS['avg(measurements.frames_slow_rate)']}
+            </SubTitle>
+            <Chart
+              statsPeriod={eventView.statsPeriod}
+              height={125}
+              data={transformedSeries['avg(measurements.frames_slow_rate)']}
+              start={eventView.start as string}
+              end={eventView.end as string}
+              loading={seriesIsLoading}
+              utc={false}
+              grid={{
+                left: '0',
+                right: '0',
+                top: '16px',
+                bottom: '0',
+              }}
+              showLegend
+              definedAxisTicks={2}
+              isLineChart
+              aggregateOutputFormat="percentage"
+              tooltipFormatterOptions={{
+                valueFormatter: value =>
+                  tooltipFormatterUsingAggregateOutputType(value, 'percentage'),
+              }}
+              errored={isError}
+            />
 
-                  <Chart
-                    statsPeriod={eventView.statsPeriod}
-                    height={125}
-                    data={[frozenFrames]}
-                    start=""
-                    end=""
-                    loading={loading}
-                    showLegend
-                    utc={false}
-                    grid={{
-                      left: '0',
-                      right: '0',
-                      top: '16px',
-                      bottom: '0',
-                    }}
-                    aggregateOutputFormat="percentage"
-                    definedAxisTicks={2}
-                    stacked
-                    isLineChart
-                    chartColors={[THROUGHPUT_COLOR]}
-                    tooltipFormatterOptions={{
-                      valueFormatter: value =>
-                        tooltipFormatterUsingAggregateOutputType(value, 'percentage'),
-                    }}
-                  />
-                </MiniChartPanel>
-              </ChartsContainerItem>
-            </Fragment>
-          );
-        }}
-      </EventsRequest>
+            <Spacer />
+            <SubTitle>
+              {READABLE_YAXIS_LABELS['avg(measurements.frames_frozen_rate)']}
+            </SubTitle>
+            <Chart
+              statsPeriod={eventView.statsPeriod}
+              height={125}
+              data={transformedSeries['avg(measurements.frames_frozen_rate)']}
+              start=""
+              end=""
+              loading={seriesIsLoading}
+              showLegend
+              utc={false}
+              grid={{
+                left: '0',
+                right: '0',
+                top: '16px',
+                bottom: '0',
+              }}
+              aggregateOutputFormat="percentage"
+              definedAxisTicks={2}
+              stacked
+              isLineChart
+              tooltipFormatterOptions={{
+                valueFormatter: value =>
+                  tooltipFormatterUsingAggregateOutputType(value, 'percentage'),
+              }}
+              errored={isError}
+            />
+          </MiniChartPanel>
+        </ChartsContainerItem>
+      </Fragment>
     );
   }
 
@@ -327,3 +385,9 @@ const ChartsContainerItem = styled('div')`
 export const Spacer = styled('div')`
   margin-top: ${space(3)};
 `;
+
+const SubTitle = styled('div')`
+  margin-bottom: ${space(1.5)};
+  font-size: ${p => p.theme.fontSizeSmall};
+  font-weight: bold;
+`;