Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions src/sentry/integrations/slack/unfurl/explore.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def _unfurl_explore(
continue

params = link.args["query"]
chart_type = link.args.get("chart_type")

y_axes = params.getlist("yAxis")
if not y_axes:
Expand Down Expand Up @@ -110,9 +111,11 @@ def _unfurl_explore(
_logger.warning("Failed to load events-timeseries for explore unfurl")
continue

chart_data = {
chart_data: dict[str, Any] = {
"timeSeries": resp.data.get("timeSeries", []),
}
if chart_type is not None:
chart_data["type"] = chart_type

try:
url = charts.generate_chart(style, chart_data)
Expand All @@ -138,27 +141,37 @@ def _unfurl_explore(
return unfurls


CHART_TYPE_TO_DISPLAY_TYPE = {
0: "bar",
1: "line",
2: "area",
}


def map_explore_query_args(url: str, args: Mapping[str, str | None]) -> Mapping[str, Any]:
"""
Extracts explore arguments from the explore link's query string.
Parses aggregateField JSON params to extract yAxes and groupBy.
Parses visualize/aggregateField JSON params to extract yAxes, groupBy, and chartType.
"""
# Slack uses HTML escaped ampersands in its Event Links
url = html.unescape(url)
parsed_url = urlparse(url)
raw_query = QueryDict(parsed_url.query)

# Parse aggregateField JSON params
aggregate_fields = raw_query.getlist("aggregateField")
# Parse visualize (spans explore) or aggregateField (logs explore) JSON params
visualize_fields = raw_query.getlist("visualize") or raw_query.getlist("aggregateField")
y_axes: list[str] = []
group_bys: list[str] = []
for field_json in aggregate_fields:
chart_type: str | None = None
for field_json in visualize_fields:
try:
parsed = json.loads(field_json)
if "yAxes" in parsed and isinstance(parsed["yAxes"], list):
y_axes.extend(parsed["yAxes"])
if "groupBy" in parsed and parsed["groupBy"]:
group_bys.append(parsed["groupBy"])
if chart_type is None and "chartType" in parsed:
chart_type = CHART_TYPE_TO_DISPLAY_TYPE.get(parsed["chartType"])
except (json.JSONDecodeError, TypeError):
continue

Expand All @@ -178,7 +191,7 @@ def map_explore_query_args(url: str, args: Mapping[str, str | None]) -> Mapping[
if values:
query.setlist(param, values)

return dict(**args, query=query)
return dict(**args, query=query, chart_type=chart_type)


explore_traces_link_regex = re.compile(
Expand Down
56 changes: 56 additions & 0 deletions tests/sentry/integrations/slack/test_unfurl.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@
{
"org_slug": "org1",
"query": QueryDict("yAxis=avg(span.duration)&project=1&statsPeriod=24h"),
"chart_type": None,
},
),
),
Expand All @@ -208,6 +209,7 @@
{
"org_slug": "org1",
"query": QueryDict("yAxis=count(span.duration)&statsPeriod=24h"),
"chart_type": None,
},
),
),
Expand Down Expand Up @@ -1669,3 +1671,57 @@ def test_unfurl_explore_end_to_end(
unfurls[url]
== SlackDiscoverMessageBuilder(title="Explore Traces", chart_url="chart-url").build()
)

@patch(
"sentry.integrations.slack.unfurl.explore.client.get",
)
@patch("sentry.charts.backend.generate_chart", return_value="chart-url")
def test_unfurl_explore_with_visualize_chart_type(
self, mock_generate_chart: MagicMock, mock_client_get: MagicMock
) -> None:
mock_client_get.return_value = MagicMock(data=self._build_mock_timeseries_response())
# visualize param with chartType=0 (bar)
url = f"https://sentry.io/organizations/{self.organization.slug}/explore/traces/?visualize=%7B%22yAxes%22%3A%5B%22avg(span.duration)%22%5D%2C%22chartType%22%3A0%7D&project={self.project.id}&statsPeriod=24h"
link_type, args = match_link(url)

if not args or not link_type:
raise AssertionError("Missing link_type/args")

assert link_type == LinkType.EXPLORE

links = [
UnfurlableUrl(url=url, args=args),
]

with self.feature(["organizations:data-browsing-widget-unfurl"]):
unfurls = link_handlers[link_type].fn(self.integration, links, self.user)

assert len(unfurls) == 1
assert len(mock_generate_chart.mock_calls) == 1
chart_data = mock_generate_chart.call_args[0][1]
assert chart_data["type"] == "bar"

@patch(
"sentry.integrations.slack.unfurl.explore.client.get",
)
@patch("sentry.charts.backend.generate_chart", return_value="chart-url")
def test_unfurl_explore_without_chart_type_omits_type(
self, mock_generate_chart: MagicMock, mock_client_get: MagicMock
) -> None:
mock_client_get.return_value = MagicMock(data=self._build_mock_timeseries_response())
url = f"https://sentry.io/organizations/{self.organization.slug}/explore/traces/?aggregateField=%7B%22yAxes%22%3A%5B%22avg(span.duration)%22%5D%7D&project={self.project.id}&statsPeriod=24h"
link_type, args = match_link(url)

if not args or not link_type:
raise AssertionError("Missing link_type/args")

links = [
UnfurlableUrl(url=url, args=args),
]

with self.feature(["organizations:data-browsing-widget-unfurl"]):
unfurls = link_handlers[link_type].fn(self.integration, links, self.user)

assert len(unfurls) == 1
chart_data = mock_generate_chart.call_args[0][1]
assert "type" not in chart_data
Loading