Skip to content

Commit 53fc469

Browse files
DominikB2014claude
andcommitted
feat(slack): Respect chart type in Explore Slack unfurls
Parse the chartType from aggregateField JSON in Explore URLs and use it to select the correct chartcuterie chart style. Previously all unfurls rendered as area charts regardless of the user's selection. - BAR (chartType=0) → TOTAL_DAILY / TOP5_DAILY - LINE (chartType=1) → TOTAL_PERIOD_LINE / TOP5_PERIOD_LINE - AREA (chartType=2, default) → TOTAL_PERIOD / TOP5_PERIOD Depends on #112580 for the TOTAL_PERIOD_LINE render descriptor. Refs DAIN-1481 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5402dcb commit 53fc469

File tree

3 files changed

+105
-2
lines changed

3 files changed

+105
-2
lines changed

src/sentry/charts/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class ChartType(Enum):
1313
"""
1414

1515
SLACK_DISCOVER_TOTAL_PERIOD = "slack:discover.totalPeriod"
16+
SLACK_DISCOVER_TOTAL_PERIOD_LINE = "slack:discover.totalPeriodLine"
1617
SLACK_DISCOVER_TOTAL_DAILY = "slack:discover.totalDaily"
1718
SLACK_DISCOVER_TOP5_PERIOD = "slack:discover.top5Period"
1819
SLACK_DISCOVER_TOP5_PERIOD_LINE = "slack:discover.top5PeriodLine"

src/sentry/integrations/slack/unfurl/explore.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@
5252

5353
TOP_N = 5
5454

55+
# Frontend ChartType enum values from static/app/views/insights/common/components/chart.tsx
56+
FRONTEND_CHART_TYPE_BAR = 0
57+
FRONTEND_CHART_TYPE_LINE = 1
58+
5559

5660
def _serialize_single_series(series: dict[str, Any]) -> dict[str, Any]:
5761
"""Convert a single TimeSeries into events-stats format."""
@@ -157,17 +161,27 @@ def _unfurl_explore(
157161
params.setlist("yAxis", y_axes)
158162

159163
group_bys = params.getlist("groupBy")
164+
chart_type_str = params.get("chartType")
165+
is_bar = chart_type_str == str(FRONTEND_CHART_TYPE_BAR)
166+
is_line = chart_type_str == str(FRONTEND_CHART_TYPE_LINE)
160167

161168
# Only one yAxis is charted; multiple charts per unfurl not yet supported.
162169
if group_bys:
163170
aggregate_fn = y_axes[-1].split("(")[0]
164-
if aggregate_fn in LINE_PLOT_FIELDS:
171+
if is_bar:
172+
style = ChartType.SLACK_DISCOVER_TOP5_DAILY
173+
elif is_line or aggregate_fn in LINE_PLOT_FIELDS:
165174
style = ChartType.SLACK_DISCOVER_TOP5_PERIOD_LINE
166175
else:
167176
style = ChartType.SLACK_DISCOVER_TOP5_PERIOD
168177
params.setlist("topEvents", [str(TOP_N)])
169178
else:
170-
style = ChartType.SLACK_DISCOVER_TOTAL_PERIOD
179+
if is_bar:
180+
style = ChartType.SLACK_DISCOVER_TOTAL_DAILY
181+
elif is_line:
182+
style = ChartType.SLACK_DISCOVER_TOTAL_PERIOD_LINE
183+
else:
184+
style = ChartType.SLACK_DISCOVER_TOTAL_PERIOD
171185

172186
if not params.get("statsPeriod") and not params.get("start"):
173187
params["statsPeriod"] = DEFAULT_PERIOD
@@ -230,11 +244,14 @@ def map_explore_query_args(url: str, args: Mapping[str, str | None]) -> Mapping[
230244
aggregate_fields = raw_query.getlist("aggregateField")
231245
y_axes: list[str] = []
232246
group_bys: list[str] = []
247+
chart_type: int | None = None
233248
for field_json in aggregate_fields:
234249
try:
235250
parsed = json.loads(field_json)
236251
if "yAxes" in parsed and isinstance(parsed["yAxes"], list):
237252
y_axes.extend(parsed["yAxes"])
253+
if "chartType" in parsed and isinstance(parsed["chartType"], int):
254+
chart_type = parsed["chartType"]
238255
if "groupBy" in parsed and parsed["groupBy"]:
239256
group_bys.append(parsed["groupBy"])
240257
except (json.JSONDecodeError, TypeError):
@@ -247,6 +264,9 @@ def map_explore_query_args(url: str, args: Mapping[str, str | None]) -> Mapping[
247264
query = QueryDict(mutable=True)
248265
query.setlist("yAxis", y_axes)
249266

267+
if chart_type is not None:
268+
query["chartType"] = str(chart_type)
269+
250270
if group_bys:
251271
query.setlist("groupBy", group_bys)
252272

tests/sentry/integrations/slack/test_unfurl.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,6 +1523,88 @@ def test_unfurl_explore_no_feature_flag(
15231523
assert len(unfurls) == 0
15241524
assert len(mock_generate_chart.mock_calls) == 0
15251525

1526+
@patch(
1527+
"sentry.integrations.slack.unfurl.explore.client.get",
1528+
)
1529+
@patch("sentry.charts.backend.generate_chart", return_value="chart-url")
1530+
def test_unfurl_explore_bar_chart(
1531+
self, mock_generate_chart: MagicMock, mock_client_get: MagicMock
1532+
) -> None:
1533+
mock_client_get.return_value = MagicMock(
1534+
data=self._build_mock_timeseries_response(y_axis="count(span.duration)")
1535+
)
1536+
# chartType 0 = BAR
1537+
url = f"https://sentry.io/organizations/{self.organization.slug}/explore/traces/?aggregateField=%7B%22groupBy%22%3A%22%22%7D&aggregateField=%7B%22yAxes%22%3A%5B%22count(span.duration)%22%5D%2C%22chartType%22%3A0%7D&project={self.project.id}&statsPeriod=24h"
1538+
link_type, args = match_link(url)
1539+
1540+
if not args or not link_type:
1541+
raise AssertionError("Missing link_type/args")
1542+
1543+
links = [
1544+
UnfurlableUrl(url=url, args=args),
1545+
]
1546+
1547+
with self.feature(["organizations:data-browsing-widget-unfurl"]):
1548+
unfurls = link_handlers[link_type].fn(self.integration, links, self.user)
1549+
1550+
assert len(unfurls) == 1
1551+
assert len(mock_generate_chart.mock_calls) == 1
1552+
assert mock_generate_chart.call_args[0][0] == ChartType.SLACK_DISCOVER_TOTAL_DAILY
1553+
1554+
@patch(
1555+
"sentry.integrations.slack.unfurl.explore.client.get",
1556+
)
1557+
@patch("sentry.charts.backend.generate_chart", return_value="chart-url")
1558+
def test_unfurl_explore_bar_chart_with_groupby(
1559+
self, mock_generate_chart: MagicMock, mock_client_get: MagicMock
1560+
) -> None:
1561+
mock_client_get.return_value = MagicMock(data=self._build_mock_timeseries_response())
1562+
# chartType 0 = BAR with groupBy
1563+
url = f"https://sentry.io/organizations/{self.organization.slug}/explore/traces/?aggregateField=%7B%22groupBy%22%3A%22span.op%22%7D&aggregateField=%7B%22yAxes%22%3A%5B%22avg(span.duration)%22%5D%2C%22chartType%22%3A0%7D&project={self.project.id}&statsPeriod=24h"
1564+
link_type, args = match_link(url)
1565+
1566+
if not args or not link_type:
1567+
raise AssertionError("Missing link_type/args")
1568+
1569+
links = [
1570+
UnfurlableUrl(url=url, args=args),
1571+
]
1572+
1573+
with self.feature(["organizations:data-browsing-widget-unfurl"]):
1574+
unfurls = link_handlers[link_type].fn(self.integration, links, self.user)
1575+
1576+
assert len(unfurls) == 1
1577+
assert len(mock_generate_chart.mock_calls) == 1
1578+
assert mock_generate_chart.call_args[0][0] == ChartType.SLACK_DISCOVER_TOP5_DAILY
1579+
1580+
@patch(
1581+
"sentry.integrations.slack.unfurl.explore.client.get",
1582+
)
1583+
@patch("sentry.charts.backend.generate_chart", return_value="chart-url")
1584+
def test_unfurl_explore_line_chart(
1585+
self, mock_generate_chart: MagicMock, mock_client_get: MagicMock
1586+
) -> None:
1587+
mock_client_get.return_value = MagicMock(
1588+
data=self._build_mock_timeseries_response(y_axis="count(span.duration)")
1589+
)
1590+
# chartType 1 = LINE
1591+
url = f"https://sentry.io/organizations/{self.organization.slug}/explore/traces/?aggregateField=%7B%22groupBy%22%3A%22%22%7D&aggregateField=%7B%22yAxes%22%3A%5B%22count(span.duration)%22%5D%2C%22chartType%22%3A1%7D&project={self.project.id}&statsPeriod=24h"
1592+
link_type, args = match_link(url)
1593+
1594+
if not args or not link_type:
1595+
raise AssertionError("Missing link_type/args")
1596+
1597+
links = [
1598+
UnfurlableUrl(url=url, args=args),
1599+
]
1600+
1601+
with self.feature(["organizations:data-browsing-widget-unfurl"]):
1602+
unfurls = link_handlers[link_type].fn(self.integration, links, self.user)
1603+
1604+
assert len(unfurls) == 1
1605+
assert len(mock_generate_chart.mock_calls) == 1
1606+
assert mock_generate_chart.call_args[0][0] == ChartType.SLACK_DISCOVER_TOTAL_PERIOD_LINE
1607+
15261608
@patch(
15271609
"sentry.integrations.slack.unfurl.explore.client.get",
15281610
)

0 commit comments

Comments
 (0)