Skip to content

Commit 2875a3b

Browse files
authored
feat(cmp_alerts): Change alert data in dashed line in alert details (#29623)
* use basechart in test too * more mock * revert back to area chart * fix series * complete bypass area chart * feat(cmp_alerts): Change alert data in dashed line in alert details * use additionalSeries for line chart too * fix marklines to render
1 parent 65ff4f2 commit 2875a3b

File tree

6 files changed

+203
-141
lines changed

6 files changed

+203
-141
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import MarkLine from 'app/components/charts/components/markLine';
2+
import {LineChartSeries} from 'app/components/charts/lineChart';
3+
import {t} from 'app/locale';
4+
import {Series} from 'app/types/echarts';
5+
import {MINUTE} from 'app/utils/formatters';
6+
import theme from 'app/utils/theme';
7+
import {AlertRuleThresholdType, Trigger} from 'app/views/alerts/incidentRules/types';
8+
9+
const checkChangeStatus = (
10+
value: number,
11+
thresholdType: AlertRuleThresholdType,
12+
triggers: Trigger[]
13+
): string => {
14+
const criticalTrigger = triggers?.find(trig => trig.label === 'critical');
15+
const warningTrigger = triggers?.find(trig => trig.label === 'warning');
16+
const criticalTriggerAlertThreshold =
17+
typeof criticalTrigger?.alertThreshold === 'number'
18+
? criticalTrigger.alertThreshold
19+
: undefined;
20+
const warningTriggerAlertThreshold =
21+
typeof warningTrigger?.alertThreshold === 'number'
22+
? warningTrigger.alertThreshold
23+
: undefined;
24+
25+
// Need to catch the critical threshold cases before warning threshold cases
26+
if (
27+
thresholdType === AlertRuleThresholdType.ABOVE &&
28+
criticalTriggerAlertThreshold &&
29+
value >= criticalTriggerAlertThreshold
30+
) {
31+
return 'critical';
32+
}
33+
if (
34+
thresholdType === AlertRuleThresholdType.ABOVE &&
35+
warningTriggerAlertThreshold &&
36+
value >= warningTriggerAlertThreshold
37+
) {
38+
return 'warning';
39+
}
40+
// When threshold is below(lower than in comparison alerts) the % diff value is negative
41+
// It crosses the threshold if its abs value is greater than threshold
42+
// -80% change crosses below 60% threshold -1 * (-80) > 60
43+
if (
44+
thresholdType === AlertRuleThresholdType.BELOW &&
45+
criticalTriggerAlertThreshold &&
46+
-1 * value >= criticalTriggerAlertThreshold
47+
) {
48+
return 'critical';
49+
}
50+
if (
51+
thresholdType === AlertRuleThresholdType.BELOW &&
52+
warningTriggerAlertThreshold &&
53+
-1 * value >= warningTriggerAlertThreshold
54+
) {
55+
return 'warning';
56+
}
57+
58+
return '';
59+
};
60+
61+
export const getComparisonMarkLines = (
62+
timeseriesData: Series[] = [],
63+
comparisonTimeseriesData: Series[] = [],
64+
timeWindow: number,
65+
triggers: Trigger[],
66+
thresholdType: AlertRuleThresholdType
67+
): LineChartSeries[] => {
68+
const changeStatuses: {name: number | string; status: string}[] = [];
69+
70+
if (
71+
timeseriesData?.[0]?.data !== undefined &&
72+
timeseriesData[0].data.length > 1 &&
73+
comparisonTimeseriesData?.[0]?.data !== undefined &&
74+
comparisonTimeseriesData[0].data.length > 1
75+
) {
76+
const changeData = comparisonTimeseriesData[0].data;
77+
const baseData = timeseriesData[0].data;
78+
79+
if (triggers.some(({alertThreshold}) => typeof alertThreshold === 'number')) {
80+
const lastPointLimit =
81+
(baseData[changeData.length - 1].name as number) - timeWindow * MINUTE;
82+
changeData.forEach(({name, value: comparisonValue}, idx) => {
83+
const baseValue = baseData[idx].value;
84+
const comparisonPercentage =
85+
comparisonValue === 0
86+
? baseValue === 0
87+
? 0
88+
: Infinity
89+
: ((baseValue - comparisonValue) / comparisonValue) * 100;
90+
const status = checkChangeStatus(comparisonPercentage, thresholdType, triggers);
91+
if (
92+
idx === 0 ||
93+
idx === changeData.length - 1 ||
94+
status !== changeStatuses[changeStatuses.length - 1].status
95+
) {
96+
changeStatuses.push({name, status});
97+
}
98+
});
99+
100+
return changeStatuses.slice(0, -1).map(({name, status}, idx) => ({
101+
seriesName: t('status'),
102+
type: 'line',
103+
markLine: MarkLine({
104+
silent: true,
105+
lineStyle: {
106+
color:
107+
status === 'critical'
108+
? theme.red300
109+
: status === 'warning'
110+
? theme.yellow300
111+
: theme.green300,
112+
type: 'solid',
113+
width: 4,
114+
},
115+
data: [
116+
[
117+
{coord: [name, 0]},
118+
{
119+
coord: [
120+
Math.min(changeStatuses[idx + 1].name as number, lastPointLimit),
121+
0,
122+
],
123+
},
124+
] as any,
125+
],
126+
}),
127+
data: [],
128+
}));
129+
}
130+
}
131+
132+
return [];
133+
};

static/app/views/alerts/incidentRules/triggers/chart/index.tsx

Lines changed: 11 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import minBy from 'lodash/minBy';
88
import {fetchTotalCount} from 'app/actionCreators/events';
99
import {Client} from 'app/api';
1010
import Feature from 'app/components/acl/feature';
11-
import MarkLine from 'app/components/charts/components/markLine';
1211
import EventsRequest from 'app/components/charts/eventsRequest';
1312
import {LineChartSeries} from 'app/components/charts/lineChart';
1413
import OptionSelector from 'app/components/charts/optionSelector';
@@ -25,21 +24,19 @@ import {t} from 'app/locale';
2524
import space from 'app/styles/space';
2625
import {Organization, Project} from 'app/types';
2726
import {Series, SeriesDataUnit} from 'app/types/echarts';
28-
import {MINUTE} from 'app/utils/formatters';
2927
import {
3028
getCrashFreeRateSeries,
3129
MINUTES_THRESHOLD_TO_DISPLAY_SECONDS,
3230
} from 'app/utils/sessions';
33-
import theme from 'app/utils/theme';
3431
import withApi from 'app/utils/withApi';
32+
import {getComparisonMarkLines} from 'app/views/alerts/changeAlerts/comparisonMarklines';
3533
import {COMPARISON_DELTA_OPTIONS} from 'app/views/alerts/incidentRules/constants';
3634
import {isSessionAggregate, SESSION_AGGREGATE_TO_FIELD} from 'app/views/alerts/utils';
3735
import {AlertWizardAlertNames} from 'app/views/alerts/wizard/options';
3836
import {getAlertTypeFromAggregateDataset} from 'app/views/alerts/wizard/utils';
3937

4038
import {
4139
AlertRuleComparisonType,
42-
AlertRuleThresholdType,
4340
Dataset,
4441
IncidentRule,
4542
SessionsAggregate,
@@ -227,80 +224,6 @@ class TriggersChart extends React.PureComponent<Props, State> {
227224
);
228225
}
229226

230-
getComparisonMarkLines(
231-
timeseriesData: Series[] = [],
232-
comparisonTimeseriesData: Series[] = []
233-
): LineChartSeries[] {
234-
const {timeWindow} = this.props;
235-
const changeStatuses: {name: number | string; status: string}[] = [];
236-
237-
if (
238-
timeseriesData?.[0]?.data !== undefined &&
239-
timeseriesData[0].data.length > 1 &&
240-
comparisonTimeseriesData?.[0]?.data !== undefined &&
241-
comparisonTimeseriesData[0].data.length > 1
242-
) {
243-
const changeData = comparisonTimeseriesData[0].data;
244-
const baseData = timeseriesData[0].data;
245-
246-
if (
247-
this.props.triggers.some(({alertThreshold}) => typeof alertThreshold === 'number')
248-
) {
249-
const lastPointLimit =
250-
(baseData[changeData.length - 1].name as number) - timeWindow * MINUTE;
251-
changeData.forEach(({name, value: comparisonValue}, idx) => {
252-
const baseValue = baseData[idx].value;
253-
const comparisonPercentage =
254-
comparisonValue === 0
255-
? baseValue === 0
256-
? 0
257-
: Infinity
258-
: ((baseValue - comparisonValue) / comparisonValue) * 100;
259-
const status = this.checkChangeStatus(comparisonPercentage);
260-
if (
261-
idx === 0 ||
262-
idx === changeData.length - 1 ||
263-
status !== changeStatuses[changeStatuses.length - 1].status
264-
) {
265-
changeStatuses.push({name, status});
266-
}
267-
});
268-
269-
return changeStatuses.slice(0, -1).map(({name, status}, idx) => ({
270-
seriesName: t('status'),
271-
type: 'line',
272-
markLine: MarkLine({
273-
silent: true,
274-
lineStyle: {
275-
color:
276-
status === 'critical'
277-
? theme.red300
278-
: status === 'warning'
279-
? theme.yellow300
280-
: theme.green300,
281-
type: 'solid',
282-
width: 4,
283-
},
284-
data: [
285-
[
286-
{coord: [name, 0]},
287-
{
288-
coord: [
289-
Math.min(changeStatuses[idx + 1].name as number, lastPointLimit),
290-
0,
291-
],
292-
},
293-
] as any,
294-
],
295-
}),
296-
data: [],
297-
}));
298-
}
299-
}
300-
301-
return [];
302-
}
303-
304227
async fetchTotalCount() {
305228
const {api, organization, environment, projects, query} = this.props;
306229
const statsPeriod = this.getStatsPeriod();
@@ -318,55 +241,6 @@ class TriggersChart extends React.PureComponent<Props, State> {
318241
}
319242
}
320243

321-
checkChangeStatus(value: number): string {
322-
const {thresholdType, triggers} = this.props;
323-
const criticalTrigger = triggers?.find(trig => trig.label === 'critical');
324-
const warningTrigger = triggers?.find(trig => trig.label === 'warning');
325-
const criticalTriggerAlertThreshold =
326-
typeof criticalTrigger?.alertThreshold === 'number'
327-
? criticalTrigger.alertThreshold
328-
: undefined;
329-
const warningTriggerAlertThreshold =
330-
typeof warningTrigger?.alertThreshold === 'number'
331-
? warningTrigger.alertThreshold
332-
: undefined;
333-
334-
// Need to catch the critical threshold cases before warning threshold cases
335-
if (
336-
thresholdType === AlertRuleThresholdType.ABOVE &&
337-
criticalTriggerAlertThreshold &&
338-
value >= criticalTriggerAlertThreshold
339-
) {
340-
return 'critical';
341-
}
342-
if (
343-
thresholdType === AlertRuleThresholdType.ABOVE &&
344-
warningTriggerAlertThreshold &&
345-
value >= warningTriggerAlertThreshold
346-
) {
347-
return 'warning';
348-
}
349-
// When threshold is below(lower than in comparison alerts) the % diff value is negative
350-
// It crosses the threshold if its abs value is greater than threshold
351-
// -80% change crosses below 60% threshold -1 * (-80) > 60
352-
if (
353-
thresholdType === AlertRuleThresholdType.BELOW &&
354-
criticalTriggerAlertThreshold &&
355-
-1 * value >= criticalTriggerAlertThreshold
356-
) {
357-
return 'critical';
358-
}
359-
if (
360-
thresholdType === AlertRuleThresholdType.BELOW &&
361-
warningTriggerAlertThreshold &&
362-
-1 * value >= warningTriggerAlertThreshold
363-
) {
364-
return 'warning';
365-
}
366-
367-
return '';
368-
}
369-
370244
renderChart(
371245
timeseriesData: Series[] = [],
372246
isLoading: boolean,
@@ -448,11 +322,14 @@ class TriggersChart extends React.PureComponent<Props, State> {
448322
aggregate,
449323
environment,
450324
comparisonDelta,
325+
triggers,
326+
thresholdType,
451327
} = this.props;
452328

453329
const period = this.getStatsPeriod();
454-
const renderComparisonStats =
455-
organization.features.includes('change-alerts') && comparisonDelta;
330+
const renderComparisonStats = Boolean(
331+
organization.features.includes('change-alerts') && comparisonDelta
332+
);
456333

457334
return isSessionAggregate(aggregate) ? (
458335
<SessionsRequest
@@ -513,9 +390,12 @@ class TriggersChart extends React.PureComponent<Props, State> {
513390
{({loading, reloading, timeseriesData, comparisonTimeseriesData}) => {
514391
let comparisonMarkLines: LineChartSeries[] = [];
515392
if (renderComparisonStats && comparisonTimeseriesData) {
516-
comparisonMarkLines = this.getComparisonMarkLines(
393+
comparisonMarkLines = getComparisonMarkLines(
517394
timeseriesData,
518-
comparisonTimeseriesData
395+
comparisonTimeseriesData,
396+
timeWindow,
397+
triggers,
398+
thresholdType
519399
);
520400
}
521401

static/app/views/alerts/issueRuleEditor/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import withTeams from 'app/utils/withTeams';
4444
import {
4545
CHANGE_ALERT_CONDITION_IDS,
4646
CHANGE_ALERT_PLACEHOLDERS_LABELS,
47-
} from 'app/views/alerts/issueRuleEditor/constants/changeAlerts';
47+
} from 'app/views/alerts/changeAlerts/constants';
4848
import AsyncView from 'app/views/asyncView';
4949
import Input from 'app/views/settings/components/forms/controls/input';
5050
import Field from 'app/views/settings/components/forms/field';

static/app/views/alerts/issueRuleEditor/ruleNodeList.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ import {
1212
IssueAlertRuleCondition,
1313
IssueAlertRuleConditionTemplate,
1414
} from 'app/types/alerts';
15-
import {EVENT_FREQUENCY_PERCENT_CONDITION} from 'app/views/projectInstall/issueAlertOptions';
16-
1715
import {
1816
CHANGE_ALERT_CONDITION_IDS,
1917
COMPARISON_INTERVAL_CHOICES,
2018
COMPARISON_TYPE_CHOICE_VALUES,
2119
COMPARISON_TYPE_CHOICES,
22-
} from './constants/changeAlerts';
20+
} from 'app/views/alerts/changeAlerts/constants';
21+
import {EVENT_FREQUENCY_PERCENT_CONDITION} from 'app/views/projectInstall/issueAlertOptions';
22+
2323
import RuleNode from './ruleNode';
2424

2525
type Props = {

0 commit comments

Comments
 (0)