diff --git a/src/sentry/incidents/endpoints/organization_incident_details.py b/src/sentry/incidents/endpoints/organization_incident_details.py index 513fb2fffd2749..df105400011fb4 100644 --- a/src/sentry/incidents/endpoints/organization_incident_details.py +++ b/src/sentry/incidents/endpoints/organization_incident_details.py @@ -14,13 +14,11 @@ from sentry.api.exceptions import ResourceDoesNotExist from sentry.api.serializers import serialize from sentry.api.utils import to_valid_int_id -from sentry.incidents.endpoints.serializers.incident import DetailedIncidentSerializer from sentry.incidents.endpoints.serializers.utils import get_object_id_from_fake_id from sentry.incidents.endpoints.serializers.workflow_engine_incident import ( WorkflowEngineDetailedIncidentSerializer, ) -from sentry.incidents.logic import update_incident_status -from sentry.incidents.models.incident import Incident, IncidentStatus, IncidentStatusMethod +from sentry.incidents.models.incident import Incident, IncidentStatus from sentry.models.groupopenperiod import GroupOpenPeriod from sentry.models.organization import Organization from sentry.workflow_engine.models import IncidentGroupOpenPeriod @@ -139,21 +137,3 @@ def get( incident, request.user, WorkflowEngineDetailedIncidentSerializer(expand=expand) ) ) - - @track_alert_endpoint_execution("PUT", "sentry-api-0-organization-incident-details") - def put(self, request: Request, organization: Organization, incident) -> Response: - serializer = IncidentSerializer(data=request.data) - if serializer.is_valid(): - result = serializer.validated_data - if result["status"] == IncidentStatus.CLOSED: - incident = update_incident_status( - incident=incident, - status=result["status"], - status_method=IncidentStatusMethod.MANUAL, - ) - return Response( - serialize(incident, request.user, DetailedIncidentSerializer()), status=200 - ) - else: - return Response("Status cannot be changed.", status=400) - return Response(serializer.errors, status=400) diff --git a/src/sentry/integrations/messaging/metrics.py b/src/sentry/integrations/messaging/metrics.py index bd1914674036f2..308448a523855c 100644 --- a/src/sentry/integrations/messaging/metrics.py +++ b/src/sentry/integrations/messaging/metrics.py @@ -48,7 +48,6 @@ class MessagingInteractionType(StrEnum): PROCESS_SHARED_LINK = "PROCESS_SHARED_LINK" UNFURL_LINK = "UNFURL_LINK" UNFURL_ISSUES = "UNFURL_ISSUES" - UNFURL_METRIC_ALERTS = "UNFURL_METRIC_ALERTS" UNFURL_DISCOVER = "UNFURL_DISCOVER" UNFURL_EXPLORE = "UNFURL_EXPLORE" UNFURL_DASHBOARDS = "UNFURL_DASHBOARDS" diff --git a/src/sentry/integrations/slack/__init__.py b/src/sentry/integrations/slack/__init__.py index 74da44c00f3c09..f8bc73c554022a 100644 --- a/src/sentry/integrations/slack/__init__.py +++ b/src/sentry/integrations/slack/__init__.py @@ -25,7 +25,6 @@ from .requests.event import * # noqa: F401,F403 from .unfurl.discover import * # noqa: F401,F403 from .unfurl.issues import * # noqa: F401,F403 -from .unfurl.metric_alerts import * # noqa: F401,F403 from .urls import * # noqa: F401,F403 from .utils.auth import * # noqa: F401,F403 from .utils.channel import * # noqa: F401,F403 diff --git a/src/sentry/integrations/slack/unfurl/handlers.py b/src/sentry/integrations/slack/unfurl/handlers.py index bbf30c3003096f..58676bd26a709d 100644 --- a/src/sentry/integrations/slack/unfurl/handlers.py +++ b/src/sentry/integrations/slack/unfurl/handlers.py @@ -5,14 +5,12 @@ from sentry.integrations.slack.unfurl.discover import discover_handler from sentry.integrations.slack.unfurl.explore import explore_handler from sentry.integrations.slack.unfurl.issues import issues_handler -from sentry.integrations.slack.unfurl.metric_alerts import metric_alert_handler from sentry.integrations.slack.unfurl.types import Handler, LinkType link_handlers: dict[LinkType, Handler] = { LinkType.EXPLORE: explore_handler, LinkType.DASHBOARDS: dashboards_handler, LinkType.DISCOVER: discover_handler, - LinkType.METRIC_ALERT: metric_alert_handler, LinkType.ISSUES: issues_handler, } diff --git a/src/sentry/integrations/slack/unfurl/metric_alerts.py b/src/sentry/integrations/slack/unfurl/metric_alerts.py deleted file mode 100644 index 2c1c2f06b91dee..00000000000000 --- a/src/sentry/integrations/slack/unfurl/metric_alerts.py +++ /dev/null @@ -1,196 +0,0 @@ -from __future__ import annotations - -import html -import re -from collections.abc import Mapping -from typing import Any -from urllib.parse import urlparse - -import sentry_sdk -from django.db.models import Q -from django.http.request import QueryDict - -from sentry import features -from sentry.api.serializers import serialize -from sentry.incidents.charts import build_metric_alert_chart -from sentry.incidents.endpoints.serializers.alert_rule import AlertRuleSerializer -from sentry.incidents.endpoints.serializers.incident import ( - DetailedIncidentSerializer, - DetailedIncidentSerializerResponse, -) -from sentry.incidents.models.alert_rule import AlertRule -from sentry.incidents.models.incident import Incident -from sentry.incidents.typings.metric_detector import AlertContext, OpenPeriodContext -from sentry.integrations.messaging.metrics import ( - MessagingInteractionEvent, - MessagingInteractionType, -) -from sentry.integrations.models.integration import Integration -from sentry.integrations.services.integration import RpcIntegration, integration_service -from sentry.integrations.slack.message_builder.metric_alerts import SlackMetricAlertMessageBuilder -from sentry.integrations.slack.spec import SlackMessagingSpec -from sentry.integrations.slack.unfurl.types import ( - Handler, - UnfurlableUrl, - UnfurledUrl, - make_type_coercer, -) -from sentry.models.organization import Organization -from sentry.users.models.user import User -from sentry.users.services.user import RpcUser - -map_incident_args = make_type_coercer( - { - "org_slug": str, - "alert_rule_id": int, - "incident_id": int, - "period": str, - "start": str, - "end": str, - } -) - - -def unfurl_metric_alerts( - integration: Integration | RpcIntegration, - links: list[UnfurlableUrl], - user: User | RpcUser | None = None, -) -> UnfurledUrl: - with MessagingInteractionEvent( - MessagingInteractionType.UNFURL_METRIC_ALERTS, SlackMessagingSpec(), user=user - ).capture() as lifecycle: - lifecycle.add_extras({"integration_id": integration.id}) - return _unfurl_metric_alerts(integration, links, user) - - -def _unfurl_metric_alerts( - integration: Integration | RpcIntegration, - links: list[UnfurlableUrl], - user: User | RpcUser | None = None, -) -> UnfurledUrl: - alert_filter_query = Q() - incident_filter_query = Q() - # Since we don't have real ids here, we use the org slug so that we can - # make sure the identifiers correspond to the correct organization. - for link in links: - org_slug = link.args["org_slug"] - alert_rule_id = link.args["alert_rule_id"] - incident_id = link.args["incident_id"] - - if incident_id: - incident_filter_query |= Q( - identifier=incident_id, organization__slug=org_slug, alert_rule__id=alert_rule_id - ) - else: - alert_filter_query |= Q(id=alert_rule_id, organization__slug=org_slug) - - org_integrations = integration_service.get_organization_integrations( - integration_id=integration.id - ) - organizations = Organization.objects.filter( - id__in=[oi.organization_id for oi in org_integrations] - ) - alert_rule_map = { - rule.id: rule - for rule in AlertRule.objects.filter( - alert_filter_query, - # Filter by integration organization here as well to make sure that - # we have permission to access these incidents. - organization_id__in=[org.id for org in organizations], - ) - } - - if not alert_rule_map: - return {} - - incident_map = {} - if bool(incident_filter_query): - incident_map = { - i.identifier: i - for i in Incident.objects.filter( - incident_filter_query, - # Filter by integration organization here as well to make sure that - # we have permission to access these incidents. - organization_id__in=organizations, - ) - } - - orgs_by_slug: dict[str, Organization] = {org.slug: org for org in organizations} - - result = {} - for link in links: - if link.args["alert_rule_id"] not in alert_rule_map: - continue - org = orgs_by_slug.get(link.args["org_slug"]) - if org is None: - continue - - alert_rule = alert_rule_map[link.args["alert_rule_id"]] - selected_incident = incident_map.get(link.args["incident_id"]) - - chart_url = None - if features.has("organizations:metric-alert-chartcuterie", org): - try: - alert_rule_serialized_response = serialize(alert_rule, user, AlertRuleSerializer()) - incident_serialized_response: DetailedIncidentSerializerResponse = serialize( - selected_incident, None, DetailedIncidentSerializer() - ) - chart_url = build_metric_alert_chart( - organization=org, - alert_rule_serialized_response=alert_rule_serialized_response, - snuba_query=alert_rule.snuba_query, - alert_context=AlertContext.from_alert_rule_incident(alert_rule), - open_period_context=( - OpenPeriodContext.from_incident(selected_incident) - if selected_incident - else None - ), - selected_incident_serialized=incident_serialized_response, - period=link.args["period"], - start=link.args["start"], - end=link.args["end"], - user=user, - subscription=selected_incident.subscription if selected_incident else None, - ) - except Exception as e: - sentry_sdk.capture_exception(e) - - result[link.url] = SlackMetricAlertMessageBuilder( - alert_rule=alert_rule, - incident=selected_incident, - chart_url=chart_url, - ).build() - - return result - - -def map_metric_alert_query_args(url: str, args: Mapping[str, str | None]) -> Mapping[str, Any]: - """Extracts selected incident id and some query parameters""" - - # Slack uses HTML escaped ampersands in its Event Links, when need - # to be unescaped for QueryDict to split properly. - url = html.unescape(url) - parsed_url = urlparse(url) - params = QueryDict(parsed_url.query) - incident_id = params.get("alert", None) - period = params.get("period", None) - start = params.get("start", None) - end = params.get("end", None) - - data = {**args, "incident_id": incident_id, "period": period, "start": start, "end": end} - return map_incident_args(url, data) - - -metric_alerts_link_regex = re.compile( - r"^https?\://(?#url_prefix)[^/]+/organizations/(?P[^/]+)/issues/alerts/rules/details/(?P\d+)" -) - -customer_domain_metric_alerts_link_regex = re.compile( - r"^https?\://(?P[^/]+?)\.(?#url_prefix)[^/]+/issues/alerts/rules/details/(?P\d+)" -) - -metric_alert_handler = Handler( - fn=unfurl_metric_alerts, - matcher=[metric_alerts_link_regex, customer_domain_metric_alerts_link_regex], - arg_mapper=map_metric_alert_query_args, -) diff --git a/tests/sentry/incidents/endpoints/test_organization_incident_details.py b/tests/sentry/incidents/endpoints/test_organization_incident_details.py index c0d303e3a8ca69..c48358bb5a8212 100644 --- a/tests/sentry/incidents/endpoints/test_organization_incident_details.py +++ b/tests/sentry/incidents/endpoints/test_organization_incident_details.py @@ -2,7 +2,7 @@ from sentry.incidents.endpoints.serializers.utils import get_fake_id_from_object_id from sentry.incidents.grouptype import MetricIssue -from sentry.incidents.models.incident import Incident, IncidentStatus +from sentry.incidents.models.incident import IncidentStatus from sentry.models.groupopenperiod import GroupOpenPeriod from sentry.testutils.abstract import Abstract from sentry.testutils.cases import APITestCase @@ -100,37 +100,3 @@ def test_fake_id_cross_org_returns_404(self) -> None: resp = self.get_response(other_org.slug, fake_id) assert resp.status_code == 404 - - -class OrganizationIncidentUpdateStatusTest(BaseIncidentDetailsTest): - method = "put" - - def get_success_response(self, *args, **params): - params.setdefault("status", IncidentStatus.CLOSED.value) - return super().get_success_response(*args, **params) - - def test_simple(self) -> None: - incident = self.create_incident() - with self.feature("organizations:incidents"): - self.get_success_response( - incident.organization.slug, incident.identifier, status=IncidentStatus.CLOSED.value - ) - - incident = Incident.objects.get(id=incident.id) - assert incident.status == IncidentStatus.CLOSED.value - - def test_cannot_open(self) -> None: - incident = self.create_incident() - with self.feature("organizations:incidents"): - resp = self.get_response( - incident.organization.slug, incident.identifier, status=IncidentStatus.OPEN.value - ) - assert resp.status_code == 400 - assert resp.data.startswith("Status cannot be changed") - - def test_invalid_status(self) -> None: - incident = self.create_incident() - with self.feature("organizations:incidents"): - resp = self.get_response(incident.organization.slug, incident.identifier, status=5000) - assert resp.status_code == 400 - assert resp.data["status"][0].startswith("Invalid value for status") diff --git a/tests/sentry/integrations/slack/test_unfurl.py b/tests/sentry/integrations/slack/test_unfurl.py index 31fcbe96acc6f9..04e85bac98f674 100644 --- a/tests/sentry/integrations/slack/test_unfurl.py +++ b/tests/sentry/integrations/slack/test_unfurl.py @@ -5,17 +5,14 @@ import pytest from django.http.request import QueryDict from django.test import RequestFactory -from django.utils import timezone from sentry.charts.types import ChartType from sentry.discover.models import DiscoverSavedQuery, DiscoverSavedQueryTypes from sentry.incidents.grouptype import MetricIssue -from sentry.incidents.logic import CRITICAL_TRIGGER_LABEL from sentry.incidents.models.incident import Incident from sentry.integrations.services.integration.serial import serialize_integration from sentry.integrations.slack.message_builder.discover import SlackDiscoverMessageBuilder from sentry.integrations.slack.message_builder.issues import SlackIssuesMessageBuilder -from sentry.integrations.slack.message_builder.metric_alerts import SlackMetricAlertMessageBuilder from sentry.integrations.slack.unfurl.dashboards import build_widget_timeseries_params from sentry.integrations.slack.unfurl.handlers import link_handlers, match_link from sentry.integrations.slack.unfurl.types import LinkType, UnfurlableUrl @@ -23,10 +20,6 @@ from sentry.models.groupopenperiod import GroupOpenPeriod from sentry.search.eap.types import SupportedTraceItemType from sentry.snuba import discover, errors, transactions -from sentry.snuba.dataset import Dataset -from sentry.snuba.models import SnubaQueryEventType -from sentry.snuba.ourlogs import OurLogs -from sentry.snuba.spans_rpc import Spans from sentry.testutils.cases import TestCase from sentry.testutils.helpers import install_slack from sentry.testutils.helpers.datetime import before_now, freeze_time @@ -61,118 +54,6 @@ "https://org1.sentry.io/alerts/rules/details/12345/", (None, None), ), - ( - "https://sentry.io/organizations/org1/issues/alerts/rules/details/12345/", - ( - LinkType.METRIC_ALERT, - { - "alert_rule_id": 12345, - "incident_id": None, - "org_slug": "org1", - "period": None, - "start": None, - "end": None, - }, - ), - ), - ( - "https://org1.sentry.io/issues/alerts/rules/details/12345/", - ( - LinkType.METRIC_ALERT, - { - "alert_rule_id": 12345, - "incident_id": None, - "org_slug": "org1", - "period": None, - "start": None, - "end": None, - }, - ), - ), - ( - "https://sentry.io/organizations/org1/issues/alerts/rules/details/12345/?alert=1337", - ( - LinkType.METRIC_ALERT, - { - "alert_rule_id": 12345, - "incident_id": 1337, - "org_slug": "org1", - "period": None, - "start": None, - "end": None, - }, - ), - ), - ( - "https://org1.sentry.io/issues/alerts/rules/details/12345/?alert=1337", - ( - LinkType.METRIC_ALERT, - { - "alert_rule_id": 12345, - "incident_id": 1337, - "org_slug": "org1", - "period": None, - "start": None, - "end": None, - }, - ), - ), - ( - "https://sentry.io/organizations/org1/issues/alerts/rules/details/12345/?period=14d", - ( - LinkType.METRIC_ALERT, - { - "alert_rule_id": 12345, - "incident_id": None, - "org_slug": "org1", - "period": "14d", - "start": None, - "end": None, - }, - ), - ), - ( - "https://org1.sentry.io/issues/alerts/rules/details/12345/?period=14d", - ( - LinkType.METRIC_ALERT, - { - "alert_rule_id": 12345, - "incident_id": None, - "org_slug": "org1", - "period": "14d", - "start": None, - "end": None, - }, - ), - ), - ( - "https://sentry.io/organizations/org1/issues/alerts/rules/details/12345/?end=2022-05-05T06%3A05%3A52&start=2022-05-04T00%3A46%3A19", - ( - LinkType.METRIC_ALERT, - { - "alert_rule_id": 12345, - "incident_id": None, - "org_slug": "org1", - "period": None, - "start": "2022-05-04T00:46:19", - "end": "2022-05-05T06:05:52", - }, - ), - ), - ( - "https://org1.sentry.io/issues/alerts/rules/details/12345/?end=2022-05-05T06%3A05%3A52&start=2022-05-04T00%3A46%3A19", - ( - LinkType.METRIC_ALERT, - { - "alert_rule_id": 12345, - "incident_id": None, - "org_slug": "org1", - "period": None, - "start": "2022-05-04T00:46:19", - "end": "2022-05-05T06:05:52", - }, - ), - ), ( "https://sentry.io/organizations/org1/discover/results/?project=1&yAxis=count()", ( @@ -430,342 +311,6 @@ def test_escape_issue(self) -> None: unfurls = link_handlers[LinkType.ISSUES].fn(self.integration, links) assert unfurls[links[0].url]["blocks"][1]["text"]["text"] == "```" + escape_text + "```" - def test_unfurl_metric_alert(self) -> None: - 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) - - links = [ - UnfurlableUrl( - url=f"https://sentry.io/organizations/{self.organization.slug}/issues/alerts/rules/details/{incident.alert_rule.id}/?alert={incident.identifier}", - args={ - "org_slug": self.organization.slug, - "alert_rule_id": incident.alert_rule.id, - "incident_id": incident.identifier, - "period": None, - "start": None, - "end": None, - }, - ), - ] - unfurls = link_handlers[LinkType.METRIC_ALERT].fn(self.integration, links) - assert ( - links[0].url - == f"https://sentry.io/organizations/{self.organization.slug}/issues/alerts/rules/details/{incident.alert_rule.id}/?alert={incident.identifier}" - ) - assert ( - unfurls[links[0].url] - == SlackMetricAlertMessageBuilder(incident.alert_rule, incident).build() - ) - - @patch("sentry.charts.backend.generate_chart", return_value="chart-url") - def test_unfurl_metric_alerts_chart(self, mock_generate_chart: MagicMock) -> None: - alert_rule = self.create_alert_rule() - incident = self.create_incident( - status=2, - organization=self.organization, - projects=[self.project], - alert_rule=alert_rule, - date_started=timezone.now() - timedelta(minutes=2), - ) - 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) - self._wire_workflow_engine_for_incident(alert_rule, incident) - - url = f"https://sentry.io/organizations/{self.organization.slug}/issues/alerts/rules/details/{alert_rule.id}/?alert={incident.identifier}" - links = [ - UnfurlableUrl( - url=url, - args={ - "org_slug": self.organization.slug, - "alert_rule_id": alert_rule.id, - "incident_id": incident.identifier, - "period": None, - "start": None, - "end": None, - }, - ), - ] - - with self.feature( - [ - "organizations:incidents", - "organizations:discover-basic", - "organizations:metric-alert-chartcuterie", - ] - ): - unfurls = link_handlers[LinkType.METRIC_ALERT].fn(self.integration, links) - - assert ( - unfurls[links[0].url] - == SlackMetricAlertMessageBuilder(alert_rule, incident, 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["rule"]["id"] == str(alert_rule.id) - assert chart_data["selectedIncident"]["identifier"] == str(incident.identifier) - series_data = chart_data["timeseriesData"][0]["data"] - assert len(series_data) > 0 - # Validate format of timeseries - assert type(series_data[0]["name"]) is int - assert type(series_data[0]["value"]) is float - assert chart_data["incidents"][0]["id"] == str(incident.id) - - @patch("sentry.charts.backend.generate_chart", return_value="chart-url") - def test_unfurl_metric_alerts_chart_transaction(self, mock_generate_chart: MagicMock) -> None: - # Using the transactions dataset - alert_rule = self.create_alert_rule(query="p95", dataset=Dataset.Transactions) - incident = self.create_incident( - status=2, - organization=self.organization, - projects=[self.project], - alert_rule=alert_rule, - date_started=timezone.now() - timedelta(minutes=2), - ) - self._wire_workflow_engine_for_incident(alert_rule, incident) - - url = f"https://sentry.io/organizations/{self.organization.slug}/issues/alerts/rules/details/{alert_rule.id}/?alert={incident.identifier}" - links = [ - UnfurlableUrl( - url=url, - args={ - "org_slug": self.organization.slug, - "alert_rule_id": alert_rule.id, - "incident_id": incident.identifier, - "period": None, - "start": None, - "end": None, - }, - ), - ] - - with self.feature( - [ - "organizations:incidents", - "organizations:performance-view", - "organizations:metric-alert-chartcuterie", - ] - ): - unfurls = link_handlers[LinkType.METRIC_ALERT].fn(self.integration, links) - - assert ( - unfurls[links[0].url] - == SlackMetricAlertMessageBuilder(alert_rule, incident, 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["rule"]["id"] == str(alert_rule.id) - assert chart_data["selectedIncident"]["identifier"] == str(incident.identifier) - series_data = chart_data["timeseriesData"][0]["data"] - assert len(series_data) > 0 - # Validate format of timeseries - assert type(series_data[0]["name"]) is int - assert type(series_data[0]["value"]) is float - assert chart_data["incidents"][0]["id"] == str(incident.id) - - @patch("sentry.charts.backend.generate_chart", return_value="chart-url") - def test_unfurl_metric_alerts_chart_eap_spans(self, mock_generate_chart: MagicMock) -> None: - # Using the EventsAnalyticsPlatform dataset - alert_rule = self.create_alert_rule( - query="span.op:foo", dataset=Dataset.EventsAnalyticsPlatform - ) - incident = self.create_incident( - status=2, - organization=self.organization, - projects=[self.project], - alert_rule=alert_rule, - date_started=timezone.now() - timedelta(minutes=2), - ) - trigger = self.create_alert_rule_trigger(alert_rule, CRITICAL_TRIGGER_LABEL, 100) - self.create_alert_rule_trigger_action(alert_rule_trigger=trigger) - self._wire_workflow_engine_for_incident(alert_rule, incident) - - url = f"https://sentry.io/organizations/{self.organization.slug}/issues/alerts/rules/details/{alert_rule.id}/?alert={incident.identifier}" - links = [ - UnfurlableUrl( - url=url, - args={ - "org_slug": self.organization.slug, - "alert_rule_id": alert_rule.id, - "incident_id": incident.identifier, - "period": None, - "start": None, - "end": None, - }, - ), - ] - - with self.feature( - [ - "organizations:incidents", - "organizations:performance-view", - "organizations:metric-alert-chartcuterie", - ] - ): - unfurls = link_handlers[LinkType.METRIC_ALERT].fn(self.integration, links) - - assert ( - unfurls[links[0].url] - == SlackMetricAlertMessageBuilder(alert_rule, incident, 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["rule"]["id"] == str(alert_rule.id) - assert chart_data["rule"]["dataset"] == "events_analytics_platform" - assert chart_data["selectedIncident"]["identifier"] == str(incident.identifier) - series_data = chart_data["timeseriesData"][0]["data"] - assert len(series_data) > 0 - # Validate format of timeseries - assert type(series_data[0]["name"]) is int - assert type(series_data[0]["value"]) is float - assert chart_data["incidents"][0]["id"] == str(incident.id) - - @patch( - "sentry.api.bases.organization_events.OrganizationEventsEndpointBase.get_event_stats_data", - ) - @patch("sentry.charts.backend.generate_chart", return_value="chart-url") - def test_unfurl_metric_alerts_chart_eap_spans_events_stats_call( - self, mock_generate_chart, mock_get_event_stats_data - ): - # Using the EventsAnalyticsPlatform dataset - alert_rule = self.create_alert_rule( - query="span.op:foo", dataset=Dataset.EventsAnalyticsPlatform - ) - incident = self.create_incident( - status=2, - organization=self.organization, - projects=[self.project], - alert_rule=alert_rule, - date_started=timezone.now() - timedelta(minutes=2), - ) - - url = f"https://sentry.io/organizations/{self.organization.slug}/issues/alerts/rules/details/{alert_rule.id}/?alert={incident.identifier}" - links = [ - UnfurlableUrl( - url=url, - args={ - "org_slug": self.organization.slug, - "alert_rule_id": alert_rule.id, - "incident_id": incident.identifier, - "period": None, - "start": None, - "end": None, - }, - ), - ] - - with self.feature( - [ - "organizations:incidents", - "organizations:performance-view", - "organizations:metric-alert-chartcuterie", - ] - ): - link_handlers[LinkType.METRIC_ALERT].fn(self.integration, links) - - dataset = mock_get_event_stats_data.mock_calls[0][2]["dataset"] - assert dataset == Spans - - @patch( - "sentry.api.bases.organization_events.OrganizationEventsEndpointBase.get_event_stats_data", - ) - @patch("sentry.charts.backend.generate_chart", return_value="chart-url") - def test_unfurl_metric_alerts_chart_eap_ourlogs_events_stats_call( - self, mock_generate_chart, mock_get_event_stats_data - ): - # Using the EventsAnalyticsPlatform dataset with TRACE_ITEM_LOG event type - alert_rule = self.create_alert_rule( - query="log.level:error", - dataset=Dataset.EventsAnalyticsPlatform, - event_types=[SnubaQueryEventType.EventType.TRACE_ITEM_LOG], - ) - incident = self.create_incident( - status=2, - organization=self.organization, - projects=[self.project], - alert_rule=alert_rule, - date_started=timezone.now() - timedelta(minutes=2), - ) - - url = f"https://sentry.io/organizations/{self.organization.slug}/issues/alerts/rules/details/{alert_rule.id}/?alert={incident.identifier}" - links = [ - UnfurlableUrl( - url=url, - args={ - "org_slug": self.organization.slug, - "alert_rule_id": alert_rule.id, - "incident_id": incident.identifier, - "period": None, - "start": None, - "end": None, - }, - ), - ] - - with self.feature( - [ - "organizations:incidents", - "organizations:performance-view", - "organizations:metric-alert-chartcuterie", - ] - ): - link_handlers[LinkType.METRIC_ALERT].fn(self.integration, links) - - dataset = mock_get_event_stats_data.mock_calls[0][2]["dataset"] - assert dataset == OurLogs - - @patch("sentry.charts.backend.generate_chart", return_value="chart-url") - def test_unfurl_metric_alerts_chart_crash_free(self, mock_generate_chart: MagicMock) -> None: - alert_rule = self.create_alert_rule( - query="", - aggregate="percentage(sessions_crashed, sessions) AS _crash_rate_alert_aggregate", - dataset=Dataset.Metrics, - time_window=60, - resolve_threshold=10, - threshold_period=1, - ) - - url = f"https://sentry.io/organizations/{self.organization.slug}/issues/alerts/rules/details/{alert_rule.id}/" - links = [ - UnfurlableUrl( - url=url, - args={ - "org_slug": self.organization.slug, - "alert_rule_id": alert_rule.id, - "incident_id": None, - "period": None, - "start": None, - "end": None, - }, - ), - ] - - with self.feature( - [ - "organizations:incidents", - "organizations:discover-basic", - "organizations:metric-alert-chartcuterie", - ] - ): - unfurls = link_handlers[LinkType.METRIC_ALERT].fn(self.integration, links) - - assert ( - unfurls[links[0].url] - == SlackMetricAlertMessageBuilder(alert_rule, 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["rule"]["id"] == str(alert_rule.id) - assert chart_data["selectedIncident"] is None - assert len(chart_data["sessionResponse"]["groups"]) >= 1 - assert len(chart_data["incidents"]) == 0 - @patch( "sentry.api.bases.organization_events.OrganizationEventsEndpointBase.get_event_stats_data", return_value={