Skip to content

Commit

Permalink
Revert "chore(alerts): Remove activated alert models (#83255)"
Browse files Browse the repository at this point in the history
This reverts commit d8966ed.

Co-authored-by: ceorourke <[email protected]>
  • Loading branch information
getsentry-bot and ceorourke committed Jan 10, 2025
1 parent e4a33bb commit 0123b4a
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 71 deletions.
2 changes: 1 addition & 1 deletion migrations_lockfile.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ remote_subscriptions: 0003_drop_remote_subscription

replays: 0004_index_together

sentry: 0813_rm_alertruleactivations
sentry: 0812_rm_activation_incident

social_auth: 0002_default_auto_field

Expand Down
1 change: 1 addition & 0 deletions src/sentry/incidents/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .alert_rule_activations import * # NOQA
96 changes: 96 additions & 0 deletions src/sentry/incidents/models/alert_rule_activations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from __future__ import annotations

import logging
from datetime import datetime
from typing import TYPE_CHECKING, ClassVar

from django.db import models
from django.utils import timezone

from sentry.backup.scopes import RelocationScope
from sentry.db.models import FlexibleForeignKey, Model, region_silo_model
from sentry.db.models.manager.base import BaseManager
from sentry.models.releases.constants import DB_VERSION_LENGTH

if TYPE_CHECKING:
from sentry.incidents.models.alert_rule import AlertRule

logger = logging.getLogger(__name__)


@region_silo_model
class AlertRuleActivationCondition(Model):
"""
This model represents the activation condition for an activated AlertRule
label is an optional identifier for an activation condition
condition_type is AlertRuleActivationConditionType (Release creation / Deploy creation)
TODO: implement extra query params for advanced conditional rules (eg. +10m after event occurs)
"""

__relocation_scope__ = RelocationScope.Organization

alert_rule = FlexibleForeignKey("sentry.AlertRule", related_name="activation_condition")
label = models.TextField()
condition_type = models.SmallIntegerField(null=True)

date_added = models.DateTimeField(default=timezone.now)

class Meta:
app_label = "sentry"
db_table = "sentry_alertruleactivationcondition"
unique_together = (("alert_rule", "label"),)


class AlertRuleActivationsManager(BaseManager["AlertRuleActivations"]):
def get_activations_in_window(self, alert_rule: AlertRule, start: datetime, end: datetime):
"""
Return all activations for this alert rule that were activated in the window
"""
return self.filter(alert_rule=alert_rule, date_added__gte=start, date_added__lte=end)


@region_silo_model
class AlertRuleActivations(Model):
"""
This model represents the record of activations for Alert Rules with monitor_type 'activated'
"""

__relocation_scope__ = RelocationScope.Excluded

objects: ClassVar[AlertRuleActivationsManager] = AlertRuleActivationsManager()

alert_rule = FlexibleForeignKey("sentry.AlertRule", related_name="activations")
# date_added timestamp indicates when this particular run was activated
date_added = models.DateTimeField(default=timezone.now)
# If finished_at is null, this indicates whether the run is ongoing or completed
finished_at = models.DateTimeField(null=True)
# metric value represents the query results at the end of the activated time window.
# since activated alerts are not run on a continuously shifting time window, the value
# of this metric value will only continually tick upwards until the monitor window has expired.
metric_value = models.FloatField(null=True)
# query_subscriptions are cleaned up after every run
# if a query_subscription is null, finished_at must be NOT be null
query_subscription = FlexibleForeignKey(
"sentry.QuerySubscription", null=True, on_delete=models.SET_NULL
)
# condition_type is AlertRuleActivationConditionType (Release creation / Deploy creation)
condition_type = models.SmallIntegerField(default=0)
# The activator is the identifier for the specific triggered instance (eg. release/deploy version)
activator = models.CharField(max_length=DB_VERSION_LENGTH, default="default_activator")

class Meta:
app_label = "sentry"
db_table = "sentry_alertruleactivations"
indexes = [models.Index(fields=("alert_rule", "date_added"))]

def is_complete(self) -> bool:
return bool(self.finished_at)

