Skip to content

Commit 8e37373

Browse files
committed
improve resend slack notification request
1 parent 1a93a41 commit 8e37373

File tree

3 files changed

+154
-50
lines changed

3 files changed

+154
-50
lines changed

src/plugins/slack/actions/actions.py

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,42 @@
1-
from typing import Any
1+
import logging
2+
from typing import Any, cast
23

34
import registry
4-
from models import Alert, Notification, NotificationStatus
5+
from models import Monitor, Notification, NotificationStatus
56
from utils.async_tools import do_concurrently
67

7-
from ..notifications.slack_notification import clear_slack_notification
8+
from ..notifications import slack_notification
9+
10+
_logger = logging.getLogger("plugin.slack.actions")
11+
12+
13+
async def _resend_notification(notification: Notification):
14+
"""Clear a single notification and send it again"""
15+
await registry.wait_monitor_loaded(notification.monitor_id)
16+
17+
monitor = await Monitor.get_by_id(notification.monitor_id)
18+
if monitor is None:
19+
return # pragma: no cover
20+
21+
# Get the SlackNotification option from the monitor code
22+
notification_option = None
23+
for notification_option in monitor.code.notification_options:
24+
if isinstance(notification_option, slack_notification.SlackNotification):
25+
break
26+
if notification_option is None:
27+
_logger.warning(f"No 'SlackNotification' option for {monitor}")
28+
return
29+
30+
# Clear the notification and send it again
31+
await slack_notification.clear_slack_notification(notification)
32+
await slack_notification.slack_notification(
33+
event_payload={
34+
"event_data": {
35+
"id": notification.alert_id,
36+
}
37+
},
38+
notification_options=cast(slack_notification.SlackNotification, notification_option),
39+
)
840

941

1042
async def resend_notifications(message_payload: dict[Any, Any]):
@@ -17,18 +49,7 @@ async def resend_notifications(message_payload: dict[Any, Any]):
1749
Notification.data["channel"].astext == message_payload["slack_channel"],
1850
)
1951

20-
if len(notifications) == 0:
21-
return
22-
23-
monitors_ids = {notification.monitor_id for notification in notifications}
24-
for monitor_id in monitors_ids:
25-
await registry.wait_monitor_loaded(monitor_id)
26-
2752
await do_concurrently(*[
28-
clear_slack_notification(notification)
53+
_resend_notification(notification)
2954
for notification in notifications
3055
])
31-
32-
alert_ids = list({notification.alert_id for notification in notifications})
33-
alerts = await Alert.get_all(Alert.id.in_(alert_ids))
34-
await do_concurrently(*[alert.update() for alert in alerts])

src/plugins/slack/services/websocket.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from .. import slack
1212
from .pattern_match import get_message_request
1313

14-
_logger = logging.getLogger("slack_websocket")
14+
_logger = logging.getLogger("plugin.slack.websocket")
1515

1616
_handler: AsyncSocketModeHandler | None
1717

tests/plugins/slack/actions/test_actions.py

Lines changed: 117 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,87 @@
22

33
import pytest
44

5-
from plugins.slack.actions import resend_notifications
65
import registry as registry
7-
from models import Alert, Monitor, Notification
6+
import plugins.slack.actions.actions as actions
7+
import plugins.slack.notifications.slack_notification as slack_notification
8+
from models import Alert, Monitor, Notification, NotificationStatus
9+
from tests.test_utils import assert_message_in_log
810

911
pytestmark = pytest.mark.asyncio(loop_scope="session")
1012

1113

12-
async def test_resend_notifications(mocker, sample_monitor: Monitor):
14+
async def test_resend_notification_no_slack_notification_option(
15+
caplog, monkeypatch, sample_monitor: Monitor
16+
):
17+
"""'_resend_notification' should just return when there is no SlackNotification option"""
18+
clear_notification_mock = AsyncMock()
19+
monkeypatch.setattr(slack_notification, "clear_slack_notification", clear_notification_mock)
20+
slack_notification_mock = AsyncMock()
21+
monkeypatch.setattr(slack_notification, "slack_notification", slack_notification_mock)
22+
monkeypatch.setattr(sample_monitor.code, "notification_options", [], raising=False)
23+
24+
alert = await Alert.create(
25+
monitor_id=sample_monitor.id,
26+
priority=2,
27+
)
28+
notification = await Notification.create(
29+
monitor_id=alert.monitor_id,
30+
alert_id=alert.id,
31+
target="slack",
32+
data={"channel": "channel", "ts": "123"},
33+
)
34+
35+
await actions._resend_notification(notification)
36+
37+
clear_notification_mock.assert_not_called()
38+
slack_notification_mock.assert_not_called()
39+
assert_message_in_log(caplog, f"No 'SlackNotification' option for {sample_monitor}")
40+
41+
42+
async def test_resend_notification(
43+
mocker, monkeypatch, sample_monitor: Monitor
44+
):
45+
"""'_resend_notification' should clear the notification and send it again"""
46+
wait_monitor_loaded_spy: AsyncMock = mocker.spy(registry, "wait_monitor_loaded")
47+
clear_notification_mock = AsyncMock()
48+
monkeypatch.setattr(slack_notification, "clear_slack_notification", clear_notification_mock)
49+
slack_notification_mock = AsyncMock()
50+
monkeypatch.setattr(slack_notification, "slack_notification", slack_notification_mock)
51+
notification_options = slack_notification.SlackNotification(
52+
channel="channel",
53+
title="title",
54+
issues_fields=["col"],
55+
min_priority_to_send=3,
56+
mention="mention",
57+
min_priority_to_mention=2,
58+
)
59+
monkeypatch.setattr(
60+
sample_monitor.code, "notification_options", [notification_options], raising=False
61+
)
62+
63+
alert = await Alert.create(
64+
monitor_id=sample_monitor.id,
65+
priority=2,
66+
)
67+
notification = await Notification.create(
68+
monitor_id=alert.monitor_id,
69+
alert_id=alert.id,
70+
target="slack",
71+
data={"channel": "channel", "ts": "123"},
72+
)
73+
74+
await actions._resend_notification(notification)
75+
76+
wait_monitor_loaded_spy.assert_awaited_once_with(sample_monitor.id)
77+
clear_notification_mock.assert_awaited_once()
78+
slack_notification_mock.assert_awaited_once()
79+
80+
81+
async def test_resend_notifications(monkeypatch, sample_monitor: Monitor):
1382
"""'resend_notifications' should clear all notifications for the provided channel and
1483
update all alerts"""
15-
wait_monitor_loaded_spy: AsyncMock = mocker.spy(registry, "wait_monitor_loaded")
16-
alert_update_spy: AsyncMock = mocker.spy(Alert, "update")
84+
resend_notification_mock = AsyncMock()
85+
monkeypatch.setattr(actions, "_resend_notification", resend_notification_mock)
1786

