snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
This commit is contained in:
0
zerver/webhooks/newrelic/__init__.py
Normal file
0
zerver/webhooks/newrelic/__init__.py
Normal file
76
zerver/webhooks/newrelic/doc.md
Normal file
76
zerver/webhooks/newrelic/doc.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Zulip New Relic integration
|
||||
|
||||
Get Zulip notification for New Relic incidents.
|
||||
|
||||
{start_tabs}
|
||||
|
||||
1. {!create-an-incoming-webhook.md!}
|
||||
|
||||
1. {!generate-webhook-url-basic.md!}
|
||||
|
||||
1. In New Relic, go to the **Alerts** menu, and select **Destinations**.
|
||||
Choose **Webhook** in the **Add a destination** section.
|
||||
|
||||
1. Set a **Webhook name**, such as `Zulip`. Set the **Endpoint URL** to
|
||||
the URL generated above. Click **Save destination**.
|
||||
|
||||
1. In the **Alerts** menu, select **Workflows**. Click on
|
||||
**+ Add a Workflow**.
|
||||
|
||||
1. Set your workflow name, and filter the trigger conditions. In the
|
||||
**Notify** section, choose **Webhook**. In the **Edit notification
|
||||
message** menu, select the destination for Zulip created above.
|
||||
|
||||
1. In the **Payload** section, you can configure the payload for this
|
||||
workflow. The default payload template is sufficient to get the
|
||||
integration working, but using the message template below will enable
|
||||
the integration to notify you of any **acknowledged** New Relic
|
||||
incidents. To include additional custom fields, refer to
|
||||
[configuration options](#configuration-options):
|
||||
|
||||
{
|
||||
{% raw %}
|
||||
"id": {{ json issueId }},
|
||||
"issueUrl": {{ json issuePageUrl }},
|
||||
"title": {{ json annotations.title.[0] }},
|
||||
"priority": {{ json priority }},
|
||||
"totalIncidents": {{json totalIncidents}},
|
||||
"state": {{ json state }},
|
||||
"createdAt": {{ createdAt }},
|
||||
"updatedAt": {{ updatedAt }},
|
||||
"alertPolicyNames": {{ json accumulations.policyName }},
|
||||
"alertConditionNames": {{ json accumulations.conditionName }},
|
||||
"owner": {{ json owner }},
|
||||
"zulipCustomFields": {}
|
||||
{% endraw %}
|
||||
}
|
||||
|
||||
1. Click **Send test notification** to receive a test notification. Select
|
||||
**Save message**, and click **Activate Workflow**.
|
||||
|
||||
{end_tabs}
|
||||
|
||||
{!congrats.md!}
|
||||
|
||||

|
||||
|
||||
### Configuration options
|
||||
|
||||
* With New Relic's [custom payload feature][1], you can include custom
|
||||
fields in your Zulip notifications by configuring a `zulipCustomFields`
|
||||
dictionary in your notification payload template. The keys of
|
||||
`zulipCustomFields` will be displayed in the Zulip notification
|
||||
message, so we recommend that they be human-readable and descriptive.
|
||||
The values of the dictionary can be strings, integers, booleans, or
|
||||
lists of the those same data types.
|
||||
|
||||
### Related documentation
|
||||
|
||||
* [**New Relic webhook integration**][2]
|
||||
|
||||
* [**New Relic message templates**][1]
|
||||
|
||||
{!webhooks-url-specification.md!}
|
||||
|
||||
[1]: https://docs.newrelic.com/docs/alerts-applied-intelligence/notifications/message-templates/
|
||||
[2]: https://docs.newrelic.com/docs/alerts/get-notified/notification-integrations/#webhook
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"issueUrl": "https://radar-api.service.newrelic.com/accounts/1/issues/0ea2df1c-adab-45d2-aae0-042b609d2322?notifier=SLACK",
|
||||
"title": "PIETER-UBUNTU query result is > 2.0 for 1 minutes on 'High CPU'",
|
||||
"priority": "CRITICAL",
|
||||
"totalIncidents": 1,
|
||||
"state": "CREATED",
|
||||
"createdAt": 1713592289021,
|
||||
"updatedAt": 1713592289021,
|
||||
"alertPolicyNames": ["Golden Signals"],
|
||||
"alertConditionNames": ["High CPU"]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"id": "13bbcdca-f0b6-470d-b0be-b34583c58869",
|
||||
"issueUrl": "https://radar-api.service.newrelic.com/accounts/4420147/issues/13bbcdca-f0b6-470d-b0be-b34583c58869?notifier=WEBHOOK",
|
||||
"title": "PIETER-UBUNTU query result is > 1.0 for 5 minutes on 'Storage on Host Exceeded Threshold'",
|
||||
"priority": "HIGH",
|
||||
"impactedEntities": [
|
||||
"PIETER-UBUNTU"
|
||||
],
|
||||
"totalIncidents": 1,
|
||||
"state": "ACTIVATED",
|
||||
"trigger": "USER_ACTION",
|
||||
"isCorrelated": "false",
|
||||
"createdAt": 1713769949493,
|
||||
"updatedAt": 1713770077412,
|
||||
"sources": [
|
||||
"newrelic"
|
||||
],
|
||||
"alertPolicyNames": [
|
||||
"Golden Signals"
|
||||
],
|
||||
"alertConditionNames": [
|
||||
"Storage on Host Exceeded Threshold"
|
||||
],
|
||||
"workflowName": "issue workflow"
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"id": "13bbcdca-f0b6-470d-b0be-b34583c58869",
|
||||
"issueUrl": "https://radar-api.service.newrelic.com/accounts/4420147/issues/13bbcdca-f0b6-470d-b0be-b34583c58869?notifier=WEBHOOK",
|
||||
"title": "PIETER-UBUNTU query result is > 1.0 for 5 minutes on 'Storage on Host Exceeded Threshold'",
|
||||
"priority": "HIGH",
|
||||
"totalIncidents": 1,
|
||||
"state": "ACTIVATED",
|
||||
"createdAt": 1713769949493,
|
||||
"updatedAt": 1713770077412,
|
||||
"alertPolicyNames": [
|
||||
"Golden Signals"
|
||||
],
|
||||
"alertConditionNames": [
|
||||
"Storage on Host Exceeded Threshold"
|
||||
],
|
||||
"owner": "Pieter Cardillo Kwok",
|
||||
"zulipCustomFields": {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"id": "c5faa7e6-7b54-402d-af79-f99601e0278c",
|
||||
"issueUrl": "https://radar-api.service.newrelic.com/accounts/4420147/issues/c5faa7e6-7b54-402d-af79-f99601e0278c?notifier=WEBHOOK",
|
||||
"title": "zulip_app query result is > 1.0 for 1 minutes on 'Zulip Server Low Storage'",
|
||||
"priority": "CRITICAL",
|
||||
"impactedEntities": [
|
||||
"zulip_app"
|
||||
],
|
||||
"totalIncidents": 1,
|
||||
"state": "ACTIVATED",
|
||||
"trigger": "STATE_CHANGE",
|
||||
"isCorrelated": false,
|
||||
"createdAt": 1713755131352,
|
||||
"updatedAt": 1713769708699,
|
||||
"sources": [
|
||||
"newrelic"
|
||||
],
|
||||
"alertPolicyNames": [
|
||||
"Golden Signals"
|
||||
],
|
||||
"alertConditionNames": [
|
||||
"Zulip Server Low Storage"
|
||||
],
|
||||
"workflowName": "testWorkflow"
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"id": "13bbcdca-f0b6-470d-b0be-b34583c58869",
|
||||
"issueUrl": "https://radar-api.service.newrelic.com/accounts/4420147/issues/13bbcdca-f0b6-470d-b0be-b34583c58869?notifier=WEBHOOK",
|
||||
"title": "PIETER-UBUNTU query result is > 1.0 for 5 minutes on 'Storage on Host Exceeded Threshold'",
|
||||
"priority": "HIGH",
|
||||
"totalIncidents": 1,
|
||||
"state": "ACTIVATED",
|
||||
"createdAt": 1713769949493,
|
||||
"updatedAt": 1713769949494,
|
||||
"alertPolicyNames": [
|
||||
"Golden Signals"
|
||||
],
|
||||
"alertConditionNames": [
|
||||
"Storage on Host Exceeded Threshold"
|
||||
],
|
||||
"owner": "N/A",
|
||||
"zulipCustomFields": {
|
||||
"Your custom payload": "somedata123"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "95a9344a-2590-48ce-8d83-07e28b6d22c6",
|
||||
"issueUrl": "https://radar-api.service.newrelic.com/accounts/1/issues/0ea2df1c-adab-45d2-aae0-042b609d2322?notifier=SLACK",
|
||||
"title": "main_app-UBUNTU query result is > 2.0 for 1 minutes on 'High CPU'",
|
||||
"priority": "CRITICAL",
|
||||
"impactedEntities": ["main_app-UBUNTU"],
|
||||
"totalIncidents": 1,
|
||||
"state": "CLOSED",
|
||||
"trigger": "INCIDENT_CLOSED",
|
||||
"isCorrelated": false,
|
||||
"createdAt": 1713766590228,
|
||||
"updatedAt": 1713766657383,
|
||||
"sources": ["newrelic"],
|
||||
"alertPolicyNames": ["Golden Signals"],
|
||||
"alertConditionNames": ["High CPU"],
|
||||
"workflowName": "DBA Team workflow"
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"id": "13bbcdca-f0b6-470d-b0be-b34583c58869",
|
||||
"issueUrl": "https://radar-api.service.newrelic.com/accounts/4420147/issues/13bbcdca-f0b6-470d-b0be-b34583c58869?notifier=WEBHOOK",
|
||||
"title": "PIETER-UBUNTU query result is > 1.0 for 5 minutes on 'Storage on Host Exceeded Threshold'",
|
||||
"priority": "HIGH",
|
||||
"totalIncidents": 1,
|
||||
"state": "CLOSED",
|
||||
"createdAt": 1713769949493,
|
||||
"updatedAt": 1713770135419,
|
||||
"alertPolicyNames": [
|
||||
"Golden Signals"
|
||||
],
|
||||
"alertConditionNames": [
|
||||
"Storage on Host Exceeded Threshold"
|
||||
],
|
||||
"owner": "Pieter Cardillo Kwok",
|
||||
"zulipCustomFields": {
|
||||
"Your custom payload": "somedata123"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"id": "208c5e92-f250-40be-b7c2-50508c268c15",
|
||||
"issueUrl": "https://radar-api.service.newrelic.com/accounts/1/issues/0ea2df1c-adab-45d2-aae0-042b609d2322?notifier=SLACK",
|
||||
"title": "MAIN-APP-UBUNTU query result is > 2.0 for 1 minutes on 'High CPU'",
|
||||
"priority": "CRITICAL",
|
||||
"impactedEntities": [
|
||||
"MAIN-APP-UBUNTU"
|
||||
],
|
||||
"totalIncidents": 1,
|
||||
"state": "CREATED",
|
||||
"trigger": "INCIDENT_ADDED",
|
||||
"isCorrelated": false,
|
||||
"createdAt": 1713767789495,
|
||||
"updatedAt": 1713767789495,
|
||||
"sources": [
|
||||
"newrelic"
|
||||
],
|
||||
"alertPolicyNames": [
|
||||
"Golden Signals"
|
||||
],
|
||||
"alertConditionNames": [
|
||||
"High CPU"
|
||||
],
|
||||
"workflowName": "DBA Team workflow"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"id": "208c5e92-f250-40be-b7c2-50508c268c15",
|
||||
"issueUrl": "https://radar-api.service.newrelic.com/accounts/1/issues/0ea2df1c-adab-45d2-aae0-042b609d2322?notifier=SLACK",
|
||||
"title": "PIETER-UBUNTU query result is > 2.0 for 1 minutes on 'High CPU'",
|
||||
"priority": "CRITICAL",
|
||||
"totalIncidents": 1,
|
||||
"state": "CREATED",
|
||||
"createdAt": 1713767789495,
|
||||
"updatedAt": 1713767789495,
|
||||
"alertPolicyNames": ["Golden Signals"],
|
||||
"alertConditionNames": ["High CPU"],
|
||||
"owner": "John Doe",
|
||||
"zulipCustomFields":{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"id": "13bbcdca-f0b6-470d-b0be-b34583c58869",
|
||||
"issueUrl": "https://radar-api.service.newrelic.com/accounts/4420147/issues/13bbcdca-f0b6-470d-b0be-b34583c58869?notifier=WEBHOOK",
|
||||
"title": "PIETER-UBUNTU query result is > 1.0 for 5 minutes on 'Storage on Host Exceeded Threshold'",
|
||||
"priority": "HIGH",
|
||||
"impactedEntities": [
|
||||
"PIETER-UBUNTU"
|
||||
],
|
||||
"totalIncidents": 1,
|
||||
"state": "ACTIVATED",
|
||||
"trigger": "STATE_CHANGE",
|
||||
"isCorrelated": "false",
|
||||
"createdAt": 1713769949493,
|
||||
"updatedAt": 1713769949494,
|
||||
"sources": [
|
||||
"newrelic"
|
||||
],
|
||||
"alertPolicyNames": [
|
||||
"Golden Signals"
|
||||
],
|
||||
"alertConditionNames": [
|
||||
"Storage on Host Exceeded Threshold"
|
||||
],
|
||||
"workflowName": "issue workflow",
|
||||
"owner": "N/A",
|
||||
"zulipCustomFields": {
|
||||
"Your custom payload": "somedata123",
|
||||
"custom status 1": true,
|
||||
"Custom list 1": ["SSD", 2000, false, null, 13.33],
|
||||
"Custom field 1": null,
|
||||
"Custom field 2": 9000
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"id": "95a9344a-2590-48ce-8d83-07e28b6d22c6",
|
||||
"issueUrl": "https://radar-api.service.newrelic.com/accounts/1/issues/0ea2df1c-adab-45d2-aae0-042b609d2322?notifier=SLACK",
|
||||
"title": "main_app-UBUNTU query result is > 2.0 for 1 minutes on 'High CPU'",
|
||||
"priority": "CRITICAL",
|
||||
"totalIncidents": 1,
|
||||
"state": "CLOSED",
|
||||
"createdAt": 1713766590228,
|
||||
"updatedAt": 1713766657383,
|
||||
"alertPolicyNames": ["Golden Signals"],
|
||||
"alertConditionNames": ["High CPU"],
|
||||
"zulipCustomFields": {
|
||||
"Your custom payload": "somedata123",
|
||||
"custom status 1": true,
|
||||
"Custom list 1": ["SSD", 2000, false, null, 13.33],
|
||||
"Custom field 1": null,
|
||||
"Custom field 2": 9000
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"id": "13bbcdca-f0b6-470d-b0be-b34583c58869",
|
||||
"issueUrl": "https://radar-api.service.newrelic.com/accounts/4420147/issues/13bbcdca-f0b6-470d-b0be-b34583c58869?notifier=WEBHOOK",
|
||||
"title": "PIETER-UBUNTU query result is > 1.0 for 5 minutes on 'Storage on Host Exceeded Threshold'",
|
||||
"priority": "HIGH",
|
||||
"totalIncidents": 1,
|
||||
"state": "ACTIVATED",
|
||||
"trigger": "STATE_CHANGE",
|
||||
"createdAt": 1713769949493,
|
||||
"updatedAt": 1713769949494,
|
||||
"alertPolicyNames": [
|
||||
"Golden Signals"
|
||||
],
|
||||
"alertConditionNames": [
|
||||
"Storage on Host Exceeded Threshold"
|
||||
],
|
||||
"owner": "N/A",
|
||||
"zulipCustomFields": {
|
||||
"Invalid fields 1": ["SSD", 2000, false, null, {}],
|
||||
"Invalid field 2": {},
|
||||
"Is valid": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"unrecognized1": "https://radar-api.service.newrelic.com/accounts/1/issues/0ea2df1c-adab-45d2-aae0-042b609d2322?notifier=SLACK",
|
||||
"unrecognized2": "PIETER-UBUNTU query result is > 2.0 for 1 minutes on 'High CPU'",
|
||||
"unrecognized3": "CRITICAL",
|
||||
"updatedField1": 1,
|
||||
"updatedField2": "CREATED",
|
||||
"updatedField3": 1713592289021,
|
||||
"changedField1": 1713592289021,
|
||||
"changedField2": ["Golden Signals"],
|
||||
"changedField3": ["High CPU"]
|
||||
}
|
||||
354
zerver/webhooks/newrelic/tests.py
Normal file
354
zerver/webhooks/newrelic/tests.py
Normal file
@@ -0,0 +1,354 @@
|
||||
from zerver.lib.test_classes import WebhookTestCase
|
||||
|
||||
|
||||
class NewRelicHookTests(WebhookTestCase):
|
||||
def test_incident_activated_new_default_payload(self) -> None:
|
||||
expected_topic_name = "zulip_app query result is > 1.0 for 1 minutes on 'Zulip S..."
|
||||
expected_message = """
|
||||
:red_circle: **[zulip_app query result is > 1.0 for 1 minutes on 'Zulip Server Low Storage'](https://radar-api.service.newrelic.com/accounts/4420147/issues/c5faa7e6-7b54-402d-af79-f99601e0278c?notifier=WEBHOOK)**
|
||||
|
||||
```quote
|
||||
**Priority**: CRITICAL
|
||||
**State**: ACTIVATED
|
||||
**Updated at**: <time:2024-04-22T07:08:28+00:00>
|
||||
|
||||
```
|
||||
|
||||
```spoiler :file: Incident details
|
||||
|
||||
- **Alert policies**: `Golden Signals`
|
||||
- **Conditions**: `Zulip Server Low Storage`
|
||||
- **Total incidents**: 1
|
||||
- **Incident created at**: <time:2024-04-22T03:05:31+00:00>
|
||||
|
||||
```
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"incident_activated_new_default_payload",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
def test_incident_activated_new_provided_base_payload(self) -> None:
|
||||
expected_topic_name = "PIETER-UBUNTU query result is > 1.0 for 5 minutes on 'Sto..."
|
||||
expected_message = """
|
||||
:orange_circle: **[PIETER-UBUNTU query result is > 1.0 for 5 minutes on 'Storage on Host Exceeded Threshold'](https://radar-api.service.newrelic.com/accounts/4420147/issues/13bbcdca-f0b6-470d-b0be-b34583c58869?notifier=WEBHOOK)**
|
||||
|
||||
```quote
|
||||
**Priority**: HIGH
|
||||
**State**: ACTIVATED
|
||||
**Updated at**: <time:2024-04-22T07:12:29+00:00>
|
||||
|
||||
```
|
||||
|
||||
```spoiler :file: Incident details
|
||||
|
||||
- **Alert policies**: `Golden Signals`
|
||||
- **Conditions**: `Storage on Host Exceeded Threshold`
|
||||
- **Total incidents**: 1
|
||||
- **Incident created at**: <time:2024-04-22T07:12:29+00:00>
|
||||
- **Your custom payload**: somedata123
|
||||
|
||||
```
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"incident_activated_new_provided_base_payload",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
def test_incident_closed_default_payload(self) -> None:
|
||||
expected_topic_name = "main_app-UBUNTU query result is > 2.0 for 1 minutes on 'H..."
|
||||
expected_message = """
|
||||
:red_circle: **[main_app-UBUNTU query result is > 2.0 for 1 minutes on 'High CPU'](https://radar-api.service.newrelic.com/accounts/1/issues/0ea2df1c-adab-45d2-aae0-042b609d2322?notifier=SLACK)**
|
||||
|
||||
```quote
|
||||
**Priority**: CRITICAL
|
||||
**State**: CLOSED
|
||||
**Updated at**: <time:2024-04-22T06:17:37+00:00>
|
||||
|
||||
```
|
||||
|
||||
```spoiler :file: Incident details
|
||||
|
||||
- **Alert policies**: `Golden Signals`
|
||||
- **Conditions**: `High CPU`
|
||||
- **Total incidents**: 1
|
||||
- **Incident created at**: <time:2024-04-22T06:16:30+00:00>
|
||||
|
||||
```
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"incident_closed_default_payload",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
def test_incident_closed_provided_base_payload(self) -> None:
|
||||
expected_topic_name = "PIETER-UBUNTU query result is > 1.0 for 5 minutes on 'Sto..."
|
||||
expected_message = """
|
||||
:orange_circle: **[PIETER-UBUNTU query result is > 1.0 for 5 minutes on 'Storage on Host Exceeded Threshold'](https://radar-api.service.newrelic.com/accounts/4420147/issues/13bbcdca-f0b6-470d-b0be-b34583c58869?notifier=WEBHOOK)**
|
||||
|
||||
```quote
|
||||
**Priority**: HIGH
|
||||
**State**: CLOSED
|
||||
**Updated at**: <time:2024-04-22T07:15:35+00:00>
|
||||
**Acknowledged by**: Pieter Cardillo Kwok
|
||||
```
|
||||
|
||||
```spoiler :file: Incident details
|
||||
|
||||
- **Alert policies**: `Golden Signals`
|
||||
- **Conditions**: `Storage on Host Exceeded Threshold`
|
||||
- **Total incidents**: 1
|
||||
- **Incident created at**: <time:2024-04-22T07:12:29+00:00>
|
||||
- **Your custom payload**: somedata123
|
||||
|
||||
```
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"incident_closed_provided_base_payload",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
def test_incident_acknowledged_default_payload(self) -> None:
|
||||
expected_topic_name = "PIETER-UBUNTU query result is > 1.0 for 5 minutes on 'Sto..."
|
||||
expected_message = """
|
||||
:orange_circle: **[PIETER-UBUNTU query result is > 1.0 for 5 minutes on 'Storage on Host Exceeded Threshold'](https://radar-api.service.newrelic.com/accounts/4420147/issues/13bbcdca-f0b6-470d-b0be-b34583c58869?notifier=WEBHOOK)**
|
||||
|
||||
```quote
|
||||
**Priority**: HIGH
|
||||
**State**: ACTIVATED
|
||||
**Updated at**: <time:2024-04-22T07:14:37+00:00>
|
||||
|
||||
```
|
||||
|
||||
```spoiler :file: Incident details
|
||||
|
||||
- **Alert policies**: `Golden Signals`
|
||||
- **Conditions**: `Storage on Host Exceeded Threshold`
|
||||
- **Total incidents**: 1
|
||||
- **Incident created at**: <time:2024-04-22T07:12:29+00:00>
|
||||
|
||||
```
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"incident_acknowledged_default_payload",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
def test_incident_acknowledged_provided_base_payload(self) -> None:
|
||||
expected_topic_name = "PIETER-UBUNTU query result is > 1.0 for 5 minutes on 'Sto..."
|
||||
expected_message = """
|
||||
:orange_circle: **[PIETER-UBUNTU query result is > 1.0 for 5 minutes on 'Storage on Host Exceeded Threshold'](https://radar-api.service.newrelic.com/accounts/4420147/issues/13bbcdca-f0b6-470d-b0be-b34583c58869?notifier=WEBHOOK)**
|
||||
|
||||
```quote
|
||||
**Priority**: HIGH
|
||||
**State**: ACTIVATED
|
||||
**Updated at**: <time:2024-04-22T07:14:37+00:00>
|
||||
**Acknowledged by**: Pieter Cardillo Kwok
|
||||
```
|
||||
|
||||
```spoiler :file: Incident details
|
||||
|
||||
- **Alert policies**: `Golden Signals`
|
||||
- **Conditions**: `Storage on Host Exceeded Threshold`
|
||||
- **Total incidents**: 1
|
||||
- **Incident created at**: <time:2024-04-22T07:12:29+00:00>
|
||||
|
||||
```
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"incident_acknowledged_provided_base_payload",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
def test_incident_created_default_payload(self) -> None:
|
||||
expected_topic_name = "MAIN-APP-UBUNTU query result is > 2.0 for 1 minutes on 'H..."
|
||||
expected_message = """
|
||||
:red_circle: **[MAIN-APP-UBUNTU query result is > 2.0 for 1 minutes on 'High CPU'](https://radar-api.service.newrelic.com/accounts/1/issues/0ea2df1c-adab-45d2-aae0-042b609d2322?notifier=SLACK)**
|
||||
|
||||
```quote
|
||||
**Priority**: CRITICAL
|
||||
**State**: CREATED
|
||||
**Updated at**: <time:2024-04-22T06:36:29+00:00>
|
||||
|
||||
```
|
||||
|
||||
```spoiler :file: Incident details
|
||||
|
||||
- **Alert policies**: `Golden Signals`
|
||||
- **Conditions**: `High CPU`
|
||||
- **Total incidents**: 1
|
||||
- **Incident created at**: <time:2024-04-22T06:36:29+00:00>
|
||||
|
||||
```
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"incident_created_default_payload",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
def test_incident_created_provided_base_payload(self) -> None:
|
||||
expected_topic_name = "PIETER-UBUNTU query result is > 2.0 for 1 minutes on 'Hig..."
|
||||
expected_message = """
|
||||
:red_circle: **[PIETER-UBUNTU query result is > 2.0 for 1 minutes on 'High CPU'](https://radar-api.service.newrelic.com/accounts/1/issues/0ea2df1c-adab-45d2-aae0-042b609d2322?notifier=SLACK)**
|
||||
|
||||
```quote
|
||||
**Priority**: CRITICAL
|
||||
**State**: CREATED
|
||||
**Updated at**: <time:2024-04-22T06:36:29+00:00>
|
||||
**Acknowledged by**: John Doe
|
||||
```
|
||||
|
||||
```spoiler :file: Incident details
|
||||
|
||||
- **Alert policies**: `Golden Signals`
|
||||
- **Conditions**: `High CPU`
|
||||
- **Total incidents**: 1
|
||||
- **Incident created at**: <time:2024-04-22T06:36:29+00:00>
|
||||
|
||||
```
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"incident_created_provided_base_payload",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
def test_incident_default_base_with_zulip_custom_fields(self) -> None:
|
||||
expected_topic_name = "PIETER-UBUNTU query result is > 1.0 for 5 minutes on 'Sto..."
|
||||
expected_message = """
|
||||
:orange_circle: **[PIETER-UBUNTU query result is > 1.0 for 5 minutes on 'Storage on Host Exceeded Threshold'](https://radar-api.service.newrelic.com/accounts/4420147/issues/13bbcdca-f0b6-470d-b0be-b34583c58869?notifier=WEBHOOK)**
|
||||
|
||||
```quote
|
||||
**Priority**: HIGH
|
||||
**State**: ACTIVATED
|
||||
**Updated at**: <time:2024-04-22T07:12:29+00:00>
|
||||
|
||||
```
|
||||
|
||||
```spoiler :file: Incident details
|
||||
|
||||
- **Alert policies**: `Golden Signals`
|
||||
- **Conditions**: `Storage on Host Exceeded Threshold`
|
||||
- **Total incidents**: 1
|
||||
- **Incident created at**: <time:2024-04-22T07:12:29+00:00>
|
||||
- **Your custom payload**: somedata123
|
||||
- **Custom status 1**: True
|
||||
- **Custom list 1**: SSD, 2000, False, None, 13.33
|
||||
- **Custom field 1**: None
|
||||
- **Custom field 2**: 9000
|
||||
|
||||
```
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"incident_default_base_with_zulip_custom_fields",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
def test_incident_provided_base_with_zulip_custom_fields(self) -> None:
|
||||
expected_topic_name = "main_app-UBUNTU query result is > 2.0 for 1 minutes on 'H..."
|
||||
expected_message = """
|
||||
:red_circle: **[main_app-UBUNTU query result is > 2.0 for 1 minutes on 'High CPU'](https://radar-api.service.newrelic.com/accounts/1/issues/0ea2df1c-adab-45d2-aae0-042b609d2322?notifier=SLACK)**
|
||||
|
||||
```quote
|
||||
**Priority**: CRITICAL
|
||||
**State**: CLOSED
|
||||
**Updated at**: <time:2024-04-22T06:17:37+00:00>
|
||||
|
||||
```
|
||||
|
||||
```spoiler :file: Incident details
|
||||
|
||||
- **Alert policies**: `Golden Signals`
|
||||
- **Conditions**: `High CPU`
|
||||
- **Total incidents**: 1
|
||||
- **Incident created at**: <time:2024-04-22T06:16:30+00:00>
|
||||
- **Your custom payload**: somedata123
|
||||
- **Custom status 1**: True
|
||||
- **Custom list 1**: SSD, 2000, False, None, 13.33
|
||||
- **Custom field 1**: None
|
||||
- **Custom field 2**: 9000
|
||||
|
||||
```
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"incident_provided_base_with_zulip_custom_fields",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
def test_incident_with_invalid_zulip_custom_fields(self) -> None:
|
||||
expected_topic_name = "PIETER-UBUNTU query result is > 1.0 for 5 minutes on 'Sto..."
|
||||
expected_message = """
|
||||
:orange_circle: **[PIETER-UBUNTU query result is > 1.0 for 5 minutes on 'Storage on Host Exceeded Threshold'](https://radar-api.service.newrelic.com/accounts/4420147/issues/13bbcdca-f0b6-470d-b0be-b34583c58869?notifier=WEBHOOK)**
|
||||
|
||||
```quote
|
||||
**Priority**: HIGH
|
||||
**State**: ACTIVATED
|
||||
**Updated at**: <time:2024-04-22T07:12:29+00:00>
|
||||
|
||||
```
|
||||
|
||||
```spoiler :file: Incident details
|
||||
|
||||
- **Alert policies**: `Golden Signals`
|
||||
- **Conditions**: `Storage on Host Exceeded Threshold`
|
||||
- **Total incidents**: 1
|
||||
- **Incident created at**: <time:2024-04-22T07:12:29+00:00>
|
||||
- **Invalid fields 1**: *Value is not a supported data type*
|
||||
- **Invalid field 2**: *Value is not a supported data type*
|
||||
- **Is valid**: True
|
||||
|
||||
```
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"incident_with_invalid_zulip_custom_fields",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
def test_missing_essential_fields_default_payload(self) -> None:
|
||||
expected_topic_name = "New Relic incident alerts"
|
||||
expected_message = """
|
||||
:danger: A New Relic [incident](https://one.newrelic.com/alerts-ai) updated
|
||||
|
||||
**Warning**: Unable to use the default notification format because at least one expected field was missing from the incident payload. See [New Relic integration documentation](/integrations/newrelic).
|
||||
|
||||
**Missing fields**: `issueUrl`, `title`, `priority`, `totalIncidents`, `state`, `createdAt`, `updatedAt`, `alertPolicyNames`, `alertConditionNames`
|
||||
""".strip()
|
||||
|
||||
self.check_webhook(
|
||||
"missing_essential_fields_default_payload",
|
||||
expected_topic_name,
|
||||
expected_message,
|
||||
content_type="application/json",
|
||||
)
|
||||
180
zerver/webhooks/newrelic/view.py
Normal file
180
zerver/webhooks/newrelic/view.py
Normal file
@@ -0,0 +1,180 @@
|
||||
# Webhooks for external integrations.
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
|
||||
from zerver.decorator import webhook_view
|
||||
from zerver.lib.response import json_success
|
||||
from zerver.lib.timestamp import datetime_to_global_time, timestamp_to_datetime
|
||||
from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint
|
||||
from zerver.lib.validator import (
|
||||
WildValue,
|
||||
check_float,
|
||||
check_int,
|
||||
check_list,
|
||||
check_none_or,
|
||||
check_string,
|
||||
check_string_in,
|
||||
check_union,
|
||||
)
|
||||
from zerver.lib.webhooks.common import check_send_webhook_message
|
||||
from zerver.models import UserProfile
|
||||
|
||||
MISSING_FIELDS_NOTIFICATION = """
|
||||
:danger: A New Relic [incident]({url}) updated
|
||||
|
||||
**Warning**: Unable to use the default notification format because at least one expected field was missing from the incident payload. See [New Relic integration documentation](/integrations/newrelic).
|
||||
|
||||
**Missing fields**: {formatted_missing_fields}
|
||||
"""
|
||||
|
||||
NOTIFICATION_TEMPLATE = """
|
||||
{priority_symbol} **[{title}]({incident_url})**
|
||||
|
||||
```quote
|
||||
**Priority**: {priority}
|
||||
**State**: {state}
|
||||
**Updated at**: {time_updated}
|
||||
{owner}
|
||||
```
|
||||
|
||||
```spoiler :file: Incident details
|
||||
{details}
|
||||
```
|
||||
"""
|
||||
|
||||
NOTIFICATION_DETAILS = """
|
||||
- **Alert policies**: {alert_policy}
|
||||
- **Conditions**: {conditions}
|
||||
- **Total incidents**: {total_incidents}
|
||||
- **Incident created at**: {time_created}
|
||||
"""
|
||||
|
||||
ALL_EVENT_TYPES = ["CREATED", "ACTIVATED", "CLOSED"]
|
||||
|
||||
PRIORITIES = {
|
||||
"CRITICAL": ":red_circle:",
|
||||
"HIGH": ":orange_circle:",
|
||||
"MEDIUM": ":yellow:",
|
||||
"LOW": ":blue_circle:",
|
||||
}
|
||||
|
||||
DEFAULT_NEWRELIC_URL = "https://one.newrelic.com/alerts-ai"
|
||||
|
||||
|
||||
EXPECTED_FIELDS = [
|
||||
"issueUrl",
|
||||
"title",
|
||||
"priority",
|
||||
"totalIncidents",
|
||||
"state",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
"alertPolicyNames",
|
||||
"alertConditionNames",
|
||||
]
|
||||
|
||||
|
||||
def get_timestamp_string(payload: WildValue, field: str) -> str:
|
||||
unix_time = payload[field].tame(check_int)
|
||||
return datetime_to_global_time(timestamp_to_datetime(unix_time / 1000))
|
||||
|
||||
|
||||
def parse_payload(payload: WildValue) -> dict[str, str]:
|
||||
priority = payload["priority"].tame(check_string_in(PRIORITIES.keys()))
|
||||
priority_symbol = PRIORITIES.get(priority, ":alert:")
|
||||
conditions_list = payload.get("alertConditionNames", ["Unknown condition"]).tame(
|
||||
check_list(check_string)
|
||||
)
|
||||
conditions = ", ".join([f"`{c}`" for c in conditions_list])
|
||||
policy_list = payload.get("alertPolicyNames", ["Unknown policy"]).tame(check_list(check_string))
|
||||
alert_policy = ", ".join([f"`{p}`" for p in policy_list])
|
||||
|
||||
owner = payload.get("owner").tame(check_none_or(check_string))
|
||||
acknowledged = ""
|
||||
if owner and owner != "N/A":
|
||||
acknowledged = f"**Acknowledged by**: {owner}"
|
||||
|
||||
message_context: dict[str, str] = {
|
||||
"title": payload["title"].tame(check_string),
|
||||
"incident_url": payload.get("issueUrl", DEFAULT_NEWRELIC_URL).tame(check_string),
|
||||
"total_incidents": str(payload["totalIncidents"].tame(check_int)),
|
||||
"state": payload["state"].tame(check_string_in(ALL_EVENT_TYPES)),
|
||||
"time_created": get_timestamp_string(payload, "createdAt"),
|
||||
"time_updated": get_timestamp_string(payload, "updatedAt"),
|
||||
"priority": priority,
|
||||
"priority_symbol": priority_symbol,
|
||||
"conditions": conditions,
|
||||
"alert_policy": alert_policy,
|
||||
"owner": acknowledged,
|
||||
}
|
||||
|
||||
return message_context
|
||||
|
||||
|
||||
def format_zulip_custom_fields(payload: WildValue) -> str:
|
||||
body_custom_field_detail: str = ""
|
||||
zulip_custom_fields = payload.get("zulipCustomFields", {})
|
||||
|
||||
for key, value in zulip_custom_fields.items():
|
||||
custom_field_name = key.capitalize()
|
||||
try:
|
||||
details = value.tame(
|
||||
check_none_or(
|
||||
check_union(
|
||||
[
|
||||
check_int,
|
||||
check_float,
|
||||
check_string,
|
||||
check_list(
|
||||
check_none_or(check_union([check_int, check_float, check_string]))
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
if isinstance(details, list):
|
||||
custom_field_detail = ", ".join([f"{detail}" for detail in details])
|
||||
else:
|
||||
custom_field_detail = f"{details}"
|
||||
|
||||
custom_field_message = f"- **{custom_field_name}**: {custom_field_detail}\n"
|
||||
body_custom_field_detail += custom_field_message
|
||||
except ValidationError:
|
||||
invalid_field_message = (
|
||||
f"- **{custom_field_name}**: *Value is not a supported data type*\n"
|
||||
)
|
||||
body_custom_field_detail += invalid_field_message
|
||||
return body_custom_field_detail
|
||||
|
||||
|
||||
def check_for_expected_fields(payload: WildValue) -> list[str]:
|
||||
return [key for key in EXPECTED_FIELDS if key not in payload]
|
||||
|
||||
|
||||
@webhook_view("NewRelic", all_event_types=ALL_EVENT_TYPES)
|
||||
@typed_endpoint
|
||||
def api_newrelic_webhook(
|
||||
request: HttpRequest,
|
||||
user_profile: UserProfile,
|
||||
*,
|
||||
payload: JsonBodyPayload[WildValue],
|
||||
) -> HttpResponse:
|
||||
missing_fields = check_for_expected_fields(payload)
|
||||
if missing_fields:
|
||||
formatted_missing_fields = ", ".join([f"`{fields}`" for fields in missing_fields])
|
||||
content = MISSING_FIELDS_NOTIFICATION.format(
|
||||
url=DEFAULT_NEWRELIC_URL,
|
||||
formatted_missing_fields=formatted_missing_fields,
|
||||
)
|
||||
topic = "New Relic incident alerts"
|
||||
check_send_webhook_message(request, user_profile, topic, content)
|
||||
return json_success(request)
|
||||
|
||||
message_context = parse_payload(payload)
|
||||
incident_details = NOTIFICATION_DETAILS.format(**message_context)
|
||||
incident_details += format_zulip_custom_fields(payload)
|
||||
content = NOTIFICATION_TEMPLATE.format(details=incident_details, **message_context)
|
||||
topic = message_context["title"]
|
||||
check_send_webhook_message(request, user_profile, topic, content, message_context["state"])
|
||||
return json_success(request)
|
||||
Reference in New Issue
Block a user