Update alert group state by backsync (#4089)

# What this PR does
Adds method to update alert group state by backsync
Related to https://github.com/grafana/oncall-private/issues/2542
Should be merged with
https://github.com/grafana/oncall-private/pull/2606

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] 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:
Yulya Artyukhina 2024-03-27 13:37:01 +01:00 committed by GitHub
parent 8ae962cec1
commit 3c93375244
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 253 additions and 106 deletions

View file

@ -9,6 +9,7 @@ class ActionSource(IntegerChoices):
PHONE = 2, "Phone"
TELEGRAM = 3, "Telegram"
API = 4, "API"
BACKSYNC = 5, "Backsync"
TASK_DELAY_SECONDS = 1

View file

@ -0,0 +1,18 @@
# Generated by Django 4.2.10 on 2024-03-20 12:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('alerts', '0048_alertgroupexternalid'),
]
operations = [
migrations.AlterField(
model_name='alertgrouplogrecord',
name='action_source',
field=models.SmallIntegerField(default=None, null=True, verbose_name=[(0, 'Slack'), (1, 'Web'), (2, 'Phone'), (3, 'Telegram'), (4, 'API'), (5, 'Backsync')]),
),
]

View file

@ -612,11 +612,37 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
"""Update metrics cache for response time and state as needed."""
update_metrics_for_alert_group.apply_async((self.id, organization_id, previous_state, state))
def acknowledge_by_user(self, user: User, action_source: typing.Optional[ActionSource] = None) -> None:
def update_state_by_backsync(self, new_state: AlertGroupState) -> None:
if self.state == new_state:
return
logger.debug(f"Update state {self.state} -> {new_state} for alert_group {self.pk}")
if new_state == AlertGroupState.FIRING:
if self.state == AlertGroupState.ACKNOWLEDGED:
self.un_acknowledge_by_user_or_backsync(action_source=ActionSource.BACKSYNC)
elif self.state == AlertGroupState.RESOLVED:
self.un_resolve_by_user_or_backsync(action_source=ActionSource.BACKSYNC)
elif self.state == AlertGroupState.SILENCED:
self.un_silence_by_user_or_backsync(action_source=ActionSource.BACKSYNC)
elif new_state == AlertGroupState.ACKNOWLEDGED:
self.acknowledge_by_user_or_backsync(action_source=ActionSource.BACKSYNC)
elif new_state == AlertGroupState.RESOLVED:
self.resolve_by_user_or_backsync(action_source=ActionSource.BACKSYNC)
elif new_state == AlertGroupState.SILENCED:
self.silence_by_user_or_backsync(action_source=ActionSource.BACKSYNC)
def acknowledge_by_user_or_backsync(
self, user: typing.Optional[User] = None, action_source: typing.Optional[ActionSource] = None
) -> None:
from apps.alerts.models import AlertGroupLogRecord
initial_state = self.state
logger.debug(f"Started acknowledge_by_user for alert_group {self.pk}")
reason = "Acknowledge button" if user else "Backsync signal"
acknowledged_by = AlertGroup.USER if user else AlertGroup.SOURCE
step_specific_info = (
{"source_integration_name": self.channel.verbal_name} if action_source == ActionSource.BACKSYNC else None
)
organization_id = user.organization_id if user else self.channel.organization_id
logger.debug(f"Started acknowledge_by_user_or_backsync for alert_group {self.pk}")
# if incident was silenced or resolved, unsilence/unresolve it without starting escalation
if self.silenced:
@ -625,28 +651,34 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
type=AlertGroupLogRecord.TYPE_UN_SILENCE,
author=user,
silence_delay=None,
reason="Acknowledge button",
reason=reason,
action_source=action_source,
step_specific_info=step_specific_info,
)
if self.resolved:
self.unresolve()
self.log_records.create(
type=AlertGroupLogRecord.TYPE_UN_RESOLVED,
author=user,
reason="Acknowledge button",
reason=reason,
action_source=action_source,
step_specific_info=step_specific_info,
)
self.acknowledge(acknowledged_by_user=user, acknowledged_by=AlertGroup.USER)
self.acknowledge(acknowledged_by_user=user, acknowledged_by=acknowledged_by)
# Update alert group state and response time metrics cache
self._update_metrics(organization_id=user.organization_id, previous_state=initial_state, state=self.state)
self._update_metrics(organization_id=organization_id, previous_state=initial_state, state=self.state)
self.stop_escalation()
self.start_ack_reminder_if_needed()
if user: # ack reminder works only for actions performed by user
self.start_ack_reminder_if_needed()
with transaction.atomic():
log_record = self.log_records.create(
type=AlertGroupLogRecord.TYPE_ACK, author=user, action_source=action_source
type=AlertGroupLogRecord.TYPE_ACK,
author=user,
action_source=action_source,
step_specific_info=step_specific_info,
)
logger.debug(
@ -657,9 +689,9 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
transaction.on_commit(partial(send_alert_group_signal.delay, log_record.pk))
for dependent_alert_group in self.dependent_alert_groups.all():
dependent_alert_group.acknowledge_by_user(user, action_source=action_source)
dependent_alert_group.acknowledge_by_user_or_backsync(user, action_source=action_source)
logger.debug(f"Finished acknowledge_by_user for alert_group {self.pk}")
logger.debug(f"Finished acknowledge_by_user_or_backsync for alert_group {self.pk}")
def acknowledge_by_source(self):
from apps.alerts.models import AlertGroupLogRecord
@ -694,21 +726,30 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
for dependent_alert_group in self.dependent_alert_groups.all():
dependent_alert_group.acknowledge_by_source()
def un_acknowledge_by_user(self, user: User, action_source: typing.Optional[ActionSource] = None) -> None:
def un_acknowledge_by_user_or_backsync(
self, user: typing.Optional[User] = None, action_source: typing.Optional[ActionSource] = None
) -> None:
from apps.alerts.models import AlertGroupLogRecord
initial_state = self.state
logger.debug(f"Started un_acknowledge_by_user for alert_group {self.pk}")
step_specific_info = (
{"source_integration_name": self.channel.verbal_name} if action_source == ActionSource.BACKSYNC else None
)
organization_id = user.organization_id if user else self.channel.organization_id
logger.debug(f"Started un_acknowledge_by_user_or_backsync for alert_group {self.pk}")
self.unacknowledge()
# Update alert group state metric cache
self._update_metrics(organization_id=user.organization_id, previous_state=initial_state, state=self.state)
self._update_metrics(organization_id=organization_id, previous_state=initial_state, state=self.state)
if self.is_root_alert_group:
self.start_escalation_if_needed()
with transaction.atomic():
log_record = self.log_records.create(
type=AlertGroupLogRecord.TYPE_UN_ACK, author=user, action_source=action_source
type=AlertGroupLogRecord.TYPE_UN_ACK,
author=user,
action_source=action_source,
step_specific_info=step_specific_info,
)
logger.debug(
@ -719,14 +760,21 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
transaction.on_commit(partial(send_alert_group_signal.delay, log_record.pk))
for dependent_alert_group in self.dependent_alert_groups.all():
dependent_alert_group.un_acknowledge_by_user(user, action_source=action_source)
logger.debug(f"Finished un_acknowledge_by_user for alert_group {self.pk}")
dependent_alert_group.un_acknowledge_by_user_or_backsync(user, action_source=action_source)
logger.debug(f"Finished un_acknowledge_by_user_or_backsync for alert_group {self.pk}")
def resolve_by_user(self, user: User, action_source: typing.Optional[ActionSource] = None) -> None:
def resolve_by_user_or_backsync(
self, user: typing.Optional[User] = None, action_source: typing.Optional[ActionSource] = None
) -> None:
from apps.alerts.models import AlertGroupLogRecord
initial_state = self.state
reason = "Resolve button" if user else "Backsync signal"
resolved_by = AlertGroup.USER if user else AlertGroup.SOURCE
step_specific_info = (
{"source_integration_name": self.channel.verbal_name} if action_source == ActionSource.BACKSYNC else None
)
organization_id = user.organization_id if user else self.channel.organization_id
# if incident was silenced, unsilence it without starting escalation
if self.silenced:
self.un_silence()
@ -734,17 +782,21 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
type=AlertGroupLogRecord.TYPE_UN_SILENCE,
author=user,
silence_delay=None,
reason="Resolve button",
reason=reason,
action_source=action_source,
step_specific_info=step_specific_info,
)
self.resolve(resolved_by=AlertGroup.USER, resolved_by_user=user)
self.resolve(resolved_by=resolved_by, resolved_by_user=user)
# Update alert group state and response time metrics cache
self._update_metrics(organization_id=user.organization_id, previous_state=initial_state, state=self.state)
self._update_metrics(organization_id=organization_id, previous_state=initial_state, state=self.state)
self.stop_escalation()
with transaction.atomic():
log_record = self.log_records.create(
type=AlertGroupLogRecord.TYPE_RESOLVED, author=user, action_source=action_source
type=AlertGroupLogRecord.TYPE_RESOLVED,
author=user,
action_source=action_source,
step_specific_info=step_specific_info,
)
logger.debug(
@ -755,7 +807,7 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
transaction.on_commit(partial(send_alert_group_signal.delay, log_record.pk))
for dependent_alert_group in self.dependent_alert_groups.all():
dependent_alert_group.resolve_by_user(user, action_source=action_source)
dependent_alert_group.resolve_by_user_or_backsync(user, action_source=action_source)
def resolve_by_source(self):
from apps.alerts.models import AlertGroupLogRecord
@ -835,18 +887,29 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
for dependent_alert_group in self.dependent_alert_groups.all():
dependent_alert_group.resolve_by_disable_maintenance()
def un_resolve_by_user(self, user: User, action_source: typing.Optional[ActionSource] = None) -> None:
def un_resolve_by_user_or_backsync(
self, user: typing.Optional[User] = None, action_source: typing.Optional[ActionSource] = None
) -> None:
from apps.alerts.models import AlertGroupLogRecord
if self.wiped_at is None:
initial_state = self.state
step_specific_info = (
{"source_integration_name": self.channel.verbal_name}
if action_source == ActionSource.BACKSYNC
else None
)
organization_id = user.organization_id if user else self.channel.organization_id
self.unresolve()
# Update alert group state metric cache
self._update_metrics(organization_id=user.organization_id, previous_state=initial_state, state=self.state)
self._update_metrics(organization_id=organization_id, previous_state=initial_state, state=self.state)
with transaction.atomic():
log_record = self.log_records.create(
type=AlertGroupLogRecord.TYPE_UN_RESOLVED, author=user, action_source=action_source
type=AlertGroupLogRecord.TYPE_UN_RESOLVED,
author=user,
action_source=action_source,
step_specific_info=step_specific_info,
)
if self.is_root_alert_group:
@ -861,7 +924,7 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
transaction.on_commit(partial(send_alert_group_signal.delay, log_record.pk))
for dependent_alert_group in self.dependent_alert_groups.all():
dependent_alert_group.un_resolve_by_user(user, action_source=action_source)
dependent_alert_group.un_resolve_by_user_or_backsync(user, action_source=action_source)
def attach_by_user(
self, user: User, root_alert_group: "AlertGroup", action_source: typing.Optional[ActionSource] = None
@ -873,15 +936,15 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
self.save(update_fields=["root_alert_group"])
self.stop_escalation()
if root_alert_group.acknowledged and not self.acknowledged:
self.acknowledge_by_user(user, action_source=action_source)
self.acknowledge_by_user_or_backsync(user, action_source=action_source)
elif not root_alert_group.acknowledged and self.acknowledged:
self.un_acknowledge_by_user(user, action_source=action_source)
self.un_acknowledge_by_user_or_backsync(user, action_source=action_source)
if root_alert_group.silenced and not self.silenced:
self.silence_by_user(user, action_source=action_source, silence_delay=None)
self.silence_by_user_or_backsync(user, action_source=action_source, silence_delay=None)
if not root_alert_group.silenced and self.silenced:
self.un_silence_by_user(user, action_source=action_source)
self.un_silence_by_user_or_backsync(user, action_source=action_source)
with transaction.atomic():
log_record = self.log_records.create(
@ -997,26 +1060,39 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
transaction.on_commit(partial(send_alert_group_signal.delay, log_record.pk))
def silence_by_user(
self, user: User, silence_delay: typing.Optional[int], action_source: typing.Optional[ActionSource] = None
def silence_by_user_or_backsync(
self,
user: typing.Optional[User] = None,
silence_delay: typing.Optional[int] = None,
action_source: typing.Optional[ActionSource] = None,
) -> None:
from apps.alerts.models import AlertGroupLogRecord
initial_state = self.state
reason = "Silence button" if user else "Backsync signal"
step_specific_info = (
{"source_integration_name": self.channel.verbal_name} if action_source == ActionSource.BACKSYNC else None
)
organization_id = user.organization_id if user else self.channel.organization_id
if self.resolved:
self.unresolve()
self.log_records.create(
type=AlertGroupLogRecord.TYPE_UN_RESOLVED,
author=user,
reason="Silence button",
reason=reason,
action_source=action_source,
step_specific_info=step_specific_info,
)
if self.acknowledged:
self.unacknowledge()
self.log_records.create(
type=AlertGroupLogRecord.TYPE_UN_ACK, author=user, reason="Silence button", action_source=action_source
type=AlertGroupLogRecord.TYPE_UN_ACK,
author=user,
reason=reason,
action_source=action_source,
step_specific_info=step_specific_info,
)
if self.silenced:
@ -1025,8 +1101,9 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
type=AlertGroupLogRecord.TYPE_UN_SILENCE,
author=user,
silence_delay=None,
reason="Silence button",
reason=reason,
action_source=action_source,
step_specific_info=step_specific_info,
)
now = timezone.now()
@ -1048,15 +1125,16 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
raw_escalation_snapshot=self.raw_escalation_snapshot,
)
# Update alert group state and response time metrics cache
self._update_metrics(organization_id=user.organization_id, previous_state=initial_state, state=self.state)
self._update_metrics(organization_id=organization_id, previous_state=initial_state, state=self.state)
with transaction.atomic():
log_record = self.log_records.create(
type=AlertGroupLogRecord.TYPE_SILENCE,
author=user,
silence_delay=silence_delay_timedelta,
reason="Silence button",
reason=reason,
action_source=action_source,
step_specific_info=step_specific_info,
)
logger.debug(
@ -1068,16 +1146,22 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
transaction.on_commit(partial(send_alert_group_signal.delay, log_record.pk))
for dependent_alert_group in self.dependent_alert_groups.all():
dependent_alert_group.silence_by_user(user, silence_delay, action_source)
dependent_alert_group.silence_by_user_or_backsync(user, silence_delay, action_source)
def un_silence_by_user(self, user: User, action_source: typing.Optional[ActionSource] = None) -> None:
def un_silence_by_user_or_backsync(
self, user: typing.Optional[User] = None, action_source: typing.Optional[ActionSource] = None
) -> None:
from apps.alerts.models import AlertGroupLogRecord
initial_state = self.state
reason = "Unsilence button" if user else "Backsync signal"
step_specific_info = (
{"source_integration_name": self.channel.verbal_name} if action_source == ActionSource.BACKSYNC else None
)
organization_id = user.organization_id if user else self.channel.organization_id
self.un_silence()
# Update alert group state metric cache
self._update_metrics(organization_id=user.organization_id, previous_state=initial_state, state=self.state)
self._update_metrics(organization_id=organization_id, previous_state=initial_state, state=self.state)
if self.is_root_alert_group:
self.start_escalation_if_needed()
@ -1088,8 +1172,9 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
author=user,
silence_delay=None,
# 2.Look like some time ago there was no TYPE_UN_SILENCE
reason="Unsilence button",
reason=reason,
action_source=action_source,
step_specific_info=step_specific_info,
)
logger.debug(
@ -1101,7 +1186,7 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
transaction.on_commit(partial(send_alert_group_signal.delay, log_record.pk))
for dependent_alert_group in self.dependent_alert_groups.all():
dependent_alert_group.un_silence_by_user(user, action_source=action_source)
dependent_alert_group.un_silence_by_user_or_backsync(user, action_source=action_source)
def wipe_by_user(self, user: User) -> None:
from apps.alerts.models import AlertGroupLogRecord

View file

@ -224,7 +224,7 @@ class AlertGroupLogRecord(models.Model):
escalation_policy_step = models.IntegerField(null=True, default=None)
step_specific_info = JSONField(null=True, default=None)
STEP_SPECIFIC_INFO_KEYS = ["schedule_name", "custom_button_name", "usergroup_handle"]
STEP_SPECIFIC_INFO_KEYS = ["schedule_name", "custom_button_name", "usergroup_handle", "source_integration_name"]
def render_log_line_json(self):
time = humanize.naturaldelta(self.alert_group.started_at - self.created_at)
@ -271,6 +271,8 @@ class AlertGroupLogRecord(models.Model):
if self.action_source == ActionSource.API:
author_name = "API"
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:
author_name = "{{author}}"
@ -390,7 +392,7 @@ class AlertGroupLogRecord(models.Model):
else:
result += f"silenced by {author_name} for {humanize.naturaldelta(self.silence_delay)}"
elif self.type == AlertGroupLogRecord.TYPE_UN_SILENCE:
if self.author is not None:
if author_name is not None:
result += f"unsilenced by {author_name}"
else:
result += "alert group unsilenced"

View file

@ -71,7 +71,7 @@ def test_acknowledge_by_user_invokes_start_ack_reminder(ack_reminder_test_setup)
organization, alert_group, user = ack_reminder_test_setup(acknowledged=False)
with patch.object(alert_group, "start_ack_reminder_if_needed") as mock_start_ack_reminder:
alert_group.acknowledge_by_user(user, ActionSource.SLACK)
alert_group.acknowledge_by_user_or_backsync(user, ActionSource.SLACK)
mock_start_ack_reminder.assert_called_once_with()

View file

@ -2,7 +2,7 @@ from unittest.mock import call, patch
import pytest
from apps.alerts.constants import ActionSource
from apps.alerts.constants import ActionSource, AlertGroupState
from apps.alerts.incident_appearance.renderers.phone_call_renderer import AlertGroupPhoneCallRenderer
from apps.alerts.models import Alert, AlertGroup, AlertGroupLogRecord
from apps.alerts.tasks import wipe
@ -327,7 +327,7 @@ def test_silence_by_user_for_period(
author=user,
).exists()
alert_group.silence_by_user(user, silence_delay=silence_delay)
alert_group.silence_by_user_or_backsync(user, silence_delay=silence_delay)
assert alert_group.log_records.filter(
type=AlertGroupLogRecord.TYPE_SILENCE,
@ -364,7 +364,7 @@ def test_silence_by_user_forever(
author=user,
).exists()
alert_group.silence_by_user(user, silence_delay=None)
alert_group.silence_by_user_or_backsync(user, silence_delay=None)
assert alert_group.log_records.filter(
type=AlertGroupLogRecord.TYPE_SILENCE,
@ -476,32 +476,32 @@ def test_alert_group_log_record_action_source(
root_alert_group = make_alert_group(alert_receive_channel)
# Silence alert group
alert_group.silence_by_user(user, 42, action_source=action_source)
alert_group.silence_by_user_or_backsync(user, 42, action_source=action_source)
log_record = alert_group.log_records.last()
assert (log_record.type, log_record.action_source) == (AlertGroupLogRecord.TYPE_SILENCE, action_source)
# Unsilence alert group
alert_group.un_silence_by_user(user, action_source=action_source)
alert_group.un_silence_by_user_or_backsync(user, action_source=action_source)
log_record = alert_group.log_records.last()
assert (log_record.type, log_record.action_source) == (AlertGroupLogRecord.TYPE_UN_SILENCE, action_source)
# Acknowledge alert group
alert_group.acknowledge_by_user(user, action_source=action_source)
alert_group.acknowledge_by_user_or_backsync(user, action_source=action_source)
log_record = alert_group.log_records.last()
assert (log_record.type, log_record.action_source) == (AlertGroupLogRecord.TYPE_ACK, action_source)
# Unacknowledge alert group
alert_group.un_acknowledge_by_user(user, action_source=action_source)
alert_group.un_acknowledge_by_user_or_backsync(user, action_source=action_source)
log_record = alert_group.log_records.last()
assert (log_record.type, log_record.action_source) == (AlertGroupLogRecord.TYPE_UN_ACK, action_source)
# Resolve alert group
alert_group.resolve_by_user(user, action_source=action_source)
alert_group.resolve_by_user_or_backsync(user, action_source=action_source)
log_record = alert_group.log_records.last()
assert (log_record.type, log_record.action_source) == (AlertGroupLogRecord.TYPE_RESOLVED, action_source)
# Unresolve alert group
alert_group.un_resolve_by_user(user, action_source=action_source)
alert_group.un_resolve_by_user_or_backsync(user, action_source=action_source)
log_record = alert_group.log_records.last()
assert (log_record.type, log_record.action_source) == (AlertGroupLogRecord.TYPE_UN_RESOLVED, action_source)
@ -594,16 +594,16 @@ def test_filter_active_alert_groups(
# alert groups with active escalation
alert_group_active = make_alert_group(alert_receive_channel)
alert_group_active_silenced = make_alert_group(alert_receive_channel)
alert_group_active_silenced.silence_by_user(user, silence_delay=1800) # silence by period
alert_group_active_silenced.silence_by_user_or_backsync(user, silence_delay=1800) # silence by period
# alert groups with inactive escalation
alert_group_1 = make_alert_group(alert_receive_channel)
alert_group_1.acknowledge_by_user(user)
alert_group_1.acknowledge_by_user_or_backsync(user)
alert_group_2 = make_alert_group(alert_receive_channel)
alert_group_2.resolve_by_user(user)
alert_group_2.resolve_by_user_or_backsync(user)
alert_group_3 = make_alert_group(alert_receive_channel)
alert_group_3.attach_by_user(user, alert_group_active)
alert_group_4 = make_alert_group(alert_receive_channel)
alert_group_4.silence_by_user(user, silence_delay=None) # silence forever
alert_group_4.silence_by_user_or_backsync(user, silence_delay=None) # silence forever
active_alert_groups = AlertGroup.objects.filter_active()
assert active_alert_groups.count() == 2
@ -688,3 +688,44 @@ def test_integration_config_on_alert_group_created(make_organization, make_alert
assert alert.group.alerts.count() == 2
mock_on_alert_group_created.assert_called_once_with(alert.group)
@patch.object(AlertGroup, "start_escalation_if_needed")
@pytest.mark.django_db
@pytest.mark.parametrize(
"new_state,log_type,to_firing_log_type",
[
(AlertGroupState.ACKNOWLEDGED, AlertGroupLogRecord.TYPE_ACK, AlertGroupLogRecord.TYPE_UN_ACK),
(AlertGroupState.RESOLVED, AlertGroupLogRecord.TYPE_RESOLVED, AlertGroupLogRecord.TYPE_UN_RESOLVED),
(AlertGroupState.SILENCED, AlertGroupLogRecord.TYPE_SILENCE, AlertGroupLogRecord.TYPE_UN_SILENCE),
],
)
def test_update_state_by_backsync(
mock_start_escalation_if_needed,
new_state,
log_type,
to_firing_log_type,
make_organization,
make_alert_receive_channel,
make_alert_group,
):
organization = make_organization()
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
expected_log_data = (ActionSource.BACKSYNC, None, {"source_integration_name": alert_receive_channel.verbal_name})
assert alert_group.state == AlertGroupState.FIRING
# set to new_state
alert_group.update_state_by_backsync(new_state)
alert_group.refresh_from_db()
assert alert_group.state == new_state
last_log = alert_group.log_records.last()
assert (last_log.action_source, last_log.author, last_log.step_specific_info) == expected_log_data
assert last_log.type == log_type
# set back to firing
alert_group.update_state_by_backsync(AlertGroupState.FIRING)
alert_group.refresh_from_db()
assert alert_group.state == AlertGroupState.FIRING
last_log = alert_group.log_records.last()
assert (last_log.action_source, last_log.author, last_log.step_specific_info) == expected_log_data
assert last_log.type == to_firing_log_type
mock_start_escalation_if_needed.assert_called_once()

View file

@ -2149,8 +2149,8 @@ def test_timeline_api_action(
alert_group = make_alert_group(alert_receive_channel, channel_filter=channel_filter)
make_alert(alert_group=alert_group, raw_request_data=alert_raw_request_data)
alert_group.acknowledge_by_user(user, action_source=ActionSource.WEB)
alert_group.resolve_by_user(user, action_source=ActionSource.API)
alert_group.acknowledge_by_user_or_backsync(user, action_source=ActionSource.WEB)
alert_group.resolve_by_user_or_backsync(user, action_source=ActionSource.API)
client = APIClient()
url = reverse("api-internal:alertgroup-detail", kwargs={"pk": alert_group.public_primary_key})

View file

@ -469,7 +469,7 @@ class AlertGroupView(
raise BadRequest(detail="Can't acknowledge maintenance alert group")
if alert_group.root_alert_group is not None:
raise BadRequest(detail="Can't acknowledge an attached alert group")
alert_group.acknowledge_by_user(self.request.user, action_source=ActionSource.WEB)
alert_group.acknowledge_by_user_or_backsync(self.request.user, action_source=ActionSource.WEB)
return Response(AlertGroupSerializer(alert_group, context={"request": self.request}).data)
@ -492,7 +492,7 @@ class AlertGroupView(
if alert_group.resolved:
raise BadRequest(detail="Can't unacknowledge a resolved alert group")
alert_group.un_acknowledge_by_user(self.request.user, action_source=ActionSource.WEB)
alert_group.un_acknowledge_by_user_or_backsync(self.request.user, action_source=ActionSource.WEB)
return Response(AlertGroupSerializer(alert_group, context={"request": self.request}).data)
@ -544,7 +544,7 @@ class AlertGroupView(
},
status=status.HTTP_400_BAD_REQUEST,
)
alert_group.resolve_by_user(self.request.user, action_source=ActionSource.WEB)
alert_group.resolve_by_user_or_backsync(self.request.user, action_source=ActionSource.WEB)
return Response(AlertGroupSerializer(alert_group, context={"request": self.request}).data)
@extend_schema(responses=AlertGroupSerializer)
@ -563,7 +563,7 @@ class AlertGroupView(
if not alert_group.resolved:
raise BadRequest(detail="The alert group is not resolved")
alert_group.un_resolve_by_user(self.request.user, action_source=ActionSource.WEB)
alert_group.un_resolve_by_user_or_backsync(self.request.user, action_source=ActionSource.WEB)
return Response(AlertGroupSerializer(alert_group, context={"request": self.request}).data)
@extend_schema(
@ -628,7 +628,7 @@ class AlertGroupView(
if alert_group.root_alert_group is not None:
raise BadRequest(detail="Can't silence an attached alert group")
alert_group.silence_by_user(request.user, silence_delay=delay, action_source=ActionSource.WEB)
alert_group.silence_by_user_or_backsync(request.user, silence_delay=delay, action_source=ActionSource.WEB)
return Response(AlertGroupSerializer(alert_group, context={"request": request}).data)
@extend_schema(
@ -672,7 +672,7 @@ class AlertGroupView(
if alert_group.root_alert_group is not None:
raise BadRequest(detail="Can't unsilence an attached alert group")
alert_group.un_silence_by_user(request.user, action_source=ActionSource.WEB)
alert_group.un_silence_by_user_or_backsync(request.user, action_source=ActionSource.WEB)
return Response(AlertGroupSerializer(alert_group, context={"request": request}).data)

View file

@ -122,22 +122,22 @@ def test_update_metric_alert_groups_total_cache_on_action(
mock_cache_set_called_args = mock_cache_set.call_args_list
arg_idx = get_called_arg_index_and_compare_results(expected_result_firing)
alert_group.acknowledge_by_user(user)
alert_group.acknowledge_by_user_or_backsync(user)
arg_idx = get_called_arg_index_and_compare_results(expected_result_acked)
alert_group.un_acknowledge_by_user(user)
alert_group.un_acknowledge_by_user_or_backsync(user)
arg_idx = get_called_arg_index_and_compare_results(expected_result_firing)
alert_group.resolve_by_user(user)
alert_group.resolve_by_user_or_backsync(user)
arg_idx = get_called_arg_index_and_compare_results(expected_result_resolved)
alert_group.un_resolve_by_user(user)
alert_group.un_resolve_by_user_or_backsync(user)
arg_idx = get_called_arg_index_and_compare_results(expected_result_firing)
alert_group.silence_by_user(user, silence_delay=None)
alert_group.silence_by_user_or_backsync(user, silence_delay=None)
arg_idx = get_called_arg_index_and_compare_results(expected_result_silenced)
alert_group.un_silence_by_user(user)
alert_group.un_silence_by_user_or_backsync(user)
get_called_arg_index_and_compare_results(expected_result_firing)
@ -212,30 +212,30 @@ def test_update_metric_alert_groups_response_time_cache_on_action(
# alert_groups_response_time cache shouldn't be updated on create alert group
assert_cache_was_not_changed_by_response_time_metric()
alert_group_1.acknowledge_by_user(user)
alert_group_1.acknowledge_by_user_or_backsync(user)
arg_idx = get_called_arg_index_and_compare_results()
# assert that only the first action counts
alert_group_1.un_acknowledge_by_user(user)
alert_group_1.un_acknowledge_by_user_or_backsync(user)
assert_cache_was_not_changed_by_response_time_metric()
alert_group_1.resolve_by_user(user)
alert_group_1.resolve_by_user_or_backsync(user)
assert_cache_was_not_changed_by_response_time_metric()
alert_group_1.un_resolve_by_user(user)
alert_group_1.un_resolve_by_user_or_backsync(user)
assert_cache_was_not_changed_by_response_time_metric()
alert_group_1.silence_by_user(user, silence_delay=None)
alert_group_1.silence_by_user_or_backsync(user, silence_delay=None)
assert_cache_was_not_changed_by_response_time_metric()
alert_group_1.un_silence_by_user(user)
alert_group_1.un_silence_by_user_or_backsync(user)
assert_cache_was_not_changed_by_response_time_metric()
# check that response_time cache updates on other actions with other alert groups
alert_group_2.resolve_by_user(user)
alert_group_2.resolve_by_user_or_backsync(user)
arg_idx = get_called_arg_index_and_compare_results()
alert_group_3.silence_by_user(user, silence_delay=None)
alert_group_3.silence_by_user_or_backsync(user, silence_delay=None)
get_called_arg_index_and_compare_results()

View file

@ -135,7 +135,7 @@ class IncidentView(
if alert_group.is_maintenance_incident:
raise BadRequest(detail="Can't acknowledge a maintenance alert group")
alert_group.acknowledge_by_user(self.request.user, action_source=ActionSource.API)
alert_group.acknowledge_by_user_or_backsync(self.request.user, action_source=ActionSource.API)
return Response(status=status.HTTP_200_OK)
@action(methods=["post"], detail=True)
@ -154,7 +154,7 @@ class IncidentView(
if alert_group.is_maintenance_incident:
raise BadRequest(detail="Can't unacknowledge a maintenance alert group")
alert_group.un_acknowledge_by_user(self.request.user, action_source=ActionSource.API)
alert_group.un_acknowledge_by_user_or_backsync(self.request.user, action_source=ActionSource.API)
return Response(status=status.HTTP_200_OK)
@action(methods=["post"], detail=True)
@ -170,7 +170,7 @@ class IncidentView(
if alert_group.is_maintenance_incident:
alert_group.stop_maintenance(self.request.user)
else:
alert_group.resolve_by_user(self.request.user, action_source=ActionSource.API)
alert_group.resolve_by_user_or_backsync(self.request.user, action_source=ActionSource.API)
return Response(status=status.HTTP_200_OK)
@ -187,5 +187,5 @@ class IncidentView(
if alert_group.is_maintenance_incident:
raise BadRequest(detail="Can't unresolve a maintenance alert group")
alert_group.un_resolve_by_user(self.request.user, action_source=ActionSource.API)
alert_group.un_resolve_by_user_or_backsync(self.request.user, action_source=ActionSource.API)
return Response(status=status.HTTP_200_OK)

View file

@ -291,7 +291,7 @@ class SilenceGroupStep(AlertGroupActionsMixin, scenario_step.ScenarioStep):
# Deprecated handler kept for backward compatibility (so older Slack messages can still be processed)
silence_delay = int(value)
alert_group.silence_by_user(self.user, silence_delay, action_source=ActionSource.SLACK)
alert_group.silence_by_user_or_backsync(self.user, silence_delay, action_source=ActionSource.SLACK)
def process_signal(self, log_record: AlertGroupLogRecord) -> None:
self.alert_group_slack_service.update_alert_group_slack_message(log_record.alert_group)
@ -311,7 +311,7 @@ class UnSilenceGroupStep(AlertGroupActionsMixin, scenario_step.ScenarioStep):
self.open_unauthorized_warning(payload)
return
alert_group.un_silence_by_user(self.user, action_source=ActionSource.SLACK)
alert_group.un_silence_by_user_or_backsync(self.user, action_source=ActionSource.SLACK)
def process_signal(self, log_record: AlertGroupLogRecord) -> None:
self.alert_group_slack_service.update_alert_group_slack_message(log_record.alert_group)
@ -659,7 +659,7 @@ class ResolveGroupStep(AlertGroupActionsMixin, scenario_step.ScenarioStep):
)
return
alert_group.resolve_by_user(self.user, action_source=ActionSource.SLACK)
alert_group.resolve_by_user_or_backsync(self.user, action_source=ActionSource.SLACK)
def process_signal(self, log_record: AlertGroupLogRecord) -> None:
alert_group = log_record.alert_group
@ -683,7 +683,7 @@ class UnResolveGroupStep(AlertGroupActionsMixin, scenario_step.ScenarioStep):
self.open_unauthorized_warning(payload)
return
alert_group.un_resolve_by_user(self.user, action_source=ActionSource.SLACK)
alert_group.un_resolve_by_user_or_backsync(self.user, action_source=ActionSource.SLACK)
def process_signal(self, log_record: AlertGroupLogRecord) -> None:
self.alert_group_slack_service.update_alert_group_slack_message(log_record.alert_group)
@ -703,7 +703,7 @@ class AcknowledgeGroupStep(AlertGroupActionsMixin, scenario_step.ScenarioStep):
self.open_unauthorized_warning(payload)
return
alert_group.acknowledge_by_user(self.user, action_source=ActionSource.SLACK)
alert_group.acknowledge_by_user_or_backsync(self.user, action_source=ActionSource.SLACK)
def process_signal(self, log_record: AlertGroupLogRecord) -> None:
self.alert_group_slack_service.update_alert_group_slack_message(log_record.alert_group)
@ -723,7 +723,7 @@ class UnAcknowledgeGroupStep(AlertGroupActionsMixin, scenario_step.ScenarioStep)
self.open_unauthorized_warning(payload)
return
alert_group.un_acknowledge_by_user(self.user, action_source=ActionSource.SLACK)
alert_group.un_acknowledge_by_user_or_backsync(self.user, action_source=ActionSource.SLACK)
def process_signal(self, log_record: AlertGroupLogRecord) -> None:
from apps.alerts.models import AlertGroupLogRecord

View file

@ -92,7 +92,7 @@ def test_actions_keyboard_acknowledged(
alert_group = make_alert_group(alert_receive_channel)
make_alert(alert_group=alert_group, raw_request_data=alert_receive_channel.config.example_payload)
alert_group.acknowledge_by_user(user)
alert_group.acknowledge_by_user_or_backsync(user)
renderer = TelegramKeyboardRenderer(alert_group=alert_group)
keyboard = renderer.render_actions_keyboard()
@ -127,7 +127,7 @@ def test_actions_keyboard_resolved(
alert_group = make_alert_group(alert_receive_channel)
make_alert(alert_group=alert_group, raw_request_data=alert_receive_channel.config.example_payload)
alert_group.resolve_by_user(user)
alert_group.resolve_by_user_or_backsync(user)
renderer = TelegramKeyboardRenderer(alert_group=alert_group)
keyboard = renderer.render_actions_keyboard()
@ -155,7 +155,7 @@ def test_actions_keyboard_silenced(
alert_group = make_alert_group(alert_receive_channel)
make_alert(alert_group=alert_group, raw_request_data=alert_receive_channel.config.example_payload)
alert_group.silence_by_user(user, silence_delay=None)
alert_group.silence_by_user_or_backsync(user, silence_delay=None)
renderer = TelegramKeyboardRenderer(alert_group=alert_group)
keyboard = renderer.render_actions_keyboard()

View file

@ -150,7 +150,7 @@ def test_personal_message(
alert_group = make_alert_group(alert_receive_channel, channel_filter=default_channel_filter)
make_alert(alert_group=alert_group, raw_request_data=alert_receive_channel.config.tests["payload"])
alert_group.acknowledge_by_user(user)
alert_group.acknowledge_by_user_or_backsync(user)
renderer = TelegramMessageRenderer(alert_group=alert_group)
text = renderer.render_personal_message()

View file

@ -110,15 +110,15 @@ class ButtonPressHandler(UpdateHandler):
@staticmethod
def _map_action_context_to_fn(action_context: ActionContext) -> Tuple[Callable, dict]:
action_to_fn = {
Action.RESOLVE: "resolve_by_user",
Action.UNRESOLVE: "un_resolve_by_user",
Action.ACKNOWLEDGE: "acknowledge_by_user",
Action.UNACKNOWLEDGE: "un_acknowledge_by_user",
Action.RESOLVE: "resolve_by_user_or_backsync",
Action.UNRESOLVE: "un_resolve_by_user_or_backsync",
Action.ACKNOWLEDGE: "acknowledge_by_user_or_backsync",
Action.UNACKNOWLEDGE: "un_acknowledge_by_user_or_backsync",
Action.SILENCE: {
"fn_name": "silence_by_user",
"fn_name": "silence_by_user_or_backsync",
"kwargs": {"silence_delay": int(action_context.action_data) if action_context.action_data else None},
},
Action.UNSILENCE: "un_silence_by_user",
Action.UNSILENCE: "un_silence_by_user_or_backsync",
}
fn_info = action_to_fn[action_context.action]

View file

@ -74,11 +74,11 @@ def process_digit(call_sid, digit):
f"twilio_phone_call_sid={call_sid} digit={digit} alert_group_id={alert_group.id} user_id={user.id}"
)
if digit == "1":
alert_group.acknowledge_by_user(user, action_source=ActionSource.PHONE)
alert_group.acknowledge_by_user_or_backsync(user, action_source=ActionSource.PHONE)
elif digit == "2":
alert_group.resolve_by_user(user, action_source=ActionSource.PHONE)
alert_group.resolve_by_user_or_backsync(user, action_source=ActionSource.PHONE)
elif digit == "3":
alert_group.silence_by_user(user, silence_delay=1800, action_source=ActionSource.PHONE)
alert_group.silence_by_user_or_backsync(user, silence_delay=1800, action_source=ActionSource.PHONE)
def get_gather_url():

View file

@ -81,4 +81,4 @@ def update_zvonok_call_status(call_id: str, call_status: str, user_choice: Optio
f"alert_group_id={alert_group.id} user_id={user.id}"
)
alert_group.acknowledge_by_user(user, action_source=ActionSource.PHONE)
alert_group.acknowledge_by_user_or_backsync(user, action_source=ActionSource.PHONE)