Skip to content

Commit 28e4119

Browse files
feat(span-first): Add code to translate transaction alerts to spans (#102130)
Adding a function that will translate transaction alerts to spans. If it's a transaction alert, extrapolation is set to NONE, if it's a generic metric alert, it's set to SERVER_WEIGHTED. Note, the `SERVER_WEIGHTED` mode is not yet available in EAP, but an update to entity subscription to follow as soon as it is. --------- Co-authored-by: nikkikapadia <[email protected]>
1 parent c35b745 commit 28e4119

File tree

3 files changed

+959
-0
lines changed

3 files changed

+959
-0
lines changed
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import logging
2+
3+
from django.db import router, transaction
4+
5+
from sentry import features
6+
from sentry.discover.translation.mep_to_eap import QueryParts, translate_mep_to_eap
7+
from sentry.incidents.models.alert_rule import AlertRuleDetectionType
8+
from sentry.incidents.subscription_processor import MetricIssueDetectorConfig
9+
from sentry.incidents.utils.types import DATA_SOURCE_SNUBA_QUERY_SUBSCRIPTION
10+
from sentry.seer.anomaly_detection.store_data import SeerMethod
11+
from sentry.seer.anomaly_detection.store_data_workflow_engine import (
12+
handle_send_historical_data_to_seer,
13+
)
14+
from sentry.snuba.dataset import Dataset
15+
from sentry.snuba.models import (
16+
ExtrapolationMode,
17+
QuerySubscription,
18+
SnubaQuery,
19+
SnubaQueryEventType,
20+
)
21+
from sentry.snuba.tasks import update_subscription_in_snuba
22+
from sentry.utils.db import atomic_transaction
23+
from sentry.workflow_engine.models.data_condition import DataCondition
24+
from sentry.workflow_engine.models.data_source import DataSource
25+
26+
logger = logging.getLogger(__name__)
27+
28+
29+
def snapshot_snuba_query(snuba_query: SnubaQuery):
30+
if snuba_query.dataset in [Dataset.PerformanceMetrics.value, Dataset.Transactions.value]:
31+
query_snapshot = {
32+
"type": snuba_query.type,
33+
"dataset": snuba_query.dataset,
34+
"query": snuba_query.query,
35+
"aggregate": snuba_query.aggregate,
36+
"time_window": snuba_query.time_window,
37+
}
38+
snuba_query.query_snapshot = query_snapshot
39+
snuba_query.save()
40+
41+
return snuba_query
42+
43+
44+
def _get_old_query_info(snuba_query: SnubaQuery):
45+
old_query_type = SnubaQuery.Type(snuba_query.type)
46+
old_dataset = Dataset(snuba_query.dataset)
47+
old_query = snuba_query.query
48+
old_aggregate = snuba_query.aggregate
49+
50+
return old_query_type, old_dataset, old_query, old_aggregate
51+
52+
53+
def translate_detector_and_update_subscription_in_snuba(snuba_query: SnubaQuery):
54+
data_source: DataSource = DataSource.objects.get(
55+
source_id=str(snuba_query.id), type=DATA_SOURCE_SNUBA_QUERY_SUBSCRIPTION
56+
)
57+
if not features.has(
58+
"organizations:migrate-transaction-alerts-to-spans", data_source.organization
59+
):
60+
logger.info("Feature flag not enabled")
61+
return
62+
63+
detectors = data_source.detectors.all()
64+
snapshot_snuba_query(snuba_query)
65+
66+
snapshot = snuba_query.query_snapshot
67+
if not snapshot:
68+
return
69+
70+
old_query_type, old_dataset, old_query, old_aggregate = _get_old_query_info(snuba_query)
71+
72+
eap_query_parts, dropped_fields = translate_mep_to_eap(
73+
QueryParts(
74+
selected_columns=[snapshot["aggregate"]],
75+
query=snapshot["query"],
76+
equations=None,
77+
orderby=None,
78+
)
79+
)
80+
81+
if dropped_fields["selected_columns"]:
82+
return
83+
84+
translated_aggregate = eap_query_parts["selected_columns"][0]
85+
translated_query = eap_query_parts["query"]
86+
87+
snuba_query.aggregate = translated_aggregate
88+
snuba_query.query = translated_query
89+
snuba_query.dataset = Dataset.EventsAnalyticsPlatform.value
90+
91+
if snapshot["dataset"] == Dataset.PerformanceMetrics.value:
92+
snuba_query.extrapolation_mode = ExtrapolationMode.SERVER_WEIGHTED.value
93+
elif snapshot["dataset"] == Dataset.Transactions.value:
94+
snuba_query.extrapolation_mode = ExtrapolationMode.NONE.value
95+
96+
with atomic_transaction(
97+
using=(
98+
router.db_for_write(SnubaQuery),
99+
router.db_for_write(SnubaQueryEventType),
100+
)
101+
):
102+
snuba_query.save()
103+
SnubaQueryEventType.objects.filter(snuba_query=snuba_query).delete()
104+
SnubaQueryEventType.objects.create(
105+
snuba_query=snuba_query, type=SnubaQueryEventType.EventType.TRACE_ITEM_SPAN.value
106+
)
107+
108+
query_subscriptions = list(snuba_query.subscriptions.all())
109+
for subscription in query_subscriptions:
110+
with transaction.atomic(router.db_for_write(QuerySubscription)):
111+
subscription.update(status=QuerySubscription.Status.UPDATING.value)
112+
113+
transaction.on_commit(
114+
lambda: update_subscription_in_snuba(
115+
query_subscription_id=subscription.id,
116+
old_query_type=old_query_type.value,
117+
old_dataset=old_dataset.value,
118+
old_aggregate=old_aggregate,
119+
old_query=old_query,
120+
),
121+
using=router.db_for_write(QuerySubscription),
122+
)
123+
124+
for detector in detectors:
125+
detector_cfg: MetricIssueDetectorConfig = detector.config
126+
if detector_cfg["detection_type"] == AlertRuleDetectionType.DYNAMIC.value:
127+
data_condition = DataCondition.objects.get(
128+
condition_group=detector.workflow_condition_group
129+
)
130+
handle_send_historical_data_to_seer(
131+
detector,
132+
data_source,
133+
data_condition,
134+
snuba_query,
135+
detector.project,
136+
SeerMethod.UPDATE,
137+
event_types=[SnubaQueryEventType.EventType.TRACE_ITEM_SPAN],
138+
)
139+
140+
return
141+
142+
143+
def rollback_detector_query_and_update_subscription_in_snuba(snuba_query: SnubaQuery):
144+
data_source: DataSource = DataSource.objects.get(
145+
source_id=str(snuba_query.id), type=DATA_SOURCE_SNUBA_QUERY_SUBSCRIPTION
146+
)
147+
if not features.has(
148+
"organizations:migrate-transaction-alerts-to-spans", data_source.organization
149+
):
150+
logger.info("Feature flag not enabled")
151+
return
152+
153+
snapshot = snuba_query.query_snapshot
154+
155+
if not snapshot:
156+
return
157+
158+
detectors = data_source.detectors.all()
159+
160+
old_query_type, old_dataset, old_query, old_aggregate = _get_old_query_info(snuba_query)
161+
with atomic_transaction(
162+
using=(
163+
router.db_for_write(SnubaQuery),
164+
router.db_for_write(SnubaQueryEventType),
165+
)
166+
):
167+
snuba_query.update(
168+
type=snapshot["type"],
169+
dataset=snapshot["dataset"],
170+
query=snapshot["query"],
171+
aggregate=snapshot["aggregate"],
172+
time_window=snapshot["time_window"],
173+
)
174+
SnubaQueryEventType.objects.filter(snuba_query=snuba_query).delete()
175+
SnubaQueryEventType.objects.create(
176+
snuba_query=snuba_query, type=SnubaQueryEventType.EventType.TRANSACTION.value
177+
)
178+
179+
query_subscriptions = list(snuba_query.subscriptions.all())
180+
for subscription in query_subscriptions:
181+
with transaction.atomic(router.db_for_write(QuerySubscription)):
182+
subscription.update(status=QuerySubscription.Status.UPDATING.value)
183+
184+
transaction.on_commit(
185+
lambda: update_subscription_in_snuba(
186+
query_subscription_id=subscription.id,
187+
old_query_type=old_query_type.value,
188+
old_dataset=old_dataset.value,
189+
old_aggregate=old_aggregate,
190+
old_query=old_query,
191+
),
192+
using=router.db_for_write(QuerySubscription),
193+
)
194+
195+
for detector in detectors:
196+
detector_cfg: MetricIssueDetectorConfig = detector.config
197+
if detector_cfg["detection_type"] == AlertRuleDetectionType.DYNAMIC.value:
198+
data_condition = DataCondition.objects.get(
199+
condition_group=detector.workflow_condition_group
200+
)
201+
handle_send_historical_data_to_seer(
202+
detector,
203+
data_source,
204+
data_condition,
205+
snuba_query,
206+
detector.project,
207+
SeerMethod.UPDATE,
208+
event_types=[SnubaQueryEventType.EventType.TRANSACTION],
209+
)
210+
211+
return

src/sentry/features/temporary.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ def register_temporary_features(manager: FeatureManager) -> None:
132132
manager.add("organizations:discover-saved-queries-deprecation", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
133133
# Enable migration of transaction widgets and queries to spans
134134
manager.add("organizations:migrate-transaction-queries-to-spans", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
135+
# Enable migration of transaction alerts and queries to spans
136+
manager.add("organizations:migrate-transaction-alerts-to-spans", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
135137
# Enable new cell actions styling and features for tables that use the component
136138
manager.add("organizations:discover-cell-actions-v2", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
137139
# Enable trace-based health checks rule in dynamic sampling

0 commit comments

Comments
 (0)