From de56cd7a29680b5e3a1ffdcd3272159a71c4cfc3 Mon Sep 17 00:00:00 2001 From: Eguchi Kazuki Date: Thu, 16 Dec 2021 14:40:51 +0900 Subject: [PATCH] implement Pagerduty Co-Authored-By: Yuta Nakano --- .../admin-config/admin-config.component.ts | 3 +- .../alert-details.component.html | 27 ++++++++++++++++ .../alert-details/alert-details.component.ts | 19 ++++++++--- .../children/recipients-manager/models.ts | 3 +- .../recipients-manager.component.html | 32 +++++++++++++++++++ .../recipients-manager.component.ts | 27 ++++++++++++++++ .../app/alerts/containers/alerts.component.ts | 4 ++- server/config/app_config.json | 6 +++- 8 files changed, 113 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/admin/components/admin-config/admin-config.component.ts b/frontend/src/app/admin/components/admin-config/admin-config.component.ts index 65d7d5f75..155996db0 100644 --- a/frontend/src/app/admin/components/admin-config/admin-config.component.ts +++ b/frontend/src/app/admin/components/admin-config/admin-config.component.ts @@ -27,7 +27,8 @@ const alertRecipientLabelMap: any = { http: 'HTTP', oc: 'OC', slack: 'Slack', - opsgenie: 'OpsGenie' + opsgenie: 'OpsGenie', + pagerduty: 'PagerDuty' }; @Component({ diff --git a/frontend/src/app/alerts/components/alert-details/alert-details.component.html b/frontend/src/app/alerts/components/alert-details/alert-details.component.html index 48cf8f91b..075330209 100644 --- a/frontend/src/app/alerts/components/alert-details/alert-details.component.html +++ b/frontend/src/app/alerts/components/alert-details/alert-details.component.html @@ -720,6 +720,15 @@ +
+
+
PagerDuty AutoClose
+
+ + Enabling AutoClose will ignore `Send one notification per` setting for PagerDuty. Alerts will be sent per unique tag set. +
+
+
@@ -939,6 +948,15 @@ +
+
+
PagerDuty AutoClose
+
+ + Enabling AutoClose will ignore `Send one notification per` setting for PagerDuty. Alerts will be sent per unique tag set. +
+
+
@@ -1194,6 +1212,15 @@ +
+
+
PagerDuty AutoClose
+
+ {{ data.notification.pagerdutyAutoClose === true ? 'Yes' : 'No' }} + Enabling AutoClose will ignore `Send one notification per` setting for Paferduty. Alerts will be sent per unique tag set. +
+
+
diff --git a/frontend/src/app/alerts/components/alert-details/alert-details.component.ts b/frontend/src/app/alerts/components/alert-details/alert-details.component.ts index af8de73a5..fc0a896df 100644 --- a/frontend/src/app/alerts/components/alert-details/alert-details.component.ts +++ b/frontend/src/app/alerts/components/alert-details/alert-details.component.ts @@ -574,7 +574,9 @@ export class AlertDetailsComponent implements OnInit, OnDestroy, AfterContentIni // OC conditional values runbookId: data.notification.runbookId || '', ocSeverity: data.notification.ocSeverity || this.defaultOCSeverity, - ocTier: data.notification.ocTier || this.defaultOCTier + ocTier: data.notification.ocTier || this.defaultOCTier, + // PagerDuty conditional values + pagerdutyAutoClose: data.notification.pagerdutyAutoClose || false }) }); this.prevTimeSampler = data.threshold.singleMetric.timeSampler || 'at_least_once'; @@ -757,7 +759,9 @@ export class AlertDetailsComponent implements OnInit, OnDestroy, AfterContentIni // OC conditional values runbookId: data.notification.runbookId || '', ocSeverity: data.notification.ocSeverity || this.defaultOCSeverity, - ocTier: data.notification.ocTier || this.defaultOCTier + ocTier: data.notification.ocTier || this.defaultOCTier, + // PagerDuty conditional values + pagerdutyAutoClose: data.notification.pagerdutyAutoClose || false }) }); this.setTags(); @@ -837,7 +841,9 @@ export class AlertDetailsComponent implements OnInit, OnDestroy, AfterContentIni opsgenieTags: this.fb.array(data.notification.opsgenieTags || []), runbookId: data.notification.runbookId || '', ocSeverity: data.notification.ocSeverity || this.defaultOCSeverity, - ocTier: data.notification.ocTier || this.defaultOCTier + ocTier: data.notification.ocTier || this.defaultOCTier, + // PagerDuty conditional values + pagerdutyAutoClose: data.notification.pagerdutyAutoClose || false }) }); this.options.axes.y.valueRange[0] = 0; @@ -2028,6 +2034,10 @@ export class AlertDetailsComponent implements OnInit, OnDestroy, AfterContentIni this.alertForm['controls'].notification.get('opsgenieAutoClose').setValue(false); this.alertForm.get('notification')['controls']['opsgenieTags'] = this.fb.array([]); } + + if (this.notificationRecipients.value.pagerduty && !event.pagerduty) { + this.alertForm['controls'].notification.get('pagerdutyAutoClose').setValue(false); + } this.notificationRecipients.setValue(event); } @@ -2111,7 +2121,8 @@ export class AlertDetailsComponent implements OnInit, OnDestroy, AfterContentIni slack: 'Slack', http: 'Webhook', oc: 'OC', - email: 'Email' + email: 'Email', + pagerduty: 'PagerDuty' } return types[type]; } diff --git a/frontend/src/app/alerts/components/alert-details/children/recipients-manager/models.ts b/frontend/src/app/alerts/components/alert-details/children/recipients-manager/models.ts index 381bb5338..cc9a3e70d 100644 --- a/frontend/src/app/alerts/components/alert-details/children/recipients-manager/models.ts +++ b/frontend/src/app/alerts/components/alert-details/children/recipients-manager/models.ts @@ -26,7 +26,8 @@ export enum RecipientType { slack = 'slack', http = 'http', oc = 'oc', - email = 'email' + email = 'email', + pagerduty = 'pagerduty' } export class Recipient { diff --git a/frontend/src/app/alerts/components/alert-details/children/recipients-manager/recipients-manager.component.html b/frontend/src/app/alerts/components/alert-details/children/recipients-manager/recipients-manager.component.html index 40c547ea3..a8be56906 100644 --- a/frontend/src/app/alerts/components/alert-details/children/recipients-manager/recipients-manager.component.html +++ b/frontend/src/app/alerts/components/alert-details/children/recipients-manager/recipients-manager.component.html @@ -256,6 +256,38 @@
+ + +
+
+
Team Name
+ + + + Unique Team Name required. Min length is 2. Max length is 36. + + +
+
+
Routing Key
+ + + + Routing Key required. Length is {{pagerDutyRoutingKeyMaxLength}}. + + +
+
+
+ + Follow steps outlined in the user-guide to create an API key. + +
+
diff --git a/frontend/src/app/alerts/components/alert-details/children/recipients-manager/recipients-manager.component.ts b/frontend/src/app/alerts/components/alert-details/children/recipients-manager/recipients-manager.component.ts index a7f74312a..014bcd1c6 100644 --- a/frontend/src/app/alerts/components/alert-details/children/recipients-manager/recipients-manager.component.ts +++ b/frontend/src/app/alerts/components/alert-details/children/recipients-manager/recipients-manager.component.ts @@ -65,6 +65,7 @@ export class AlertConfigurationContactsComponent implements OnInit, OnChanges, O ]; slackWebhookMaxLength = 200; opsGenieApiKeyMaxLength = 200; + pagerDutyRoutingKeyMaxLength = 32; _mode = Mode; // for template _recipientType = RecipientType; // for template @@ -83,6 +84,8 @@ export class AlertConfigurationContactsComponent implements OnInit, OnChanges, O httpName = new FormControl(''); httpEndpoint = new FormControl(''); emailAddress = new FormControl(''); + pagerDutyName = new FormControl(''); + pagerDutyRoutingKey = new FormControl(''); // state control private nsRecipientSub: Subscription; @@ -113,6 +116,10 @@ export class AlertConfigurationContactsComponent implements OnInit, OnChanges, O if (this.emailAddress.errors) { return true; } + } else if (this.recipientType === RecipientType.pagerduty) { + if (this.pagerDutyName.errors || this.pagerDutyRoutingKey.errors) { + return true; + } } return false; } @@ -292,6 +299,8 @@ export class AlertConfigurationContactsComponent implements OnInit, OnChanges, O this.httpName.setValue(this.recipientsFormData[RecipientType.http].name); this.httpEndpoint.setValue(this.recipientsFormData[RecipientType.http].endpoint); this.emailAddress.setValue(this.recipientsFormData[RecipientType.email].name); + this.pagerDutyName.setValue(this.recipientsFormData[RecipientType.pagerduty].name); + this.pagerDutyRoutingKey.setValue(this.recipientsFormData[RecipientType.pagerduty].routingkey); } addUserInputToAlertRecipients($event: MatChipInputEvent) { @@ -473,6 +482,8 @@ export class AlertConfigurationContactsComponent implements OnInit, OnChanges, O return 'OC'; } else if (type === RecipientType.email) { return 'Email'; + } else if (type === RecipientType.pagerduty) { + return 'PagerDuty'; } return ''; } @@ -484,6 +495,7 @@ export class AlertConfigurationContactsComponent implements OnInit, OnChanges, O let emptyHTTPRecipient = this.createDefaultRecipient(RecipientType.http); let emptyOCRecipient = this.createDefaultRecipient(RecipientType.oc); let emptyEmailRecipient = this.createDefaultRecipient(RecipientType.email); + let emptyPagerDutyRecipient = this.createDefaultRecipient(RecipientType.pagerduty); // Set Defaults emptyOpsGenieRecipient.apikey = ''; @@ -493,12 +505,14 @@ export class AlertConfigurationContactsComponent implements OnInit, OnChanges, O emptyOCRecipient.context = 'analysis'; emptyOCRecipient.opsdbproperty = ''; emptyEmailRecipient.name = ''; + emptyPagerDutyRecipient.routingkey = ''; emptyRecipients[RecipientType.opsgenie] = emptyOpsGenieRecipient; emptyRecipients[RecipientType.slack] = emptySlackRecipient; emptyRecipients[RecipientType.http] = emptyHTTPRecipient; emptyRecipients[RecipientType.oc] = emptyOCRecipient; emptyRecipients[RecipientType.email] = emptyEmailRecipient; + emptyRecipients[RecipientType.pagerduty] = emptyPagerDutyRecipient; this.recipientsFormData = emptyRecipients; } @@ -534,6 +548,10 @@ export class AlertConfigurationContactsComponent implements OnInit, OnChanges, O return apiKey && apiKey.length > 0 && apiKey.length <= this.opsGenieApiKeyMaxLength; } + isPagerDutyRoutingKeyCorrectLength(routingkey: string): boolean { + return routingkey && routingkey.length > 0 && routingkey.length <= this.pagerDutyRoutingKeyMaxLength; + } + getRecipientItemsByType(type) { if (this.viewMode === Mode.all) { // all mode (show only unselected) @@ -619,6 +637,13 @@ export class AlertConfigurationContactsComponent implements OnInit, OnChanges, O }; } + pagerDutyRoutingKeyValidator(): ValidatorFn { + return (control: AbstractControl): { [key: string]: any } | null => { + let forbidden = !this.isPagerDutyRoutingKeyCorrectLength(control.value); + return forbidden ? {'forbiddenName': {value: control.value}} : null; + }; + } + urlValidator() : ValidatorFn { return (control: AbstractControl): { [key: string]: any } | null => { let forbidden = !/^https:\/\/(www\.)?(([-a-zA-Z0-9@:%._[\]]{1,256}\.[a-zA-Z0-9()]{0,6}\b)|(\[?[a-fA-F0-9]*:[a-fA-F0-9:]+\]))([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/.test(control.value); @@ -636,6 +661,8 @@ export class AlertConfigurationContactsComponent implements OnInit, OnChanges, O this.ocName = new FormControl('', [this.forbiddenNameValidator(this.getAllRecipientsForType(RecipientType.oc), this.recipientsFormData[this.recipientType])]); this.httpName = new FormControl('', [this.forbiddenNameValidator(this.getAllRecipientsForType(RecipientType.http), this.recipientsFormData[this.recipientType])]); this.emailAddress = new FormControl('', [this.forbiddenNameValidator(this.getAllRecipientsForType(RecipientType.email), this.recipientsFormData[this.recipientType]), this.emailValidator()]); + this.pagerDutyName = new FormControl('', [this.forbiddenNameValidator(this.getAllRecipientsForType(RecipientType.pagerduty), this.recipientsFormData[this.recipientType])]); + this.pagerDutyRoutingKey = new FormControl('', [this.pagerDutyRoutingKeyValidator()]); } trimRecipientName(name) { diff --git a/frontend/src/app/alerts/containers/alerts.component.ts b/frontend/src/app/alerts/containers/alerts.component.ts index ad52e8039..409b642cb 100644 --- a/frontend/src/app/alerts/containers/alerts.component.ts +++ b/frontend/src/app/alerts/containers/alerts.component.ts @@ -354,7 +354,7 @@ export class AlertsComponent implements OnInit, OnDestroy, AfterViewChecked { this.sparklineDisplay = this.sparklineDisplayMenuOptions[0]; // icons - const svgIcons = ['email', 'http', 'oc', 'opsgenie', 'slack']; + const svgIcons = ['email', 'http', 'oc', 'opsgenie', 'slack', 'pagerduty']; } @@ -1391,6 +1391,8 @@ export class AlertsComponent implements OnInit, OnDestroy, AfterViewChecked { return 'OC'; } else if (type === RecipientType.email) { return 'Email'; + } else if (type === RecipientType.pagerduty) { + return 'PagerDuty'; } return ''; } diff --git a/server/config/app_config.json b/server/config/app_config.json index 72303a17e..516dde5b4 100644 --- a/server/config/app_config.json +++ b/server/config/app_config.json @@ -33,7 +33,7 @@ }, "oc": { "enable": false, - "onboardUrl": "<% OC onboad url %>", + "onboardUrl": "<% OC onboard url %>", "guideUrl": "<% OC guide url %>" }, "http": { @@ -41,6 +41,10 @@ }, "email": { "enable": true + }, + "pagerduty": { + "enable": false, + "guideUrl": "<% pagerduty guide url %>" } } },