Browse Source

feat(ui): Add markers to release chart (#27126)

Matej Minar 3 years ago
parent
commit
55f9cac8d8

+ 1 - 0
static/app/types/echarts.tsx

@@ -21,6 +21,7 @@ export type Series = {
   lineStyle?: EChartOption.LineStyle;
   stack?: string; // https://echarts.apache.org/en/option.html#series-line.stack
   z?: number; // https://echarts.apache.org/en/option.html#series-line.z
+  markLine?: EChartOption.SeriesLine['markLine'];
 };
 
 export type ReactEchartsRef = ReactEchartsCore & {

+ 8 - 1
static/app/types/index.tsx

@@ -1469,7 +1469,14 @@ type ReleaseData = {
     firstReleaseVersion: string | null;
     lastReleaseVersion: string | null;
   };
-  adoptionStages?: {};
+  adoptionStages?: Record<
+    'string',
+    {
+      stage: string | null;
+      adopted: string | null;
+      unadopted: string | null;
+    }
+  >;
 };
 
 type BaseRelease = {

+ 2 - 0
static/app/views/releases/detail/overview/index.tsx

@@ -463,6 +463,7 @@ class ReleaseOverview extends AsyncView<Props> {
                               defaultPeriod={RELEASE_PERIOD_KEY}
                             />
                             <ReleaseComparisonChart
+                              release={release}
                               releaseSessions={thisRelease}
                               allSessions={allReleases}
                               platform={project.platform}
@@ -470,6 +471,7 @@ class ReleaseOverview extends AsyncView<Props> {
                               loading={loading}
                               reloading={reloading}
                               errored={errored}
+                              project={project}
                             />
                           </Fragment>
                         ) : (

+ 54 - 6
static/app/views/releases/detail/overview/releaseComparisonChart/index.tsx

@@ -1,8 +1,10 @@
 import {Fragment} from 'react';
 import {browserHistory} from 'react-router';
+import {withTheme} from '@emotion/react';
 import styled from '@emotion/styled';
 import {Location} from 'history';
 import round from 'lodash/round';
+import moment from 'moment';
 
 import ErrorPanel from 'app/components/charts/errorPanel';
 import {ChartContainer} from 'app/components/charts/styles';
@@ -18,14 +20,20 @@ import {IconArrow, IconWarning} from 'app/icons';
 import {t} from 'app/locale';
 import overflowEllipsis from 'app/styles/overflowEllipsis';
 import space from 'app/styles/space';
-import {ReleaseComparisonChartType, SessionApiResponse, SessionField} from 'app/types';
+import {
+  ReleaseComparisonChartType,
+  ReleaseProject,
+  ReleaseWithHealth,
+  SessionApiResponse,
+  SessionField,
+} from 'app/types';
 import {defined, percent} from 'app/utils';
 import {decodeScalar} from 'app/utils/queryString';
 import {getCount, getCrashFreeRate, getCrashFreeSeries} from 'app/utils/sessions';
-import {Color} from 'app/utils/theme';
+import {Color, Theme} from 'app/utils/theme';
 import {displayCrashFreePercent} from 'app/views/releases/utils';
 
-import {releaseComparisonChartLabels} from '../../utils';
+import {generateReleaseMarkLine, releaseComparisonChartLabels} from '../../utils';
 import {
   fillChartDataFromSessionsResponse,
   initSessionsBreakdownChartData,
@@ -43,6 +51,8 @@ type ComparisonRow = {
 };
 
 type Props = {
+  release: ReleaseWithHealth;
+  project: ReleaseProject;
   releaseSessions: SessionApiResponse | null;
   allSessions: SessionApiResponse | null;
   platform: PlatformKey;
@@ -50,9 +60,12 @@ type Props = {
   loading: boolean;
   reloading: boolean;
   errored: boolean;
+  theme: Theme;
 };
 
 function ReleaseComparisonChart({
+  release,
+  project,
   releaseSessions,
   allSessions,
   platform,
@@ -60,6 +73,7 @@ function ReleaseComparisonChart({
   loading,
   reloading,
   errored,
+  theme,
 }: Props) {
   const activeChart = decodeScalar(
     location.query.chart,
@@ -181,6 +195,36 @@ function ReleaseComparisonChart({
       return {};
     }
 
+    const adoptionStages = release.adoptionStages?.[project.slug];
+
+    const markLines = [
+      generateReleaseMarkLine(
+        t('Release Created'),
+        moment(release.dateCreated).valueOf(),
+        theme
+      ),
+    ];
+
+    if (adoptionStages?.adopted) {
+      markLines.push(
+        generateReleaseMarkLine(
+          t('Adopted'),
+          moment(adoptionStages.adopted).valueOf(),
+          theme
+        )
+      );
+    }
+
+    if (adoptionStages?.unadopted) {
+      markLines.push(
+        generateReleaseMarkLine(
+          t('Unadopted'),
+          moment(adoptionStages.unadopted).valueOf(),
+          theme
+        )
+      );
+    }
+
     switch (chartType) {
       case ReleaseComparisonChartType.CRASH_FREE_SESSIONS:
         return {
@@ -205,6 +249,7 @@ function ReleaseComparisonChart({
               ),
             },
           ],
+          markLines,
         };
       case ReleaseComparisonChartType.CRASH_FREE_USERS:
         return {
@@ -229,6 +274,7 @@ function ReleaseComparisonChart({
               ),
             },
           ],
+          markLines,
         };
       case ReleaseComparisonChartType.SESSION_COUNT:
         return {
@@ -240,6 +286,7 @@ function ReleaseComparisonChart({
               chartData: initSessionsBreakdownChartData(),
             })
           ),
+          markLines,
         };
       case ReleaseComparisonChartType.USER_COUNT:
         return {
@@ -251,6 +298,7 @@ function ReleaseComparisonChart({
               chartData: initSessionsBreakdownChartData(),
             })
           ),
+          markLines,
         };
       default:
         return {};
@@ -267,7 +315,7 @@ function ReleaseComparisonChart({
     });
   }
 
-  const {series, previousSeries} = getSeries(activeChart);
+  const {series, previousSeries, markLines} = getSeries(activeChart);
 
   if (errored) {
     return (
@@ -287,7 +335,7 @@ function ReleaseComparisonChart({
             <TransparentLoadingMask visible={reloading} />
 
             <SessionsChart
-              series={series ?? []}
+              series={[...(series ?? []), ...(markLines ?? [])]}
               previousSeries={previousSeries ?? []}
               chartType={activeChart}
               platform={platform}
@@ -391,4 +439,4 @@ const Change = styled('div')<{color?: Color}>`
   ${p => p.color && `color: ${p.theme[p.color]}`}
 `;
 
-export default ReleaseComparisonChart;
+export default withTheme(ReleaseComparisonChart);

+ 4 - 0
static/app/views/releases/detail/overview/releaseComparisonChart/sessionsChart.tsx

@@ -83,6 +83,10 @@ class SessionsChart extends React.Component<Props> {
     const legend = {
       right: 10,
       top: 0,
+      // do not show adoption markers in the legend
+      data: [...series, ...previousSeries]
+        .filter(s => !s.markLine)
+        .map(s => s.seriesName),
     };
 
     return (

+ 25 - 0
static/app/views/releases/detail/utils.tsx

@@ -1,6 +1,7 @@
 import {Location} from 'history';
 import pick from 'lodash/pick';
 
+import MarkLine from 'app/components/charts/components/markLine';
 import {URL_PARAM} from 'app/constants/globalSelectionHeader';
 import {t} from 'app/locale';
 import {
@@ -14,6 +15,7 @@ import {
 } from 'app/types';
 import {getUtcDateString} from 'app/utils/dates';
 import EventView from 'app/utils/discover/eventView';
+import {Theme} from 'app/utils/theme';
 import {QueryResults} from 'app/utils/tokenizeSearch';
 
 import {commonTermsDescription, SessionTerm} from '../utils/sessionTerm';
@@ -152,3 +154,26 @@ export const releaseComparisonChartHelp = {
   ),
   [ReleaseComparisonChartType.USER_COUNT]: t('The number of users in a given period.'),
 };
+
+export function generateReleaseMarkLine(title: string, position: number, theme: Theme) {
+  return {
+    seriesName: title,
+    type: 'line',
+    data: [],
+    markLine: MarkLine({
+      silent: true,
+      lineStyle: {color: theme.gray300, type: 'solid'},
+      label: {
+        position: 'insideEndBottom',
+        formatter: title,
+        font: 'Rubik',
+        fontSize: 11,
+      } as any, // TODO(ts): weird echart types,
+      data: [
+        {
+          xAxis: position,
+        },
+      ] as any, // TODO(ts): weird echart types
+    }),
+  };
+}

+ 1 - 1
static/app/views/releases/list/releaseHealth/content.tsx

@@ -38,7 +38,7 @@ type Props = {
   showPlaceholders: boolean;
   isTopRelease: boolean;
   getHealthData: ReleaseHealthRequestRenderProps['getHealthData'];
-  adoptionStages?: Record<string, {stage: string; unadopted: string; adopted: string}>;
+  adoptionStages?: Release['adoptionStages'];
 };
 
 const Content = ({