Skip to content

Commit a86b840

Browse files
committed
create event payload interface
1 parent 3cabe98 commit a86b840

File tree

10 files changed

+152
-112
lines changed

10 files changed

+152
-112
lines changed

docs/monitor.md

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -268,24 +268,21 @@ Reactions are defined as a list of **async functions** that are triggered when s
268268
Below is an example of defining a reaction function that responds to the creation of a new issue:
269269

270270
```python
271-
async def reaction_issue_created(event_payload: dict[str, Any]) -> None:
271+
from monitor_utils import EventPayload
272+
273+
274+
async def reaction_issue_created(event_payload: EventPayload) -> None:
272275
# Do something
273276
```
274277

278+
### Event payload
275279
The event payload provided to each reaction function contains structured information about the event source, details, and any additional context. This allows reaction functions to respond precisely to specific events.
276-
277-
```python
278-
{
279-
"event_source": "Specifies the model that generated the event (e.g., `monitor`, `issue`, `alert`)."
280-
"event_source_id": "The unique identifier of the object that triggered the event (e.g., `monitor_id`, `issue_id`)."
281-
"event_source_monitor_id": "The monitor ID associated with the object that generated the event."
282-
"event_name": "Name of the event, such as `alert_created` or `issue_solved`.",
283-
"event_data": {
284-
"Object with detailed information about the event source."
285-
},
286-
"extra_payload": "Additional information that may be sent along with the event, providing further context.",
287-
}
288-
```
280+
- `event_source`: Specifies the model that generated the event (e.g., `monitor`, `issue`, `alert`).
281+
- `event_source_id`: The unique identifier of the object that triggered the event (e.g., `monitor_id`, `issue_id`).
282+
- `event_source_monitor_id`: The monitor ID associated with the object that generated the event.
283+
- `event_name`: Name of the event, such as `alert_created` or `issue_solved`.
284+
- `event_data`: Dictionary with detailed information about the event source.
285+
- `extra_payload`: Additional information that may be sent along with the event, providing further context.
289286

290287
Reaction functions can be assigned to specific events when creating an instance of `ReactionOptions`. This configuration ensures that designated functions are triggered whenever specified events occur.
291288

