-
Notifications
You must be signed in to change notification settings - Fork 35
/
alertHandler.ts
114 lines (109 loc) · 3.79 KB
/
alertHandler.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import {Response} from 'express';
import Item, {ItemColumn} from '../../../models/Item.model';
import SentryInstallation from '../../../models/SentryInstallation.model';
import {
AlertRuleSettings,
convertSentryFieldsToDict,
SentryField,
} from '../alertRuleAction';
// Returns the alert settings as a mapping of field.name to field.value
function getAlertRuleSettings(
sentryInstallation: SentryInstallation,
data: Record<string, any>,
action?: string
): AlertRuleSettings {
let fields = [] as SentryField[];
// For issue alerts...
if (data.event) {
fields = data?.issue_alert?.settings ?? [];
}
// For metric alerts...
else {
const triggers = data?.metric_alert?.alert_rule?.triggers ?? [];
const relevantTrigger = triggers.find(({label}: {label: string}) => label === action);
const integrationAction = relevantTrigger?.actions?.find(
({sentry_app_installation_uuid: uuid}: {sentry_app_installation_uuid: string}) =>
uuid === sentryInstallation.uuid
);
fields = integrationAction?.settings ?? [];
}
return convertSentryFieldsToDict(fields);
}
async function handleIssueAlert(
sentryInstallation: SentryInstallation,
data: Record<string, any>
) {
const settings = getAlertRuleSettings(sentryInstallation, data);
await Item.create({
organizationId: sentryInstallation.organizationId,
title: `🚨 Issue Alert: ${settings.title ?? data.event.title}`,
description: settings.description ?? `Latest Trigger: ${data.event.web_url}`,
column: ItemColumn.Todo,
sentryId: data.event.issue_id,
// data.issue_alert is only present for Alert Rule Action webhooks
sentryAlertId: data?.issue_alert?.id,
assigneeId: settings.userId,
});
console.info('Created item from Sentry issue alert trigger');
}
async function handleMetricAlert(
sentryInstallation: SentryInstallation,
data: Record<string, any>,
action: 'resolved' | 'warning' | 'critical'
) {
const [item, isItemNew] = await Item.findOrCreate({
where: {
organizationId: sentryInstallation.organizationId,
// XXX(Ecosystem): The metric alert ID changes frequently, making it unreliable??
sentryAlertId: data.metric_alert.id,
},
});
let itemTitlePrefix = '';
switch (action) {
case 'resolved':
itemTitlePrefix = '✅ Resolved Metric';
break;
case 'warning':
itemTitlePrefix = '⚠️ Warning Metric';
break;
case 'critical':
itemTitlePrefix = '🔥 Critical Metric';
break;
}
const settings = getAlertRuleSettings(sentryInstallation, data, action);
await item.update({
title: `${itemTitlePrefix}: ${settings.title ?? data.metric_alert.title}`,
description: settings.description ?? data.description_text,
column: ItemColumn.Todo,
assigneeId: settings.userId,
});
console.info(
`${isItemNew ? 'Created' : 'Modified'} item from metric alert ${action} trigger`
);
}
export default async function alertHandler(
response: Response,
resource: 'event_alert' | 'metric_alert',
action: string,
sentryInstallation: SentryInstallation,
data: Record<string, any>
): Promise<void> {
// Issue Alerts (or Event Alerts) only have one type of action: 'triggered'
if (resource === 'event_alert') {
await handleIssueAlert(sentryInstallation, data);
response.status(202);
}
// Metric Alerts have three types of actions: 'resolved', 'warning', and 'critical'
else if (resource === 'metric_alert') {
if (action === 'resolved' || action === 'warning' || action === 'critical') {
await handleMetricAlert(sentryInstallation, data, action);
response.status(202);
} else {
console.info(`Unexpected Sentry metric alert action: ${action}`);
response.status(400);
}
} else {
console.info(`Unexpected Sentry resource: ${resource}`);
response.status(400);
}
}