Browse Source

feat(ui): Add events chart to release details (#27355)

Matej Minar 3 years ago
parent
commit
f03662987b

+ 6 - 0
static/app/components/charts/eventsChart.tsx

@@ -324,6 +324,10 @@ export type EventsChartProps = {
   chartHeader?: React.ReactNode;
   releaseQueryExtra?: Query;
   preserveReleaseQueryParams?: boolean;
+  /**
+   * Chart zoom will change 'pageStart' instead of 'start'
+   */
+  usePageZoom?: boolean;
 } & Pick<
   ChartProps,
   | 'currentSeriesName'
@@ -381,6 +385,7 @@ class EventsChart extends React.Component<EventsChartProps> {
       releaseQueryExtra,
       disableableSeries,
       chartComponent,
+      usePageZoom,
       ...props
     } = this.props;
     // Include previous only on relative dates (defaults to relative if no start and end)
@@ -471,6 +476,7 @@ class EventsChart extends React.Component<EventsChartProps> {
         start={start}
         end={end}
         utc={utc}
+        usePageDate={usePageZoom}
         {...props}
       >
         {zoomRenderProps => (

+ 3 - 0
static/app/types/index.tsx

@@ -2115,6 +2115,9 @@ export enum ReleaseComparisonChartType {
   CRASH_FREE_SESSIONS = 'crashFreeSessions',
   SESSION_COUNT = 'sessionCount',
   USER_COUNT = 'userCount',
+  ERROR_COUNT = 'errorCount',
+  TRANSACTION_COUNT = 'transactionCount',
+  FAILURE_RATE = 'failureRate',
 }
 
 export enum HealthStatsPeriodOption {

+ 63 - 17
static/app/views/releases/detail/overview/releaseComparisonChart/index.tsx

@@ -44,7 +44,8 @@ import {
   initSessionsBreakdownChartData,
 } from '../chart/utils';
 
-import SessionsChart from './sessionsChart';
+import ReleaseEventsChart from './releaseEventsChart';
+import ReleaseSessionsChart from './releaseSessionsChart';
 
 type ComparisonRow = {
   type: ReleaseComparisonChartType;
@@ -122,6 +123,7 @@ function ReleaseComparisonChart({
       ? percent(releaseUsersCount - allUsersCount, allUsersCount)
       : null;
 
+  // TODO(release-comparison): conditional based on sessions/transactions/discover existence
   const charts: ComparisonRow[] = [
     {
       type: ReleaseComparisonChartType.CRASH_FREE_SESSIONS,
@@ -193,6 +195,31 @@ function ReleaseComparisonChart({
         : null,
       diffColor: null,
     },
+    // TODO(release-comparison): calculate totals/diffs
+    {
+      type: ReleaseComparisonChartType.ERROR_COUNT,
+      thisRelease: null,
+      allReleases: null,
+      diff: null,
+      diffDirection: null,
+      diffColor: null,
+    },
+    {
+      type: ReleaseComparisonChartType.TRANSACTION_COUNT,
+      thisRelease: null,
+      allReleases: null,
+      diff: null,
+      diffDirection: null,
+      diffColor: null,
+    },
+    {
+      type: ReleaseComparisonChartType.FAILURE_RATE,
+      thisRelease: null,
+      allReleases: null,
+      diff: null,
+      diffDirection: null,
+      diffColor: null,
+    },
   ];
 
   function getSeries(chartType: ReleaseComparisonChartType) {
@@ -305,6 +332,15 @@ function ReleaseComparisonChart({
     );
   }
 
+  const chartDiff = chart.diff ? (
+    <Change color={defined(chart.diffColor) ? chart.diffColor : undefined}>
+      {chart.diff}{' '}
+      {defined(chart.diffDirection) && (
+        <IconArrow direction={chart.diffDirection} size="xs" />
+      )}
+    </Change>
+  ) : null;
+
   const {
     statsPeriod: period,
     start,
@@ -321,29 +357,39 @@ function ReleaseComparisonChart({
     <Fragment>
       <ChartPanel>
         <ChartContainer>
-          <TransitionChart loading={loading} reloading={reloading}>
-            <TransparentLoadingMask visible={reloading} />
-
-            <SessionsChart
-              series={[...(series ?? []), ...(markLines ?? [])]}
-              previousSeries={previousSeries ?? []}
+          {[
+            ReleaseComparisonChartType.ERROR_COUNT,
+            ReleaseComparisonChartType.TRANSACTION_COUNT,
+            ReleaseComparisonChartType.FAILURE_RATE,
+          ].includes(activeChart) ? (
+            <ReleaseEventsChart
+              version={release.version}
               chartType={activeChart}
-              platform={platform}
               period={period ?? undefined}
               start={start}
               end={end}
               utc={utc === 'true'}
               value={chart.thisRelease}
-              diff={
-                <Change color={defined(chart.diffColor) ? chart.diffColor : undefined}>
-                  {chart.diff}{' '}
-                  {defined(chart.diffDirection) && (
-                    <IconArrow direction={chart.diffDirection} size="xs" />
-                  )}
-                </Change>
-              }
+              diff={chartDiff}
             />
-          </TransitionChart>
+          ) : (
+            <TransitionChart loading={loading} reloading={reloading}>
+              <TransparentLoadingMask visible={reloading} />
+
+              <ReleaseSessionsChart
+                series={[...(series ?? []), ...(markLines ?? [])]}
+                previousSeries={previousSeries ?? []}
+                chartType={activeChart}
+                platform={platform}
+                period={period ?? undefined}
+                start={start}
+                end={end}
+                utc={utc === 'true'}
+                value={chart.thisRelease}
+                diff={chartDiff}
+              />
+            </TransitionChart>
+          )}
         </ChartContainer>
       </ChartPanel>
       <ChartTable

+ 162 - 0
static/app/views/releases/detail/overview/releaseComparisonChart/releaseEventsChart.tsx

@@ -0,0 +1,162 @@
+import {Fragment} from 'react';
+import {withRouter} from 'react-router';
+import {WithRouterProps} from 'react-router/lib/withRouter';
+import {withTheme} from '@emotion/react';
+
+import {Client} from 'app/api';
+import EventsChart from 'app/components/charts/eventsChart';
+import {HeaderTitleLegend, HeaderValue} from 'app/components/charts/styles';
+import QuestionTooltip from 'app/components/questionTooltip';
+import {t} from 'app/locale';
+import {DateString, Organization, ReleaseComparisonChartType} from 'app/types';
+import {Theme} from 'app/utils/theme';
+import {QueryResults} from 'app/utils/tokenizeSearch';
+import withApi from 'app/utils/withApi';
+import withOrganization from 'app/utils/withOrganization';
+import {getTermHelp, PERFORMANCE_TERM} from 'app/views/performance/data';
+
+import {releaseComparisonChartLabels} from '../../utils';
+
+type Props = WithRouterProps & {
+  version: string;
+  chartType: ReleaseComparisonChartType;
+  value: React.ReactNode;
+  diff: React.ReactNode;
+  theme: Theme;
+  organization: Organization;
+  api: Client;
+  period?: string;
+  start?: string;
+  end?: string;
+  utc?: boolean;
+};
+
+function ReleaseEventsChart({
+  version,
+  chartType,
+  value,
+  diff,
+  theme,
+  organization,
+  api,
+  router,
+  period,
+  start,
+  end,
+  utc,
+  location,
+}: Props) {
+  function getColors() {
+    const colors = theme.charts.getColorPalette(14);
+    switch (chartType) {
+      case ReleaseComparisonChartType.ERROR_COUNT:
+        return [colors[12]];
+      case ReleaseComparisonChartType.TRANSACTION_COUNT:
+        return [theme.gray300];
+      case ReleaseComparisonChartType.FAILURE_RATE:
+        return [colors[9]];
+      default:
+        return undefined;
+    }
+  }
+
+  function getQuery() {
+    const releaseFilter = `release:${version}`;
+
+    switch (chartType) {
+      case ReleaseComparisonChartType.ERROR_COUNT:
+        return new QueryResults([
+          '!event.type:transaction',
+          releaseFilter,
+        ]).formatString();
+      case ReleaseComparisonChartType.TRANSACTION_COUNT:
+        return new QueryResults(['event.type:transaction', releaseFilter]).formatString();
+      case ReleaseComparisonChartType.FAILURE_RATE:
+        return new QueryResults(['event.type:transaction', releaseFilter]).formatString();
+      default:
+        return '';
+    }
+  }
+
+  function getField() {
+    switch (chartType) {
+      case ReleaseComparisonChartType.ERROR_COUNT:
+        return ['count()'];
+      case ReleaseComparisonChartType.TRANSACTION_COUNT:
+        return ['count()'];
+      case ReleaseComparisonChartType.FAILURE_RATE:
+        return ['failure_rate()'];
+      default:
+        return undefined;
+    }
+  }
+
+  function getYAxis() {
+    switch (chartType) {
+      case ReleaseComparisonChartType.ERROR_COUNT:
+        return 'count()';
+      case ReleaseComparisonChartType.TRANSACTION_COUNT:
+        return 'count()';
+      case ReleaseComparisonChartType.FAILURE_RATE:
+        return 'failure_rate()';
+      default:
+        return '';
+    }
+  }
+
+  function getHelp() {
+    switch (chartType) {
+      case ReleaseComparisonChartType.FAILURE_RATE:
+        return getTermHelp(organization, PERFORMANCE_TERM.FAILURE_RATE);
+      default:
+        return null;
+    }
+  }
+
+  const projects = location.query.project;
+  const environments = location.query.environment;
+
+  return (
+    <EventsChart
+      query={getQuery()}
+      yAxis={getYAxis()}
+      field={getField()}
+      colors={getColors()}
+      api={api}
+      router={router}
+      organization={organization}
+      disableReleases
+      disablePrevious
+      showLegend
+      projects={projects}
+      environments={environments}
+      start={start as DateString}
+      end={end as DateString}
+      period={period ?? undefined}
+      utc={utc}
+      currentSeriesName={t('This Release')}
+      previousSeriesName={t('All Releases')}
+      disableableSeries={[t('This Release'), t('All Releases')]}
+      chartHeader={
+        <Fragment>
+          <HeaderTitleLegend>
+            {releaseComparisonChartLabels[chartType]}
+            {getHelp() && <QuestionTooltip size="sm" position="top" title={getHelp()} />}
+          </HeaderTitleLegend>
+
+          <HeaderValue>
+            {value} {diff}
+          </HeaderValue>
+        </Fragment>
+      }
+      legendOptions={{right: 10, top: 0}}
+      chartOptions={{
+        grid: {left: '10px', right: '10px', top: '70px', bottom: '0px'},
+        ['height' as any]: 240, // TODO(ts): echart types
+      }}
+      usePageZoom
+    />
+  );
+}
+
+export default withOrganization(withTheme(withRouter(withApi(ReleaseEventsChart))));

+ 2 - 2
static/app/views/releases/detail/overview/releaseComparisonChart/sessionsChart.tsx → static/app/views/releases/detail/overview/releaseComparisonChart/releaseSessionsChart.tsx

@@ -35,7 +35,7 @@ type Props = {
   utc?: boolean;
 } & WithRouterProps;
 
-class SessionsChart extends React.Component<Props> {
+class ReleaseSessionsChart extends React.Component<Props> {
   formatTooltipValue = (value: string | number | null, label?: string) => {
     if (label && Object.values(releaseMarkLinesLabels).includes(label)) {
       return '';
@@ -166,4 +166,4 @@ class SessionsChart extends React.Component<Props> {
   }
 }
 
-export default withTheme(withRouter(SessionsChart));
+export default withTheme(withRouter(ReleaseSessionsChart));

+ 4 - 1
static/app/views/releases/detail/utils.tsx

@@ -144,6 +144,9 @@ export const releaseComparisonChartLabels = {
   [ReleaseComparisonChartType.CRASH_FREE_USERS]: t('Crash Free Users'),
   [ReleaseComparisonChartType.SESSION_COUNT]: t('Session Count'),
   [ReleaseComparisonChartType.USER_COUNT]: t('User Count'),
+  [ReleaseComparisonChartType.ERROR_COUNT]: t('Error Count'),
+  [ReleaseComparisonChartType.TRANSACTION_COUNT]: t('Transaction Count'),
+  [ReleaseComparisonChartType.FAILURE_RATE]: t('Failure Rate'),
 };
 
 export const releaseComparisonChartHelp = {
@@ -173,7 +176,7 @@ function generateReleaseMarkLine(
   return {
     seriesName: title,
     type: 'line',
-    data: [{name: position, value: 0}],
+    data: [{name: position, value: null as any}], // TODO(ts): echart types
     yAxisIndex: axisIndex ?? undefined,
     xAxisIndex: axisIndex ?? undefined,
     color: theme.gray300,