feat(slack): Unfurl spans dashboard widget URLs in slack#113478
Conversation
Add a Slack unfurl handler for dashboard widget URLs of the form
/organizations/{org}/dashboard/{dashboard_id}/widget/{widget_index}/
(and the customer-domain equivalent). The handler looks up the widget,
builds an events-timeseries request per widget query, and renders a
chartcuterie SLACK_TIMESERIES chart with the widget title.
Only spans widgets are supported for now to keep the initial rollout
simple; other widget types are skipped. Widgets with multiple queries
fan out to multiple timeseries calls and the series are joined into a
single chart, matching how the dashboard renders them.
Gated behind the organizations:dashboards-widget-unfurl flag.
Refs DAIN-1479
Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 96cee3e. Configure here.
…lue keys The internal API client iterates params.items() and relies on isinstance(value, list) to propagate multi-valued params via setlist. A QueryDict's items() yields only the last value per key, so widgets with multiple aggregates or groupBy columns were being silently truncated to a single value on the way to events-timeseries. Return a plain dict[str, str | list[str]] from _build_timeseries_params so the client sees lists and forwards them correctly. Also tighten the handler types: typed args via a DashboardsUnfurlArgs TypedDict, typed combined_time_series via the existing TimeSeries TypedDict, and drop the dead -1 sentinel path now that the regex guarantees valid input. Co-Authored-By: Claude <noreply@anthropic.com>
mypy flagged the new tests for passing match_link's Optional return values straight to UnfurlableUrl and link_handlers[]. Add an assertion after each unpack to narrow link_type and args from Optional. Co-Authored-By: Claude <noreply@anthropic.com>
…dashboard-widget-urls-in-slack
Also flip api_expose to False — the unfurl check runs only on the backend. Co-Authored-By: Claude <noreply@anthropic.com>
edwardgou-sentry
left a comment
There was a problem hiding this comment.
I think the changes look fine to me.
Question though: would it make sense to have the dashboards unfurl integration reuse parts of existing integrations? ie for this pr we only have a code path for spans, should we use _unfurl_explore somehow or consolidate similar parts of the code since it's basically the same dataset and endpoint (/events-timeseries/)? It seems like the major difference is the source for the query parameters (url vs DashboardWidgetQuery), but everything else seems to mostly behave the same. This might also be cleaner once we start adding more support for other datasets within the dashboard unfurl integration too.
@edwardgou-sentry 100% valid point. I was planning to wait until we add support for other datasets before consolidating (like you mentioned at the end), but yes, there's a lot of similarity and we can likely reuse more things. |

Add a Slack unfurl handler for dashboard widget URLs of the form
/organizations/{org}/dashboard/{dashboard_id}/widget/{widget_index}/(plus the customer-domain equivalent). The handler looks up the widget
by its positional index within the dashboard (matching the frontend's
id-ordered array lookup), builds an events-timeseries request per
widget query, and renders a chartcuterie
SLACK_TIMESERIESchart withthe widget title.
Only spans widgets are supported for this initial rollout to keep the
mapping from widget → timeseries request simple; other widget types
(errors, transactions, logs, tracemetrics, etc.) are skipped. Widgets
with multiple queries fan out to multiple timeseries calls and the
series are joined into a single chart, matching how the dashboard
renders them.
Behavior mirrors the existing explore unfurl (
unfurl/explore.py):same chart backend, same
SlackDiscoverMessageBuildershape, samedefault statsPeriod, sametopEvents/default-sort handling when agroupBy is present. The dashboards handler adds a new
Referrer.DASHBOARDS_SLACK_UNFURLvalue and registers it inSENTRY_BACKEND_REFERRERSso events-timeseries accepts the call.Gated behind a new
organizations:dashboards-widget-unfurlFlagPoleflag. Non-spans widgets, non-timeseries display types (table, big
number, text, …), and unknown widgets are all no-ops.
Refs DAIN-1479