Skip to content

Commit 326c7b4

Browse files
mifu67ceorourke
authored andcommitted
saving my work
1 parent b0e37a2 commit 326c7b4

File tree

3 files changed

+180
-56
lines changed

3 files changed

+180
-56
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
from datetime import timedelta
2+
from functools import cached_property
3+
from unittest.mock import call, patch
4+
5+
from sentry.workflow_engine.models.data_condition import Condition, DataCondition
6+
from sentry.workflow_engine.types import DetectorPriorityLevel
7+
from tests.sentry.incidents.subscription_processor.test_subscription_processor_base import (
8+
ProcessUpdateBaseClass,
9+
)
10+
11+
12+
class ProcessUpdateTest(ProcessUpdateBaseClass):
13+
"""
14+
Test early return scenarios + simple cases.
15+
"""
16+
17+
# TODO: tests for early return scenarios. These will need to be added once
18+
# we've decoupled the subscription processor from the alert rule model.
19+
20+
def test_simple(self) -> None:
21+
"""
22+
Verify that an alert can trigger.
23+
"""
24+
self.send_update(self.critical_threshold + 1)
25+
assert self.get_detector_state(self.metric_detector) == DetectorPriorityLevel.HIGH
26+
27+
def test_resolve(self) -> None:
28+
detector = self.metric_detector
29+
self.send_update(self.critical_threshold + 1, timedelta(minutes=-2))
30+
assert self.get_detector_state(detector) == DetectorPriorityLevel.HIGH
31+
32+
self.send_update(self.resolve_threshold - 1, timedelta(minutes=-1))
33+
assert self.get_detector_state(detector) == DetectorPriorityLevel.OK
34+
35+
def test_resolve_percent_boundary(self) -> None:
36+
detector = self.metric_detector
37+
self.update_threshold(detector, DetectorPriorityLevel.HIGH, 0.5)
38+
self.update_threshold(detector, DetectorPriorityLevel.OK, 0.5)
39+
self.send_update(self.critical_threshold + 0.1, timedelta(minutes=-2))
40+
assert self.get_detector_state(detector) == DetectorPriorityLevel.HIGH
41+
42+
self.send_update(self.resolve_threshold, timedelta(minutes=-1))
43+
assert self.get_detector_state(detector) == DetectorPriorityLevel.OK
44+
45+
def test_reversed(self) -> None:
46+
"""
47+
Test that resolutions work when the threshold is reversed.
48+
"""
49+
detector = self.metric_detector
50+
DataCondition.objects.filter(condition_group=detector.workflow_condition_group).delete()
51+
self.set_up_data_conditions(detector, Condition.LESS, 100, None, 100)
52+
self.send_update(self.critical_threshold - 1, timedelta(minutes=-2))
53+
assert self.get_detector_state(detector) == DetectorPriorityLevel.HIGH
54+
55+
self.send_update(self.resolve_threshold, timedelta(minutes=-1))
56+
assert self.get_detector_state(detector) == DetectorPriorityLevel.OK
57+
58+
def test_multiple_triggers(self) -> None:
59+
detector = self.metric_detector
60+
DataCondition.objects.filter(condition_group=detector.workflow_condition_group).delete()
61+
self.set_up_data_conditions(detector, Condition.GREATER, 100, 50, 50)
62+
63+
self.send_update(self.warning_threshold + 1, timedelta(minutes=-5))
64+
assert self.get_detector_state(detector) == DetectorPriorityLevel.MEDIUM
65+
66+
self.send_update(self.critical_threshold + 1, timedelta(minutes=-4))
67+
assert self.get_detector_state(detector) == DetectorPriorityLevel.HIGH
68+
69+
self.send_update(self.critical_threshold - 1, timedelta(minutes=-3))
70+
assert self.get_detector_state(detector) == DetectorPriorityLevel.MEDIUM
71+
72+
self.send_update(self.warning_threshold - 1, timedelta(minutes=-2))
73+
assert self.get_detector_state(detector) == DetectorPriorityLevel.OK
74+
75+
def test_multiple_triggers_reversed(self) -> None:
76+
detector = self.metric_detector
77+
DataCondition.objects.filter(condition_group=detector.workflow_condition_group).delete()
78+
self.set_up_data_conditions(detector, Condition.LESS, 50, 100, 100)
79+
80+
self.send_update(self.warning_threshold - 1, timedelta(minutes=-5))
81+
assert self.get_detector_state(detector) == DetectorPriorityLevel.MEDIUM
82+
83+
self.send_update(self.critical_threshold - 1, timedelta(minutes=-4))
84+
assert self.get_detector_state(detector) == DetectorPriorityLevel.HIGH
85+
86+
self.send_update(self.critical_threshold + 1, timedelta(minutes=-3))
87+
assert self.get_detector_state(detector) == DetectorPriorityLevel.MEDIUM
88+
89+
self.send_update(self.warning_threshold + 1, timedelta(minutes=-2))
90+
assert self.get_detector_state(detector) == DetectorPriorityLevel.OK
91+
92+
# TODO: the subscription processor has a 10 minute cooldown period for creating new incidents
93+
# We probably need similar logic within workflow engine.
94+
95+
96+
class ProcessUpdateComparisonAlertTest(ProcessUpdateBaseClass):
97+
@cached_property
98+
def comparison_detector_above(self):
99+
detector = self.metric_detector
100+
detector.config.update({"comparison_delta": 60 * 60})
101+
self.update_threshold(detector, DetectorPriorityLevel.HIGH, 150)
102+
snuba_query = self.get_snuba_query(detector)
103+
snuba_query.update(time_window=60 * 60)
104+
return detector
105+
106+
@cached_property
107+
def comparison_detector_below(self):
108+
detector = self.metric_detector
109+
detector.config.update({"comparison_delta": 60 * 60})
110+
DataCondition.objects.filter(condition_group=detector.workflow_condition_group).delete()
111+
self.set_up_data_conditions(detector, Condition.LESS, 50, None, 50)
112+
snuba_query = self.get_snuba_query(detector)
113+
snuba_query.update(time_window=60 * 60)
114+
return detector
115+
116+
@patch("sentry.incidents.utils.process_update_helpers.metrics")
117+
def test_comparison_alert_above(self, helper_metrics):
118+
detector = self.comparison_detector_above
119+
# comparison_delta = timedelta(seconds=detector.config["comparison_delta"])
120+
self.send_update(self.critical_threshold + 1, timedelta(minutes=-10), subscription=self.sub)
121+
122+
# Shouldn't trigger, since there should be no data in the comparison period
123+
assert self.get_detector_state(detector) == DetectorPriorityLevel.OK
124+
helper_metrics.incr.assert_has_calls(
125+
[
126+
call("incidents.alert_rules.skipping_update_comparison_value_invalid"),
127+
]
128+
)
129+
self.metrics.incr.assert_has_calls(
130+
[
131+
call("incidents.alert_rules.skipping_update_invalid_aggregation_value"),
132+
]
133+
)

