11import logging
22import operator
3+ from copy import copy
34from datetime import timedelta
45
56from django .db import transaction
67from django .utils import timezone
78from django .utils .encoding import force_text
89from rest_framework import serializers
10+ from snuba_sdk .legacy import json_to_snql
911
1012from sentry import analytics
1113from sentry .api .fields .actor import ActorField
1214from sentry .api .serializers .rest_framework .base import CamelSnakeModelSerializer
1315from sentry .api .serializers .rest_framework .environment import EnvironmentField
1416from sentry .api .serializers .rest_framework .project import ProjectField
15- from sentry .exceptions import InvalidSearchQuery
17+ from sentry .exceptions import InvalidSearchQuery , UnsupportedQuerySubscription
1618from sentry .incidents .logic import (
1719 CRITICAL_TRIGGER_LABEL ,
1820 WARNING_TRIGGER_LABEL ,
4345from sentry .models import OrganizationMember , SentryAppInstallation , Team , User
4446from sentry .shared_integrations .exceptions import ApiRateLimitedError
4547from sentry .snuba .dataset import Dataset
48+ from sentry .snuba .entity_subscription import map_aggregate_to_entity_subscription
4649from sentry .snuba .models import QueryDatasets , SnubaQueryEventType
4750from sentry .snuba .tasks import build_snuba_filter
51+ from sentry .utils import json
4852from sentry .utils .compat import zip
49- from sentry .utils .snuba import raw_query
53+ from sentry .utils .snuba import raw_snql_query
5054
5155logger = logging .getLogger (__name__ )
5256
@@ -443,7 +447,8 @@ def validate_dataset(self, dataset):
443447 )
444448
445449 def validate_event_types (self , event_types ):
446- if self .initial_data .get ("dataset" ) == Dataset .Sessions .value :
450+ dataset = self .initial_data .get ("dataset" )
451+ if dataset not in [Dataset .Events .value , Dataset .Transactions .value ]:
447452 return []
448453 try :
449454 return [SnubaQueryEventType .EventType [event_type .upper ()] for event_type in event_types ]
@@ -477,13 +482,24 @@ def validate(self, data):
477482 # the query. We don't use the returned data anywhere, so it doesn't
478483 # matter which.
479484 project_id = list (self .context ["organization" ].project_set .all ()[:1 ])
485+
486+ try :
487+ entity_subscription = map_aggregate_to_entity_subscription (
488+ dataset = QueryDatasets (data ["dataset" ]),
489+ aggregate = data ["aggregate" ],
490+ extra_fields = {
491+ "org_id" : project_id [0 ].organization_id ,
492+ "event_types" : data .get ("event_types" ),
493+ },
494+ )
495+ except UnsupportedQuerySubscription as e :
496+ raise serializers .ValidationError (f"{ e } " )
497+
480498 try :
481499 snuba_filter = build_snuba_filter (
482- data [ "dataset" ] ,
500+ entity_subscription ,
483501 data ["query" ],
484- data ["aggregate" ],
485502 data .get ("environment" ),
486- data .get ("event_types" ),
487503 params = {
488504 "project_id" : [p .id for p in project_id ],
489505 "start" : timezone .now () - timedelta (minutes = 10 ),
@@ -503,18 +519,35 @@ def validate(self, data):
503519 dataset = Dataset (data ["dataset" ].value )
504520 self ._validate_time_window (dataset , data .get ("time_window" ))
505521
522+ conditions = copy (snuba_filter .conditions )
523+ time_col = entity_subscription .time_col
524+ conditions += [
525+ [time_col , ">=" , snuba_filter .start ],
526+ [time_col , "<" , snuba_filter .end ],
527+ ]
528+
529+ body = {
530+ "project" : project_id [0 ].id ,
531+ "project_id" : project_id [0 ].id ,
532+ "aggregations" : snuba_filter .aggregations ,
533+ "conditions" : conditions ,
534+ "filter_keys" : snuba_filter .filter_keys ,
535+ "having" : snuba_filter .having ,
536+ "dataset" : dataset .value ,
537+ "limit" : 1 ,
538+ ** entity_subscription .get_entity_extra_params (),
539+ }
540+
506541 try :
507- raw_query (
508- aggregations = snuba_filter .aggregations ,
509- start = snuba_filter .start ,
510- end = snuba_filter .end ,
511- conditions = snuba_filter .conditions ,
512- filter_keys = snuba_filter .filter_keys ,
513- having = snuba_filter .having ,
514- dataset = dataset ,
515- limit = 1 ,
516- referrer = "alertruleserializer.test_query" ,
542+ snql_query = json_to_snql (body , entity_subscription .entity_key .value )
543+ snql_query .validate ()
544+ except Exception as e :
545+ raise serializers .ValidationError (
546+ str (e ), params = {"params" : json .dumps (body ), "dataset" : data ["dataset" ].value }
517547 )
548+
549+ try :
550+ raw_snql_query (snql_query , referrer = "alertruleserializer.test_query" )
518551 except Exception :
519552 logger .exception ("Error while validating snuba alert rule query" )
520553 raise serializers .ValidationError (
@@ -582,7 +615,7 @@ def _translate_thresholds(self, threshold_type, comparison_delta, triggers, data
582615
583616 @staticmethod
584617 def _validate_time_window (dataset , time_window ):
585- if dataset == Dataset .Sessions :
618+ if dataset in [ Dataset .Sessions , Dataset . Metrics ] :
586619 # Validate time window
587620 if time_window not in CRASH_RATE_ALERTS_ALLOWED_TIME_WINDOWS :
588621 raise serializers .ValidationError (
0 commit comments