-
Notifications
You must be signed in to change notification settings - Fork 35
/
pd-notifier.js
351 lines (312 loc) · 13.2 KB
/
pd-notifier.js
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
// Simple script to poll PagerDuty API for new incidents, and trigger a Chrome notification for
// any it finds. Will also give user ability to ack/resolve incidents right from the notifs.
// Will poll continually at the pollInterval until it's destroyed (_destruct() is called).
function PagerDutyNotifier()
{
// Members
var self = this; // Self-reference
self.account = null; // The PagerDuty account subdomain to check.
self.apiKey = null; // Optional API key to not require active session.
self.pollInterval = 15; // Number of seconds between checking for new notifications.
self.includeLowUgency = false; // Whether to include low urgency incidents.
self.removeButtons = false; // Whether or not to unclude the action buttons.
self.openOnAck = false; // Whether to open the incident in a new tab when ack-ing.
self.notifSound = false; // Whether to play a notification sound.
self.requireInteraction = false; // Whether the notification will require user interaction to dismiss.
self.filterServices = null; // ServiceID's of services to only show alerts for.
self.filterUsers = null; // UserID's of users to only show alerts for.
self.pdapi = null; // Helper for API calls.
self.poller = null; // This points to the interval function so we can clear it if needed.
self.showBadgeUpdates = false; // Whether we show updates on the toolbar badge.
self.badgeLocation = null; // Which view should be linked to from the badge icon.
// Ctor
self._construct = function _construct()
{
// Load in configuration, and then set up everything we need.
self.loadConfiguration(function()
{
// If no account set up (first install), then do nothing. User will need to add
// config. Once they save, a reload will be triggered and things will kick off.
if (self.account == null || self.account == '') { return; }
self.pdapi = new PDAPI(self.apiKey);
self.setupPoller();
});
}
// Dtor
self._destruct = function _destruct()
{
clearInterval(self.poller);
self = null;
}
// This loads any configuration we have stored with chrome.storage
self.loadConfiguration = function loadConfiguration(callback)
{
chrome.storage.sync.get(
{
pdAccountSubdomain: '',
pdAPIKey: null,
pdIncludeLowUrgency: false,
pdRemoveButtons: false,
pdOpenOnAck: false,
pdNotifSound: false,
pdRequireInteraction: false,
pdFilterServices: null,
pdFilterUsers: null,
pdShowBadgeUpdates: false,
pdBadgeLocation: 'triggered',
},
function(items)
{
self.account = items.pdAccountSubdomain;
self.apiKey = items.pdAPIKey;
self.includeLowUgency = items.pdIncludeLowUrgency;
self.removeButtons = items.pdRemoveButtons;
self.openOnAck = items.pdOpenOnAck;
self.notifSound = items.pdNotifSound;
self.requireInteraction = items.pdRequireInteraction;
self.filterServices = items.pdFilterServices;
self.filterUsers = items.pdFilterUsers;
self.showBadgeUpdates = items.pdShowBadgeUpdates;
self.badgeLocation = items.pdBadgeLocation;
callback(true);
});
}
// This will set up the poller process.
self.setupPoller = function setupPoller()
{
self.poller = setInterval(function() { self.polled(); }, self.pollInterval * 1000);
self.polled();
}
// This is the method that's executed on each poll.
self.polled = function polled()
{
self.pollNewIncidents();
self.updateToolbarBadge();
}
// This will handle the event triggered from clicking one of the notification's buttons.
self.handlerButtonClicked = function handlerButtonClicked(notificationId, buttonIndex)
{
switch (buttonIndex)
{
case 0: // Acknowledge
self.pdapi.PUT(
'https://' + self.account + '.pagerduty.com/api/v1/incidents/' + notificationId,
'{"incident":{"type":"incident_reference","status":"acknowledged"}}'
);
if (self.openOnAck) { self.handlerNotificationClicked(notificationId); }
break;
case 1: // Resolve
self.pdapi.PUT(
'https://' + self.account + '.pagerduty.com/api/v1/incidents/' + notificationId,
'{"incident":{"type":"incident_reference","status":"resolved"}}'
);
break;
}
setTimeout(function() { self.updateToolbarBadge(); }, 200); // Force a badge update, so it changes quickly.
}
// This will handle the event triggered when clicking on the main notification area.
self.handlerNotificationClicked = function handlerNotificationClicked(notificationId)
{
window.open('https://' + self.account + '.pagerduty.com/incidents/' + notificationId);
}
// This is the poller action, which will trigger an API request and then pass any incidents
// it gets to the parsing function.
self.pollNewIncidents = function pollNewIncidents()
{
// Sanity check that an account has been set.
if (self.account == '') { return; }
// We only want events triggered since we last polled.
var since = new Date();
since.setSeconds(since.getSeconds() - self.pollInterval);
// Construct the URL
var url = 'https://' + self.account + '.pagerduty.com/api/v1/incidents?'
+ 'statuses[]=triggered&'
+ 'since=' + since.toISOString() + '&'
+ 'limit=5&'; // More than this would be silly to show notifications for.
url = self.includeFilters(url);
// Make the request.
self.pdapi.GET(url, self.parseIncidents);
}
// Adds filters to a URL we'll be using in a request
self.includeFilters = function includeFilters(url)
{
// Limit to high urgency if that's all the user wants.
if (!self.includeLowUgency) { url = url + 'urgencies[]=high&'; }
// Add a service filter if we have one.
if (self.filterServices && self.filterServices != null && self.filterServices != "")
{
self.filterServices.split(',').forEach(function(s)
{
url = url + 'service_ids[]=' + s + '&';
});
}
// Add a user filter if we have one.
if (self.filterUsers && self.filterUsers != null && self.filterUsers != "")
{
self.filterUsers.split(',').forEach(function(s)
{
url = url + 'user_ids[]=' + s + '&';
});
}
return url;
}
// This will parse the AJAX response and trigger notifications for each incident.
self.parseIncidents = function parseIncidents(data)
{
for (var i in data.incidents) { self.triggerNotification(data.incidents[i]); }
}
// This will update the icon badge in the toolbar.
self.updateToolbarBadge = function updateToolbarBadge()
{
if (!self.showBadgeUpdates)
{
chrome.browserAction.setBadgeText({ text: '' });
return;
}
// Check for any triggered incidents at all that follow our filters.
var url = self.includeFilters('https://' + self.account + '.pagerduty.com/api/v1/incidents?statuses[]=triggered&total=true&')
self.pdapi.GET(url, function(data)
{
// If there was an error in the response, show "Err" and log it.
if (data.error != null)
{
console.error("PagerDuty API returned an error while getting the incident count for the toolbar icon.", {
api_url: url,
error_returned: data.error
});
chrome.browserAction.setBadgeText({ text: 'Err.' });
chrome.browserAction.setBadgeBackgroundColor({ color: [90, 90, 90, 255] });
return;
}
// If there are no incidents, or an error in the response, show nothing on badge.
if (data.total == null || data.total == 0)
{
chrome.browserAction.setBadgeText({ text: '' });
return;
}
// Otherwise, we have incidents, show the count.
chrome.browserAction.setBadgeText({ text: '' + data.total });
chrome.browserAction.setBadgeBackgroundColor({ color: [189, 0, 0, 255] });
});
}
// This will open a tab to the dashboard using the relevant user settings.
self.openDashboard = function openDashboard()
{
// Determine the correct URL based on user's options.
statuses = '';
switch(self.badgeLocation)
{
case 'open': statuses = '?status=triggered,acknowledged'; break;
case 'triggered': statuses = '?status=triggered'; break;
case 'acknowledged': statuses = '?status=acknowledged'; break;
case 'any': statuses = '?status=acknowledged,triggered,resolved'; break;
}
// Open the tab
chrome.tabs.create({ 'url': 'https://' + self.account + '.pagerduty.com/incidents' + statuses })
}
// This will trigger the actual notification based on an incident object.
self.triggerNotification = function triggerNotification(incident)
{
// Define the buttons to show in the notification. Will be empty if user asked to remove.
var buttons = self.removeButtons ? [] : [
{
title: "Acknowledge",
iconUrl: chrome.extension.getURL("images/icon-acknowledge.png")
},
{
title: "Resolve",
iconUrl: chrome.extension.getURL("images/icon-resolve.png")
}
];
chrome.notifications.create(incident.id,
{
type: "basic",
iconUrl: chrome.extension.getURL("images/icon-256.png"),
title: incident.summary,
message: "Service: " + incident.service.summary,
contextMessage: incident.urgency.charAt(0).toUpperCase() + incident.urgency.slice(1) + " Urgency",
priority: 2,
isClickable: true,
buttons: buttons,
requireInteraction: self.requireInteraction
});
// Trigger notification sound if user wants it.
if (self.notifSound)
{
var notifSound = new Audio("audio/notification.mp3");
notifSound.play();
}
}
self._construct();
}
// Add event handlers for button/notification clicks, and delegate to the currently active notifier object.
chrome.notifications.onButtonClicked.addListener(function(notificationId, buttonIndex)
{
chrome.runtime.getBackgroundPage(function(bgpg)
{
bgpg.getNotifier().handlerButtonClicked(notificationId, buttonIndex);
chrome.notifications.clear(notificationId);
});
});
chrome.notifications.onClicked.addListener(function(notificationId)
{
chrome.runtime.getBackgroundPage(function(bgpg)
{
bgpg.getNotifier().handlerNotificationClicked(notificationId);
chrome.notifications.clear(notificationId);
});
});
// Add event handler for the toolbar icon click.
chrome.browserAction.onClicked.addListener(function(tab)
{
chrome.runtime.getBackgroundPage(function(bgpg)
{
bgpg.getNotifier().openDashboard();
});
});
// If this is the first installation, show the options page so user can set up their settings.
chrome.runtime.onInstalled.addListener(function(details)
{
if (details.reason == 'install')
{
chrome.tabs.create({ 'url': 'chrome://extensions/?options=' + chrome.runtime.id });
}
});
// The currently active notifier object, and accessor.
var _pdNotifier = null;
function getNotifier() { return _pdNotifier; }
// This will reload/trigger the the notifier (and pick up any new configuration options).
function reloadNotifier()
{
if (_pdNotifier != null) { _pdNotifier._destruct(); }
_pdNotifier = new PagerDutyNotifier();
}
// Add option to clear all notifications to icon context-menu.
chrome.contextMenus.create({
title: "Clear all notifications",
id: "pd_clear_all",
contexts: ["browser_action"],
visible: true
});
chrome.contextMenus.onClicked.addListener(function(info, tab)
{
if (info.menuItemId === "pd_clear_all")
{
chrome.notifications.getAll(function(notifs)
{
for (var i in notifs) { chrome.notifications.clear(i); }
});
}
});
// Listen for Chrome Alarms and retrigger the notifier when one is caught.
chrome.alarms.onAlarm.addListener(function(alarm)
{
chrome.runtime.getBackgroundPage(function(bgpg)
{
bgpg.reloadNotifier();
});
});
// Sets up a Chrome Alarm to retrigger the notifier every so often, to make sure it's always running.
chrome.alarms.create("pagerduty-notifier", { periodInMinutes: 1 });
// Initial run, as alarm won't trigger immediately.
reloadNotifier();