internal_monitors/active_notification_alert_solved/active_notification_alert_solved.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
"""
66

77
import os
8-
from typing import Any, TypedDict, cast
8+
from typing import TypedDict, cast
99

1010
from databases import query_application
1111
from models import Notification, NotificationStatus
1212
from monitor_utils import (
1313
AgeRule,
1414
AlertOptions,
1515
AlertPriority,
16+
EventPayload,
1617
IssueOptions,
1718
MonitorOptions,
1819
PriorityLevels,
@@ -69,9 +70,9 @@ def is_solved(issue_data: IssueDataType) -> bool:
6970
# Reactions
7071

7172

72-
async def close_notification(event_payload: dict[str, Any]) -> None:
73+
async def close_notification(event_payload: EventPayload) -> None:
7374
"""Fix the notification by closing it"""
74-
issue_object = event_payload["event_data"]
75+
issue_object = event_payload.event_data
7576
notification = await Notification.get_by_id(issue_object["data"]["notification_id"])
7677
if notification:
7778
await notification.close()

src/components/executor/reaction_handler.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from base_exception import BaseSentinelaException
1212
from configs import configs
1313
from models import Monitor
14+
from options import EventPayload
1415

1516
_logger = logging.getLogger("reaction_handler")
1617

@@ -34,9 +35,9 @@
3435
async def run(message: dict[Any, Any]) -> None:
3536
"""Process a message with type 'event' using the monitor's defined list of reactions for the
3637
event. The execution timeout is for each function individually"""
37-
message_payload = message["payload"]
38-
monitor_id = message_payload["event_source_monitor_id"]
39-
event_name = message_payload["event_name"]
38+
event_payload = EventPayload(**message["payload"])
39+
monitor_id = event_payload.event_source_monitor_id
40+
event_name = event_payload.event_name
4041

4142
monitor = await Monitor.get_by_id(monitor_id)
4243
if monitor is None:
@@ -68,19 +69,19 @@ async def run(message: dict[Any, Any]) -> None:
6869
reaction_execution_time = prometheus_reaction_execution_time.labels(**prometheus_labels)
6970
try:
7071
with reaction_execution_time.time():
71-
await asyncio.wait_for(reaction(message_payload), configs.executor_reaction_timeout)
72+
await asyncio.wait_for(reaction(event_payload), configs.executor_reaction_timeout)
7273
except asyncio.TimeoutError:
7374
prometheus_reaction_timeout_count.labels(**prometheus_labels).inc()
7475
_logger.error(
7576
f"Timed out executing reaction '{reaction_name}' with payload "
76-
f"'{json.dumps(message_payload)}'"
77+
f"'{json.dumps(event_payload.to_dict())}'"
7778
)
7879
except BaseSentinelaException as e:
7980
raise e
8081
except Exception:
8182
prometheus_reaction_error_count.labels(**prometheus_labels).inc()
8283
_logger.error(
8384
f"Error executing reaction '{reaction_name}' with payload "
84-
f"'{json.dumps(message_payload)}'"
85+
f"'{json.dumps(event_payload.to_dict())}'"
8586
)
8687
_logger.error(traceback.format_exc().strip())

src/monitor_utils/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
AgeRule,
99
AlertOptions,
1010
CountRule,
11+
EventPayload,
1112
IssueOptions,
1213
MonitorOptions,
1314
PriorityLevels,
@@ -22,6 +23,7 @@
2223
"AlertOptions",
2324
"AlertPriority",
2425
"CountRule",
26+
"EventPayload",
2527
"IssueOptions",
2628
"MonitorOptions",
2729
"PriorityLevels",
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
from typing import Any, Callable, Coroutine, Protocol, runtime_checkable
1+
from typing import Protocol, runtime_checkable
22

3-
type async_function = Callable[[dict[str, Any]], Coroutine[Any, Any, Any]]
3+
from options import reaction_function_type
44

55

66
@runtime_checkable
77
class BaseNotification(Protocol):
88
min_priority_to_send: int = 5
99

10-
def reactions_list(self) -> list[tuple[str, list[async_function]]]: ...
10+
def reactions_list(self) -> list[tuple[str, list[reaction_function_type]]]: ...

src/options/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,24 @@
22
AgeRule,
33
AlertOptions,
44
CountRule,
5+
EventPayload,
56
IssueOptions,
67
MonitorOptions,
78
PriorityLevels,
89
ReactionOptions,
910
ValueRule,
11+
reaction_function_type,
1012
)
1113

1214
__all__ = [
1315
"AgeRule",
1416
"AlertOptions",
1517
"CountRule",
18+
"EventPayload",
1619
"IssueOptions",
1720
"MonitorOptions",
1821
"PriorityLevels",
1922
"ReactionOptions",
2023
"ValueRule",
24+
"reaction_function_type",
2125
]

src/options/options.py

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,40 @@ class AlertOptions:
120120
dismiss_acknowledge_on_new_issues: bool = False
121121

122122

123-
reaction_function_type = Callable[[dict[str, Any]], Coroutine[Any, Any, Any]]
123+
@dataclass
124+
class EventPayload:
125+
"""The event payload provided to each reaction function contains structured information about
126+
the event source, details, and any additional context.
127+
- `event_source`: Specifies the model that generated the event (e.g., `monitor`, `issue`,
128+
`alert`).
129+
- `event_source_id`: The unique identifier of the object that triggered the event (e.g.,
130+
`monitor_id`, `issue_id`).
131+
- `event_source_monitor_id`: The monitor ID associated with the object that generated the event.
132+
- `event_name`: Name of the event, such as `alert_created` or `issue_solved`.
133+
- `event_data`: Object with detailed information about the event source.
134+
- `extra_payload`: Additional information that may be sent along with the event, providing
135+
further context.
136+
"""
137+
138+
event_source: str
139+
event_source_id: int
140+
event_source_monitor_id: int
141+
event_name: str
142+
event_data: dict[str, Any]
143+
extra_payload: dict[str, Any] | None = None
144+
145+
def to_dict(self) -> dict[str, Any]:
146+
return {
147+
"event_source": self.event_source,
148+
"event_source_id": self.event_source_id,
149+
"event_source_monitor_id": self.event_source_monitor_id,
150+
"event_name": self.event_name,
151+
"event_data": self.event_data,
152+
"extra_payload": self.extra_payload,
153+
}
154+
155+
156+
reaction_function_type = Callable[[EventPayload], Coroutine[Any, Any, Any]]
124157

125158

126159
@dataclass
@@ -137,23 +170,6 @@ class ReactionOptions:
137170
event source, details, and any additional context. This allows reaction functions to respond
138171
precisely to specific events.
139172
140-
```python
141-
{
142-
"event_source": "Specifies the model that generated the event (e.g., `monitor`, `issue`,
143-
`alert`)."
144-
"event_source_id": "The unique identifier of the object that triggered the event (e.g.,
145-
`monitor_id`, `issue_id`)."
146-
"event_source_monitor_id": "The monitor ID associated with the object that generated the
147-
event."
148-
"event_name": "Name of the event, such as `alert_created` or `issue_solved`.",
149-
"event_data": {
150-
"Object with detailed information about the event source."
151-
},
152-
"extra_payload": "Additional information that may be sent along with the event, providing
153-
further context.",
154-
}
155-
```
156-
157173
Check the documentation for a more detailed explanation of each event.
158174
"""
159175

src/plugins/slack/notifications/slack_notification.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,20 @@
22
import logging
33
import os
44
from functools import partial
5-
from typing import Any, Callable, Coroutine
5+
from typing import Any
66

77
from pydantic.dataclasses import dataclass
88
from pytz import timezone
99
from tabulate import tabulate
1010

1111
from configs import configs
1212
from models import Alert, AlertPriority, AlertStatus, Issue, IssueStatus, Monitor, Notification
13+
from options import EventPayload, reaction_function_type
1314

1415
from .. import slack
1516

1617
_logger = logging.getLogger("plugin.slack.notifications")
1718

18-
type async_function = Callable[[dict[str, Any]], Coroutine[Any, Any, Any]]
19-
2019
RESEND_ERRORS = [
2120
"message_not_found",
2221
"cant_update_message",
@@ -56,7 +55,7 @@ class SlackNotification:
5655
min_priority_to_mention: int = AlertPriority.moderate
5756
issue_show_limit: int = 10
5857

59-
def reactions_list(self) -> list[tuple[str, list[async_function]]]:
58+
def reactions_list(self) -> list[tuple[str, list[reaction_function_type]]]:
6059
"""Get a list of events that the notification will react to"""
6160
handle_notification_function = partial(slack_notification, notification_options=self)
6261
return [
@@ -387,11 +386,11 @@ async def notification_mention(
387386

388387

389388
async def slack_notification(
390-
event_payload: dict[str, Any],
389+
event_payload: EventPayload,
391390
notification_options: SlackNotification,
392391
) -> None:
393392
"""Handle the Slack notification for an alert"""
394-
alert_data = event_payload["event_data"]
393+
alert_data = event_payload.event_data
395394

396395
alert = await Alert.get_by_id(alert_data["id"])
397396
if alert is None:

0 commit comments

Comments
 (0)