From 8c29f40dfa64c630a286a1947c412484bda9fbe3 Mon Sep 17 00:00:00 2001 From: Dominik Buszowiecki Date: Tue, 28 Apr 2026 12:14:40 -0400 Subject: [PATCH 1/3] fix(slack): Prefer aggregateField over visualize in explore unfurl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an Explore URL contains both the canonical aggregateField param and the legacy visualize param (e.g. links produced by the dashboards "Open in Explore" flow), the unfurl arg mapper preferred visualize and discarded aggregateField. This caused the events-timeseries request to use the wrong yAxis and to drop the groupBy entirely. Flip the precedence to match the frontend's getSpansAggregateFieldsFromLocation / getLogsAggregateFieldsFromLocation: read aggregateField first, falling back to visualize only when aggregateField is absent. Metrics is unaffected — it parses its own metric JSON param earlier and never reaches this branch. Co-Authored-By: Claude --- src/sentry/integrations/slack/unfurl/explore.py | 4 +++- tests/sentry/integrations/slack/test_unfurl.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/sentry/integrations/slack/unfurl/explore.py b/src/sentry/integrations/slack/unfurl/explore.py index 57d6b23b1a6073..fe22aae5166193 100644 --- a/src/sentry/integrations/slack/unfurl/explore.py +++ b/src/sentry/integrations/slack/unfurl/explore.py @@ -272,7 +272,9 @@ def map_explore_query_args(url: str, args: Mapping[str, str | None]) -> Mapping[ except (json.JSONDecodeError, TypeError, AttributeError): pass - visualize_fields = raw_query.getlist("visualize") or raw_query.getlist("aggregateField") + # Match the frontend's getSpansAggregateFieldsFromLocation: aggregateField + # is the canonical source when present, falling back to legacy visualize. + visualize_fields = raw_query.getlist("aggregateField") or raw_query.getlist("visualize") for field_json in visualize_fields: try: parsed = json.loads(field_json) diff --git a/tests/sentry/integrations/slack/test_unfurl.py b/tests/sentry/integrations/slack/test_unfurl.py index 2f0b26ee71a484..01d7f4f98583a7 100644 --- a/tests/sentry/integrations/slack/test_unfurl.py +++ b/tests/sentry/integrations/slack/test_unfurl.py @@ -1890,6 +1890,22 @@ def test_unfurl_explore_non_dict_aggregate_field(self) -> None: assert args is not None assert args["query"].getlist("yAxis") == ["count(span.duration)"] + def test_unfurl_explore_aggregate_field_takes_precedence_over_visualize(self) -> None: + url = ( + "https://sentry.io/organizations/org1/explore/traces/" + "?aggregateField=%7B%22groupBy%22%3A%22gen_ai.tool.name%22%7D" + "&aggregateField=%7B%22yAxes%22%3A%5B%22count(span.duration)%22%5D%2C%22chartType%22%3A0%7D" + "&visualize=%7B%22chartType%22%3A0%2C%22yAxes%22%3A%5B%22count_unique(user.id)%22%5D%7D" + "&project=1&query=user.id%1234&statsPeriod=30d" + ) + link_type, args = match_link(url) + + assert link_type == LinkType.EXPLORE + assert args is not None + assert args["query"].getlist("yAxis") == ["count(span.duration)"] + assert args["query"].getlist("groupBy") == ["gen_ai.tool.name"] + assert args["chart_type"] == 0 + def test_unfurl_explore_multi_aggregate_uses_first_chart(self) -> None: # Two charts: count with chartType=2 (area, first) and avg (second). # The unfurl must render only the first chart and not merge avg's From 264e2cc4cfecab379a062e079611cf08c1a9aadd Mon Sep 17 00:00:00 2001 From: Dominik Buszowiecki Date: Tue, 28 Apr 2026 12:15:39 -0400 Subject: [PATCH 2/3] ref(slack): Drop stale visualization-params comment in explore unfurl The comment described pre-aggregateField behavior that no longer matches the code below it. Co-Authored-By: Claude --- src/sentry/integrations/slack/unfurl/explore.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/sentry/integrations/slack/unfurl/explore.py b/src/sentry/integrations/slack/unfurl/explore.py index fe22aae5166193..10a527701cd849 100644 --- a/src/sentry/integrations/slack/unfurl/explore.py +++ b/src/sentry/integrations/slack/unfurl/explore.py @@ -240,9 +240,6 @@ def map_explore_query_args(url: str, args: Mapping[str, str | None]) -> Mapping[ explore_dataset = _get_explore_dataset(url) dataset_config = _get_explore_dataset_config(explore_dataset) - # Parse visualization params from the URL. - # Each metric uses a "metric" JSON param with nested aggregateFields. - # Traces uses "visualize" and logs uses "aggregateField". y_axes: list[str] = [] group_bys: list[str] = [] chart_type: int | None = None From 02752d2e2a541533962ca216378bbc41a2dea2fe Mon Sep 17 00:00:00 2001 From: Dominik Buszowiecki Date: Tue, 28 Apr 2026 12:17:38 -0400 Subject: [PATCH 3/3] ref(slack): Adjust comments in explore unfurl arg mapper Co-Authored-By: Claude --- src/sentry/integrations/slack/unfurl/explore.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sentry/integrations/slack/unfurl/explore.py b/src/sentry/integrations/slack/unfurl/explore.py index 10a527701cd849..18270cb7e73ef5 100644 --- a/src/sentry/integrations/slack/unfurl/explore.py +++ b/src/sentry/integrations/slack/unfurl/explore.py @@ -240,6 +240,9 @@ def map_explore_query_args(url: str, args: Mapping[str, str | None]) -> Mapping[ explore_dataset = _get_explore_dataset(url) dataset_config = _get_explore_dataset_config(explore_dataset) + # Parse visualization params from the URL. + # Each metric uses a "metric" JSON param with nested aggregateFields. + # Traces uses "visualize" and logs uses "aggregateField". y_axes: list[str] = [] group_bys: list[str] = [] chart_type: int | None = None @@ -269,8 +272,6 @@ def map_explore_query_args(url: str, args: Mapping[str, str | None]) -> Mapping[ except (json.JSONDecodeError, TypeError, AttributeError): pass - # Match the frontend's getSpansAggregateFieldsFromLocation: aggregateField - # is the canonical source when present, falling back to legacy visualize. visualize_fields = raw_query.getlist("aggregateField") or raw_query.getlist("visualize") for field_json in visualize_fields: try: