Просмотр исходного кода

fix(metrics): Add transformation of transaction status unknown_error to unknown (#59779)

Riccardo Busetti 1 год назад
Родитель
Сommit
4cb8aa6055

+ 21 - 0
src/sentry/search/events/datasets/metrics.py

@@ -32,6 +32,7 @@ class MetricsDatasetConfig(DatasetConfig):
             constants.TEAM_KEY_TRANSACTION_ALIAS: self._key_transaction_filter_converter,
             "environment": self.builder._environment_filter_converter,
             "transaction": self._transaction_filter_converter,
+            "transaction.status": self._transaction_status_converter,
             "tags[transaction]": self._transaction_filter_converter,
             constants.TITLE_ALIAS: self._transaction_filter_converter,
             constants.RELEASE_ALIAS: self._release_filter_converter,
@@ -891,6 +892,26 @@ class MetricsDatasetConfig(DatasetConfig):
 
         return Condition(self.builder.resolve_column("transaction"), Op(operator), value)
 
+    def _transaction_status_converter(self, search_filter: SearchFilter) -> Optional[WhereType]:
+        operator = search_filter.operator
+        value = search_filter.value.value
+
+        # For backward compatibility, `unknown_error` is converted to `unknown`, since Relay always emits `unknown`
+        # `transaction.status`.
+        if value == "unknown_error":
+            value = "unknown"
+
+        lhs = self.builder.resolve_column("transaction.status")
+
+        if search_filter.value.is_wildcard():
+            return Condition(
+                Function("match", [lhs, f"(?i){value}"]),
+                Op(operator),
+                1,
+            )
+
+        return Condition(lhs, Op(operator), value)
+
     # Query Functions
     def _resolve_count_if(
         self,

+ 25 - 1
src/sentry/snuba/metrics/query_builder.py

@@ -166,6 +166,20 @@ def transform_null_transaction_to_unparameterized(use_case_id, org_id, alias=Non
     )
 
 
+def _refers_to_column(expression: Union[Column, Function]) -> Optional[str]:
+    """
+    Tries to compute to which column the input expression is referring to.
+    """
+
+    if isinstance(expression, Column):
+        return expression.name
+    elif isinstance(expression, Function):
+        if expression.function == "ifNull":
+            return expression.parameters[0].name
+
+    return None
+
+
 # Allow these snuba functions.
 # These are only allowed because the parser in metrics_sessions_v2
 # generates them. Long term we should not allow any functions, but rather
@@ -330,6 +344,16 @@ def resolve_tags(
                 op=op,
                 rhs=rhs_ids,
             )
+
+        # Hacky way to apply a transformation for backward compatibility, which converts `unknown_error` to `unknown`
+        # for metrics queries.
+        transformed_rhs = input_.rhs
+        if (
+            _refers_to_column(input_.lhs) == "tags[transaction.status]"
+            and input_.rhs == "unknown_error"
+        ):
+            transformed_rhs = "unknown"
+
         return Condition(
             lhs=resolve_tags(
                 use_case_id, org_id, input_.lhs, projects, allowed_tag_keys=allowed_tag_keys
@@ -338,7 +362,7 @@ def resolve_tags(
             rhs=resolve_tags(
                 use_case_id,
                 org_id,
-                input_.rhs,
+                transformed_rhs,
                 projects,
                 is_tag_value=True,
                 allowed_tag_keys=allowed_tag_keys,

+ 25 - 0
tests/sentry/api/endpoints/test_organization_metric_data.py

@@ -1567,6 +1567,31 @@ class OrganizationMetricDataTest(MetricsAPIBaseTestCase):
         )
         assert response.status_code == 400
 
+    def test_transaction_status_unknown_error(self):
+        self.store_performance_metric(
+            name=TransactionMRI.DURATION.value,
+            tags={"transaction.status": "unknown"},
+            value=10.0,
+        )
+
+        response = self.get_success_response(
+            self.organization.slug,
+            field=f"sum({TransactionMetricKey.DURATION.value})",
+            query="transaction.status:unknown_error",
+            statsPeriod="1h",
+            interval="1h",
+            per_page=1,
+            useCase="transactions",
+        )
+        groups = response.data["groups"]
+        assert groups == [
+            {
+                "by": {},
+                "series": {"sum(transaction.duration)": [10.0]},
+                "totals": {"sum(transaction.duration)": 10.0},
+            }
+        ]
+
     def test_gauges(self):
         mri = "g:custom/page_load@millisecond"
 

+ 25 - 0
tests/snuba/api/endpoints/test_organization_events_stats_mep.py

@@ -564,6 +564,31 @@ class OrganizationEventsStatsMetricsEnhancedPerformanceEndpointTest(
             [{"count": 0}],
         ]
 
+    def test_transaction_status_unknown_error(self):
+        self.store_transaction_metric(
+            123,
+            tags={"transaction.status": "unknown"},
+            timestamp=self.day_ago + timedelta(minutes=30),
+        )
+        response = self.do_request(
+            data={
+                "start": iso_format(self.day_ago),
+                "end": iso_format(self.day_ago + timedelta(hours=2)),
+                "interval": "1h",
+                "query": "transaction.status:unknown_error",
+                "yAxis": [
+                    "sum(transaction.duration)",
+                ],
+                "dataset": "metricsEnhanced",
+            },
+        )
+        assert response.status_code == 200, response.content
+        data = response.data["data"]
+        assert [attrs for time, attrs in data] == [
+            [{"count": 123}],
+            [{"count": 0}],
+        ]
+
     def test_custom_performance_metric_meta_contains_field_and_unit_data(self):
         self.store_transaction_metric(
             123,