def test_unfurl_issues(self): min_ago = iso_format(before_now(minutes=1)) event = self.store_event(data={ "fingerprint": ["group2"], "timestamp": min_ago }, project_id=self.project.id) group2 = event.group links = [ UnfurlableUrl( url= f"https://sentry.io/organizations/{self.organization.slug}/issues/{self.group.id}/", args={ "issue_id": self.group.id, "event_id": None }, ), UnfurlableUrl( url= f"https://sentry.io/organizations/{self.organization.slug}/issues/{group2.id}/{event.event_id}/", args={ "issue_id": group2.id, "event_id": event.event_id }, ), ] unfurls = link_handlers[LinkType.ISSUES].fn(self.request, self.integration, links) assert unfurls[links[0].url] == SlackIssuesMessageBuilder( self.group).build() assert (unfurls[links[1].url] == SlackIssuesMessageBuilder( group2, event, link_to_event=True).build())
def test_unfurl_incidents(self): alert_rule = self.create_alert_rule() incident = self.create_incident(status=2, organization=self.organization, projects=[self.project], alert_rule=alert_rule) incident.update(identifier=123) trigger = self.create_alert_rule_trigger(alert_rule, CRITICAL_TRIGGER_LABEL, 100) self.create_alert_rule_trigger_action(alert_rule_trigger=trigger, triggered_for_incident=incident) links = [ UnfurlableUrl( url= f"https://sentry.io/organizations/{self.organization.slug}/alerts/rules/details/{incident.identifier}/", args={ "org_slug": self.organization.slug, "incident_id": incident.identifier }, ), ] unfurls = link_handlers[LinkType.INCIDENTS].fn(self.request, self.integration, links) assert (unfurls[links[0].url] == SlackIncidentsMessageBuilder( incident, IncidentStatus.CLOSED).build())
def test_unfurl_discover_html_escaped(self, mock_generate_chart): min_ago = iso_format(before_now(minutes=1)) self.store_event(data={ "fingerprint": ["group2"], "timestamp": min_ago }, project_id=self.project.id) self.store_event(data={ "fingerprint": ["group2"], "timestamp": min_ago }, project_id=self.project.id) url = f"https://sentry.io/organizations/{self.organization.slug}/discover/results/?field=title&field=event.type&field=project&field=user.display&field=timestamp&name=All+Events&project={self.project.id}&query=&sort=-timestamp&statsPeriod=24h" link_type, args = match_link(url) if not args or not link_type: raise Exception("Missing link_type/args") links = [ UnfurlableUrl(url=url, args=args), ] with self.feature(["organizations:discover-basic"]): unfurls = link_handlers[link_type].fn(self.request, self.integration, links, self.user) assert (unfurls[url] == SlackDiscoverMessageBuilder( title=args["query"].get("name"), chart_url="chart-url").build()) assert len(mock_generate_chart.mock_calls) == 1 chart_data = mock_generate_chart.call_args[0][1] assert chart_data["seriesName"] == "count()" assert len(chart_data["stats"]["data"]) == 288
def test_unfurl_discover_multi_y_axis(self, mock_generate_chart): min_ago = iso_format(before_now(minutes=1)) self.store_event( data={"fingerprint": ["group2"], "timestamp": min_ago}, project_id=self.project.id ) self.store_event( data={"fingerprint": ["group2"], "timestamp": min_ago}, project_id=self.project.id ) url = f"https://sentry.io/organizations/{self.organization.slug}/discover/results/?field=title&field=event.type&field=project&field=user.display&field=timestamp&name=All+Events&project={self.project.id}&query=&sort=-timestamp&statsPeriod=24h&yAxis=count_unique%28user%29&yAxis=count%28%29" link_type, args = match_link(url) if not args or not link_type: raise Exception("Missing link_type/args") links = [ UnfurlableUrl(url=url, args=args), ] with self.feature( [ "organizations:discover-basic", "organizations:chart-unfurls", ] ): unfurls = link_handlers[link_type].fn(self.request, self.integration, links, self.user) assert unfurls[url] == build_discover_attachment( title=args["query"].get("name"), chart_url="chart-url" ) assert len(mock_generate_chart.mock_calls) == 1 chart_data = mock_generate_chart.call_args[0][1] assert len(chart_data["stats"]["count()"]["data"]) == 288 assert len(chart_data["stats"]["count_unique(user)"]["data"]) == 288
def test_unfurl_incidents(self): project1 = self.create_project(organization=self.org) alert_rule = self.create_alert_rule() incident = self.create_incident(status=2, organization=self.org, projects=[project1], alert_rule=alert_rule) incident.update(identifier=123) trigger = self.create_alert_rule_trigger(alert_rule, CRITICAL_TRIGGER_LABEL, 100) action = self.create_alert_rule_trigger_action( alert_rule_trigger=trigger, triggered_for_incident=incident) links = [ UnfurlableUrl( url= f"https://sentry.io/organizations/{self.org.slug}/alerts/rules/details/{incident.identifier}/", args={ "org_slug": self.org.slug, "incident_id": incident.identifier }, ), ] unfurls = link_handlers[LinkType.INCIDENTS].fn(self.request, self.integration, links) assert unfurls[links[0].url] == build_incident_attachment( action, incident)
def test_unfurl_discover(self, mock_generate_chart): project1 = self.create_project(organization=self.org) min_ago = iso_format(before_now(minutes=1)) self.store_event(data={ "fingerprint": ["group2"], "timestamp": min_ago }, project_id=project1.id) self.store_event(data={ "fingerprint": ["group2"], "timestamp": min_ago }, project_id=project1.id) url = f"https://sentry.io/organizations/{self.org.slug}/discover/results/?field=title&field=event.type&field=project&field=user.display&field=timestamp&name=All+Events&project={project1.id}&query=&sort=-timestamp&statsPeriod=24h" link_type, args = match_link(url) if not args or not link_type: raise Exception("Missing link_type/args") links = [ UnfurlableUrl(url=url, args=args), ] with self.feature("organizations:chart-unfurls"): unfurls = link_handlers[link_type].fn(self.request, self.integration, links) assert unfurls[url] == build_discover_attachment( title=args["query"].get("name"), chart_url="chart-url") assert len(mock_generate_chart.mock_calls) == 1
def test_unfurl_discover_short_url_without_project_ids( self, mock_generate_chart): query = { "fields": ["title", "event.type", "project", "user.display", "timestamp"], "query": "", "yAxis": "count_unique(users)", } saved_query = DiscoverSavedQuery.objects.create( organization=self.organization, created_by=self.user, name="Test query", query=query, version=2, ) saved_query.set_projects([self.project.id]) min_ago = iso_format(before_now(minutes=1)) self.store_event(data={ "fingerprint": ["group2"], "timestamp": min_ago }, project_id=self.project.id) self.store_event(data={ "fingerprint": ["group2"], "timestamp": min_ago }, project_id=self.project.id) url = f"https://sentry.io/organizations/{self.organization.slug}/discover/results/?id={saved_query.id}&statsPeriod=24h" link_type, args = match_link(url) if not args or not link_type: raise Exception("Missing link_type/args") links = [ UnfurlableUrl(url=url, args=args), ] with self.feature([ "organizations:discover", "organizations:discover-basic", "organizations:chart-unfurls", ]): unfurls = link_handlers[link_type].fn(self.request, self.integration, links, self.user) assert unfurls[url] == build_discover_attachment( title=args["query"].get("name"), chart_url="chart-url") assert len(mock_generate_chart.mock_calls) == 1 assert mock_generate_chart.call_args[0][ 0] == ChartType.SLACK_DISCOVER_TOTAL_PERIOD chart_data = mock_generate_chart.call_args[0][1] assert chart_data["seriesName"] == "count_unique(users)" assert len(chart_data["stats"]["data"]) == 288
def test_unfurl_world_map(self, mock_generate_chart): min_ago = iso_format(before_now(minutes=1)) self.store_event( data={ "fingerprint": ["group2"], "timestamp": min_ago, "user": { "geo": { "country_code": "CA", "region": "Canada" } }, }, project_id=self.project.id, ) self.store_event( data={ "fingerprint": ["group2"], "timestamp": min_ago, "user": { "geo": { "country_code": "AU", "region": "Australia" } }, }, project_id=self.project.id, ) url = f"https://sentry.io/organizations/{self.organization.slug}/discover/results/?display=worldmap&field=title&field=event.type&field=project&field=user.display&field=timestamp&name=All+Events&project={self.project.id}&query=&sort=-timestamp&statsPeriod=24h&yAxis=count%28%29" link_type, args = match_link(url) if not args or not link_type: raise Exception("Missing link_type/args") links = [ UnfurlableUrl(url=url, args=args), ] with self.feature(["organizations:discover-basic"]): unfurls = link_handlers[link_type].fn(self.request, self.integration, links, self.user) assert (unfurls[url] == SlackDiscoverMessageBuilder( title=args["query"].get("name"), chart_url="chart-url").build()) assert len(mock_generate_chart.mock_calls) == 1 assert mock_generate_chart.call_args[0][ 0] == ChartType.SLACK_DISCOVER_WORLDMAP chart_data = mock_generate_chart.call_args[0][1] assert chart_data["seriesName"] == "count()" assert len(chart_data["stats"]["data"]) == 2 assert sorted(x["geo.country_code"] for x in chart_data["stats"]["data"]) == ["AU", "CA"]
def test_top_daily_events_renders_bar_chart(self, mock_generate_chart): min_ago = iso_format(before_now(minutes=1)) self.store_event( data={ "message": "first", "fingerprint": ["group1"], "timestamp": min_ago }, project_id=self.project.id, ) self.store_event( data={ "message": "second", "fingerprint": ["group2"], "timestamp": min_ago }, project_id=self.project.id, ) url = f"https://sentry.io/organizations/{self.organization.slug}/discover/results/?field=message&field=event.type&field=count()&name=All+Events&query=message:[first,second]&sort=-count&statsPeriod=24h&display=dailytop5&topEvents=2" link_type, args = match_link(url) if not args or not link_type: raise Exception("Missing link_type/args") links = [ UnfurlableUrl(url=url, args=args), ] with self.feature([ "organizations:discover", "organizations:discover-basic", ]): unfurls = link_handlers[link_type].fn(self.request, self.integration, links, self.user) assert (unfurls[url] == SlackDiscoverMessageBuilder( title=args["query"].get("name"), chart_url="chart-url").build()) assert len(mock_generate_chart.mock_calls) == 1 assert mock_generate_chart.call_args[0][ 0] == ChartType.SLACK_DISCOVER_TOP5_DAILY chart_data = mock_generate_chart.call_args[0][1] assert chart_data["seriesName"] == "count()" assert len(chart_data["stats"].keys()) == 2 first_key = list(chart_data["stats"].keys())[0] # Two buckets assert len(chart_data["stats"][first_key]["data"]) == 2
def test_unfurl_issues(self): project1 = self.create_project(organization=self.org) group1 = self.create_group(project=project1) min_ago = iso_format(before_now(minutes=1)) event = self.store_event(data={ "fingerprint": ["group2"], "timestamp": min_ago }, project_id=project1.id) group2 = event.group links = [ UnfurlableUrl( url= f"https://sentry.io/organizations/{self.org.slug}/issues/{group1.id}/", args={ "issue_id": group1.id, "event_id": None }, ), UnfurlableUrl( url= f"https://sentry.io/organizations/{self.org.slug}/issues/{group2.id}/{event.event_id}/", args={ "issue_id": group2.id, "event_id": event.event_id }, ), ] unfurls = link_handlers[LinkType.ISSUES].fn(self.request, self.integration, links) assert unfurls[links[0].url] == build_group_attachment(group1) assert unfurls[links[1].url] == build_group_attachment( group2, event, link_to_event=True)
def test_unfurl_discover_short_url(self, mock_generate_chart): query = { "fields": [ "message", "event.type", "project", "user.display", "count_unique(user)" ], "query": "message:[first,second]", "yAxis": "count_unique(user)", "display": "top5", "topEvents": 2, } saved_query = DiscoverSavedQuery.objects.create( organization=self.organization, created_by=self.user, name="Test query", query=query, version=2, ) saved_query.set_projects([self.project.id]) min_ago = iso_format(before_now(minutes=1)) self.store_event( data={ "message": "first", "fingerprint": ["group2"], "timestamp": min_ago }, project_id=self.project.id, ) self.store_event( data={ "message": "second", "fingerprint": ["group2"], "timestamp": min_ago }, project_id=self.project.id, ) url = f"https://sentry.io/organizations/{self.organization.slug}/discover/results/?id={saved_query.id}&statsPeriod=24h&project={self.project.id}" link_type, args = match_link(url) if not args or not link_type: raise Exception("Missing link_type/args") links = [ UnfurlableUrl(url=url, args=args), ] with self.feature([ "organizations:discover", "organizations:discover-basic", ]): unfurls = link_handlers[link_type].fn(self.request, self.integration, links, self.user) assert (unfurls[url] == SlackDiscoverMessageBuilder( title=args["query"].get("name"), chart_url="chart-url").build()) assert len(mock_generate_chart.mock_calls) == 1 # Line chart expected since yAxis is count_unique(user) assert mock_generate_chart.call_args[0][ 0] == ChartType.SLACK_DISCOVER_TOP5_PERIOD_LINE chart_data = mock_generate_chart.call_args[0][1] assert chart_data["seriesName"] == "count_unique(user)" # 2 + 1 cause of Other assert len(chart_data["stats"].keys()) == 2 first_key = list(chart_data["stats"].keys())[0] assert len(chart_data["stats"][first_key]["data"]) == 288
def on_link_shared( self, request: Request, slack_request: SlackRequest, ) -> Optional[Response]: matches: Dict[LinkType, List[UnfurlableUrl]] = defaultdict(list) links_seen = set() integration = slack_request.integration data = slack_request.data.get("event") # An unfurl may have multiple links to unfurl for item in data["links"]: try: url = item["url"] slack_shared_link = parse_link(url) except Exception as e: logger.error("slack.parse-link-error", extra={"error": str(e)}) continue # We would like to track what types of links users are sharing, but # it's a little difficult to do in Sentry because we filter requests # from Slack bots. Instead we just log to Kibana. logger.info("slack.link-shared", extra={"slack_shared_link": slack_shared_link}) link_type, args = match_link(url) # Link can't be unfurled if link_type is None or args is None: continue if (link_type == LinkType.DISCOVER and not slack_request.has_identity and features.has( "organizations:chart-unfurls", slack_request.integration.organizations.all()[0], actor=request.user, )): analytics.record( "integrations.slack.chart_unfurl", organization_id=integration.organizations.all()[0].id, unfurls_count=0, ) self.prompt_link(data, slack_request, integration) return self.respond() # Don't unfurl the same thing multiple times seen_marker = hash(json.dumps((link_type, args), sort_keys=True)) if seen_marker in links_seen: continue links_seen.add(seen_marker) matches[link_type].append(UnfurlableUrl(url=url, args=args)) if not matches: return None # Unfurl each link type results: Dict[str, Any] = {} for link_type, unfurl_data in matches.items(): results.update(link_handlers[link_type].fn(request, integration, unfurl_data, slack_request.user)) if not results: return None access_token = self._get_access_token(integration) payload = { "token": access_token, "channel": data["channel"], "ts": data["message_ts"], "unfurls": json.dumps(results), } client = SlackClient() try: client.post("/chat.unfurl", data=payload) except ApiError as e: logger.error("slack.event.unfurl-error", extra={"error": str(e)}, exc_info=True) return self.respond()
def on_link_shared(self, request: Request, integration: Integration, token: str, data: Mapping[str, Any]) -> Optional[Response]: matches: Dict[LinkType, List[UnfurlableUrl]] = defaultdict(list) links_seen = set() # An unfurl may have multiple links to unfurl for item in data["links"]: try: # We would like to track what types of links users are sharing, # but it's a little difficult to do in sentry since we filter # requests from Slack bots. Instead we just log to Kibana logger.info( "slack.link-shared", extra={"slack_shared_link": parse_link(item["url"])}) except Exception as e: logger.error("slack.parse-link-error", extra={"error": str(e)}) link_type, args = match_link(item["url"]) # Link can't be unfurled if link_type is None or args is None: continue # Don't unfurl the same thing multiple times seen_marker = hash(json.dumps((link_type, args), sort_keys=True)) if seen_marker in links_seen: continue links_seen.add(seen_marker) matches[link_type].append(UnfurlableUrl(url=item["url"], args=args)) if not matches: return None # Unfurl each link type results: Dict[str, Any] = {} for link_type, unfurl_data in matches.items(): results.update(link_handlers[link_type].fn(request, integration, unfurl_data)) if not results: return None access_token = self._get_access_token(integration) payload = { "token": access_token, "channel": data["channel"], "ts": data["message_ts"], "unfurls": json.dumps(results), } client = SlackClient() try: client.post("/chat.unfurl", data=payload) except ApiError as e: logger.error("slack.event.unfurl-error", extra={"error": str(e)}, exc_info=True) return self.respond()