Include link information for objects referenced in alert group timeline (#5123)
Reworked https://github.com/grafana/oncall/pull/5112 (post-revert)
This commit is contained in:
parent
33a0c15b75
commit
ee2ae50f27
2 changed files with 189 additions and 7 deletions
|
|
@ -227,14 +227,50 @@ class AlertGroupLogRecord(models.Model):
|
|||
|
||||
STEP_SPECIFIC_INFO_KEYS = ["schedule_name", "custom_button_name", "usergroup_handle", "source_integration_name"]
|
||||
|
||||
def _make_log_line_link(self, url, title, html=False, for_slack=False, substitute_with_tag=False):
|
||||
if html and url:
|
||||
return f"<a href='{url}'>{title}</a>"
|
||||
elif for_slack and url:
|
||||
return f"<{url}|{title}>"
|
||||
elif substitute_with_tag:
|
||||
return f"{{{{{substitute_with_tag}}}}}"
|
||||
else:
|
||||
return title
|
||||
|
||||
def render_log_line_json(self):
|
||||
time = humanize.naturaldelta(self.alert_group.started_at - self.created_at)
|
||||
created_at = DateTimeField().to_representation(self.created_at)
|
||||
organization = self.alert_group.channel.organization
|
||||
author = self.author.short(organization) if self.author is not None else None
|
||||
escalation_chain = self.alert_group.channel_filter.escalation_chain if self.alert_group.channel_filter else None
|
||||
step_info = self.get_step_specific_info()
|
||||
escalation_chain_data = (
|
||||
{
|
||||
"pk": escalation_chain.public_primary_key,
|
||||
"title": escalation_chain.name,
|
||||
}
|
||||
if escalation_chain
|
||||
else None
|
||||
)
|
||||
schedule = (
|
||||
{
|
||||
"pk": self.escalation_policy.notify_schedule.public_primary_key,
|
||||
"title": self.escalation_policy.notify_schedule.name,
|
||||
}
|
||||
if self.escalation_policy and self.escalation_policy.notify_schedule
|
||||
else None
|
||||
)
|
||||
webhook = (
|
||||
{
|
||||
"pk": step_info["webhook_id"],
|
||||
"title": step_info.get("webhook_name", "webhook"),
|
||||
}
|
||||
if step_info and "webhook_id" in step_info
|
||||
else None
|
||||
)
|
||||
|
||||
sf = SlackFormatter(organization)
|
||||
action = sf.format(self.rendered_log_line_action(substitute_author_with_tag=True))
|
||||
action = sf.format(self.rendered_log_line_action(substitute_with_tag=True))
|
||||
action = clean_markup(action)
|
||||
|
||||
result = {
|
||||
|
|
@ -244,6 +280,9 @@ class AlertGroupLogRecord(models.Model):
|
|||
"type": self.type,
|
||||
"created_at": created_at,
|
||||
"author": author,
|
||||
"escalation_chain": escalation_chain_data,
|
||||
"schedule": schedule,
|
||||
"webhook": webhook,
|
||||
}
|
||||
return result
|
||||
|
||||
|
|
@ -258,7 +297,7 @@ class AlertGroupLogRecord(models.Model):
|
|||
result += self.rendered_log_line_action(for_slack=for_slack, html=html)
|
||||
return result
|
||||
|
||||
def rendered_log_line_action(self, for_slack=False, html=False, substitute_author_with_tag=False):
|
||||
def rendered_log_line_action(self, for_slack=False, html=False, substitute_with_tag=False):
|
||||
from apps.alerts.models import EscalationPolicy
|
||||
|
||||
result = ""
|
||||
|
|
@ -276,7 +315,7 @@ class AlertGroupLogRecord(models.Model):
|
|||
elif self.action_source == ActionSource.BACKSYNC:
|
||||
author_name = "source integration " + step_specific_info.get("source_integration_name", "")
|
||||
elif self.author:
|
||||
if substitute_author_with_tag:
|
||||
if substitute_with_tag:
|
||||
author_name = "{{author}}"
|
||||
elif for_slack:
|
||||
author_name = self.author.get_username_with_slack_verbal()
|
||||
|
|
@ -303,7 +342,9 @@ class AlertGroupLogRecord(models.Model):
|
|||
result += f'alert group assigned to route "{channel_filter.str_for_clients}"'
|
||||
|
||||
if escalation_chain is not None:
|
||||
result += f' with escalation chain "{escalation_chain.name}"'
|
||||
tag = "escalation_chain" if substitute_with_tag else False
|
||||
escalation_chain_text = self._make_log_line_link(None, escalation_chain.name, html, for_slack, tag)
|
||||
result += f' with escalation chain "{escalation_chain_text}"'
|
||||
else:
|
||||
result += " with no escalation chain, skipping escalation"
|
||||
else:
|
||||
|
|
@ -379,7 +420,9 @@ class AlertGroupLogRecord(models.Model):
|
|||
important_text = ""
|
||||
if escalation_policy_step == EscalationPolicy.STEP_NOTIFY_SCHEDULE_IMPORTANT:
|
||||
important_text = " (Important)"
|
||||
result += f'triggered step "Notify on-call from Schedule {schedule_name}{important_text}"'
|
||||
tag = "schedule" if substitute_with_tag else False
|
||||
schedule_text = self._make_log_line_link(None, schedule_name, html, for_slack, tag)
|
||||
result += f'triggered step "Notify on-call from Schedule {schedule_text}{important_text}"'
|
||||
elif escalation_policy_step == EscalationPolicy.STEP_REPEAT_ESCALATION_N_TIMES:
|
||||
result += "escalation started from the beginning"
|
||||
else:
|
||||
|
|
@ -485,7 +528,10 @@ class AlertGroupLogRecord(models.Model):
|
|||
trigger = f"{author_name}"
|
||||
else:
|
||||
trigger = trigger or "escalation chain"
|
||||
result += f"outgoing webhook `{webhook_name}` triggered by {trigger}"
|
||||
tag = "webhook" if substitute_with_tag else False
|
||||
webhook_text = self._make_log_line_link(None, webhook_name, html, for_slack, tag)
|
||||
result += f"outgoing webhook `{webhook_text}` triggered by {trigger}"
|
||||
|
||||
elif self.type == AlertGroupLogRecord.TYPE_FAILED_ATTACHMENT:
|
||||
if self.alert_group.slack_message is not None:
|
||||
result += (
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ from unittest.mock import patch
|
|||
|
||||
import pytest
|
||||
|
||||
from apps.alerts.models import AlertGroupLogRecord
|
||||
from apps.alerts.models import AlertGroupLogRecord, EscalationPolicy
|
||||
from apps.schedules.models import OnCallScheduleWeb
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
|
@ -37,3 +38,138 @@ def test_trigger_update_signal(
|
|||
with patch("apps.alerts.tasks.send_update_log_report_signal") as mock_update_log_signal:
|
||||
alert_group.log_records.create(type=log_type)
|
||||
mock_update_log_signal.apply_async.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize(
|
||||
"for_slack, html, substitute_with_tag, expected",
|
||||
[
|
||||
(True, False, False, 'with escalation chain "Escalation name"'),
|
||||
(False, True, False, 'with escalation chain "Escalation name"'),
|
||||
(False, False, True, 'with escalation chain "{{escalation_chain}}'),
|
||||
],
|
||||
)
|
||||
def test_log_record_escalation_chain_link(
|
||||
make_organization_with_slack_team_identity,
|
||||
make_alert_receive_channel,
|
||||
make_escalation_chain,
|
||||
make_channel_filter,
|
||||
make_alert_group,
|
||||
for_slack,
|
||||
html,
|
||||
substitute_with_tag,
|
||||
expected,
|
||||
):
|
||||
organization, _ = make_organization_with_slack_team_identity()
|
||||
alert_receive_channel = make_alert_receive_channel(organization)
|
||||
escalation_chain = make_escalation_chain(organization, name="Escalation name")
|
||||
channel_filter = make_channel_filter(alert_receive_channel, escalation_chain=escalation_chain)
|
||||
alert_group = make_alert_group(alert_receive_channel, channel_filter=channel_filter)
|
||||
alert_group.raw_escalation_snapshot = alert_group.build_raw_escalation_snapshot()
|
||||
|
||||
log = alert_group.log_records.create(
|
||||
type=AlertGroupLogRecord.TYPE_ROUTE_ASSIGNED,
|
||||
)
|
||||
|
||||
log_line = log.rendered_log_line_action(for_slack=for_slack, html=html, substitute_with_tag=substitute_with_tag)
|
||||
assert expected in log_line
|
||||
|
||||
log_data = log.render_log_line_json()
|
||||
escalation_chain_data = log_data.get("escalation_chain")
|
||||
assert escalation_chain_data == {"pk": escalation_chain.public_primary_key, "title": escalation_chain.name}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize(
|
||||
"for_slack, html, substitute_with_tag, expected",
|
||||
[
|
||||
(True, False, False, "Notify on-call from Schedule 'Schedule name'"),
|
||||
(False, True, False, "Notify on-call from Schedule 'Schedule name'"),
|
||||
(False, False, True, "Notify on-call from Schedule {{schedule}}"),
|
||||
],
|
||||
)
|
||||
def test_log_record_schedule_link(
|
||||
make_organization_with_slack_team_identity,
|
||||
make_alert_receive_channel,
|
||||
make_channel_filter,
|
||||
make_alert_group,
|
||||
make_schedule,
|
||||
make_escalation_chain,
|
||||
make_escalation_policy,
|
||||
for_slack,
|
||||
html,
|
||||
substitute_with_tag,
|
||||
expected,
|
||||
):
|
||||
organization, _ = make_organization_with_slack_team_identity()
|
||||
alert_receive_channel = make_alert_receive_channel(organization)
|
||||
alert_group = make_alert_group(alert_receive_channel)
|
||||
schedule = make_schedule(organization, schedule_class=OnCallScheduleWeb, name="Schedule name")
|
||||
escalation_chain = make_escalation_chain(organization, name="Escalation name")
|
||||
channel_filter = make_channel_filter(alert_receive_channel, escalation_chain=escalation_chain)
|
||||
escalation_policy = make_escalation_policy(
|
||||
escalation_chain=channel_filter.escalation_chain,
|
||||
escalation_policy_step=EscalationPolicy.STEP_NOTIFY_SCHEDULE,
|
||||
notify_schedule=schedule,
|
||||
)
|
||||
|
||||
log = alert_group.log_records.create(
|
||||
type=AlertGroupLogRecord.TYPE_ESCALATION_TRIGGERED,
|
||||
step_specific_info={"schedule_name": schedule.name},
|
||||
escalation_policy=escalation_policy,
|
||||
)
|
||||
|
||||
log_line = log.rendered_log_line_action(for_slack=for_slack, html=html, substitute_with_tag=substitute_with_tag)
|
||||
assert expected in log_line
|
||||
|
||||
log_data = log.render_log_line_json()
|
||||
schedule_data = log_data.get("schedule")
|
||||
assert schedule_data == {"pk": schedule.public_primary_key, "title": schedule.name}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize(
|
||||
"for_slack, html, substitute_with_tag, expected",
|
||||
[
|
||||
(True, False, False, "outgoing webhook `Webhook name`"),
|
||||
(False, True, False, "outgoing webhook `Webhook name`"),
|
||||
(False, False, True, "outgoing webhook `{{webhook}}`"),
|
||||
],
|
||||
)
|
||||
def test_log_record_webhook_link(
|
||||
make_organization_with_slack_team_identity,
|
||||
make_alert_receive_channel,
|
||||
make_channel_filter,
|
||||
make_alert_group,
|
||||
make_custom_webhook,
|
||||
make_escalation_chain,
|
||||
make_escalation_policy,
|
||||
for_slack,
|
||||
html,
|
||||
substitute_with_tag,
|
||||
expected,
|
||||
):
|
||||
organization, _ = make_organization_with_slack_team_identity()
|
||||
alert_receive_channel = make_alert_receive_channel(organization)
|
||||
alert_group = make_alert_group(alert_receive_channel)
|
||||
webhook = make_custom_webhook(organization, name="Webhook name")
|
||||
escalation_chain = make_escalation_chain(organization, name="Escalation name")
|
||||
channel_filter = make_channel_filter(alert_receive_channel, escalation_chain=escalation_chain)
|
||||
escalation_policy = make_escalation_policy(
|
||||
escalation_chain=channel_filter.escalation_chain,
|
||||
escalation_policy_step=EscalationPolicy.STEP_TRIGGER_CUSTOM_WEBHOOK,
|
||||
custom_webhook=webhook,
|
||||
)
|
||||
|
||||
log = alert_group.log_records.create(
|
||||
type=AlertGroupLogRecord.TYPE_CUSTOM_WEBHOOK_TRIGGERED,
|
||||
step_specific_info={"webhook_id": webhook.public_primary_key, "webhook_name": webhook.name},
|
||||
escalation_policy=escalation_policy,
|
||||
)
|
||||
|
||||
log_line = log.rendered_log_line_action(for_slack=for_slack, html=html, substitute_with_tag=substitute_with_tag)
|
||||
assert expected in log_line
|
||||
|
||||
log_data = log.render_log_line_json()
|
||||
webhook_data = log_data.get("webhook")
|
||||
assert webhook_data == {"pk": webhook.public_primary_key, "title": webhook.name}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue