Repository: getsentry/sentry

Issue:
Ignore query conditions that contain relative timestamps



Discussion:

Resolving patch:
diff --git a/src/sentry/snuba/metrics/extraction.py b/src/sentry/snuba/metrics/extraction.py
--- a/src/sentry/snuba/metrics/extraction.py
+++ b/src/sentry/snuba/metrics/extraction.py
@@ -294,8 +294,13 @@ def get_default_spec_version(cls: Any) -> SpecVersion:
     "geo.country_code",
 ]
 
-# Query fields that we do not consider for the extraction since they are not needed.
-_BLACKLISTED_METRIC_FIELDS = ["event.type", "project"]
+# Query fields that are not considered
+_IGNORED_METRIC_FIELDS = [
+    "event.type",  # on-demand extraction is enabled only for event.type:"transaction"
+    "project",  # on-demand extraction specs are emitted per project
+    "timestamp.to_day",  # relative time windows are not supported
+    "timestamp.to_hour",  # relative time windows are not supported
+]
 
 # Operators used in ``ComparingRuleCondition``.
 CompareOp = Literal["eq", "gt", "gte", "lt", "lte", "glob"]
@@ -791,8 +796,7 @@ def _is_standard_metrics_search_term(field: str) -> bool:
 
 
 def _is_on_demand_supported_field(field: str) -> bool:
-    # If it's a black listed field, we consider it as compatible with on demand.
-    if field in _BLACKLISTED_METRIC_FIELDS:
+    if field in _IGNORED_METRIC_FIELDS:
         return True
 
     try:
@@ -876,7 +880,7 @@ def _remove_blacklisted_search_filters(tokens: Sequence[QueryToken]) -> Sequence
     ret_val: List[QueryToken] = []
     for token in tokens:
         if isinstance(token, SearchFilter):
-            if token.key.name not in _BLACKLISTED_METRIC_FIELDS:
+            if token.key.name not in _IGNORED_METRIC_FIELDS:
                 ret_val.append(token)
         elif isinstance(token, ParenExpression):
             ret_val.append(ParenExpression(_remove_blacklisted_search_filters(token.children)))


Test patch:
diff --git a/tests/sentry/relay/config/test_metric_extraction.py b/tests/sentry/relay/config/test_metric_extraction.py
--- a/tests/sentry/relay/config/test_metric_extraction.py
+++ b/tests/sentry/relay/config/test_metric_extraction.py
@@ -18,7 +18,6 @@
 from sentry.search.events.constants import VITAL_THRESHOLDS
 from sentry.snuba.dataset import Dataset
 from sentry.snuba.models import QuerySubscription, SnubaQuery
-from sentry.tasks.on_demand_metrics import process_widget_specs
 from sentry.testutils.helpers import Feature
 from sentry.testutils.helpers.options import override_options
 from sentry.testutils.pytest.fixtures import django_db_all
@@ -1013,45 +1012,6 @@ def test_stateful_get_metric_extraction_config_with_low_cardinality(
         assert config
 
 
-@django_db_all
-@override_options(
-    {
-        "on_demand_metrics.widgets.use_stateful_extraction": True,
-        "on_demand_metrics.check_widgets.enable": True,
-    }
-)
-def test_stateful_check_spec_hashes_relative_time(
-    default_project: Project,
-) -> None:
-    # TODO: This test should be removed once relative time is fixed or stateful extraction is mainlined whichever happens first.
-    with Feature(
-        {
-            ON_DEMAND_METRICS_WIDGETS: True,
-            "organizations:on-demand-metrics-extraction-widgets": True,
-        }
-    ):
-        widget_query = create_widget(
-            ["epm()"],
-            "timestamp.to_day:-7d",
-            default_project,
-            columns=["user.id", "release", "count()"],
-        )
-
-        assert widget_query.widget.id
-        widget_query_ids = [widget_query.id]
-        # This should set a stateful metric extraction state.
-        process_widget_specs(widget_query_ids)
-
-        on_demand_entries = widget_query.dashboardwidgetqueryondemand_set.all()
-        assert (
-            len(on_demand_entries) == 1
-        )  # Make sure test is isolated and on-demand successfully added a row.
-
-        with mock.patch("sentry_sdk.capture_message") as capture_message:
-            get_metric_extraction_config(default_project)
-            assert capture_message.called
-
-
 @django_db_all
 def test_get_metric_extraction_config_with_unicode_character(default_project: Project) -> None:
     with Feature({ON_DEMAND_METRICS_WIDGETS: True}):
diff --git a/tests/sentry/snuba/metrics/test_extraction.py b/tests/sentry/snuba/metrics/test_extraction.py
--- a/tests/sentry/snuba/metrics/test_extraction.py
+++ b/tests/sentry/snuba/metrics/test_extraction.py
@@ -581,11 +581,28 @@ def test_spec_with_unknown_error_status() -> None:
     }
 
 
-def test_spec_ignore_fields() -> None:
-    with_ignored_field = OnDemandMetricSpec("count()", "transaction.duration:>=1 project:sentry")
-    without_ignored_field = OnDemandMetricSpec("count()", "transaction.duration:>=1")
+def test_spec_ignore_project() -> None:
+    with_project = OnDemandMetricSpec("count()", "transaction.duration:>=1 project:sentry")
+    without_project = OnDemandMetricSpec("count()", "transaction.duration:>=1")
 
-    assert with_ignored_field.condition == without_ignored_field.condition
+    assert with_project.condition == without_project.condition
+
+
+def test_spec_ignore_timestamp() -> None:
+    with_timestamp_to_hour = OnDemandMetricSpec(
+        "count()", "transaction.duration:>=1 timestamp.to_hour:-1h"
+    )
+    with_timestamp_to_day = OnDemandMetricSpec(
+        "count()", "transaction.duration:>=1 timestamp.to_day:-1d"
+    )
+
+    without_timestamp = OnDemandMetricSpec("count()", "transaction.duration:>=1")
+
+    assert (
+        with_timestamp_to_hour.condition
+        == with_timestamp_to_day.condition
+        == without_timestamp.condition
+    )
 
 
 @django_db_all