Add slack button to show log report (#4641)

# What this PR does

Add a button to show an alert group log report in Slack. After deployed
we can remove it from the thread as requested in
https://github.com/grafana/oncall/issues/3849

<img width="534" alt="Screenshot 2024-07-09 at 11 29 48 PM"
src="https://github.com/grafana/oncall/assets/2262529/a17cd366-e97b-4e61-bf06-172fb4737d56">
<img width="673" alt="Screenshot 2024-07-09 at 11 29 57 PM"
src="https://github.com/grafana/oncall/assets/2262529/a67dbe49-1972-45e6-a8b1-ce03ffe6951b">




## Which issue(s) this PR closes

Closes [issue link here]

<!--
*Note*: if you have more than one GitHub issue that this PR closes, be
sure to preface
each issue link with a [closing
keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue).
This ensures that the issue(s) are auto-closed once the PR has been
merged.
-->

## Checklist

- [ ] Unit, integration, and e2e (if applicable) tests updated
- [ ] Documentation added (or `pr:no public docs` PR label added if not
required)
- [ ] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
    show up in the autogenerated release notes.
This commit is contained in:
Ildar Iskhakov 2024-07-12 17:49:09 +08:00 committed by GitHub
parent 324b0c4286
commit deff52df07
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 101 additions and 13 deletions

View file

@ -250,6 +250,10 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
"action_id": ScenarioStep.get_step("declare_incident", "DeclareIncidentStep").routing_uid(),
}
show_timeline_button = _make_button(
":blue_book: Show Timeline", "OpenAlertGroupTimelineDialogStep", "alertgroup_timeline"
)
buttons = []
if not alert_group.is_maintenance_incident:
if not alert_group.resolved:
@ -282,6 +286,8 @@ class AlertGroupSlackRenderer(AlertGroupBaseRenderer):
if not alert_group.resolved:
buttons.append(resolve_button)
buttons.append(show_timeline_button)
return [{"type": "actions", "elements": buttons}]
def _get_invitation_attachment(self):

View file

@ -0,0 +1,78 @@
import typing
from apps.api.permissions import RBACPermission
from apps.slack.chatops_proxy_routing import make_private_metadata
from apps.slack.scenarios import scenario_step
from apps.slack.scenarios.slack_renderer import AlertGroupLogSlackRenderer
from apps.slack.types import (
Block,
BlockActionType,
EventPayload,
InteractiveMessageActionType,
ModalView,
PayloadType,
ScenarioRoute,
)
from .step_mixins import AlertGroupActionsMixin
if typing.TYPE_CHECKING:
from apps.slack.models import SlackTeamIdentity, SlackUserIdentity
class OpenAlertGroupTimelineDialogStep(AlertGroupActionsMixin, scenario_step.ScenarioStep):
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
def process_scenario(
self,
slack_user_identity: "SlackUserIdentity",
slack_team_identity: "SlackTeamIdentity",
payload: EventPayload,
) -> None:
alert_group = self.get_alert_group(slack_team_identity, payload)
if not self.is_authorized(alert_group):
self.open_unauthorized_warning(payload)
return
private_metadata = {
"organization_id": self.organization.pk,
"alert_group_pk": alert_group.pk,
"message_ts": payload.get("message_ts") or payload["container"]["message_ts"],
}
alert_receive_channel = alert_group.channel
past_log_report = AlertGroupLogSlackRenderer.render_alert_group_past_log_report_text(alert_group)
future_log_report = AlertGroupLogSlackRenderer.render_alert_group_future_log_report_text(alert_group)
blocks: typing.List[Block.Section] = []
if past_log_report:
blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": past_log_report}})
if future_log_report:
blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": future_log_report}})
view: ModalView = {
"blocks": blocks,
"type": "modal",
"title": {
"type": "plain_text",
"text": "Alert group log",
},
"private_metadata": make_private_metadata(private_metadata, alert_receive_channel.organization),
}
self._slack_client.views_open(trigger_id=payload["trigger_id"], view=view)
STEPS_ROUTING: ScenarioRoute.RoutingSteps = [
{
"payload_type": PayloadType.INTERACTIVE_MESSAGE,
"action_type": InteractiveMessageActionType.BUTTON,
"action_name": OpenAlertGroupTimelineDialogStep.routing_uid(),
"step": OpenAlertGroupTimelineDialogStep,
},
{
"payload_type": PayloadType.BLOCK_ACTIONS,
"block_action_type": BlockActionType.BUTTON,
"block_action_id": OpenAlertGroupTimelineDialogStep.routing_uid(),
"step": OpenAlertGroupTimelineDialogStep,
},
]