tests/sentry/incidents/subscription_processor/test_subscription_processor_base.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@
1818
from sentry.testutils.cases import SnubaTestCase, SpanTestCase, TestCase
1919
from sentry.testutils.fixtures import DetectorPriorityLevel
2020
from sentry.testutils.helpers.datetime import freeze_time
21+
from sentry.testutils.helpers.features import apply_feature_flag_on_cls
22+
from sentry.workflow_engine.models import DataSource, DataSourceDetector, DetectorState
2123
from sentry.workflow_engine.models.data_condition import Condition, DataCondition
2224
from sentry.workflow_engine.models.detector import Detector
2325

2426
EMPTY = object()
2527

2628

2729
@freeze_time()
30+
@apply_feature_flag_on_cls("organizations:workflow-engine-single-process-metric-issues")
2831
class ProcessUpdateBaseClass(TestCase, SpanTestCase, SnubaTestCase):
2932
@pytest.fixture(autouse=True)
3033
def _setup_metrics_patch(self):
@@ -52,6 +55,7 @@ def create_detector_data_source_and_data_conditions(self):
5255
type=MetricIssue.slug,
5356
created_by_id=self.user.id,
5457
)
58+
self.create_detector_state(detector=detector)
5559
with self.tasks():
5660
snuba_query = create_snuba_query(
5761
query_type=SnubaQuery.Type.ERROR,
@@ -138,6 +142,39 @@ def critical_threshold(self):
138142
)
139143
return critical_detector_trigger.comparison
140144

145+
@cached_property
146+
def warning_threshold(self):
147+
warning_detector_trigger = DataCondition.objects.get(
148+
condition_group=self.metric_detector.workflow_condition_group,
149+
condition_result=DetectorPriorityLevel.MEDIUM,
150+
)
151+
return warning_detector_trigger.comparison
152+
153+
@cached_property
154+
def resolve_threshold(self):
155+
resolve_detector_trigger = DataCondition.objects.get(
156+
condition_group=self.metric_detector.workflow_condition_group,
157+
condition_result=DetectorPriorityLevel.OK,
158+
)
159+
return resolve_detector_trigger.comparison
160+
161+
def get_snuba_query(self, detector: Detector):
162+
data_source_detector = DataSourceDetector.objects.get(detector=detector)
163+
data_source = DataSource.objects.get(id=data_source_detector.data_source.id)
164+
query_subscription = QuerySubscription.objects.get(id=data_source.source_id)
165+
snuba_query = SnubaQuery.objects.get(id=query_subscription.snuba_query.id)
166+
return snuba_query
167+
168+
def update_threshold(
169+
self, detector: Detector, priority_level: DetectorPriorityLevel, new_threshold: float
170+
) -> None:
171+
detector_trigger = DataCondition.objects.get(
172+
condition_group=detector.workflow_condition_group,
173+
condition_result=priority_level,
174+
)
175+
detector_trigger.comparison = new_threshold
176+
detector_trigger.save()
177+
141178
def build_subscription_update(self, subscription, time_delta=None, value=EMPTY):
142179
if time_delta is not None:
143180
timestamp = timezone.now() + time_delta
@@ -172,3 +209,13 @@ def send_update(self, value, time_delta=None, subscription=None):
172209
):
173210
processor.process_update(message)
174211
return processor
212+
213+
# assert active incident: assert that open period exists
214+
# which means occurrence created, open period for its group, date_ended is null
215+
# query for occurrence evidence_data["detector_id"] == self.detector.id?
216+
# wait but we can't even filter. sigh.
217+
# I guess I can check the detector status. ugh.
218+
# I can also check that there's no open periods for the *project*, but idk how helpful that is
219+
def get_detector_state(self, detector: Detector) -> DetectorPriorityLevel:
220+
detector_state = DetectorState.objects.get(detector=detector)
221+
return int(detector_state.state)

tests/sentry/incidents/subscription_processor/test_subscription_processor_early_quit.py

Lines changed: 0 additions & 56 deletions
This file was deleted.

0 commit comments

Comments
 (0)