Просмотр исходного кода

fix(ui): Format alert rule details chart axis and value (#29036)

Matej Minar 3 лет назад
Родитель
Сommit
d12f33290c

+ 10 - 25
static/app/views/alerts/incidentRules/triggers/chart/thresholdsChart.tsx

@@ -2,17 +2,18 @@ import {PureComponent} from 'react';
 import color from 'color';
 import debounce from 'lodash/debounce';
 import flatten from 'lodash/flatten';
-import round from 'lodash/round';
 
 import Graphic from 'app/components/charts/components/graphic';
 import LineChart, {LineChartSeries} from 'app/components/charts/lineChart';
 import space from 'app/styles/space';
 import {GlobalSelection} from 'app/types';
 import {ReactEchartsRef, Series} from 'app/types/echarts';
-import {defined} from 'app/utils';
-import {axisLabelFormatter, tooltipFormatter} from 'app/utils/discover/charts';
 import theme from 'app/utils/theme';
-import {isSessionAggregate} from 'app/views/alerts/utils';
+import {
+  alertAxisFormatter,
+  alertTooltipValueFormatter,
+  isSessionAggregate,
+} from 'app/views/alerts/utils';
 
 import {AlertRuleThresholdType, IncidentRule, Trigger} from '../../types';
 
@@ -273,24 +274,6 @@ export default class ThresholdsChart extends PureComponent<Props, State> {
     );
   };
 
-  tooltipValueFormatter = (value: number, seriesName?: string) => {
-    const {aggregate} = this.props;
-    if (isSessionAggregate(aggregate)) {
-      return defined(value) ? `${value}%` : '\u2015';
-    }
-
-    return tooltipFormatter(value, seriesName);
-  };
-
-  axisFormatter = (value: number) => {
-    const {data, aggregate} = this.props;
-    if (isSessionAggregate(aggregate)) {
-      return defined(value) ? `${round(value, 2)}%` : '\u2015';
-    }
-
-    return axisLabelFormatter(value, data.length ? data[0].seriesName : '');
-  };
-
   clampMaxValue(value: number) {
     // When we apply top buffer to the crash free percentage (99.7% * 1.03), it
     // can cross 100%, so we clamp it
@@ -302,7 +285,7 @@ export default class ThresholdsChart extends PureComponent<Props, State> {
   }
 
   render() {
-    const {data, triggers, period} = this.props;
+    const {data, triggers, period, aggregate} = this.props;
     const dataWithoutRecentBucket: LineChartSeries[] = data?.map(
       ({data: eventData, ...restOfData}) => ({
         ...restOfData,
@@ -326,13 +309,15 @@ export default class ThresholdsChart extends PureComponent<Props, State> {
 
     const chartOptions = {
       tooltip: {
-        valueFormatter: this.tooltipValueFormatter,
+        valueFormatter: (value: number, seriesName?: string) =>
+          alertTooltipValueFormatter(value, seriesName ?? '', aggregate),
       },
       yAxis: {
         min: this.state.yAxisMin ?? undefined,
         max: this.state.yAxisMax ?? undefined,
         axisLabel: {
-          formatter: this.axisFormatter,
+          formatter: (value: number) =>
+            alertAxisFormatter(value, data[0].seriesName, aggregate),
         },
       },
     };

+ 23 - 8
static/app/views/alerts/rules/details/metricChart.tsx

@@ -38,7 +38,11 @@ import {AlertWizardAlertNames} from 'app/views/alerts/wizard/options';
 import {getAlertTypeFromAggregateDataset} from 'app/views/alerts/wizard/utils';
 
 import {Incident, IncidentActivityType, IncidentStatus} from '../../types';
-import {SESSION_AGGREGATE_TO_FIELD} from '../../utils';
+import {
+  alertAxisFormatter,
+  alertTooltipValueFormatter,
+  SESSION_AGGREGATE_TO_FIELD,
+} from '../../utils';
 
 import {TimePeriodType} from './constants';
 
@@ -499,11 +503,17 @@ class MetricChart extends React.PureComponent<Props, State> {
                   top: space(2),
                   bottom: 0,
                 }}
-                yAxis={
-                  maxThresholdValue > maxSeriesValue
-                    ? {max: maxThresholdValue}
-                    : undefined
-                }
+                yAxis={{
+                  axisLabel: {
+                    formatter: (value: number) =>
+                      alertAxisFormatter(
+                        value,
+                        timeseriesData[0].seriesName,
+                        rule.aggregate
+                      ),
+                  },
+                  max: maxThresholdValue > maxSeriesValue ? maxThresholdValue : undefined,
+                }}
                 series={[...series, ...areaSeries]}
                 graphic={Graphic({
                   elements: this.getRuleChangeThresholdElements(timeseriesData),
@@ -516,6 +526,11 @@ class MetricChart extends React.PureComponent<Props, State> {
                       : [seriesParams];
                     const {marker, data: pointData, seriesName} = pointSeries[0];
                     const [pointX, pointY] = pointData as [number, number];
+                    const pointYFormatted = alertTooltipValueFormatter(
+                      pointY,
+                      seriesName ?? '',
+                      rule.aggregate
+                    );
                     const isModified =
                       dateModified && pointX <= new Date(dateModified).getTime();
 
@@ -535,8 +550,8 @@ class MetricChart extends React.PureComponent<Props, State> {
                       ? `<strong>${t('Alert Rule Modified')}</strong>`
                       : `${marker} <strong>${seriesName}</strong>`;
                     const value = isModified
-                      ? `${seriesName} ${pointY.toLocaleString()}`
-                      : pointY.toLocaleString();
+                      ? `${seriesName} ${pointYFormatted}`
+                      : pointYFormatted;
 
                     return [
                       `<div class="tooltip-series"><div>`,

+ 24 - 0
static/app/views/alerts/utils/index.tsx

@@ -1,8 +1,12 @@
+import round from 'lodash/round';
+
 import {Client} from 'app/api';
 import {t} from 'app/locale';
 import {NewQuery, Project, SessionField} from 'app/types';
 import {IssueAlertRule} from 'app/types/alerts';
+import {defined} from 'app/utils';
 import {getUtcDateString} from 'app/utils/dates';
+import {axisLabelFormatter, tooltipFormatter} from 'app/utils/discover/charts';
 import EventView from 'app/utils/discover/eventView';
 import {getAggregateAlias} from 'app/utils/discover/fields';
 import {PRESET_AGGREGATES} from 'app/views/alerts/incidentRules/presets';
@@ -279,3 +283,23 @@ export const SESSION_AGGREGATE_TO_FIELD = {
   [SessionsAggregate.CRASH_FREE_SESSIONS]: SessionField.SESSIONS,
   [SessionsAggregate.CRASH_FREE_USERS]: SessionField.USERS,
 };
+
+export function alertAxisFormatter(value: number, seriesName: string, aggregate: string) {
+  if (isSessionAggregate(aggregate)) {
+    return defined(value) ? `${round(value, 2)}%` : '\u2015';
+  }
+
+  return axisLabelFormatter(value, seriesName);
+}
+
+export function alertTooltipValueFormatter(
+  value: number,
+  seriesName: string,
+  aggregate: string
+) {
+  if (isSessionAggregate(aggregate)) {
+    return defined(value) ? `${value}%` : '\u2015';
+  }
+
+  return tooltipFormatter(value, seriesName);
+}

+ 34 - 1
tests/js/spec/views/alerts/utils.spec.jsx

@@ -5,7 +5,12 @@ import {
   Datasource,
   SessionsAggregate,
 } from 'app/views/alerts/incidentRules/types';
-import {getQueryDatasource, isSessionAggregate} from 'app/views/alerts/utils';
+import {
+  alertAxisFormatter,
+  alertTooltipValueFormatter,
+  getQueryDatasource,
+  isSessionAggregate,
+} from 'app/views/alerts/utils';
 import {getIncidentDiscoverUrl} from 'app/views/alerts/utils/getIncidentDiscoverUrl';
 
 describe('Alert utils', function () {
@@ -163,4 +168,32 @@ describe('Alert utils', function () {
       expect(isSessionAggregate('p95(transaction.duration)')).toBeFalsy();
     });
   });
+
+  describe('alertAxisFormatter', () => {
+    it('formatts', () => {
+      expect(
+        alertAxisFormatter(
+          98.312,
+          'Crash Free Rate',
+          SessionsAggregate.CRASH_FREE_SESSIONS
+        )
+      ).toBe('98.31%');
+      expect(alertAxisFormatter(0.1234, 'failure_rate()', 'failure_rate()')).toBe('12%');
+    });
+  });
+
+  describe('alertTooltipValueFormatter', () => {
+    it('formatts', () => {
+      expect(
+        alertTooltipValueFormatter(
+          98.312,
+          'Crash Free Rate',
+          SessionsAggregate.CRASH_FREE_SESSIONS
+        )
+      ).toBe('98.312%');
+      expect(alertTooltipValueFormatter(0.1234, 'failure_rate()', 'failure_rate()')).toBe(
+        '12.34%'
+      );
+    });
+  });
 });