1887
alert_test_channel = await Alert.create(
1988
monitor_id=sample_monitor.id,
@@ -30,61 +99,75 @@ async def test_resend_notifications(mocker, sample_monitor: Monitor):
3099
monitor_id=sample_monitor.id,
31100
priority=2,
32101
)
33-
notification_other_channel = await Notification.create(
102+
await Notification.create(
34103
monitor_id=alert_other_channel.monitor_id,
35104
alert_id=alert_other_channel.id,
36105
target="slack",
37106
data={"channel": "test_resend_notification_other", "ts": "123"},
38107
)
39108

40-
await resend_notifications(
109+
await actions.resend_notifications(
41110
{"slack_channel": "test_resend_notification"}
42111
)
43112

44-
await notification_test_channel.refresh()
45-
assert notification_test_channel.data == {"channel": None, "ts": None, "mention_ts": None}
46-
47-
await notification_other_channel.refresh()
48-
assert notification_other_channel.data == {
49-
"channel": "test_resend_notification_other",
50-
"ts": "123",
51-
}
52-
53-
wait_monitor_loaded_spy.assert_awaited_once_with(sample_monitor.id)
54-
alert_update_spy.assert_awaited_once()
55-
call_args = alert_update_spy.call_args
56-
assert call_args[0][0].id == alert_test_channel.id
113+
assert resend_notification_mock.await_args is not None
114+
assert resend_notification_mock.await_args[0][0].id == notification_test_channel.id
57115

58116

59117
async def test_resend_notifications_no_notifications_in_channel(
60-
mocker,
61-
sample_monitor: Monitor
118+
monkeypatch, sample_monitor: Monitor
62119
):
63120
"""'resend_notifications' should just return when there are no notifications for the
64121
provided channel"""
65-
wait_monitor_loaded_spy: AsyncMock = mocker.spy(registry, "wait_monitor_loaded")
66-
alert_update_spy: AsyncMock = mocker.spy(Alert, "update")
122+
resend_notification_mock = AsyncMock()
123+
monkeypatch.setattr(actions, "_resend_notification", resend_notification_mock)
67124

68125
alert_other_channel = await Alert.create(
69126
monitor_id=sample_monitor.id,
70127
priority=2,
71128
)
72-
notification_other_channel = await Notification.create(
129+
await Notification.create(
73130
monitor_id=alert_other_channel.monitor_id,
74131
alert_id=alert_other_channel.id,
75132
target="slack",
76133
data={"channel": "test_resend_notification_other", "ts": "123"},
77134
)
78135

79-
await resend_notifications(
80-
{"slack_channel": "test_resend_notification"}
136+
await actions.resend_notifications(
137+
{"slack_channel": "test_resend_notifications_no_notifications_in_channel"}
138+
)
139+
140+
resend_notification_mock.assert_not_called()
141+
142+
143+
async def test_resend_notifications_ignore_other_notifications(
144+
monkeypatch, sample_monitor: Monitor
145+
):
146+
"""'resend_notifications' should just not resend notifications that are not Slack notifications
147+
or are not active"""
148+
resend_notification_mock = AsyncMock()
149+
monkeypatch.setattr(actions, "_resend_notification", resend_notification_mock)
150+
151+
alert_other_channel = await Alert.create(
152+
monitor_id=sample_monitor.id,
153+
priority=2,
154+
)
155+
await Notification.create(
156+
monitor_id=alert_other_channel.monitor_id,
157+
alert_id=alert_other_channel.id,
158+
target="other_target",
159+
data={"channel": "test_resend_notifications_no_notifications_in_channel", "ts": "123"},
160+
)
161+
await Notification.create(
162+
monitor_id=alert_other_channel.monitor_id,
163+
alert_id=alert_other_channel.id,
164+
target="slack",
165+
data={"channel": "test_resend_notifications_no_notifications_in_channel", "ts": "123"},
166+
status=NotificationStatus.closed,
81167
)
82168

83-
await notification_other_channel.refresh()
84-
assert notification_other_channel.data == {
85-
"channel": "test_resend_notification_other",
86-
"ts": "123",
87-
}
169+
await actions.resend_notifications(
170+
{"slack_channel": "test_resend_notifications_no_notifications_in_channel"}
171+
)
88172

89-
wait_monitor_loaded_spy.assert_not_called()
90-
alert_update_spy.assert_not_called()
173+
resend_notification_mock.assert_not_called()

0 commit comments

Comments
 (0)