From 9edcbeabd8f58382cfcfe3894a22813944618019 Mon Sep 17 00:00:00 2001 From: Ogi <86684834+obostjancic@users.noreply.github.com> Date: Fri, 21 Jul 2023 09:53:42 +0200 Subject: [PATCH] feat(alerts): on demand metric timeseries support (#53053) --- .../endpoints/organization_events_stats.py | 5 + src/sentry/search/events/builder/discover.py | 2 + src/sentry/search/events/builder/metrics.py | 70 ++++---- src/sentry/snuba/discover.py | 1 + src/sentry/snuba/entity_subscription.py | 5 + src/sentry/snuba/functions.py | 2 + src/sentry/snuba/issue_platform.py | 1 + src/sentry/snuba/metrics/extraction.py | 21 ++- .../snuba/metrics_enhanced_performance.py | 2 + src/sentry/snuba/metrics_performance.py | 4 + src/sentry/snuba/profiles.py | 2 + src/sentry/snuba/spans_indexed.py | 1 + src/sentry/snuba/spans_metrics.py | 1 + .../search/events/builder/test_metrics.py | 154 ++++++++++++------ tests/sentry/snuba/test_extraction.py | 10 +- 15 files changed, 197 insertions(+), 84 deletions(-) diff --git a/src/sentry/api/endpoints/organization_events_stats.py b/src/sentry/api/endpoints/organization_events_stats.py index 5dfd7968f8637e..d2938accc2f19d 100644 --- a/src/sentry/api/endpoints/organization_events_stats.py +++ b/src/sentry/api/endpoints/organization_events_stats.py @@ -97,6 +97,7 @@ def get_features(self, organization: Organization, request: Request) -> Mapping[ "organizations:mep-rollout-flag", "organizations:use-metrics-layer", "organizations:starfish-view", + "organizations:on-demand-metrics-extraction", ] batch_features = features.batch_has( feature_names, @@ -182,6 +183,8 @@ def get(self, request: Request, organization: Organization) -> Response: allow_metric_aggregates = request.GET.get("preventMetricAggregates") != "1" sentry_sdk.set_tag("performance.metrics_enhanced", metrics_enhanced) + use_on_demand_metrics = request.GET.get("useOnDemandMetrics") + def get_event_stats( query_columns: Sequence[str], query: str, @@ -217,6 +220,8 @@ def get_event_stats( allow_metric_aggregates=allow_metric_aggregates, has_metrics=use_metrics, use_metrics_layer=batch_features.get("organizations:use-metrics-layer", False), + on_demand_metrics_enabled=use_on_demand_metrics + and batch_features.get("organizations:on-demand-metrics-extraction", False), ) try: diff --git a/src/sentry/search/events/builder/discover.py b/src/sentry/search/events/builder/discover.py index fb9d5abb523ed0..de7c1168d05c8d 100644 --- a/src/sentry/search/events/builder/discover.py +++ b/src/sentry/search/events/builder/discover.py @@ -219,6 +219,7 @@ def __init__( # Currently this is only used for avoiding conflicting values when doing the first query # of a top events request skip_tag_resolution: bool = False, + on_demand_metrics_enabled: bool = False, ): self.dataset = dataset @@ -234,6 +235,7 @@ def __init__( self.transform_alias_to_input_format = transform_alias_to_input_format self.raw_equations = equations self.use_metrics_layer = use_metrics_layer + self.on_demand_metrics_enabled = on_demand_metrics_enabled self.auto_fields = auto_fields self.query = query self.groupby_columns = groupby_columns diff --git a/src/sentry/search/events/builder/metrics.py b/src/sentry/search/events/builder/metrics.py index 1b3f6b9e7c0fa8..f213ef031819f6 100644 --- a/src/sentry/search/events/builder/metrics.py +++ b/src/sentry/search/events/builder/metrics.py @@ -50,6 +50,7 @@ class MetricsQueryBuilder(QueryBuilder): requires_organization_condition = True is_alerts_query = False + organization_column: str = "organization_id" def __init__( @@ -75,10 +76,6 @@ def __init__( kwargs["has_metrics"] = True assert dataset is None or dataset in [Dataset.PerformanceMetrics, Dataset.Metrics] - self._on_demand_spec = self._resolve_on_demand_spec( - dataset, kwargs.get("selected_columns", []), kwargs.get("query", "") - ) - if granularity is not None: self._granularity = granularity super().__init__( @@ -88,6 +85,10 @@ def __init__( *args, **kwargs, ) + self._on_demand_spec = self._resolve_on_demand_spec( + dataset, kwargs.get("selected_columns", []), kwargs.get("query", "") + ) + org_id = self.filter_params.get("organization_id") if org_id is None and self.params.organization is not None: org_id = self.params.organization.id @@ -98,8 +99,11 @@ def __init__( def _resolve_on_demand_spec( self, dataset: Optional[Dataset], selected_cols: List[Optional[str]], query: str ) -> Optional[OndemandMetricSpec]: + if not self.on_demand_metrics_enabled: + return None + field = selected_cols[0] if selected_cols else None - if not self.is_alerts_query or not field: + if not field: return None if not is_on_demand_query(dataset, field, query): @@ -112,13 +116,18 @@ def _resolve_on_demand_spec( return None def _get_on_demand_metrics_query(self) -> Optional[MetricsQuery]: - if not self.is_performance or not self.is_alerts_query: - return None - spec = self._on_demand_spec + # TimeseriesQueryBuilder specific parameters + if isinstance(self, TimeseriesMetricQueryBuilder): + limit = None + alias = "count" + else: + limit = self.limit + alias = spec.mri + return MetricsQuery( - select=[MetricField(spec.op, spec.mri, alias=spec.mri)], + select=[MetricField(spec.op, spec.mri, alias=alias)], where=[ Condition( lhs=Column(QUERY_HASH_KEY), @@ -126,16 +135,13 @@ def _get_on_demand_metrics_query(self) -> Optional[MetricsQuery]: rhs=spec.query_hash(), ), ], - # TODO(ogi): groupby and orderby - limit=self.limit, + limit=limit, offset=self.offset, granularity=self.resolve_granularity(), is_alerts_query=self.is_alerts_query, org_id=self.params.organization.id, project_ids=[p.id for p in self.params.projects], - # We do not need the series here, as later, we only extract the totals and assign it to the - # request.query - include_series=False, + include_series=True, start=self.params.start, end=self.params.end, ) @@ -198,7 +204,7 @@ def resolve_column_name(self, col: str) -> str: col = tag_match.group("tag") if tag_match else col # on-demand metrics require metrics layer behavior - if self.use_metrics_layer or self._on_demand_spec: + if self.use_metrics_layer or self.on_demand_metrics_enabled: if col in ["project_id", "timestamp"]: return col # TODO: update resolve params so this isn't needed @@ -571,7 +577,7 @@ def get_metrics_layer_snql_query(self) -> Request: snuba SDK. """ - if not self.use_metrics_layer and not self._on_demand_spec: + if not self.use_metrics_layer and not self.on_demand_metrics_enabled: # The reasoning for this error is because if "use_metrics_layer" is false, the MQB will not generate the # snql dialect explained below as there is not need for that because it will directly generate normal snql # that can be returned via the "get_snql_query" method. @@ -784,7 +790,7 @@ def validate_orderby_clause(self) -> None: raise IncompatibleMetricsQuery("Can't orderby tags") def run_query(self, referrer: str, use_cache: bool = False) -> Any: - if self.use_metrics_layer or self._on_demand_spec: + if self.use_metrics_layer or self.on_demand_metrics_enabled: from sentry.snuba.metrics.datasource import get_series from sentry.snuba.metrics.mqb_query_transformer import ( transform_mqb_query_to_metrics_query, @@ -792,17 +798,16 @@ def run_query(self, referrer: str, use_cache: bool = False) -> Any: try: with sentry_sdk.start_span(op="metric_layer", description="transform_query"): - if self._on_demand_spec: - metric_query = self._get_on_demand_metrics_query() + if self.on_demand_metrics_enabled: + metrics_query = self._get_on_demand_metrics_query() else: - metric_query = transform_mqb_query_to_metrics_query( + metrics_query = transform_mqb_query_to_metrics_query( self.get_metrics_layer_snql_query().query, self.is_alerts_query ) - # metric_query.where = metric_query_ondemand.where with sentry_sdk.start_span(op="metric_layer", description="run_query"): metrics_data = get_series( projects=self.params.projects, - metrics_query=metric_query, + metrics_query=metrics_query, use_case_id=UseCaseID.TRANSACTIONS if self.is_performance else UseCaseID.SESSIONS, @@ -822,7 +827,7 @@ def run_query(self, referrer: str, use_cache: bool = False) -> Any: data.update(group["totals"]) metric_layer_result["data"].append(data) for meta in metric_layer_result["meta"]: - if data[meta["name"]] is None: + if data.get(meta["name"]) is None: data[meta["name"]] = self.get_default_value(meta["type"]) return metric_layer_result @@ -984,7 +989,7 @@ def get_snql_query(self) -> Request: and returns one or more equivalent snql query(ies). """ - if self.use_metrics_layer or self._on_demand_spec: + if self.use_metrics_layer or self.on_demand_metrics_enabled: from sentry.snuba.metrics import SnubaQueryBuilder from sentry.snuba.metrics.mqb_query_transformer import ( transform_mqb_query_to_metrics_query, @@ -992,7 +997,7 @@ def get_snql_query(self) -> Request: snuba_request = self.get_metrics_layer_snql_query() - if self._on_demand_spec: + if self.on_demand_metrics_enabled: metrics_query = self._get_on_demand_metrics_query() else: metrics_query = transform_mqb_query_to_metrics_query( @@ -1081,6 +1086,7 @@ def __init__( limit: Optional[int] = 10000, use_metrics_layer: Optional[bool] = False, groupby: Optional[Column] = None, + on_demand_metrics_enabled: Optional[bool] = False, ): super().__init__( params=params, @@ -1091,6 +1097,7 @@ def __init__( auto_fields=False, functions_acl=functions_acl, use_metrics_layer=use_metrics_layer, + on_demand_metrics_enabled=on_demand_metrics_enabled, ) if self.granularity.granularity > interval: for granularity in constants.METRICS_GRANULARITIES: @@ -1210,7 +1217,7 @@ def get_snql_query(self) -> List[Request]: return queries def run_query(self, referrer: str, use_cache: bool = False) -> Any: - if self.use_metrics_layer: + if self.use_metrics_layer or self.on_demand_metrics_enabled: from sentry.snuba.metrics.datasource import get_series from sentry.snuba.metrics.mqb_query_transformer import ( transform_mqb_query_to_metrics_query, @@ -1219,13 +1226,16 @@ def run_query(self, referrer: str, use_cache: bool = False) -> Any: snuba_query = self.get_snql_query()[0].query try: with sentry_sdk.start_span(op="metric_layer", description="transform_query"): - metric_query = transform_mqb_query_to_metrics_query( - snuba_query, self.is_alerts_query - ) + if self.on_demand_metrics_enabled: + metrics_query = self._get_on_demand_metrics_query() + else: + metrics_query = transform_mqb_query_to_metrics_query( + snuba_query, self.is_alerts_query + ) with sentry_sdk.start_span(op="metric_layer", description="run_query"): metrics_data = get_series( projects=self.params.projects, - metrics_query=metric_query, + metrics_query=metrics_query, use_case_id=UseCaseID.TRANSACTIONS if self.is_performance else UseCaseID.SESSIONS, diff --git a/src/sentry/snuba/discover.py b/src/sentry/snuba/discover.py index 66cb295d8fe5d3..a52bfe1dc4aab4 100644 --- a/src/sentry/snuba/discover.py +++ b/src/sentry/snuba/discover.py @@ -262,6 +262,7 @@ def timeseries_query( allow_metric_aggregates=False, has_metrics=False, use_metrics_layer=False, + on_demand_metrics_enabled=False, ): """ High-level API for doing arbitrary user timeseries queries against events. diff --git a/src/sentry/snuba/entity_subscription.py b/src/sentry/snuba/entity_subscription.py index 093df7111371ce..b79a888560e40a 100644 --- a/src/sentry/snuba/entity_subscription.py +++ b/src/sentry/snuba/entity_subscription.py @@ -305,6 +305,10 @@ def __init__( self.use_metrics_layer = features.has( "organizations:use-metrics-layer", Organization.objects.get_from_cache(id=self.org_id) ) + self.on_demand_metrics_enabled = features.has( + "organizations:on-demand-metrics-extraction", + Organization.objects.get_from_cache(id=self.org_id), + ) @abstractmethod def get_snql_aggregations(self) -> List[str]: @@ -378,6 +382,7 @@ def build_query_builder( skip_time_conditions=True, granularity=self.get_granularity(), use_metrics_layer=self.use_metrics_layer, + on_demand_metrics_enabled=self.on_demand_metrics_enabled, ) extra_conditions = self.get_snql_extra_conditions() diff --git a/src/sentry/snuba/functions.py b/src/sentry/snuba/functions.py index 98f9abca2952ac..1c0ceba92eee2b 100644 --- a/src/sentry/snuba/functions.py +++ b/src/sentry/snuba/functions.py @@ -37,6 +37,7 @@ def query( has_metrics: bool = False, functions_acl: Optional[List[str]] = None, use_metrics_layer: bool = False, + on_demand_metrics_enabled: bool = False, ) -> Any: if not selected_columns: raise InvalidSearchQuery("No columns selected") @@ -73,6 +74,7 @@ def timeseries_query( allow_metric_aggregates: bool = False, has_metrics: bool = False, use_metrics_layer: bool = False, + on_demand_metrics_enabled: bool = False, ) -> Any: builder = ProfileFunctionsTimeseriesQueryBuilder( dataset=Dataset.Functions, diff --git a/src/sentry/snuba/issue_platform.py b/src/sentry/snuba/issue_platform.py index c8918e67644f7d..9aaa38b6a4db9d 100644 --- a/src/sentry/snuba/issue_platform.py +++ b/src/sentry/snuba/issue_platform.py @@ -109,6 +109,7 @@ def timeseries_query( allow_metric_aggregates=False, has_metrics=False, use_metrics_layer=False, + on_demand_metrics_enabled=False, ): """ High-level API for doing arbitrary user timeseries queries against events. diff --git a/src/sentry/snuba/metrics/extraction.py b/src/sentry/snuba/metrics/extraction.py index c747391311d3ec..1bce662670d79b 100644 --- a/src/sentry/snuba/metrics/extraction.py +++ b/src/sentry/snuba/metrics/extraction.py @@ -121,7 +121,8 @@ "p99": "d", } -# Query fields that on their own do not require on-demand metric extraction. +# Query fields that on their own do not require on-demand metric extraction but if present in an on-demand query +# will be converted to metric extraction conditions. _STANDARD_METRIC_FIELDS = [ "release", "dist", @@ -135,7 +136,9 @@ "browser.name", "os.name", "geo.country_code", + # These fields are skipped during on demand spec generation and will not be converted to metric extraction conditions "event.type", + "project", ] # Operators used in ``ComparingRuleCondition``. @@ -222,9 +225,8 @@ def is_on_demand_query( for field in _get_aggregate_fields(aggregate): if not _is_standard_metrics_field(field): return True - try: - return not _is_standard_metrics_query(event_search.parse_search_query(query or "")) + return not _is_standard_metrics_query(event_search.parse_search_query(query)) except InvalidSearchQuery: logger.error(f"Failed to parse search query: {query}", exc_info=True) return False @@ -304,11 +306,18 @@ def __init__(self, field: str, query: str): """ - # On-demand metrics are implicitly transaction metrics. Remove the - # filter from the query since it can't be translated to a RuleCondition. - self._query = re.sub(r"event\.type:transaction\s*", "", query) + self._init__query(query) self._init_aggregate(field) + def _init__query(self, query: str) -> None: + # On-demand metrics are implicitly transaction metrics. Remove the + # filters from the query that can't be translated to a RuleCondition. + query = re.sub(r"event\.type:transaction\s*", "", query) + # extend the following to also support project:"some-project" + query = re.sub(r"project:[\w\"]+\s*", "", query) + + self._query = query.strip() + def _init_aggregate(self, aggregate: str) -> None: """ Extracts the field name, metric type and metric operation from a Discover diff --git a/src/sentry/snuba/metrics_enhanced_performance.py b/src/sentry/snuba/metrics_enhanced_performance.py index 07e445a62194a4..c27b2d0d0f1d59 100644 --- a/src/sentry/snuba/metrics_enhanced_performance.py +++ b/src/sentry/snuba/metrics_enhanced_performance.py @@ -31,6 +31,7 @@ def query( transform_alias_to_input_format=False, has_metrics: bool = True, use_metrics_layer: bool = False, + on_demand_metrics_enabled: bool = False, ): metrics_compatible = not equations @@ -105,6 +106,7 @@ def timeseries_query( functions_acl: Optional[List[str]] = None, has_metrics: bool = True, use_metrics_layer: bool = False, + on_demand_metrics_enabled: bool = False, ) -> SnubaTSResult: """ High-level API for doing arbitrary user timeseries queries against events. diff --git a/src/sentry/snuba/metrics_performance.py b/src/sentry/snuba/metrics_performance.py index a0209ce6c69cb7..bade029baee15f 100644 --- a/src/sentry/snuba/metrics_performance.py +++ b/src/sentry/snuba/metrics_performance.py @@ -37,6 +37,7 @@ def query( transform_alias_to_input_format=False, has_metrics: bool = True, use_metrics_layer: bool = False, + on_demand_metrics_enabled: bool = False, granularity: Optional[int] = None, ): with sentry_sdk.start_span(op="mep", description="MetricQueryBuilder"): @@ -81,6 +82,7 @@ def bulk_timeseries_query( functions_acl: Optional[List[str]] = None, has_metrics: bool = True, use_metrics_layer: bool = False, + on_demand_metrics_enabled: bool = False, groupby: Optional[Column] = None, apply_formatting: Optional[bool] = True, ) -> SnubaTSResult: @@ -179,6 +181,7 @@ def timeseries_query( functions_acl: Optional[List[str]] = None, has_metrics: bool = True, use_metrics_layer: bool = False, + on_demand_metrics_enabled: bool = False, groupby: Optional[Column] = None, ) -> SnubaTSResult: """ @@ -202,6 +205,7 @@ def timeseries_query( allow_metric_aggregates=allow_metric_aggregates, use_metrics_layer=use_metrics_layer, groupby=groupby, + on_demand_metrics_enabled=on_demand_metrics_enabled, ) metrics_referrer = referrer + ".metrics-enhanced" result = metrics_query.run_query(metrics_referrer) diff --git a/src/sentry/snuba/profiles.py b/src/sentry/snuba/profiles.py index 2c0d868af39f0c..a8d7c2f8055724 100644 --- a/src/sentry/snuba/profiles.py +++ b/src/sentry/snuba/profiles.py @@ -28,6 +28,7 @@ def query( has_metrics: bool = False, functions_acl: Optional[List[str]] = None, use_metrics_layer: bool = False, + on_demand_metrics_enabled: bool = False, ) -> Any: if not selected_columns: raise InvalidSearchQuery("No columns selected") @@ -64,6 +65,7 @@ def timeseries_query( allow_metric_aggregates: bool = False, has_metrics: bool = False, use_metrics_layer: bool = False, + on_demand_metrics_enabled: bool = False, ) -> Any: builder = ProfilesTimeseriesQueryBuilder( dataset=Dataset.Profiles, diff --git a/src/sentry/snuba/spans_indexed.py b/src/sentry/snuba/spans_indexed.py index 7492158036cc52..d85900343ff32d 100644 --- a/src/sentry/snuba/spans_indexed.py +++ b/src/sentry/snuba/spans_indexed.py @@ -78,6 +78,7 @@ def timeseries_query( functions_acl: Optional[List[str]] = None, has_metrics: bool = True, use_metrics_layer: bool = False, + on_demand_metrics_enabled: bool = False, ) -> SnubaTSResult: """ High-level API for doing arbitrary user timeseries queries against events. diff --git a/src/sentry/snuba/spans_metrics.py b/src/sentry/snuba/spans_metrics.py index 2a000ea8166036..e6abc051be4501 100644 --- a/src/sentry/snuba/spans_metrics.py +++ b/src/sentry/snuba/spans_metrics.py @@ -77,6 +77,7 @@ def timeseries_query( functions_acl: Optional[List[str]] = None, has_metrics: bool = True, use_metrics_layer: bool = False, + on_demand_metrics_enabled: bool = False, groupby: Optional[Column] = None, ) -> SnubaTSResult: """ diff --git a/tests/sentry/search/events/builder/test_metrics.py b/tests/sentry/search/events/builder/test_metrics.py index a2a9593a9b50b2..54ae93880d6127 100644 --- a/tests/sentry/search/events/builder/test_metrics.py +++ b/tests/sentry/search/events/builder/test_metrics.py @@ -22,7 +22,6 @@ from sentry.snuba.dataset import Dataset from sentry.snuba.metrics.extraction import QUERY_HASH_KEY from sentry.testutils.cases import MetricsEnhancedPerformanceTestCase -from sentry.testutils.helpers import Feature pytestmark = pytest.mark.sentry_metrics @@ -1953,6 +1952,46 @@ def test_no_error_if_aggregates_disallowed_but_no_aggregates_included(self): allow_metric_aggregates=False, ) + def test_on_demand_metrics(self): + query = TimeseriesMetricQueryBuilder( + self.params, + dataset=Dataset.PerformanceMetrics, + interval=900, + query="transaction.duration:>0", + selected_columns=["count()"], + on_demand_metrics_enabled=True, + ) + result = query.run_query("test_query") + assert result["data"][:5] == [ + { + "time": self.start.isoformat(), + "count": 0, + }, + { + "time": (self.start + datetime.timedelta(hours=1)).isoformat(), + "count": 0, + }, + { + "time": (self.start + datetime.timedelta(hours=2)).isoformat(), + "count": 0, + }, + { + "time": (self.start + datetime.timedelta(hours=3)).isoformat(), + "count": 0, + }, + { + "time": (self.start + datetime.timedelta(hours=4)).isoformat(), + "count": 0, + }, + ] + self.assertCountEqual( + result["meta"], + [ + {"name": "time", "type": "DateTime('Universal')"}, + {"name": "count", "type": "Float64"}, + ], + ) + class HistogramMetricQueryBuilderTest(MetricBuilderBaseTest): def test_histogram_columns_set_on_builder(self): @@ -2055,54 +2094,75 @@ def test_query_normal_distribution(self): class AlertMetricsQueryBuilderTest(MetricBuilderBaseTest): + def test_run_on_demand_query(self): + query = AlertMetricsQueryBuilder( + self.params, + use_metrics_layer=False, + granularity=3600, + query="transaction.duration:>=100", + dataset=Dataset.PerformanceMetrics, + selected_columns=["p75(measurements.fp)"], + on_demand_metrics_enabled=True, + ) + + result = query.run_query("test_query") + + assert len(result["data"]) == 1 + + meta = result["meta"] + + assert len(meta) == 2 + assert meta[0]["name"] == "bucketed_time" + assert meta[1]["name"] == "d:transactions/on_demand@none" + def test_get_snql_query(self): - with Feature("organizations:on-demand-metrics-extraction"): - query = AlertMetricsQueryBuilder( - self.params, - use_metrics_layer=True, - granularity=3600, - query="transaction.duration:>=100", - dataset=Dataset.PerformanceMetrics, - selected_columns=["p75(measurements.fp)"], - ) + query = AlertMetricsQueryBuilder( + self.params, + use_metrics_layer=False, + granularity=3600, + query="transaction.duration:>=100", + dataset=Dataset.PerformanceMetrics, + selected_columns=["p75(measurements.fp)"], + on_demand_metrics_enabled=True, + ) - snql_request = query.get_snql_query() - assert snql_request.dataset == "generic_metrics" - snql_query = snql_request.query - self.assertCountEqual( - [ - Function( - "arrayElement", - [ - Function( - "quantilesIf(0.75)", - [ - Column("value"), - Function( - "equals", - [ - Column("metric_id"), - indexer.resolve( - UseCaseID.TRANSACTIONS, - None, - "d:transactions/on_demand@none", - ), - ], - ), - ], - ), - 1, - ], - "d:transactions/on_demand@none", - ) - ], - snql_query.select, - ) + snql_request = query.get_snql_query() + assert snql_request.dataset == "generic_metrics" + snql_query = snql_request.query + self.assertCountEqual( + [ + Function( + "arrayElement", + [ + Function( + "quantilesIf(0.75)", + [ + Column("value"), + Function( + "equals", + [ + Column("metric_id"), + indexer.resolve( + UseCaseID.TRANSACTIONS, + None, + "d:transactions/on_demand@none", + ), + ], + ), + ], + ), + 1, + ], + "d:transactions/on_demand@none", + ) + ], + snql_query.select, + ) - query_hash_index = indexer.resolve(UseCaseID.TRANSACTIONS, None, QUERY_HASH_KEY) + query_hash_index = indexer.resolve(UseCaseID.TRANSACTIONS, None, QUERY_HASH_KEY) - query_hash_clause = Condition( - lhs=Column(name=f"tags_raw[{query_hash_index}]"), op=Op.EQ, rhs="80237309" - ) + query_hash_clause = Condition( + lhs=Column(name=f"tags_raw[{query_hash_index}]"), op=Op.EQ, rhs="80237309" + ) - assert query_hash_clause in snql_query.where + assert query_hash_clause in snql_query.where diff --git a/tests/sentry/snuba/test_extraction.py b/tests/sentry/snuba/test_extraction.py index 917e4e6a245803..977a7d5bceaa99 100644 --- a/tests/sentry/snuba/test_extraction.py +++ b/tests/sentry/snuba/test_extraction.py @@ -17,8 +17,9 @@ def test_is_on_demand_query_invalid_query(): dataset = Dataset.PerformanceMetrics assert is_on_demand_query(dataset, "count()", "AND") is False - assert is_on_demand_query(dataset, "count()", "(AND transaction.duration:>=1") is False + assert is_on_demand_query(dataset, "count()", ")AND transaction.duration:>=1") is False assert is_on_demand_query(dataset, "count()", "transaction.duration:>=abc") is False + assert is_on_demand_query(dataset, "count_if(}", "") is False def test_is_on_demand_query_true(): @@ -175,3 +176,10 @@ def test_spec_countif_with_query(): {"name": "event.duration", "op": "eq", "value": 300.0}, ], } + + +def test_ignore_fields(): + with_ignored_field = OndemandMetricSpec("count()", "transaction.duration:>=1 project:sentry") + without_ignored_field = OndemandMetricSpec("count()", "transaction.duration:>=1") + + assert with_ignored_field.condition() == without_ignored_field.condition()