From 08b4c77eb774d49f3ec18c99c5c522da65c051fe Mon Sep 17 00:00:00 2001 From: Debojyoti Mandal <55194542+jyoti369@users.noreply.github.com> Date: Wed, 29 Apr 2026 12:33:26 +0530 Subject: [PATCH] feat(pagerduty): add client and client_url support for Events API v2 (#6241) Co-authored-by: Tal --- .../pagerduty-snippet-autogenerated.mdx | 2 + .../pagerduty_provider/pagerduty_provider.py | 21 ++++++++- tests/test_pagerduty_provider.py | 46 +++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/docs/snippets/providers/pagerduty-snippet-autogenerated.mdx b/docs/snippets/providers/pagerduty-snippet-autogenerated.mdx index 1f37116de7..f61254f95d 100644 --- a/docs/snippets/providers/pagerduty-snippet-autogenerated.mdx +++ b/docs/snippets/providers/pagerduty-snippet-autogenerated.mdx @@ -52,6 +52,8 @@ actions: priority: {value} # Priority reference ID for incidents status: {value} # Status for incident updates (resolved/acknowledged) resolution: {value} # Resolution note for resolved incidents + client: {value} # Name of the monitoring client triggering this event (Events API v2 only) + client_url: {value} # URL of the monitoring client triggering this event (Events API v2 only) body: {value} # Body of the incident as per https://developer.pagerduty.com/api-reference/a7d81b0e9200f-create-an-incident#request-body kwargs: {value} # Additional event/incident fields ``` diff --git a/keep/providers/pagerduty_provider/pagerduty_provider.py b/keep/providers/pagerduty_provider/pagerduty_provider.py index 4d601edf02..57127af159 100644 --- a/keep/providers/pagerduty_provider/pagerduty_provider.py +++ b/keep/providers/pagerduty_provider/pagerduty_provider.py @@ -364,6 +364,8 @@ def _build_alert( severity: typing.Literal["critical", "error", "warning", "info"] | None = None, event_type: typing.Literal["trigger", "acknowledge", "resolve"] | None = None, source: str | None = None, + client: str | None = None, + client_url: str | None = None, **kwargs, ) -> typing.Dict[str, typing.Any]: """ @@ -437,6 +439,12 @@ def _build_alert( if kwargs.get("class"): payload["payload"]["class"] = kwargs.get("class") + if client: + payload["client"] = client + + if client_url: + payload["client_url"] = client_url + if kwargs.get("images"): images = kwargs.get("images", []) if isinstance(images, str): @@ -458,6 +466,8 @@ def _send_alert( severity: typing.Literal["critical", "error", "warning", "info"] | None = None, event_type: typing.Literal["trigger", "acknowledge", "resolve"] | None = None, source: str | None = None, + client: str | None = None, + client_url: str | None = None, **kwargs, ): """ @@ -468,11 +478,14 @@ def _send_alert( alert_body: UTF-8 string of custom message for alert. Shown in incident body dedup: Any string, max 255, characters used to deduplicate alerts event_type: The type of event to send to PagerDuty + client: Name of the monitoring client triggering this event + client_url: URL of the monitoring client triggering this event """ url = "https://events.pagerduty.com/v2/enqueue" payload = self._build_alert( - title, routing_key, dedup, severity, event_type, source, **kwargs + title, routing_key, dedup, severity, event_type, source, + client=client, client_url=client_url, **kwargs ) result = requests.post(url, json=payload) result.raise_for_status() @@ -708,6 +721,8 @@ def _notify( priority: str = "", status: typing.Literal["resolved", "acknowledged"] = "", resolution: str = "", + client: str = "", + client_url: str = "", **kwargs: dict, ): """ @@ -729,6 +744,8 @@ def _notify( source (str): Source field for events API status (str): Status for incident updates (resolved/acknowledged) resolution (str): Resolution note for resolved incidents + client (str): Name of the monitoring client triggering this event (Events API v2 only) + client_url (str): URL of the monitoring client triggering this event (Events API v2 only) kwargs (dict): Additional event/incident fields """ if not routing_key: # If routing_key not specified in workflow, fallback to config routing_key @@ -741,6 +758,8 @@ def _notify( routing_key=routing_key, source=source, severity=severity, + client=client or None, + client_url=client_url or None, **kwargs, ) else: diff --git a/tests/test_pagerduty_provider.py b/tests/test_pagerduty_provider.py index 17504a1ff8..59292b011f 100644 --- a/tests/test_pagerduty_provider.py +++ b/tests/test_pagerduty_provider.py @@ -1,6 +1,7 @@ import json import os import unittest +from unittest.mock import MagicMock from keep.api.models.db.incident import IncidentSeverity, IncidentStatus from keep.providers.pagerduty_provider.pagerduty_provider import PagerdutyProvider @@ -18,6 +19,51 @@ def test_format_alert(self): self.assertEqual(formatted_alert.status, IncidentStatus.FIRING) self.assertEqual(formatted_alert.alert_sources, ["pagerduty"]) + def _make_provider(self): + """Create a minimal PagerdutyProvider for unit testing _build_alert.""" + ctx = MagicMock() + ctx.event_context = None + config = MagicMock() + config.authentication = {"routing_key": "test-key"} + provider = object.__new__(PagerdutyProvider) + provider.context_manager = ctx + provider.logger = MagicMock() + return provider + + def test_build_alert_includes_client_fields(self): + provider = self._make_provider() + payload = provider._build_alert( + title="Test alert", + routing_key="test-routing-key", + client="My Monitoring Tool", + client_url="https://monitoring.example.com", + ) + self.assertEqual(payload["client"], "My Monitoring Tool") + self.assertEqual(payload["client_url"], "https://monitoring.example.com") + # client and client_url should be top-level, not inside payload.payload + self.assertNotIn("client", payload["payload"]) + self.assertNotIn("client_url", payload["payload"]) + + def test_build_alert_omits_client_fields_when_not_provided(self): + provider = self._make_provider() + payload = provider._build_alert( + title="Test alert", + routing_key="test-routing-key", + ) + self.assertNotIn("client", payload) + self.assertNotIn("client_url", payload) + + def test_build_alert_images_and_links_in_payload(self): + provider = self._make_provider() + payload = provider._build_alert( + title="Test alert", + routing_key="test-routing-key", + images=[{"src": "https://example.com/img.png"}], + links=[{"href": "https://example.com", "text": "Example"}], + ) + self.assertIn("images", payload["payload"]) + self.assertIn("links", payload["payload"]) + if __name__ == "__main__": unittest.main()