Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(spans): Add chart unfurl support for EAP alerts #81184

Merged
merged 4 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
5 changes: 5 additions & 0 deletions src/sentry/incidents/charts.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,11 @@ def build_metric_alert_chart(
else:
if query_type == SnubaQuery.Type.PERFORMANCE and dataset == Dataset.PerformanceMetrics:
query_params["dataset"] = "metrics"
elif (
query_type == SnubaQuery.Type.PERFORMANCE and dataset == Dataset.EventsAnalyticsPlatform
):
query_params["dataset"] = "spans"
query_params["useRpc"] = "1"
elif query_type == SnubaQuery.Type.ERROR:
query_params["dataset"] = "errors"
else:
Expand Down
73 changes: 59 additions & 14 deletions src/sentry/incidents/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,14 @@
from sentry.models.project import Project
from sentry.notifications.models.notificationaction import ActionService, ActionTarget
from sentry.relay.config.metric_extraction import on_demand_metrics_feature_flags
from sentry.search.eap.types import SearchResolverConfig
from sentry.search.events.builder.base import BaseQueryBuilder
from sentry.search.events.constants import (
METRICS_LAYER_UNSUPPORTED_TRANSACTION_METRICS_FUNCTIONS,
SPANS_METRICS_FUNCTIONS,
)
from sentry.search.events.fields import is_function, resolve_field
from sentry.search.events.types import SnubaParams
from sentry.seer.anomaly_detection.delete_rule import delete_rule_in_seer
from sentry.seer.anomaly_detection.store_data import send_new_rule_data, update_rule_data
from sentry.sentry_apps.services.app import RpcSentryAppInstallation, app_service
Expand All @@ -74,7 +76,8 @@
DuplicateDisplayNameError,
IntegrationError,
)
from sentry.snuba.dataset import Dataset
from sentry.snuba import spans_rpc
from sentry.snuba.dataset import Dataset, EntityKey
from sentry.snuba.entity_subscription import (
ENTITY_TIME_COLUMNS,
EntitySubscription,
Expand All @@ -85,6 +88,7 @@
from sentry.snuba.metrics.extraction import should_use_on_demand_metrics
from sentry.snuba.metrics.naming_layer.mri import get_available_operations, is_mri, parse_mri
from sentry.snuba.models import QuerySubscription, SnubaQuery, SnubaQueryEventType
from sentry.snuba.referrer import Referrer
from sentry.snuba.subscriptions import (
bulk_delete_snuba_subscriptions,
bulk_disable_snuba_subscriptions,
Expand Down Expand Up @@ -417,20 +421,61 @@ def get_incident_aggregates(
snuba_query,
incident.organization_id,
)
query_builder = _build_incident_query_builder(
incident, entity_subscription, start, end, windowed_stats
)
try:
results = query_builder.run_query(referrer="incidents.get_incident_aggregates")
except Exception:
metrics.incr(
"incidents.get_incident_aggregates.snql.query.error",
tags={
"dataset": snuba_query.dataset,
"entity": get_entity_key_from_query_builder(query_builder).value,
},
if entity_subscription.dataset == Dataset.EventsAnalyticsPlatform:
start, end = _calculate_incident_time_range(
incident, start, end, windowed_stats=windowed_stats
)

project_ids = list(
IncidentProject.objects.filter(incident=incident).values_list("project_id", flat=True)
)
raise

params = SnubaParams(
environments=[snuba_query.environment],
projects=[Project.objects.get_from_cache(id=project_id) for project_id in project_ids],
organization=Organization.objects.get_from_cache(id=incident.organization_id),
start=start,
end=end,
)

try:
results = spans_rpc.run_table_query(
params,
query_string=snuba_query.query,
selected_columns=[entity_subscription.aggregate],
orderby=None,
offset=0,
limit=1,
referrer=Referrer.API_ALERTS_ALERT_RULE_CHART.value,
config=SearchResolverConfig(
auto_fields=True,
),
)

except Exception:
metrics.incr(
"incidents.get_incident_aggregates.snql.query.error",
tags={
"dataset": snuba_query.dataset,
"entity": EntityKey.EAPSpans.value,
},
)
raise
else:
query_builder = _build_incident_query_builder(
incident, entity_subscription, start, end, windowed_stats
)
try:
results = query_builder.run_query(referrer="incidents.get_incident_aggregates")
except Exception:
metrics.incr(
"incidents.get_incident_aggregates.snql.query.error",
tags={
"dataset": snuba_query.dataset,
"entity": get_entity_key_from_query_builder(query_builder).value,
},
)
raise

aggregated_result = entity_subscription.aggregate_query_results(results["data"], alias="count")
return aggregated_result[0]
Expand Down
104 changes: 103 additions & 1 deletion tests/sentry/integrations/slack/test_unfurl.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from sentry.integrations.slack.message_builder.metric_alerts import SlackMetricAlertMessageBuilder
from sentry.integrations.slack.unfurl.handlers import link_handlers, match_link
from sentry.integrations.slack.unfurl.types import LinkType, UnfurlableUrl
from sentry.snuba import discover, errors, transactions
from sentry.snuba import discover, errors, spans_eap, transactions
from sentry.snuba.dataset import Dataset
from sentry.testutils.cases import TestCase
from sentry.testutils.helpers import install_slack
Expand Down Expand Up @@ -399,6 +399,7 @@ def test_unfurl_metric_alerts_chart_transaction(self, mock_generate_chart):
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
Expand All @@ -407,6 +408,107 @@ def test_unfurl_metric_alerts_chart_transaction(self, mock_generate_chart):
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):
# Using the transactions 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}/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",
"organizations:performance-view",
"organizations:metric-alert-chartcuterie",
]
):
unfurls = link_handlers[LinkType.METRIC_ALERT].fn(self.request, 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.api.bases.organization_events.OrganizationEventsV2EndpointBase.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 transactions 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}/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",
"organizations:performance-view",
"organizations:metric-alert-chartcuterie",
]
):
link_handlers[LinkType.METRIC_ALERT].fn(self.request, self.integration, links)

dataset = mock_get_event_stats_data.mock_calls[0][2]["dataset"]
assert dataset == spans_eap

@patch("sentry.charts.backend.generate_chart", return_value="chart-url")
def test_unfurl_metric_alerts_chart_crash_free(self, mock_generate_chart):
alert_rule = self.create_alert_rule(
Expand Down
Loading