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

feat(ui): Scale alert rule chart (#29069)

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

+ 8 - 13
static/app/views/alerts/incidentRules/triggers/chart/thresholdsChart.tsx

@@ -10,15 +10,15 @@ import {GlobalSelection} from 'app/types';
 import {ReactEchartsRef, Series} from 'app/types/echarts';
 import theme from 'app/utils/theme';
 import {
+  ALERT_CHART_MIN_MAX_BUFFER,
   alertAxisFormatter,
   alertTooltipValueFormatter,
   isSessionAggregate,
+  shouldScaleAlertChart,
 } from 'app/views/alerts/utils';
 
 import {AlertRuleThresholdType, IncidentRule, Trigger} from '../../types';
 
-const MIN_MAX_BUFFER = 1.03;
-
 type DefaultProps = {
   data: Series[];
 };
@@ -84,12 +84,6 @@ export default class ThresholdsChart extends PureComponent<Props, State> {
 
   ref: null | ReactEchartsRef = null;
 
-  get shouldScale() {
-    // We want crash free rate charts to be scaled because they are usually too
-    // close to 100% and therefore too fine to see the spikes on 0%-100% scale.
-    return isSessionAggregate(this.props.aggregate);
-  }
-
   // If we have ref to chart and data, try to update chart axis so that
   // alertThreshold or resolveThreshold is visible in chart
   handleUpdateChartAxis = () => {
@@ -108,19 +102,20 @@ export default class ThresholdsChart extends PureComponent<Props, State> {
    * Updates the chart so that yAxis is within bounds of our max value
    */
   updateChartAxis = debounce((minThreshold: number, maxThreshold: number) => {
-    const {minValue, maxValue} = this.props;
+    const {minValue, maxValue, aggregate} = this.props;
+    const shouldScale = shouldScaleAlertChart(aggregate);
     let yAxisMax =
-      this.shouldScale && maxValue
-        ? this.clampMaxValue(Math.ceil(maxValue * MIN_MAX_BUFFER))
+      shouldScale && maxValue
+        ? this.clampMaxValue(Math.ceil(maxValue * ALERT_CHART_MIN_MAX_BUFFER))
         : null;
     let yAxisMin =
-      this.shouldScale && minValue ? Math.floor(minValue / MIN_MAX_BUFFER) : 0;
+      shouldScale && minValue ? Math.floor(minValue / ALERT_CHART_MIN_MAX_BUFFER) : 0;
 
     if (typeof maxValue === 'number' && maxThreshold > maxValue) {
       yAxisMax = maxThreshold;
     }
     if (typeof minValue === 'number' && minThreshold < minValue) {
-      yAxisMin = Math.floor(minThreshold / MIN_MAX_BUFFER);
+      yAxisMin = Math.floor(minThreshold / ALERT_CHART_MIN_MAX_BUFFER);
     }
 
     // We need to force update after we set a new yAxis min/max because `convertToPixel`

+ 34 - 6
static/app/views/alerts/rules/details/metricChart.tsx

@@ -39,10 +39,12 @@ import {getAlertTypeFromAggregateDataset} from 'app/views/alerts/wizard/utils';
 
 import {Incident, IncidentActivityType, IncidentStatus} from '../../types';
 import {
+  ALERT_CHART_MIN_MAX_BUFFER,
   alertAxisFormatter,
   alertTooltipValueFormatter,
   isSessionAggregate,
   SESSION_AGGREGATE_TO_FIELD,
+  shouldScaleAlertChart,
 } from '../../utils';
 
 import {TimePeriodType} from './constants';
@@ -96,7 +98,8 @@ function createThresholdSeries(lineColor: string, threshold: number): LineChartS
 function createStatusAreaSeries(
   lineColor: string,
   startTime: number,
-  endTime: number
+  endTime: number,
+  yPosition: number
 ): LineChartSeries {
   return {
     seriesName: 'Status Area',
@@ -104,7 +107,7 @@ function createStatusAreaSeries(
     markLine: MarkLine({
       silent: true,
       lineStyle: {color: lineColor, type: 'solid', width: 4},
-      data: [[{coord: [startTime, 0]}, {coord: [endTime, 0]}] as any],
+      data: [[{coord: [startTime, yPosition]}, {coord: [endTime, yPosition]}] as any],
     }),
     data: [],
   };
@@ -324,7 +327,7 @@ class MetricChart extends React.PureComponent<Props, State> {
       organization,
       timePeriod: {start, end},
     } = this.props;
-    const {dateModified, timeWindow} = this.props.rule || {};
+    const {dateModified, timeWindow, aggregate} = rule;
 
     if (loading || !timeseriesData) {
       return this.renderEmpty();
@@ -342,13 +345,30 @@ class MetricChart extends React.PureComponent<Props, State> {
       (currMax, coord) => Math.max(currMax, coord.value),
       0
     );
+    // find the lowest value between chart data points, warning threshold,
+    // critical threshold and then apply some breathing space
+    const minChartValue = shouldScaleAlertChart(aggregate)
+      ? Math.floor(
+          Math.min(
+            dataArr.reduce((currMax, coord) => Math.min(currMax, coord.value), Infinity),
+            typeof warningTrigger?.alertThreshold === 'number'
+              ? warningTrigger.alertThreshold
+              : Infinity,
+            typeof criticalTrigger?.alertThreshold === 'number'
+              ? criticalTrigger.alertThreshold
+              : Infinity
+          ) / ALERT_CHART_MIN_MAX_BUFFER
+        )
+      : 0;
     const firstPoint = moment(dataArr[0].name).valueOf();
     const lastPoint = moment(dataArr[dataArr.length - 1].name).valueOf();
     const totalDuration = lastPoint - firstPoint;
     let criticalDuration = 0;
     let warningDuration = 0;
 
-    series.push(createStatusAreaSeries(theme.green300, firstPoint, lastPoint));
+    series.push(
+      createStatusAreaSeries(theme.green300, firstPoint, lastPoint, minChartValue)
+    );
     if (incidents) {
       // select incidents that fall within the graph range
       const periodStart = moment.utc(firstPoint);
@@ -409,7 +429,9 @@ class MetricChart extends React.PureComponent<Props, State> {
           );
           const areaColor = warningTrigger ? theme.yellow300 : theme.red300;
           if (areaEnd > areaStart) {
-            series.push(createStatusAreaSeries(areaColor, areaStart, areaEnd));
+            series.push(
+              createStatusAreaSeries(areaColor, areaStart, areaEnd, minChartValue)
+            );
 
             if (areaColor === theme.yellow300) {
               warningDuration += Math.abs(areaEnd - areaStart);
@@ -435,7 +457,12 @@ class MetricChart extends React.PureComponent<Props, State> {
                 : theme.yellow300;
             if (statusAreaEnd > statusAreaStart) {
               series.push(
-                createStatusAreaSeries(statusAreaColor, statusAreaStart, statusAreaEnd)
+                createStatusAreaSeries(
+                  statusAreaColor,
+                  statusAreaStart,
+                  statusAreaEnd,
+                  minChartValue
+                )
               );
               if (statusAreaColor === theme.yellow300) {
                 warningDuration += Math.abs(statusAreaEnd - statusAreaStart);
@@ -516,6 +543,7 @@ class MetricChart extends React.PureComponent<Props, State> {
                       ),
                   },
                   max: maxThresholdValue > maxSeriesValue ? maxThresholdValue : undefined,
+                  min: minChartValue || undefined,
                 }}
                 series={[...series, ...areaSeries]}
                 graphic={Graphic({

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

@@ -303,3 +303,11 @@ export function alertTooltipValueFormatter(
 
   return tooltipFormatter(value, seriesName);
 }
+
+export const ALERT_CHART_MIN_MAX_BUFFER = 1.03;
+
+export function shouldScaleAlertChart(aggregate: string) {
+  // We want crash free rate charts to be scaled because they are usually too
+  // close to 100% and therefore too fine to see the spikes on 0%-100% scale.
+  return isSessionAggregate(aggregate);
+}