def get_triggers(self):
"""
Alert Rule triggers represent the thresholds required to trigger an activation
NOTE: AlertRule attr's may change and may not be reliable indicators of incident trigger reasons
"""
return self.alert_rule.alertruletrigger_set.get()
67 changes: 0 additions & 67 deletions src/sentry/migrations/0813_rm_alertruleactivations.py

This file was deleted.

15 changes: 15 additions & 0 deletions src/sentry/testutils/helpers/backups.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,18 @@
from sentry.backup.validate import validate
from sentry.data_secrecy.models import DataSecrecyWaiver
from sentry.db.models.paranoia import ParanoidModel
from sentry.incidents.models.alert_rule_activations import (
AlertRuleActivationCondition,
AlertRuleActivations,
)
from sentry.incidents.models.incident import (
IncidentActivity,
IncidentSnapshot,
IncidentTrigger,
PendingIncidentSnapshot,
TimeSeriesSnapshot,
)
from sentry.incidents.utils.types import AlertRuleActivationConditionType
from sentry.integrations.models.integration import Integration
from sentry.integrations.models.organization_integration import OrganizationIntegration
from sentry.integrations.models.project_integration import ProjectIntegration
Expand Down Expand Up @@ -523,6 +528,16 @@ def create_exhaustive_organization(
assert alert.snuba_query is not None
self.create_alert_rule_trigger_action(alert_rule_trigger=trigger)

AlertRuleActivations.objects.create(
alert_rule=alert,
finished_at=None,
metric_value=100,
query_subscription=None,
condition_type=AlertRuleActivationConditionType.RELEASE_CREATION.value,
activator="testing",
)
AlertRuleActivationCondition.objects.create(alert_rule=alert, condition_type=None)

# Incident*
incident = self.create_incident(org, [project])
IncidentActivity.objects.create(
Expand Down
34 changes: 34 additions & 0 deletions tests/sentry/incidents/test_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import responses
from django.core import mail
from django.forms import ValidationError
from django.test import override_settings
from django.utils import timezone
from slack_sdk.web.slack_response import SlackResponse
from urllib3.exceptions import MaxRetryError, TimeoutError
Expand Down Expand Up @@ -490,6 +491,39 @@ def test_release_version(self):
assert alert_rule.resolve_threshold == resolve_threshold
assert alert_rule.threshold_period == threshold_period

# This test will fail unless real migrations are run. Refer to migration 0061.
@pytest.mark.migrations # requires custom migration 0061
@override_settings(SILO_MODE=SiloMode.MONOLITH)
def test_two_archived_with_same_name(self):
name = "allowed"
alert_rule_1 = create_alert_rule(
self.organization,
[self.project],
name,
"level:error",
"count()",
1,
AlertRuleThresholdType.ABOVE,
1,
)
alert_rule_1.update(status=AlertRuleStatus.SNAPSHOT.value)

alert_rule_2 = create_alert_rule(
self.organization,
[self.project],
name,
"level:error",
"count()",
1,
AlertRuleThresholdType.ABOVE,
1,
)
alert_rule_2.update(status=AlertRuleStatus.SNAPSHOT.value)

assert alert_rule_1.name == alert_rule_2.name
assert alert_rule_1.status == AlertRuleStatus.SNAPSHOT.value
assert alert_rule_2.status == AlertRuleStatus.SNAPSHOT.value

def test_alert_rule_owner(self):
alert_rule_1 = create_alert_rule(
self.organization,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import pytest

from sentry.models.group import Group, GroupStatus
from sentry.models.groupinbox import GroupInbox, GroupInboxReason
from sentry.models.organization import Organization
Expand Down Expand Up @@ -33,7 +31,6 @@ def setup_before_migration(self, app):
GroupInbox.objects.create(group=self.ignored_group, reason=GroupInboxReason.NEW.value)
GroupInbox.objects.create(group=self.unresolved_group, reason=GroupInboxReason.NEW.value)

@pytest.mark.skip(reason="unneeded")
def test(self):
assert not GroupInbox.objects.filter(group=self.resolved_group).exists()
assert not GroupInbox.objects.filter(group=self.ignored_group).exists()
Expand Down

0 comments on commit 0123b4a

Please sign in to comment.