View file

@ -10,16 +10,13 @@ if typing.TYPE_CHECKING:
class AlertGroupLogSlackRenderer:
@staticmethod
def render_incident_log_report_for_slack(alert_group: "AlertGroup"):
def render_alert_group_past_log_report_text(alert_group: "AlertGroup"):
from apps.alerts.models import AlertGroupLogRecord
from apps.base.models import UserNotificationPolicyLogRecord
log_builder = IncidentLogBuilder(alert_group)
all_log_records = log_builder.get_log_records_list()
attachments = []
# get rendered logs
result = ""
for log_record in all_log_records: # list of AlertGroupLogRecord and UserNotificationPolicyLogRecord logs
if type(log_record) is AlertGroupLogRecord:
@ -27,14 +24,12 @@ class AlertGroupLogSlackRenderer:
elif type(log_record) is UserNotificationPolicyLogRecord:
result += f"{log_record.rendered_notification_log_line(for_slack=True)}\n"
attachments.append(
{
"text": result,
}
)
result = ""
return result
# check if escalation or invitation active
@staticmethod
def render_alert_group_future_log_report_text(alert_group: "AlertGroup"):
log_builder = IncidentLogBuilder(alert_group)
result = ""
if not (alert_group.resolved or alert_group.wiped_at or alert_group.root_alert_group):
escalation_policies_plan = log_builder.get_incident_escalation_plan(for_slack=True)
if escalation_policies_plan:
@ -43,11 +38,18 @@ class AlertGroupLogSlackRenderer:
for time in sorted(escalation_policies_plan):
for plan_line in escalation_policies_plan[time]:
result += f"*{humanize.naturaldelta(time)}:* {plan_line}\n"
return result
if len(result) > 0:
@staticmethod
def render_incident_log_report_for_slack(alert_group: "AlertGroup"):
attachments = []
past = AlertGroupLogSlackRenderer.render_alert_group_past_log_report_text(alert_group)
future = AlertGroupLogSlackRenderer.render_alert_group_future_log_report_text(alert_group)
text = past + future
if len(text) > 0:
attachments.append(
{
"text": result,
"text": text,
}
)
return attachments

View file

@ -19,6 +19,7 @@ from apps.chatops_proxy.utils import uninstall_slack as uninstall_slack_from_cha
from apps.slack.client import SlackClient
from apps.slack.errors import SlackAPIError
from apps.slack.scenarios.alertgroup_appearance import STEPS_ROUTING as ALERTGROUP_APPEARANCE_ROUTING
from apps.slack.scenarios.alertgroup_timeline import STEPS_ROUTING as ALERTGROUP_TIMELINE_ROUTING
# Importing routes from scenarios
from apps.slack.scenarios.declare_incident import STEPS_ROUTING as DECLARE_INCIDENT_ROUTING
@ -52,6 +53,7 @@ SCENARIOS_ROUTES.extend(SCHEDULES_ROUTING)
SCENARIOS_ROUTES.extend(SHIFT_SWAP_REQUESTS_ROUTING)
SCENARIOS_ROUTES.extend(SLACK_CHANNEL_INTEGRATION_ROUTING)
SCENARIOS_ROUTES.extend(ALERTGROUP_APPEARANCE_ROUTING)
SCENARIOS_ROUTES.extend(ALERTGROUP_TIMELINE_ROUTING)
SCENARIOS_ROUTES.extend(RESOLUTION_NOTE_ROUTING)
SCENARIOS_ROUTES.extend(SLACK_USERGROUP_UPDATE_ROUTING)
SCENARIOS_ROUTES.extend(CHANNEL_ROUTING)