-
Notifications
You must be signed in to change notification settings - Fork 285
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
21 changed files
with
542 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import logging | ||
|
||
from rest_framework import status | ||
|
||
from apps.alerts.models import AlertGroup | ||
from apps.alerts.representative import AlertGroupAbstractRepresentative | ||
from apps.mattermost.alert_rendering import MattermostMessageRenderer | ||
from apps.mattermost.client import MattermostClient | ||
from apps.mattermost.exceptions import MattermostAPIException, MattermostAPITokenInvalid | ||
from apps.mattermost.tasks import on_alert_group_action_triggered_async, on_create_alert_async | ||
|
||
logger = logging.getLogger(__name__) | ||
logger.setLevel(logging.DEBUG) | ||
|
||
|
||
class AlertGroupMattermostRepresentative(AlertGroupAbstractRepresentative): | ||
def __init__(self, log_record) -> None: | ||
self.log_record = log_record | ||
|
||
def is_applicable(self): | ||
from apps.mattermost.models import MattermostChannel | ||
|
||
organization = self.log_record.alert_group.channel.organization | ||
handler_exists = self.log_record.type in self.get_handler_map().keys() | ||
|
||
mattermost_channels = MattermostChannel.objects.filter(organization=organization) | ||
return handler_exists and mattermost_channels.exists() | ||
|
||
@staticmethod | ||
def get_handler_map(): | ||
from apps.alerts.models import AlertGroupLogRecord | ||
|
||
return { | ||
AlertGroupLogRecord.TYPE_ACK: "alert_group_action", | ||
AlertGroupLogRecord.TYPE_UN_ACK: "alert_group_action", | ||
AlertGroupLogRecord.TYPE_AUTO_UN_ACK: "alert_group_action", | ||
AlertGroupLogRecord.TYPE_RESOLVED: "alert_group_action", | ||
AlertGroupLogRecord.TYPE_UN_RESOLVED: "alert_group_action", | ||
AlertGroupLogRecord.TYPE_ACK_REMINDER_TRIGGERED: "alert_group_action", | ||
AlertGroupLogRecord.TYPE_SILENCE: "alert_group_action", | ||
AlertGroupLogRecord.TYPE_UN_SILENCE: "alert_group_action", | ||
AlertGroupLogRecord.TYPE_ATTACHED: "alert_group_action", | ||
AlertGroupLogRecord.TYPE_UNATTACHED: "alert_group_action", | ||
} | ||
|
||
def on_alert_group_action(self, alert_group: AlertGroup): | ||
logger.info(f"Update mattermost message for alert_group {alert_group.pk}") | ||
payload = MattermostMessageRenderer(alert_group).render_alert_group_message() | ||
mattermost_message = alert_group.mattermost_message | ||
try: | ||
client = MattermostClient() | ||
client.update_post(post_id=mattermost_message.post_id, data=payload) | ||
except MattermostAPITokenInvalid: | ||
logger.error(f"Mattermost API token is invalid could not create post for alert {alert_group.pk}") | ||
except MattermostAPIException as ex: | ||
logger.error(f"Mattermost API error {ex}") | ||
if ex.status not in [status.HTTP_401_UNAUTHORIZED]: | ||
raise ex | ||
|
||
@staticmethod | ||
def on_create_alert(**kwargs): | ||
alert_pk = kwargs["alert"] | ||
on_create_alert_async.apply_async((alert_pk,)) | ||
|
||
@staticmethod | ||
def on_alert_group_action_triggered(**kwargs): | ||
from apps.alerts.models import AlertGroupLogRecord | ||
|
||
log_record = kwargs["log_record"] | ||
if isinstance(log_record, AlertGroupLogRecord): | ||
log_record_id = log_record.pk | ||
else: | ||
log_record_id = log_record | ||
on_alert_group_action_triggered_async.apply_async((log_record_id,)) | ||
|
||
def get_handler(self): | ||
handler_name = self.get_handler_name() | ||
logger.info(f"Using '{handler_name}' handler to process alert action in mattermost") | ||
if hasattr(self, handler_name): | ||
handler = getattr(self, handler_name) | ||
else: | ||
handler = None | ||
|
||
return handler | ||
|
||
def get_handler_name(self): | ||
return self.HANDLER_PREFIX + self.get_handler_map()[self.log_record.type] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
from apps.alerts.incident_appearance.renderers.base_renderer import AlertBaseRenderer, AlertGroupBaseRenderer | ||
from apps.alerts.incident_appearance.templaters.alert_templater import AlertTemplater | ||
from apps.alerts.models import Alert, AlertGroup | ||
from apps.mattermost.utils import MattermostEventAuthenticator | ||
from common.api_helpers.utils import create_engine_url | ||
from common.utils import is_string_with_visible_characters, str_or_backup | ||
|
||
|
||
class MattermostMessageRenderer: | ||
def __init__(self, alert_group: AlertGroup): | ||
self.alert_group = alert_group | ||
|
||
def render_alert_group_message(self): | ||
attachments = AlertGroupMattermostRenderer(self.alert_group).render_alert_group_attachments() | ||
return {"props": {"attachments": attachments}} | ||
|
||
|
||
class AlertMattermostTemplater(AlertTemplater): | ||
RENDER_FOR_MATTERMOST = "mattermost" | ||
|
||
def _render_for(self) -> str: | ||
return self.RENDER_FOR_MATTERMOST | ||
|
||
|
||
class AlertMattermostRenderer(AlertBaseRenderer): | ||
def __init__(self, alert: Alert): | ||
super().__init__(alert) | ||
self.channel = alert.group.channel | ||
|
||
@property | ||
def templater_class(self): | ||
return AlertMattermostTemplater | ||
|
||
def render_alert_attachments(self): | ||
attachments = [] | ||
title = str_or_backup(self.templated_alert.title, "Alert") | ||
message = "" | ||
if is_string_with_visible_characters(self.templated_alert.message): | ||
message = self.templated_alert.message | ||
attachments.append( | ||
{ | ||
"fallback": "{}: {}".format(self.channel.get_integration_display(), self.alert.title), | ||
"title": title, | ||
"title_link": self.templated_alert.source_link, | ||
"text": message, | ||
"image_url": self.templated_alert.image_url, | ||
} | ||
) | ||
return attachments | ||
|
||
|
||
class AlertGroupMattermostRenderer(AlertGroupBaseRenderer): | ||
def __init__(self, alert_group: AlertGroup): | ||
super().__init__(alert_group) | ||
|
||
self.alert_renderer = self.alert_renderer_class(self.alert_group.alerts.last()) | ||
|
||
@property | ||
def alert_renderer_class(self): | ||
return AlertMattermostRenderer | ||
|
||
def render_alert_group_attachments(self): | ||
attachments = self.alert_renderer.render_alert_attachments() | ||
alert_group = self.alert_group | ||
|
||
if alert_group.resolved: | ||
attachments.append( | ||
{ | ||
"fallback": "Resolved...", | ||
"text": alert_group.get_resolve_text(), | ||
} | ||
) | ||
elif alert_group.acknowledged: | ||
attachments.append( | ||
{ | ||
"fallback": "Acknowledged...", | ||
"text": alert_group.get_acknowledge_text(), | ||
} | ||
) | ||
|
||
# append buttons to the initial attachment | ||
attachments[0]["actions"] = self._get_buttons_attachments() | ||
|
||
return self._set_attachments_color(attachments) | ||
|
||
def _get_buttons_attachments(self): | ||
actions = [] | ||
|
||
def _make_actions(id, name, token): | ||
return { | ||
"id": id, | ||
"name": name, | ||
"integration": { | ||
"url": create_engine_url("api/internal/v1/mattermost/event/"), | ||
"context": { | ||
"action": id, | ||
"token": token, | ||
}, | ||
}, | ||
} | ||
|
||
token = MattermostEventAuthenticator.create_token(organization=self.alert_group.channel.organization) | ||
if not self.alert_group.resolved: | ||
if self.alert_group.acknowledged: | ||
actions.append(_make_actions("unacknowledge", "Unacknowledge", token)) | ||
else: | ||
actions.append(_make_actions("acknowledge", "Acknonwledge", token)) | ||
|
||
if self.alert_group.resolved: | ||
actions.append(_make_actions("unresolve", "Unresolve", token)) | ||
else: | ||
actions.append(_make_actions("resolve", "Resolve", token)) | ||
|
||
return actions | ||
|
||
def _set_attachments_color(self, attachments): | ||
color = "#a30200" # danger | ||
if self.alert_group.silenced: | ||
color = "#dddddd" # slack-grey | ||
if self.alert_group.acknowledged: | ||
color = "#daa038" # warning | ||
if self.alert_group.resolved: | ||
color = "#2eb886" # good | ||
|
||
for attachment in attachments: | ||
attachment["color"] = color | ||
|
||
return attachments |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
from .channel import MattermostChannel # noqa: F401 | ||
from .message import MattermostMessage # noqa F401 | ||
from .user import MattermostUser # noqa F401 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.