diff --git a/src/sentry/api/endpoints/organization_events_trace.py b/src/sentry/api/endpoints/organization_events_trace.py index 85a9183cd73b06..c8001c93147c0d 100644 --- a/src/sentry/api/endpoints/organization_events_trace.py +++ b/src/sentry/api/endpoints/organization_events_trace.py @@ -250,9 +250,12 @@ def load_span_serialized_performance_issues(self, light: bool) -> None: offender_span_ids = problem.evidence_data.get("offender_span_ids", []) for group_id in self.event["occurrence_to_issue_id"][problem.id]: if group_id not in memoized_groups: - memoized_groups[group_id] = Group.objects.get( - id=group_id, project=self.event["project.id"] - ) + try: + memoized_groups[group_id] = Group.objects.get( + id=group_id, project=self.event["project.id"] + ) + except Group.DoesNotExist: + continue group = memoized_groups[group_id] if event_span.get("span_id") in offender_span_ids: start_timestamp = float(event_span["precise.start_ts"]) diff --git a/src/sentry/api/serializers/models/group_stream.py b/src/sentry/api/serializers/models/group_stream.py index befe4a8cf2ef41..11381c7168984b 100644 --- a/src/sentry/api/serializers/models/group_stream.py +++ b/src/sentry/api/serializers/models/group_stream.py @@ -490,8 +490,10 @@ def get_attrs( ) attrs[item]["sentryAppIssues"] = sentry_app_issues - if self._expand("latestEventHasAttachments") and features.has( - "organizations:event-attachments", item.project.organization + if ( + self._expand("latestEventHasAttachments") + and item_list + and features.has("organizations:event-attachments", item_list[0].project.organization) ): for item in item_list: latest_event = item.get_latest_event() diff --git a/src/sentry/feedback/endpoints/organization_feedback_categories.py b/src/sentry/feedback/endpoints/organization_feedback_categories.py index 5679f018cd11ae..1bb71c8270eafb 100644 --- a/src/sentry/feedback/endpoints/organization_feedback_categories.py +++ b/src/sentry/feedback/endpoints/organization_feedback_categories.py @@ -152,7 +152,7 @@ def get(self, request: Request, organization: Organization) -> Response: ) if len(recent_feedbacks) < MIN_FEEDBACKS_CONTEXT: - logger.error("Too few feedbacks to generate categories") + logger.info("Too few feedbacks to generate categories") return Response( { "categories": None, diff --git a/src/sentry/incidents/action_handlers.py b/src/sentry/incidents/action_handlers.py index eea95adbd0c7a4..8b0f74f2aa059d 100644 --- a/src/sentry/incidents/action_handlers.py +++ b/src/sentry/incidents/action_handlers.py @@ -16,8 +16,6 @@ from sentry.charts.types import ChartSize from sentry.constants import CRASH_RATE_ALERT_AGGREGATE_ALIAS from sentry.incidents.charts import build_metric_alert_chart -from sentry.incidents.endpoints.serializers.alert_rule import AlertRuleSerializerResponse -from sentry.incidents.endpoints.serializers.incident import DetailedIncidentSerializerResponse from sentry.incidents.endpoints.serializers.utils import get_fake_id_from_object_id from sentry.incidents.models.alert_rule import ( AlertRuleDetectionType, @@ -152,8 +150,6 @@ def format_duration(minutes: int | float) -> str: def generate_incident_trigger_email_context( project: Project, organization: Organization, - alert_rule_serialized_response: AlertRuleSerializerResponse, - incident_serialized_response: DetailedIncidentSerializerResponse, metric_issue_context: MetricIssueContext, alert_context: AlertContext, open_period_context: OpenPeriodContext, @@ -207,8 +203,6 @@ def generate_incident_trigger_email_context( try: chart_url = build_metric_alert_chart( organization=organization, - alert_rule_serialized_response=alert_rule_serialized_response, - selected_incident_serialized=incident_serialized_response, snuba_query=snuba_query, alert_context=alert_context, open_period_context=open_period_context, @@ -302,8 +296,6 @@ def email_users( metric_issue_context: MetricIssueContext, open_period_context: OpenPeriodContext, alert_context: AlertContext, - alert_rule_serialized_response: AlertRuleSerializerResponse, - incident_serialized_response: DetailedIncidentSerializerResponse, trigger_status: TriggerStatus, targets: list[tuple[int, str]], project: Project, @@ -323,8 +315,6 @@ def email_users( project=project, organization=project.organization, metric_issue_context=metric_issue_context, - alert_rule_serialized_response=alert_rule_serialized_response, - incident_serialized_response=incident_serialized_response, alert_context=alert_context, open_period_context=open_period_context, trigger_status=trigger_status, diff --git a/src/sentry/incidents/charts.py b/src/sentry/incidents/charts.py index 1907931a962635..232cbf1004b650 100644 --- a/src/sentry/incidents/charts.py +++ b/src/sentry/incidents/charts.py @@ -11,8 +11,6 @@ from sentry.api.utils import get_datetime_from_stats_period from sentry.charts import backend as charts from sentry.charts.types import ChartSize, ChartType -from sentry.incidents.endpoints.serializers.alert_rule import AlertRuleSerializerResponse -from sentry.incidents.endpoints.serializers.incident import DetailedIncidentSerializerResponse from sentry.incidents.logic import translate_aggregate_field from sentry.incidents.typings.metric_detector import AlertContext, OpenPeriodContext from sentry.models.apikey import ApiKey @@ -27,7 +25,6 @@ from sentry.workflow_engine.endpoints.serializers.detector_serializer import ( DetectorSerializerResponse, ) -from sentry.workflow_engine.models import AlertRuleDetector CRASH_FREE_SESSIONS = "percentage(sessions_crashed, sessions) AS _crash_rate_alert_aggregate" CRASH_FREE_USERS = "percentage(users_crashed, users) AS _crash_rate_alert_aggregate" @@ -140,41 +137,17 @@ def fetch_metric_issue_open_periods( ) -> list[Any]: detector_id = open_period_identifier try: - # temporarily fetch the alert rule ID from the detector ID - alert_rule_detector = AlertRuleDetector.objects.filter( - detector_id=open_period_identifier, alert_rule_id__isnull=False - ).first() - if alert_rule_detector is not None: - # open_period_identifier is a metric detector ID -> get the alert rule ID - open_period_identifier = alert_rule_detector.alert_rule_id + resp = client.get( + auth=ApiKey(organization_id=organization.id, scope_list=["org:read"]), + user=user, + path=f"/organizations/{organization.slug}/open-periods/", + params={ + "detectorId": detector_id, + "bucketSize": time_window, + **time_period, + }, + ) - if features.has( - "organizations:workflow-engine-ui", - organization, - ): - resp = client.get( - auth=ApiKey(organization_id=organization.id, scope_list=["org:read"]), - user=user, - path=f"/organizations/{organization.slug}/open-periods/", - params={ - "detectorId": detector_id, - "bucketSize": time_window, - **time_period, - }, - ) - else: - resp = client.get( - auth=ApiKey(organization_id=organization.id, scope_list=["org:read"]), - user=user, - path=f"/organizations/{organization.slug}/incidents/", - params={ - "alertRule": open_period_identifier, - "expand": "activities", - "includeSnapshots": True, - "project": -1, - **time_period, - }, - ) return resp.data except Exception as exc: logger.error( @@ -187,11 +160,9 @@ def fetch_metric_issue_open_periods( def build_metric_alert_chart( organization: Organization, - alert_rule_serialized_response: AlertRuleSerializerResponse, snuba_query: SnubaQuery, alert_context: AlertContext, open_period_context: OpenPeriodContext | None = None, - selected_incident_serialized: DetailedIncidentSerializerResponse | None = None, period: str | None = None, start: str | None = None, end: str | None = None, @@ -206,22 +177,12 @@ def build_metric_alert_chart( dataset = Dataset(snuba_query.dataset) query_type = SnubaQuery.Type(snuba_query.type) is_crash_free_alert = query_type == SnubaQuery.Type.CRASH_RATE - using_new_charts = features.has( - "organizations:workflow-engine-ui", - organization, + + style = ( + ChartType.SLACK_METRIC_DETECTOR_SESSIONS + if is_crash_free_alert + else ChartType.SLACK_METRIC_DETECTOR_EVENTS ) - if is_crash_free_alert: - style = ( - ChartType.SLACK_METRIC_DETECTOR_SESSIONS - if using_new_charts - else ChartType.SLACK_METRIC_ALERT_SESSIONS - ) - else: - style = ( - ChartType.SLACK_METRIC_DETECTOR_EVENTS - if using_new_charts - else ChartType.SLACK_METRIC_ALERT_EVENTS - ) if open_period_context: time_period = incident_date_range( @@ -237,33 +198,17 @@ def build_metric_alert_chart( "start": period_start.strftime(TIME_FORMAT), "end": timezone.now().strftime(TIME_FORMAT), } - if features.has( - "organizations:workflow-engine-ui", - organization, - ): - # TODO(mifu67): create detailed serializer for open period, pass here. - # But we don't need it to render the chart, so it's fine for now. - chart_data_detector = { - "detector": detector_serialized_response, - "openPeriods": fetch_metric_issue_open_periods( - organization, - alert_context.action_identifier_id, - time_period, - user, - snuba_query.time_window, - ), - } - else: - chart_data_alert_rule = { - "rule": alert_rule_serialized_response, - "selectedIncident": selected_incident_serialized, - "incidents": fetch_metric_issue_open_periods( - organization, - alert_context.action_identifier_id, - time_period, - user, - ), - } + + chart_data_detector = { + "detector": detector_serialized_response, + "openPeriods": fetch_metric_issue_open_periods( + organization, + alert_context.action_identifier_id, + time_period, + user, + snuba_query.time_window, + ), + } allow_mri = features.has( "organizations:insights-alerts", @@ -343,10 +288,7 @@ def build_metric_alert_chart( ) try: - if using_new_charts: - chart_data.update(chart_data_detector) - else: - chart_data.update(chart_data_alert_rule) + chart_data.update(chart_data_detector) return charts.generate_chart(style, chart_data, size=size) except RuntimeError as exc: logger.error( diff --git a/src/sentry/integrations/discord/actions/metric_alert.py b/src/sentry/integrations/discord/actions/metric_alert.py index 4d66e89eda94b0..cf323a14400f19 100644 --- a/src/sentry/integrations/discord/actions/metric_alert.py +++ b/src/sentry/integrations/discord/actions/metric_alert.py @@ -4,8 +4,6 @@ from sentry import features from sentry.incidents.charts import build_metric_alert_chart -from sentry.incidents.endpoints.serializers.alert_rule import AlertRuleSerializerResponse -from sentry.incidents.endpoints.serializers.incident import DetailedIncidentSerializerResponse from sentry.incidents.typings.metric_detector import ( AlertContext, MetricIssueContext, @@ -37,25 +35,17 @@ def send_incident_alert_notification( notification_context: NotificationContext, metric_issue_context: MetricIssueContext, open_period_context: OpenPeriodContext, - alert_rule_serialized_response: AlertRuleSerializerResponse | None, - incident_serialized_response: DetailedIncidentSerializerResponse | None, detector_serialized_response: DetectorSerializerResponse | None = None, notification_uuid: str | None = None, ) -> bool: chart_url = None - if ( - features.has("organizations:metric-alert-chartcuterie", organization) - and alert_rule_serialized_response - and incident_serialized_response - ): + if features.has("organizations:metric-alert-chartcuterie", organization): try: chart_url = build_metric_alert_chart( organization=organization, - alert_rule_serialized_response=alert_rule_serialized_response, snuba_query=metric_issue_context.snuba_query, alert_context=alert_context, open_period_context=open_period_context, - selected_incident_serialized=incident_serialized_response, subscription=metric_issue_context.subscription, detector_serialized_response=detector_serialized_response, ) diff --git a/src/sentry/integrations/msteams/utils.py b/src/sentry/integrations/msteams/utils.py index efeacb9861b838..3f3b0779a8fbfa 100644 --- a/src/sentry/integrations/msteams/utils.py +++ b/src/sentry/integrations/msteams/utils.py @@ -3,8 +3,6 @@ import enum import logging -from sentry.incidents.endpoints.serializers.alert_rule import AlertRuleSerializerResponse -from sentry.incidents.endpoints.serializers.incident import DetailedIncidentSerializerResponse from sentry.incidents.typings.metric_detector import ( AlertContext, MetricIssueContext, @@ -115,8 +113,6 @@ def send_incident_alert_notification( notification_context: NotificationContext, metric_issue_context: MetricIssueContext, open_period_context: OpenPeriodContext, - alert_rule_serialized_response: AlertRuleSerializerResponse | None, - incident_serialized_response: DetailedIncidentSerializerResponse | None, notification_uuid: str | None = None, ) -> bool: from .card_builder.incident_attachment import build_incident_attachment diff --git a/src/sentry/integrations/slack/utils/notifications.py b/src/sentry/integrations/slack/utils/notifications.py index 2f13358a655584..2ffab100ed8e19 100644 --- a/src/sentry/integrations/slack/utils/notifications.py +++ b/src/sentry/integrations/slack/utils/notifications.py @@ -13,8 +13,6 @@ from sentry import features from sentry.constants import METRIC_ALERTS_THREAD_DEFAULT, ObjectStatus from sentry.incidents.charts import build_metric_alert_chart -from sentry.incidents.endpoints.serializers.alert_rule import AlertRuleSerializerResponse -from sentry.incidents.endpoints.serializers.incident import DetailedIncidentSerializerResponse from sentry.incidents.models.incident import IncidentStatus from sentry.incidents.typings.metric_detector import ( AlertContext, @@ -129,25 +127,17 @@ def _build_notification_payload( alert_context: AlertContext, metric_issue_context: MetricIssueContext, open_period_context: OpenPeriodContext, - alert_rule_serialized_response: AlertRuleSerializerResponse | None, - incident_serialized_response: DetailedIncidentSerializerResponse | None, detector_serialized_response: DetectorSerializerResponse | None, notification_uuid: str | None, ) -> tuple[str, str]: chart_url = None - if ( - features.has("organizations:metric-alert-chartcuterie", organization) - and alert_rule_serialized_response - and incident_serialized_response - ): + if features.has("organizations:metric-alert-chartcuterie", organization): try: chart_url = build_metric_alert_chart( organization=organization, - alert_rule_serialized_response=alert_rule_serialized_response, snuba_query=metric_issue_context.snuba_query, alert_context=alert_context, open_period_context=open_period_context, - selected_incident_serialized=incident_serialized_response, subscription=metric_issue_context.subscription, detector_serialized_response=detector_serialized_response, ) @@ -290,8 +280,6 @@ def send_incident_alert_notification( notification_context: NotificationContext, metric_issue_context: MetricIssueContext, open_period_context: OpenPeriodContext, - alert_rule_serialized_response: AlertRuleSerializerResponse | None, - incident_serialized_response: DetailedIncidentSerializerResponse | None, detector_serialized_response: DetectorSerializerResponse | None = None, notification_uuid: str | None = None, ) -> bool: @@ -315,8 +303,6 @@ def send_incident_alert_notification( alert_context=alert_context, metric_issue_context=metric_issue_context, open_period_context=open_period_context, - alert_rule_serialized_response=alert_rule_serialized_response, - incident_serialized_response=incident_serialized_response, notification_uuid=notification_uuid, detector_serialized_response=detector_serialized_response, ) diff --git a/src/sentry/issues/endpoints/group_details.py b/src/sentry/issues/endpoints/group_details.py index 9dbc8dbcc7b9a6..a57c255b2baeea 100644 --- a/src/sentry/issues/endpoints/group_details.py +++ b/src/sentry/issues/endpoints/group_details.py @@ -49,7 +49,7 @@ from sentry.users.services.user.service import user_service from sentry.utils import metrics -delete_logger = logging.getLogger("sentry.deletions.api") +logger = logging.getLogger(__name__) def get_group_global_count(group: Group) -> str: @@ -390,7 +390,7 @@ def put(self, request: Request, group: Group) -> Response: sample_rate=1.0, tags={"status": e.status_code, "detail": "group_details:update:Response"}, ) - logging.exception( + logger.exception( "group_details:put client.ApiError", ) return Response(e.body, status=e.status_code) diff --git a/src/sentry/issues/endpoints/organization_group_index.py b/src/sentry/issues/endpoints/organization_group_index.py index 7d77f5a1887e4d..d29d3df89c2fa0 100644 --- a/src/sentry/issues/endpoints/organization_group_index.py +++ b/src/sentry/issues/endpoints/organization_group_index.py @@ -387,8 +387,7 @@ def get(self, request: Request, organization: Organization) -> Response: serialized_groups = serialize( groups, request.user, serializer(), request=request ) - if event_id: - serialized_groups[0]["matchingEventId"] = event_id + serialized_groups[0]["matchingEventId"] = event_id response = Response(serialized_groups) response["X-Sentry-Direct-Hit"] = "1" return response diff --git a/src/sentry/issues/ignored.py b/src/sentry/issues/ignored.py index fd409e88efd975..fc4eed73f9708e 100644 --- a/src/sentry/issues/ignored.py +++ b/src/sentry/issues/ignored.py @@ -138,21 +138,21 @@ def handle_ignored( Group.objects.filter(id=group.id, status=GroupStatus.UNRESOLVED).update( substatus=GroupSubStatus.UNTIL_CONDITION_MET, status=GroupStatus.IGNORED ) - serialized_user = None - with in_test_hide_transaction_boundary(): - if acting_user: - serialized_user = user_service.serialize_many( - filter=dict(user_ids=[acting_user.id]), - as_user=serialize_generic_user(acting_user), - ) - new_status_details = IgnoredStatusDetails( - ignoreCount=ignore_count, - ignoreUntil=ignore_until, - ignoreUserCount=ignore_user_count, - ignoreUserWindow=ignore_user_window, - ignoreWindow=ignore_window, - actor=serialized_user[0] if serialized_user else None, - ) + serialized_user = None + with in_test_hide_transaction_boundary(): + if acting_user: + serialized_user = user_service.serialize_many( + filter=dict(user_ids=[acting_user.id]), + as_user=serialize_generic_user(acting_user), + ) + new_status_details = IgnoredStatusDetails( + ignoreCount=ignore_count, + ignoreUntil=ignore_until, + ignoreUserCount=ignore_user_count, + ignoreUserWindow=ignore_user_window, + ignoreWindow=ignore_window, + actor=serialized_user[0] if serialized_user else None, + ) else: GroupSnooze.objects.filter(group__in=group_list).delete() diff --git a/src/sentry/notifications/notification_action/action_handler_registry/webhook_handler.py b/src/sentry/notifications/notification_action/action_handler_registry/webhook_handler.py index eb186a330dafae..feafd2dfaf7a31 100644 --- a/src/sentry/notifications/notification_action/action_handler_registry/webhook_handler.py +++ b/src/sentry/notifications/notification_action/action_handler_registry/webhook_handler.py @@ -94,4 +94,5 @@ def execute(invocation: ActionInvocation) -> None: group_event=invocation.event_data.event, sentry_app_slug=target_identifier, rule_label=get_triggering_rule_name(invocation), + organization=organization, ) diff --git a/src/sentry/notifications/notification_action/metric_alert_registry/handlers/discord_metric_alert_handler.py b/src/sentry/notifications/notification_action/metric_alert_registry/handlers/discord_metric_alert_handler.py index 1291f98036ad14..939e620588a392 100644 --- a/src/sentry/notifications/notification_action/metric_alert_registry/handlers/discord_metric_alert_handler.py +++ b/src/sentry/notifications/notification_action/metric_alert_registry/handlers/discord_metric_alert_handler.py @@ -11,8 +11,6 @@ from sentry.models.organization import Organization from sentry.models.project import Project from sentry.notifications.notification_action.metric_alert_registry.handlers.utils import ( - get_alert_rule_serializer, - get_detailed_incident_serializer, get_detector_serializer, ) from sentry.notifications.notification_action.registry import metric_alert_handler_registry @@ -48,9 +46,7 @@ def send_alert( if not open_period: raise ValueError("Open period not found") - alert_rule_serialized_response = get_alert_rule_serializer(detector) detector_serialized_response = get_detector_serializer(detector) - incident_serialized_response = get_detailed_incident_serializer(open_period) logger.info( "notification_action.execute_via_metric_alert_handler.discord", @@ -66,7 +62,5 @@ def send_alert( open_period_context=open_period_context, organization=organization, notification_uuid=notification_uuid, - alert_rule_serialized_response=alert_rule_serialized_response, - incident_serialized_response=incident_serialized_response, detector_serialized_response=detector_serialized_response, ) diff --git a/src/sentry/notifications/notification_action/metric_alert_registry/handlers/email_metric_alert_handler.py b/src/sentry/notifications/notification_action/metric_alert_registry/handlers/email_metric_alert_handler.py index 93b33efdf0a6db..b39bc6f45b53c7 100644 --- a/src/sentry/notifications/notification_action/metric_alert_registry/handlers/email_metric_alert_handler.py +++ b/src/sentry/notifications/notification_action/metric_alert_registry/handlers/email_metric_alert_handler.py @@ -16,8 +16,6 @@ from sentry.models.team import Team from sentry.notifications.models.notificationaction import ActionTarget from sentry.notifications.notification_action.metric_alert_registry.handlers.utils import ( - get_alert_rule_serializer, - get_detailed_incident_serializer, get_detector_serializer, ) from sentry.notifications.notification_action.registry import metric_alert_handler_registry @@ -53,9 +51,7 @@ def send_alert( if not open_period: raise ValueError("Open period not found") - alert_rule_serialized_response = get_alert_rule_serializer(detector) detector_serialized_response = get_detector_serializer(detector) - incident_serialized_response = get_detailed_incident_serializer(open_period) recipients = list( get_email_addresses( @@ -76,8 +72,6 @@ def send_alert( metric_issue_context=metric_issue_context, open_period_context=open_period_context, alert_context=alert_context, - alert_rule_serialized_response=alert_rule_serialized_response, - incident_serialized_response=incident_serialized_response, detector_serialized_response=detector_serialized_response, trigger_status=trigger_status, targets=targets, diff --git a/src/sentry/notifications/notification_action/metric_alert_registry/handlers/msteams_metric_alert_handler.py b/src/sentry/notifications/notification_action/metric_alert_registry/handlers/msteams_metric_alert_handler.py index d544b27979ac07..f3f8bd75293782 100644 --- a/src/sentry/notifications/notification_action/metric_alert_registry/handlers/msteams_metric_alert_handler.py +++ b/src/sentry/notifications/notification_action/metric_alert_registry/handlers/msteams_metric_alert_handler.py @@ -10,10 +10,6 @@ from sentry.models.groupopenperiod import GroupOpenPeriod from sentry.models.organization import Organization from sentry.models.project import Project -from sentry.notifications.notification_action.metric_alert_registry.handlers.utils import ( - get_alert_rule_serializer, - get_detailed_incident_serializer, -) from sentry.notifications.notification_action.registry import metric_alert_handler_registry from sentry.notifications.notification_action.types import BaseMetricAlertHandler from sentry.workflow_engine.models import Action, Detector @@ -45,9 +41,6 @@ def send_alert( if not open_period: raise ValueError("Open period not found") - alert_rule_serialized_response = get_alert_rule_serializer(detector) - incident_serialized_response = get_detailed_incident_serializer(open_period) - logger.info( "notification_action.execute_via_metric_alert_handler.msteams", extra={ @@ -62,6 +55,4 @@ def send_alert( open_period_context=open_period_context, organization=organization, notification_uuid=notification_uuid, - alert_rule_serialized_response=alert_rule_serialized_response, - incident_serialized_response=incident_serialized_response, ) diff --git a/src/sentry/notifications/notification_action/metric_alert_registry/handlers/slack_metric_alert_handler.py b/src/sentry/notifications/notification_action/metric_alert_registry/handlers/slack_metric_alert_handler.py index 704f7cc8246dcb..6205dde482e11c 100644 --- a/src/sentry/notifications/notification_action/metric_alert_registry/handlers/slack_metric_alert_handler.py +++ b/src/sentry/notifications/notification_action/metric_alert_registry/handlers/slack_metric_alert_handler.py @@ -4,8 +4,6 @@ from sentry import features from sentry.incidents.charts import build_metric_alert_chart -from sentry.incidents.endpoints.serializers.alert_rule import AlertRuleSerializerResponse -from sentry.incidents.endpoints.serializers.incident import DetailedIncidentSerializerResponse from sentry.incidents.models.incident import IncidentStatus, TriggerStatus from sentry.incidents.typings.metric_detector import ( AlertContext, @@ -19,8 +17,6 @@ from sentry.models.organization import Organization from sentry.models.project import Project from sentry.notifications.notification_action.metric_alert_registry.handlers.utils import ( - get_alert_rule_serializer, - get_detailed_incident_serializer, get_detector_serializer, ) from sentry.notifications.notification_action.registry import metric_alert_handler_registry @@ -49,9 +45,7 @@ def _send_via_notification_platform( open_period_context: OpenPeriodContext, notification_uuid: str, organization: Organization, - alert_rule_serialized_response: AlertRuleSerializerResponse, detector_serialized_response: DetectorSerializerResponse, - incident_serialized_response: DetailedIncidentSerializerResponse, ) -> None: if notification_context.integration_id is None: raise ValueError("Integration ID is None") @@ -68,19 +62,13 @@ def _send_via_notification_platform( ) chart_url = None - if ( - features.has("organizations:metric-alert-chartcuterie", organization) - and alert_rule_serialized_response - and incident_serialized_response - ): + if features.has("organizations:metric-alert-chartcuterie", organization): try: chart_url = build_metric_alert_chart( organization=organization, - alert_rule_serialized_response=alert_rule_serialized_response, snuba_query=metric_issue_context.snuba_query, alert_context=alert_context, open_period_context=open_period_context, - selected_incident_serialized=incident_serialized_response, subscription=metric_issue_context.subscription, detector_serialized_response=detector_serialized_response, ) @@ -147,18 +135,7 @@ def send_alert( if not open_period: raise ValueError("Open period not found") - alert_rule_serialized_response = get_alert_rule_serializer(detector) detector_serialized_response = get_detector_serializer(detector) - incident_serialized_response = get_detailed_incident_serializer(open_period) - - logger.info( - "notification_action.execute_via_metric_alert_handler.slack", - extra={ - "action_id": alert_context.action_identifier_id, - "serialized_incident": incident_serialized_response, - "serialized_alert_rule": alert_rule_serialized_response, - }, - ) if NotificationService.has_access(organization, NotificationSource.METRIC_ALERT): _send_via_notification_platform( @@ -168,9 +145,7 @@ def send_alert( open_period_context=open_period_context, notification_uuid=notification_uuid, organization=organization, - alert_rule_serialized_response=alert_rule_serialized_response, detector_serialized_response=detector_serialized_response, - incident_serialized_response=incident_serialized_response, ) else: send_incident_alert_notification( @@ -180,8 +155,6 @@ def send_alert( open_period_context=open_period_context, organization=organization, notification_uuid=notification_uuid, - alert_rule_serialized_response=alert_rule_serialized_response, - incident_serialized_response=incident_serialized_response, detector_serialized_response=detector_serialized_response, ) diff --git a/src/sentry/notifications/notification_action/metric_alert_registry/handlers/utils.py b/src/sentry/notifications/notification_action/metric_alert_registry/handlers/utils.py index d1f8edba7e3690..cf3ecf29c6fb15 100644 --- a/src/sentry/notifications/notification_action/metric_alert_registry/handlers/utils.py +++ b/src/sentry/notifications/notification_action/metric_alert_registry/handlers/utils.py @@ -1,14 +1,8 @@ from sentry.api.serializers import serialize -from sentry.incidents.endpoints.serializers.alert_rule import AlertRuleSerializerResponse from sentry.incidents.endpoints.serializers.incident import ( - DetailedIncidentSerializerResponse, IncidentSerializerResponse, ) -from sentry.incidents.endpoints.serializers.workflow_engine_detector import ( - WorkflowEngineDetectorSerializer, -) from sentry.incidents.endpoints.serializers.workflow_engine_incident import ( - WorkflowEngineDetailedIncidentSerializer, WorkflowEngineIncidentSerializer, ) from sentry.models.groupopenperiod import GroupOpenPeriod @@ -19,21 +13,9 @@ from sentry.workflow_engine.models import Detector -def get_alert_rule_serializer(detector: Detector) -> AlertRuleSerializerResponse: - return serialize(detector, None, WorkflowEngineDetectorSerializer()) - - def get_detector_serializer(detector: Detector) -> DetectorSerializerResponse: return serialize(detector, None, DetectorSerializer()) -# TODO(mifu67): These take an open period, get the incident, and call the incident -# serializer on the incident. They will need to be replaced. -def get_detailed_incident_serializer( - open_period: GroupOpenPeriod, -) -> DetailedIncidentSerializerResponse: - return serialize(open_period, None, WorkflowEngineDetailedIncidentSerializer()) - - def get_incident_serializer(open_period: GroupOpenPeriod) -> IncidentSerializerResponse: return serialize(open_period, None, WorkflowEngineIncidentSerializer()) diff --git a/src/sentry/notifications/notification_action/utils.py b/src/sentry/notifications/notification_action/utils.py index 166e411b25651b..eae7d9efd874bc 100644 --- a/src/sentry/notifications/notification_action/utils.py +++ b/src/sentry/notifications/notification_action/utils.py @@ -147,8 +147,6 @@ def metric_alert_notification_data_factory( issue_notif_context: IssueNotificationContext, ) -> MetricAlertNotificationData: from sentry.notifications.notification_action.metric_alert_registry.handlers.utils import ( - get_alert_rule_serializer, - get_detailed_incident_serializer, get_detector_serializer, ) @@ -173,24 +171,16 @@ def metric_alert_notification_data_factory( referrer=referrer, ) - alert_rule_serialized_response = get_alert_rule_serializer(issue_notif_context.detector) detector_serialized_response = get_detector_serializer(issue_notif_context.detector) - incident_serialized_response = get_detailed_incident_serializer(issue_notif_context.open_period) chart_url = None - if ( - features.has("organizations:metric-alert-chartcuterie", organization) - and alert_rule_serialized_response - and incident_serialized_response - ): + if features.has("organizations:metric-alert-chartcuterie", organization): try: chart_url = build_metric_alert_chart( organization=organization, - alert_rule_serialized_response=alert_rule_serialized_response, snuba_query=metric_issue_context.snuba_query, alert_context=alert_context, open_period_context=open_period_context, - selected_incident_serialized=incident_serialized_response, subscription=metric_issue_context.subscription, detector_serialized_response=detector_serialized_response, ) diff --git a/src/sentry/preprod/api/endpoints/snapshots/preprod_artifact_snapshot_download.py b/src/sentry/preprod/api/endpoints/snapshots/preprod_artifact_snapshot_download.py index 15b5481ccff074..5e276def1f53bd 100644 --- a/src/sentry/preprod/api/endpoints/snapshots/preprod_artifact_snapshot_download.py +++ b/src/sentry/preprod/api/endpoints/snapshots/preprod_artifact_snapshot_download.py @@ -40,8 +40,8 @@ logger = logging.getLogger(__name__) -FETCH_MAX_WORKERS = 32 -FETCH_BATCH_SIZE = 200 +FETCH_MAX_WORKERS = 16 +FETCH_BATCH_SIZE = 100 BATCH_TIMEOUT = 300 diff --git a/src/sentry/preprod/api/endpoints/snapshots/preprod_artifact_snapshot_latest_base.py b/src/sentry/preprod/api/endpoints/snapshots/preprod_artifact_snapshot_latest_base.py index b94c9d70244277..980f6ee27112de 100644 --- a/src/sentry/preprod/api/endpoints/snapshots/preprod_artifact_snapshot_latest_base.py +++ b/src/sentry/preprod/api/endpoints/snapshots/preprod_artifact_snapshot_latest_base.py @@ -15,7 +15,6 @@ from sentry.api.api_publish_status import ApiPublishStatus from sentry.api.base import cell_silo_endpoint from sentry.api.bases.organization import ( - NoProjects, OrganizationEndpoint, OrganizationReleasePermission, ) @@ -142,14 +141,13 @@ def get( compact = request.GET.get("compact_metadata", "0") in ("1", "true") try: - params = self.get_filter_params(request, organization, date_filter_optional=True) - except NoProjects: - return Response({"detail": "No snapshot found"}, status=404) + project_id = int(request.GET["project"]) if "project" in request.GET else None + except (ValueError, TypeError): + return Response({"detail": "Invalid project parameter"}, status=400) qs = ( PreprodArtifact.objects.filter( project__organization_id=organization.id, - project_id__in=params["project_id"], app_id=app_id, preprodsnapshotmetrics__isnull=False, ) @@ -161,6 +159,8 @@ def get( .select_related("commit_comparison", "project", "preprodsnapshotmetrics") ) + if project_id is not None: + qs = qs.filter(project_id=project_id) if branch: qs = qs.filter(commit_comparison__head_ref=branch) diff --git a/src/sentry/ratelimits/utils.py b/src/sentry/ratelimits/utils.py index b8e21727248bc0..910a1007636114 100644 --- a/src/sentry/ratelimits/utils.py +++ b/src/sentry/ratelimits/utils.py @@ -103,7 +103,7 @@ def get_rate_limit_key( else: assert False # Can't happen as asserted by is_api_token_auth check - if request_user.is_sentry_app: + if getattr(request_user, "is_sentry_app", False): category = "org" id = get_organization_id_from_token(token_id) diff --git a/src/sentry/sentry_apps/services/legacy_webhook/service.py b/src/sentry/sentry_apps/services/legacy_webhook/service.py index 46efcff2e47595..ec814aab2b497c 100644 --- a/src/sentry/sentry_apps/services/legacy_webhook/service.py +++ b/src/sentry/sentry_apps/services/legacy_webhook/service.py @@ -3,8 +3,10 @@ import logging from typing import Any, TypedDict +from sentry import features from sentry.eventstore.models import GroupEvent from sentry.models.options.project_option import ProjectOption +from sentry.models.organization import Organization from sentry.models.rule import Rule from sentry.sentry_apps.services.app import app_service from sentry.sentry_apps.tasks.sentry_apps import send_alert_webhook_v2 @@ -90,16 +92,29 @@ def send_sentry_app_webhook( group_event: GroupEvent, sentry_app_slug: str | None, rule_label: str, + organization: Organization, ) -> None: + logging_context = { + "organization_id": organization.id, + "sentry_app_slug": sentry_app_slug, + } + if not sentry_app_slug: - logger.warning("webhook_action_handler.missing_target_identifier") + logger.warning("webhook_action_handler.missing_target_identifier", extra=logging_context) return sentry_app = app_service.get_sentry_app_by_slug(slug=sentry_app_slug) if sentry_app is None: logger.warning( "webhook_action_handler.sentry_app_not_found", - extra={"sentry_app_slug": sentry_app_slug}, + extra=logging_context, + ) + return + + if features.has("organizations:legacy-webhook-dry-run", organization): + logger.info( + "webhook_action_handler.sentry_app_dry_run", + extra=logging_context, ) return diff --git a/static/app/components/activity/note/mentionStyle.tsx b/static/app/components/activity/note/mentionStyle.tsx index 44d7e837d6ad03..303ebf6b8d0285 100644 --- a/static/app/components/activity/note/mentionStyle.tsx +++ b/static/app/components/activity/note/mentionStyle.tsx @@ -48,7 +48,6 @@ export function mentionStyle({theme, minHeight, inputStyle}: Options) { maxHeight: 142, minWidth: 220, overflow: 'auto', - zIndex: theme.zIndex.dropdown, backgroundColor: theme.tokens.background.primary, border: '1px solid rgba(0,0,0,0.15)', borderRadius: theme.radius.md, diff --git a/static/app/components/replays/breadcrumbs/breadcrumbItem.tsx b/static/app/components/replays/breadcrumbs/breadcrumbItem.tsx index 59b38ef236800f..9aa17e619e728e 100644 --- a/static/app/components/replays/breadcrumbs/breadcrumbItem.tsx +++ b/static/app/components/replays/breadcrumbs/breadcrumbItem.tsx @@ -153,30 +153,29 @@ export function BreadcrumbItem({ const StyledTimelineItem = styled(Timeline.Item)` width: 100%; - position: relative; padding: ${p => p.theme.space.xs} ${p => p.theme.space.sm}; margin: 0; &:hover { - background: ${p => p.theme.colors.surface200}; + background-color: ${p => p.theme.colors.surface200}; .timeline-icon-wrapper { - background: ${p => p.theme.colors.surface200}; + background-color: ${p => p.theme.colors.surface200}; } } cursor: pointer; /* vertical line connecting items */ - &:not(:last-child)::before { - content: ''; - position: absolute; - left: 16.5px; - width: 1px; - top: -2px; - bottom: -9px; + &:not(:last-child) { /* eslint-disable-next-line @sentry/scraps/use-semantic-token */ - background: ${p => p.theme.tokens.border.primary}; - z-index: 0; + background-image: linear-gradient( + ${p => p.theme.tokens.border.primary}, + ${p => p.theme.tokens.border.primary} + ); + background-position: 16.5px -2px; + background-repeat: no-repeat; + background-size: 1px calc(100% + 11px); } - &:first-child::before { - top: 4px; + &:first-child { + background-position: 16.5px 4px; + background-size: 1px calc(100% + 5px); } `; diff --git a/static/app/components/timeline/index.tsx b/static/app/components/timeline/index.tsx index bc9f29fd9c0f0d..645c3339681ec7 100644 --- a/static/app/components/timeline/index.tsx +++ b/static/app/components/timeline/index.tsx @@ -104,7 +104,6 @@ const Row = styled('div')<{hasMarker: boolean; showLastLine?: boolean}>` const MarkerWrapper = styled('div')` grid-column: span 1; - z-index: 1; display: grid; place-items: center; min-width: 22px; @@ -116,7 +115,6 @@ const IconWrapper = styled('div')` border-radius: 100%; border: 1px solid; background: ${p => p.theme.tokens.background.primary}; - z-index: 1; svg { display: block; margin: ${p => p.theme.space.xs}; diff --git a/static/app/views/issueDetails/activitySection/index.tsx b/static/app/views/issueDetails/activitySection/index.tsx index 2841bd368d191b..630947ce936186 100644 --- a/static/app/views/issueDetails/activitySection/index.tsx +++ b/static/app/views/issueDetails/activitySection/index.tsx @@ -4,7 +4,7 @@ import styled from '@emotion/styled'; import {SentryAppAvatar, UserAvatar} from '@sentry/scraps/avatar'; import {LinkButton} from '@sentry/scraps/button'; -import {Flex, Grid} from '@sentry/scraps/layout'; +import {Container, Flex, Grid} from '@sentry/scraps/layout'; import {Text} from '@sentry/scraps/text'; import {Tooltip} from '@sentry/scraps/tooltip'; @@ -380,7 +380,6 @@ export function ActivitySection({ item => !filterComments || item.type === GroupActivityType.NOTE ); const inputVariant = variant === 'sidebar' ? 'compact' : 'full'; - const useTwoColumnLayout = organization.features.includes('issue-activity-feed-v2'); const renderActivityItem = (item: GroupActivity) => ( {filteredActivities.slice(0, 3).map(renderActivityItem)} - + + + + {t('View %s more', filteredActivities.length - 3)} - } - marker={useTwoColumnLayout ? : undefined} - icon={} - /> + + )} @@ -486,7 +486,39 @@ const Timestamp = styled(TimeSince)` `; const RotatedEllipsisIcon = styled(IconEllipsis)` - transform: rotate(90deg) translateY(1px); + position: relative; + left: 1px; + transform: rotate(90deg) translate(1px, 1px); +`; + +const MoreActivityRow = styled('div')` + position: relative; + display: grid; + align-items: center; + grid-template-columns: 22px minmax(0, 1fr); + grid-column-gap: ${p => p.theme.space.md}; + margin: ${p => p.theme.space.md} 0 0; + + &::after { + content: ''; + position: absolute; + left: 10.5px; + top: 50%; + bottom: 0; + width: 1px; + background: ${p => p.theme.tokens.background.primary}; + } +`; + +const MoreActivityIcon = styled('div')` + position: relative; + z-index: 1; + display: grid; + place-items: center; + width: 22px; + min-height: 22px; + color: ${p => p.theme.tokens.content.secondary}; + background: ${p => p.theme.tokens.background.primary}; `; const NoteWrapper = styled('div')<{size: 'sm' | 'md'}>` diff --git a/static/app/views/issueDetails/streamline/eventDetailsHeader.spec.tsx b/static/app/views/issueDetails/streamline/eventDetailsHeader.spec.tsx index addaa9e294466f..19832696d81afe 100644 --- a/static/app/views/issueDetails/streamline/eventDetailsHeader.spec.tsx +++ b/static/app/views/issueDetails/streamline/eventDetailsHeader.spec.tsx @@ -81,7 +81,9 @@ describe('EventDetailsHeader', () => { expect(screen.getByRole('button', {name: 'All Envs'})).toBeInTheDocument(); // Date selection is based on first seen unless selected by the user - expect(screen.getByRole('button', {name: 'Since First Seen'})).toBeInTheDocument(); + expect( + screen.getByRole('button', {name: 'Since First Seen (19 days)'}) + ).toBeInTheDocument(); expect(screen.getByPlaceholderText('Filter events\u2026')).toBeInTheDocument(); expect( screen.getByRole('button', { diff --git a/static/app/views/issueDetails/streamline/eventDetailsHeader.tsx b/static/app/views/issueDetails/streamline/eventDetailsHeader.tsx index b26db89c1dc9d3..e826c4c785a9d5 100644 --- a/static/app/views/issueDetails/streamline/eventDetailsHeader.tsx +++ b/static/app/views/issueDetails/streamline/eventDetailsHeader.tsx @@ -11,9 +11,9 @@ import { TimeRangeSelector, TimeRangeSelectTrigger, } from 'sentry/components/timeRangeSelector'; -import {getRelativeSummary} from 'sentry/components/timeRangeSelector/utils'; +import {getRelativeDate} from 'sentry/components/timeSince'; import {TourElement} from 'sentry/components/tours/components'; -import {t} from 'sentry/locale'; +import {t, tct} from 'sentry/locale'; import type {Event} from 'sentry/types/event'; import type {Group} from 'sentry/types/group'; import type {Project} from 'sentry/types/project'; @@ -135,6 +135,9 @@ export function EventDetailsHeader({group, event, project}: EventDetailsHeaderPr /> )} diff --git a/static/app/views/issueDetails/streamline/sidebar/autofixSection.spec.tsx b/static/app/views/issueDetails/streamline/sidebar/autofixSection.spec.tsx index 97faad0df3b30b..3e315e3700e804 100644 --- a/static/app/views/issueDetails/streamline/sidebar/autofixSection.spec.tsx +++ b/static/app/views/issueDetails/streamline/sidebar/autofixSection.spec.tsx @@ -488,8 +488,13 @@ describe('AutofixSection', () => { }); it('shows org setup UI when SCM integration is missing', async () => { + const seatBasedOrg = OrganizationFixture({ + hideAiFeatures: false, + features: ['gen-ai-features', 'seat-based-seer-enabled'], + }); + MockApiClient.addMockResponse({ - url: `/organizations/${organization.slug}/seer/onboarding-check/`, + url: `/organizations/${seatBasedOrg.slug}/seer/onboarding-check/`, body: { hasSupportedScmIntegration: false, isAutofixEnabled: false, @@ -505,7 +510,7 @@ describe('AutofixSection', () => { }); render(, { - organization, + organization: seatBasedOrg, }); expect(await screen.findByText('Finish Configuring Seer')).toBeInTheDocument(); @@ -517,6 +522,11 @@ describe('AutofixSection', () => { }); it('shows project setup UI when repos are not linked', async () => { + const seatBasedOrg = OrganizationFixture({ + hideAiFeatures: false, + features: ['gen-ai-features', 'seat-based-seer-enabled'], + }); + MockApiClient.addMockResponse({ url: `/organizations/${mockProject.organization.slug}/issues/${mockGroup.id}/autofix/setup/`, body: AutofixSetupFixture({ @@ -532,7 +542,7 @@ describe('AutofixSection', () => { }); render(, { - organization, + organization: seatBasedOrg, }); expect(await screen.findByText('Finish Configuring Seer')).toBeInTheDocument(); @@ -545,6 +555,31 @@ describe('AutofixSection', () => { ); }); + it('skips setup UI for non-seat-based orgs without SCM integration', async () => { + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/seer/onboarding-check/`, + body: { + hasSupportedScmIntegration: false, + isAutofixEnabled: false, + isCodeReviewEnabled: false, + isSeerConfigured: false, + needsConfigReminder: false, + }, + }); + + MockApiClient.addMockResponse({ + url: `/organizations/${mockProject.organization.slug}/issues/${mockGroup.id}/autofix/`, + body: {autofix: null}, + }); + + render(, { + organization, + }); + + expect(await screen.findByText('Have Seer...')).toBeInTheDocument(); + expect(screen.queryByText('Finish Configuring Seer')).not.toBeInTheDocument(); + }); + it('shows empty state when there are no artifacts', async () => { MockApiClient.addMockResponse({ url: `/organizations/${mockProject.organization.slug}/issues/${mockGroup.id}/autofix/`, diff --git a/static/app/views/issueDetails/streamline/sidebar/autofixSection.tsx b/static/app/views/issueDetails/streamline/sidebar/autofixSection.tsx index bdbff1b46d299d..e27c52f77383a6 100644 --- a/static/app/views/issueDetails/streamline/sidebar/autofixSection.tsx +++ b/static/app/views/issueDetails/streamline/sidebar/autofixSection.tsx @@ -155,40 +155,43 @@ export function AutofixContent({aiConfig, group, project, event}: AutofixContent // scm integration not linked to project !aiConfig.seerReposLinked; - if (needOrgSetup || needProjSetup) { - return ( - - {t('Finish Configuring Seer')} - - {t( - 'Your organization has access to Seer, which will allow you to run Autofix on your issues, but you aren’t getting the most out of it.' - )} - - {t('Autofix can:')} - -
  • {t('Determine the root cause of your issue and how to reproduce it')}
  • -
  • {t('Propose a solution')}
  • -
  • {t('Create a code fix')}
  • -
    - - {needOrgSetup ? ( - } - > - {t('Set Up Seer')} - - ) : needProjSetup ? ( - } - > - {t('Set Up Seer for This Project')} - - ) : null} + // non seat based seer plans are allowed to run autofix without the SCM integration + if (organization.features.includes('seat-based-seer-enabled')) { + if (needOrgSetup || needProjSetup) { + return ( + + {t('Finish Configuring Seer')} + + {t( + 'Your organization has access to Seer, which will allow you to run Autofix on your issues, but you aren’t getting the most out of it.' + )} + + {t('Autofix can:')} + +
  • {t('Determine the root cause of your issue and how to reproduce it')}
  • +
  • {t('Propose a solution')}
  • +
  • {t('Create a code fix')}
  • +
    + + {needOrgSetup ? ( + } + > + {t('Set Up Seer')} + + ) : needProjSetup ? ( + } + > + {t('Set Up Seer for This Project')} + + ) : null} +
    -
    - ); + ); + } } return ( diff --git a/static/app/views/issueDetails/streamline/sidebar/sidebar.tsx b/static/app/views/issueDetails/streamline/sidebar/sidebar.tsx index 63e08407f08588..cc531cadcdf94f 100644 --- a/static/app/views/issueDetails/streamline/sidebar/sidebar.tsx +++ b/static/app/views/issueDetails/streamline/sidebar/sidebar.tsx @@ -103,7 +103,9 @@ export function StreamlinedSidebar({group, event, project}: Props) { )} - + + + {showPeopleSection && ( None: + def test_get_open_periods_from_detector(self) -> None: group = self.create_group( project=self.project, type=MetricIssue.type_id, priority=PriorityLevel.HIGH ) @@ -239,20 +225,20 @@ def test_get_incidents_from_detector(self) -> None: chart_data = fetch_metric_issue_open_periods( self.organization, self.detector.id, time_period ) - assert chart_data[0]["alertRule"]["id"] == str(self.alert_rule.id) - assert chart_data[0]["projects"] == [self.project.slug] - assert chart_data[0]["dateStarted"] == group_open_period.date_started + assert chart_data[0]["id"] == str(group_open_period.id) + assert chart_data[0]["start"] == group_open_period.date_started assert len(chart_data[0]["activities"]) == 2 opened_activity_resp = chart_data[0]["activities"][0] closed_activity_resp = chart_data[0]["activities"][1] assert opened_activity_resp["id"] == str(opened_gopa.id) - assert opened_activity_resp["type"] == IncidentActivityType.STATUS_CHANGE.value + assert opened_activity_resp["type"] == OpenPeriodActivityType(opened_gopa.type).to_str() + assert opened_activity_resp["value"] == PriorityLevel(group.priority).to_str() assert opened_activity_resp["dateCreated"] == opened_gopa.date_added assert closed_activity_resp["id"] == str(closed_gopa.id) - assert closed_activity_resp["type"] == IncidentActivityType.STATUS_CHANGE.value + assert closed_activity_resp["type"] == OpenPeriodActivityType(closed_gopa.type).to_str() assert closed_activity_resp["dateCreated"] == closed_gopa.date_added @freeze_time(frozen_time) diff --git a/tests/sentry/notifications/notification_action/action_handler_registry/test_webhook_handler.py b/tests/sentry/notifications/notification_action/action_handler_registry/test_webhook_handler.py index 51024cd561b6bb..e8cf060017eaf8 100644 --- a/tests/sentry/notifications/notification_action/action_handler_registry/test_webhook_handler.py +++ b/tests/sentry/notifications/notification_action/action_handler_registry/test_webhook_handler.py @@ -9,6 +9,7 @@ WebhookActionHandler, ) from sentry.plugins.base import plugins +from sentry.testutils.helpers.features import with_feature from sentry.types.activity import ActivityType from sentry.utils import json from sentry.workflow_engine.models import Action @@ -172,6 +173,37 @@ def test_new_path_sentry_app_action_routes_to_sentry_app_webhook( assert "rule_label" in call_kwargs mock_legacy.assert_not_called() + @with_feature( + { + "organizations:legacy-webhook-new-path": True, + "organizations:legacy-webhook-disable-old-path": True, + "organizations:legacy-webhook-dry-run": True, + } + ) + @mock.patch("sentry.sentry_apps.services.legacy_webhook.service.send_alert_webhook_v2") + def test_new_path_sentry_app_dry_run_does_not_send( + self, mock_send_alert: mock.MagicMock + ) -> None: + action = self.create_action( + type=Action.Type.WEBHOOK, + config={"target_identifier": "my-app"}, + ) + invocation = ActionInvocation( + event_data=self.event_data, + action=action, + detector=self.detector, + notification_uuid=str(uuid.uuid4()), + workflow_id=self.workflow.id, + ) + self.create_sentry_app(name="My App", organization=self.organization) + self.create_sentry_app_installation( + slug="my-app", organization=self.organization, user=self.user + ) + + WebhookActionHandler.execute(invocation) + + mock_send_alert.delay.assert_not_called() + @mock.patch( "sentry.notifications.notification_action.action_handler_registry.webhook_handler.send_sentry_app_webhook" ) diff --git a/tests/sentry/notifications/notification_action/metric_alert_registry/test_discord_metric_alert_handler.py b/tests/sentry/notifications/notification_action/metric_alert_registry/test_discord_metric_alert_handler.py index 85370c5e2f5689..68313a290ebca9 100644 --- a/tests/sentry/notifications/notification_action/metric_alert_registry/test_discord_metric_alert_handler.py +++ b/tests/sentry/notifications/notification_action/metric_alert_registry/test_discord_metric_alert_handler.py @@ -14,8 +14,6 @@ from sentry.notifications.models.notificationaction import ActionTarget from sentry.notifications.notification_action.metric_alert_registry import DiscordMetricAlertHandler from sentry.notifications.notification_action.metric_alert_registry.handlers.utils import ( - get_alert_rule_serializer, - get_detailed_incident_serializer, get_detector_serializer, ) from sentry.testutils.helpers.datetime import freeze_time @@ -78,8 +76,6 @@ def test_send_alert(self, mock_send_incident_alert_notification: mock.MagicMock) notification_context=notification_context, metric_issue_context=metric_issue_context, open_period_context=open_period_context, - alert_rule_serialized_response=get_alert_rule_serializer(self.detector), - incident_serialized_response=get_detailed_incident_serializer(self.open_period), detector_serialized_response=get_detector_serializer(self.detector), notification_uuid=notification_uuid, ) diff --git a/tests/sentry/notifications/notification_action/metric_alert_registry/test_email_metric_alert_handler.py b/tests/sentry/notifications/notification_action/metric_alert_registry/test_email_metric_alert_handler.py index b9bcef8ffd9dc6..c16c2d41f831d2 100644 --- a/tests/sentry/notifications/notification_action/metric_alert_registry/test_email_metric_alert_handler.py +++ b/tests/sentry/notifications/notification_action/metric_alert_registry/test_email_metric_alert_handler.py @@ -14,8 +14,6 @@ from sentry.notifications.models.notificationaction import ActionTarget from sentry.notifications.notification_action.metric_alert_registry import EmailMetricAlertHandler from sentry.notifications.notification_action.metric_alert_registry.handlers.utils import ( - get_alert_rule_serializer, - get_detailed_incident_serializer, get_detector_serializer, ) from sentry.testutils.helpers.datetime import freeze_time @@ -78,8 +76,6 @@ def test_send_alert(self, mock_email_users: mock.MagicMock) -> None: metric_issue_context=metric_issue_context, open_period_context=open_period_context, alert_context=alert_context, - alert_rule_serialized_response=get_alert_rule_serializer(self.detector), - incident_serialized_response=get_detailed_incident_serializer(self.open_period), detector_serialized_response=get_detector_serializer(self.detector), trigger_status=TriggerStatus.ACTIVE, targets=[(self.user.id, self.user.email)], diff --git a/tests/sentry/notifications/notification_action/metric_alert_registry/test_msteams_metric_alert_handler.py b/tests/sentry/notifications/notification_action/metric_alert_registry/test_msteams_metric_alert_handler.py index 9c157f0c636a19..0cae9547145bfb 100644 --- a/tests/sentry/notifications/notification_action/metric_alert_registry/test_msteams_metric_alert_handler.py +++ b/tests/sentry/notifications/notification_action/metric_alert_registry/test_msteams_metric_alert_handler.py @@ -13,10 +13,6 @@ from sentry.models.activity import Activity from sentry.notifications.models.notificationaction import ActionTarget from sentry.notifications.notification_action.metric_alert_registry import MSTeamsMetricAlertHandler -from sentry.notifications.notification_action.metric_alert_registry.handlers.utils import ( - get_alert_rule_serializer, - get_detailed_incident_serializer, -) from sentry.testutils.helpers.datetime import freeze_time from sentry.types.activity import ActivityType from sentry.workflow_engine.models import Action @@ -78,8 +74,6 @@ def test_send_alert(self, mock_send_incident_alert_notification: mock.MagicMock) notification_context=notification_context, metric_issue_context=metric_issue_context, open_period_context=open_period_context, - alert_rule_serialized_response=get_alert_rule_serializer(self.detector), - incident_serialized_response=get_detailed_incident_serializer(self.open_period), notification_uuid=notification_uuid, ) diff --git a/tests/sentry/notifications/notification_action/metric_alert_registry/test_slack_metric_alert_handler.py b/tests/sentry/notifications/notification_action/metric_alert_registry/test_slack_metric_alert_handler.py index 0e811f50026181..a71dfb06c18cc0 100644 --- a/tests/sentry/notifications/notification_action/metric_alert_registry/test_slack_metric_alert_handler.py +++ b/tests/sentry/notifications/notification_action/metric_alert_registry/test_slack_metric_alert_handler.py @@ -21,8 +21,6 @@ from sentry.notifications.models.notificationaction import ActionTarget from sentry.notifications.notification_action.metric_alert_registry import SlackMetricAlertHandler from sentry.notifications.notification_action.metric_alert_registry.handlers.utils import ( - get_alert_rule_serializer, - get_detailed_incident_serializer, get_detector_serializer, ) from sentry.testutils.helpers.datetime import freeze_time @@ -128,8 +126,6 @@ def test_send_alert_falls_back_to_legacy_when_no_access( notification_context=kwargs["notification_context"], metric_issue_context=kwargs["metric_issue_context"], open_period_context=kwargs["open_period_context"], - alert_rule_serialized_response=get_alert_rule_serializer(self.detector), - incident_serialized_response=get_detailed_incident_serializer(self.open_period), detector_serialized_response=get_detector_serializer(self.detector), notification_uuid=kwargs["notification_uuid"], ) diff --git a/tests/sentry/search/events/builder/test_spans_indexed.py b/tests/sentry/search/events/builder/test_spans_indexed.py index 38cffc5411cde1..773756593ff8ae 100644 --- a/tests/sentry/search/events/builder/test_spans_indexed.py +++ b/tests/sentry/search/events/builder/test_spans_indexed.py @@ -1,16 +1,9 @@ from datetime import datetime, timedelta, timezone -from itertools import chain import pytest -from snuba_sdk import AliasedExpression, And, Column, Condition, Function, Op, Or +from snuba_sdk import AliasedExpression, Column -from sentry.exceptions import InvalidSearchQuery -from sentry.search.events.builder.spans_indexed import ( - SPAN_ID_FIELDS, - SPAN_UUID_FIELDS, - SpansEAPQueryBuilder, - SpansIndexedQueryBuilder, -) +from sentry.search.events.builder.spans_indexed import SpansEAPQueryBuilder from sentry.snuba.dataset import Dataset from sentry.testutils.factories import Factories from sentry.testutils.pytest.fixtures import django_db_all @@ -47,471 +40,6 @@ def params(now, today): } -span_duration = Function( - "if", - [ - Function("greater", [Column("exclusive_time"), Column("duration")]), - Column("exclusive_time"), - Column("duration"), - ], - "span.duration", -) - - -@pytest.mark.parametrize( - ["field", "expected"], - [ - pytest.param("span.duration", span_duration, id="span.duration"), - pytest.param( - "profile.id", - AliasedExpression(Column("profile_id"), alias="profile.id"), - id="profile.id", - ), - ], -) -@django_db_all -def test_field_alias(params, field, expected) -> None: - builder = SpansIndexedQueryBuilder( - Dataset.SpansIndexed, - params, - selected_columns=[field], - ) - assert expected in builder.columns - - -def tags(key, column="tags"): - return Function("ifNull", [Column(f"{column}[{key}]"), ""]) - - -def has_tag(key, column="tags"): - return Condition(Function("has", [Column("tags.key"), key]), Op.EQ, 1) - - -@pytest.mark.parametrize( - ["condition", "expected"], - [ - pytest.param( - "span.duration:1s", - Condition(span_duration, Op.EQ, 1000), - id="span.duration:1s", - marks=pytest.mark.querybuilder, - ), - pytest.param( - "span.duration:>1s", Condition(span_duration, Op.GT, 1000), id="span.duration:>1s" - ), - pytest.param( - "span.duration:<1s", Condition(span_duration, Op.LT, 1000), id="span.duration:<1s" - ), - pytest.param( - "span.duration:>=1s", Condition(span_duration, Op.GTE, 1000), id="span.duration:>=1s" - ), - pytest.param( - "span.duration:<=1s", Condition(span_duration, Op.LTE, 1000), id="span.duration:<=1s" - ), - pytest.param( - "span.duration:<=1s", Condition(span_duration, Op.LTE, 1000), id="span.duration:<=1s" - ), - pytest.param( - "span.op:db", - Condition(Column("op"), Op.EQ, "db"), - id="span.op:db", - ), - pytest.param( - "span.op:[db,http.client]", - Condition(Column("op"), Op.IN, ["db", "http.client"]), - id="span.op:[db,http.client]", - ), - pytest.param( - "span.status:ok", - Condition(Column("span_status"), Op.EQ, 0), - id="span.status:ok", - ), - pytest.param( - "span.status:[invalid_argument,not_found]", - Condition(Column("span_status"), Op.IN, [3, 5]), - id="span.status:[invalid_argument,not_found]", - ), - pytest.param( - "foo:*bar*", - And( - conditions=[ - Condition(Function("positionCaseInsensitive", [tags("foo"), "bar"]), Op.NEQ, 0), - has_tag("foo"), - ], - ), - id="foo:*bar*", - ), - pytest.param( - "!foo:*bar*", - And( - conditions=[ - Condition(Function("positionCaseInsensitive", [tags("foo"), "bar"]), Op.EQ, 0), - has_tag("foo"), - ], - ), - id="!foo:*bar*", - ), - pytest.param( - r"foo:Bar*", - And( - conditions=[ - Condition( - Function("startsWith", [Function("lower", [tags("foo")]), "bar"]), Op.EQ, 1 - ), - has_tag("foo"), - ], - ), - id=r"foo:Bar*", - ), - pytest.param( - r"!foo:Bar*", - And( - conditions=[ - Condition( - Function("startsWith", [Function("lower", [tags("foo")]), "bar"]), Op.NEQ, 1 - ), - has_tag("foo"), - ], - ), - id=r"!foo:Bar*", - ), - pytest.param( - r"foo:*Bar", - And( - conditions=[ - Condition( - Function("endsWith", [Function("lower", [tags("foo")]), "bar"]), Op.EQ, 1 - ), - has_tag("foo"), - ], - ), - id=r"foo:*Bar", - ), - pytest.param( - r"!foo:*Bar", - And( - conditions=[ - Condition( - Function("endsWith", [Function("lower", [tags("foo")]), "bar"]), Op.NEQ, 1 - ), - has_tag("foo"), - ], - ), - id=r"!foo:*Bar", - ), - pytest.param( - r"foo:*Bar\*", - And( - conditions=[ - Condition( - Function("endsWith", [Function("lower", [tags("foo")]), "bar*"]), Op.EQ, 1 - ), - has_tag("foo"), - ], - ), - id=r"foo:*Bar\*", - ), - pytest.param( - r"!foo:*Bar\*", - And( - conditions=[ - Condition( - Function("endsWith", [Function("lower", [tags("foo")]), "bar*"]), Op.NEQ, 1 - ), - has_tag("foo"), - ], - ), - id=r"!foo:*Bar\*", - ), - pytest.param( - r"foo:*b*a*r*", - And( - conditions=[ - Condition(Function("match", [tags("foo"), "(?i)^.*b.*a.*r.*$"]), Op.EQ, 1), - has_tag("foo"), - ], - ), - id=r"foo:*b*a*r*", - ), - pytest.param( - r"!foo:*b*a*r*", - And( - conditions=[ - Condition(Function("match", [tags("foo"), "(?i)^.*b.*a.*r.*$"]), Op.NEQ, 1), - has_tag("foo"), - ], - ), - id=r"!foo:*b*a*r*", - ), - pytest.param( - "message:*bar*", - Condition( - Function("positionCaseInsensitive", [Column("description"), "bar"]), Op.NEQ, 0 - ), - id="message:*bar*", - ), - pytest.param( - "!message:*bar*", - Condition( - Function("positionCaseInsensitive", [Column("description"), "bar"]), Op.EQ, 0 - ), - id="!message:*bar*", - ), - pytest.param( - r"message:Bar*", - Condition( - Function("startsWith", [Function("lower", [Column("description")]), "bar"]), - Op.EQ, - 1, - ), - id=r"message:Bar*", - ), - pytest.param( - r"!message:Bar*", - Condition( - Function("startsWith", [Function("lower", [Column("description")]), "bar"]), - Op.NEQ, - 1, - ), - id=r"!message:Bar*", - ), - pytest.param( - r"message:*Bar", - Condition( - Function("endsWith", [Function("lower", [Column("description")]), "bar"]), Op.EQ, 1 - ), - id=r"message:*Bar", - ), - pytest.param( - r"!message:*Bar", - Condition( - Function("endsWith", [Function("lower", [Column("description")]), "bar"]), Op.NEQ, 1 - ), - id=r"!message:*Bar", - ), - pytest.param( - r"message:*Bar\*", - Condition( - Function("endsWith", [Function("lower", [Column("description")]), "bar*"]), Op.EQ, 1 - ), - id=r"message:*Bar\*", - ), - pytest.param( - r"!message:*Bar\*", - Condition( - Function("endsWith", [Function("lower", [Column("description")]), "bar*"]), - Op.NEQ, - 1, - ), - id=r"!message:*Bar\*", - ), - pytest.param( - r"message:*b*a*r*", - Condition(Function("match", [Column("description"), "(?i).*b.*a.*r.*"]), Op.EQ, 1), - id=r"message:*b*a*r*", - ), - pytest.param( - r"!message:*b*a*r*", - Condition(Function("match", [Column("description"), "(?i).*b.*a.*r.*"]), Op.NEQ, 1), - id=r"!message:*b*a*r*", - ), - ], -) -@django_db_all -def test_where(params, condition, expected) -> None: - builder = SpansIndexedQueryBuilder( - Dataset.SpansIndexed, - params, - query=condition, - selected_columns=["count"], - ) - assert expected in builder.where - - -@django_db_all -def test_where_project(params) -> None: - project = next(iter(params["project_objects"])) - - for query in [f"project:{project.slug}", f"project.id:{project.id}"]: - builder = SpansIndexedQueryBuilder( - Dataset.SpansIndexed, - params, - query=query, - selected_columns=["count"], - ) - - assert Condition(Column("project_id"), Op.EQ, project.id) in builder.where - - -@pytest.mark.parametrize( - ["query", "expected"], - [ - pytest.param( - "span.op:params test", - Condition( - Function("positionCaseInsensitive", [Column("description"), "test"]), - Op.NEQ, - 0, - ), - id="span.op:params test", - ), - pytest.param( - "testing", - Condition( - Function("positionCaseInsensitive", [Column("description"), "testing"]), - Op.NEQ, - 0, - ), - id="testing", - ), - pytest.param( - "span.description:test1 test2", - Condition( - Function("positionCaseInsensitive", [Column("description"), "test2"]), - Op.NEQ, - 0, - ), - id="span.description:test1 test2", - ), - pytest.param( - "*testing*", - Condition( - Function("positionCaseInsensitive", [Column("description"), "testing"]), - Op.NEQ, - 0, - ), - id="*testing*", - ), - pytest.param( - "*test*ing*", - Condition( - Function("match", [Column("description"), "(?i).*test.*ing.*"]), - Op.EQ, - 1, - ), - id="*test*ing*", - ), - ], -) -@django_db_all -def test_free_text_search(params, query, expected) -> None: - builder = SpansIndexedQueryBuilder( - Dataset.SpansIndexed, - params, - query=query, - selected_columns=["count"], - ) - assert expected in builder.where - - -@pytest.mark.parametrize( - ["column"], - [pytest.param(column) for column in chain(SPAN_ID_FIELDS, SPAN_UUID_FIELDS)], -) -@pytest.mark.parametrize( - ["query", "message"], - [ - pytest.param("bad_span_id", "must be a valid", id="bad span id"), - pytest.param("*wild*card*", "Wildcard conditions are not permitted", id="wildcard"), - ], -) -@django_db_all -def test_id_column_validation_failed(params, column, query, message) -> None: - with pytest.raises(InvalidSearchQuery) as err: - SpansIndexedQueryBuilder( - Dataset.SpansIndexed, - params, - query=f"{column}:{query}", - selected_columns=["count"], - ) - - assert message in str(err) - assert f"`{column}`" in str(err) - - -@pytest.mark.parametrize( - ["column"], - [pytest.param(column) for column in ["profile.id", "profile_id"]], -) -@django_db_all -def test_profile_id_column_has(params, column) -> None: - builder = SpansIndexedQueryBuilder( - Dataset.SpansIndexed, - params, - query=f"has:{column}", - selected_columns=["count"], - ) - - assert ( - Condition( - Function("isNull", [Column("profile_id")]), - Op.NEQ, - 1, - ) - in builder.where - ) - - -@pytest.mark.parametrize( - ["column", "query"], - [pytest.param(column, "0" * 32, id=column) for column in SPAN_UUID_FIELDS] - + [pytest.param(column, "0" * 16, id=column) for column in SPAN_ID_FIELDS] - + [pytest.param(column, "0" * 10, id=column) for column in SPAN_ID_FIELDS], -) -@pytest.mark.parametrize( - ["operator"], - [pytest.param("", id="IN"), pytest.param("!", id="NOT IN")], -) -@django_db_all -def test_id_column_permit_in_operator(params, column, query, operator) -> None: - builder = SpansIndexedQueryBuilder( - Dataset.SpansIndexed, - params, - query=f"{operator}{column}:[{query}]", - selected_columns=["count"], - ) - - resolved_column = builder.resolve_column(column) - - condition = Condition( - resolved_column, - Op.IN if operator == "" else Op.NOT_IN, - [query], - ) - - nullable_condition = Or( - conditions=[ - Condition(Function("isNull", [resolved_column]), Op.EQ, 1), - condition, - ], - ) - - non_nullable_condition = Condition( - Function("ifNull", [resolved_column, ""]), - Op.IN if operator == "" else Op.NOT_IN, - [query], - ) - - assert ( - condition in builder.where - or nullable_condition in builder.where - or non_nullable_condition in builder.where - ) - - -@django_db_all -def test_span_module_optimization_where_clause(params) -> None: - builder = SpansIndexedQueryBuilder( - Dataset.SpansIndexed, - params, - query="span.module:http", - selected_columns=["count"], - ) - - condition = Condition(builder.resolve_field("sentry_tags[category]"), Op.EQ, "http") - assert condition in builder.where - - @pytest.mark.parametrize( ["attribute", "expected"], [ diff --git a/tests/sentry/sentry_apps/services/legacy_webhook/test_service.py b/tests/sentry/sentry_apps/services/legacy_webhook/test_service.py index a41d7c69822394..8e556deb0aa7f5 100644 --- a/tests/sentry/sentry_apps/services/legacy_webhook/test_service.py +++ b/tests/sentry/sentry_apps/services/legacy_webhook/test_service.py @@ -170,6 +170,7 @@ def test_sends_signed_webhook_to_sentry_app(self, safe_urlopen: MagicMock) -> No group_event=self.group_event, sentry_app_slug=self.sentry_app.slug, rule_label="My Rule", + organization=self.organization, ) assert safe_urlopen.called @@ -206,10 +207,14 @@ def test_missing_app_logs_warning(self, mock_task: mock.MagicMock) -> None: group_event=self.group_event, sentry_app_slug="nonexistent-app", rule_label="My Rule", + organization=self.organization, ) mock_logger.warning.assert_called_once_with( "webhook_action_handler.sentry_app_not_found", - extra={"sentry_app_slug": "nonexistent-app"}, + extra={ + "organization_id": self.organization.id, + "sentry_app_slug": "nonexistent-app", + }, ) mock_task.delay.assert_not_called() diff --git a/tests/snuba/api/endpoints/test_organization_events_meta.py b/tests/snuba/api/endpoints/test_organization_events_meta.py index 510712b3048dc9..925da8af467c4d 100644 --- a/tests/snuba/api/endpoints/test_organization_events_meta.py +++ b/tests/snuba/api/endpoints/test_organization_events_meta.py @@ -12,7 +12,6 @@ SnubaTestCase, SpanTestCase, ) -from sentry.testutils.helpers import override_options from sentry.testutils.helpers.datetime import before_now from sentry.testutils.thread_leaks.pytest import thread_leak_allowlist from sentry.utils.samples import load_data @@ -482,78 +481,6 @@ def test_related_issues_transactions_with_quotes(self) -> None: class OrganizationSpansSamplesEndpoint(OrganizationEventsEndpointTestBase, SnubaTestCase): url_name = "sentry-api-0-organization-spans-samples" - @mock.patch("sentry.search.events.builder.base.raw_snql_query") - def test_is_segment_properly_converted_in_filter( - self, mock_raw_snql_query: mock.MagicMock - ) -> None: - self.login_as(user=self.user) - project = self.create_project() - url = reverse(self.url_name, kwargs={"organization_id_or_slug": project.organization.slug}) - - response = self.client.get( - url, - { - "query": "span.is_segment:1 transaction:api/0/foo", - "lowerBound": "0", - "firstBound": "10", - "secondBound": "20", - "upperBound": "200", - "column": "span.duration", - }, - format="json", - extra={"project": [project.id]}, - ) - - assert response.status_code == 200, response.content - - # the SQL should have is_segment converted into an int for all requests - assert all( - "is_segment = 1" in call_args[0][0].serialize() - for call_args in mock_raw_snql_query.call_args_list - ) - - def test_is_using_sample_rate(self) -> None: - self.login_as(user=self.user) - project = self.create_project() - url = reverse(self.url_name, kwargs={"organization_id_or_slug": project.organization.slug}) - - def request(): - return self.client.get( - url, - { - "query": "span.is_segment:1 transaction:api/0/foo", - "lowerBound": "0", - "firstBound": "10", - "secondBound": "20", - "upperBound": "200", - "column": "span.duration", - }, - format="json", - extra={"project": [project.id]}, - ) - - response = request() - - assert response.status_code == 200, response.content - - with mock.patch("sentry.search.events.builder.base.raw_snql_query") as mock_raw_snql_query: - response = request() - assert response.status_code == 200, response.content - - assert "MATCH (spans)" in mock_raw_snql_query.call_args_list[0][0][0].serialize() - - with ( - override_options({"insights.span-samples-query.sample-rate": 100_000_000.0}), - mock.patch("sentry.search.events.builder.base.raw_snql_query") as mock_raw_snql_query, - ): - response = request() - assert response.status_code == 200, response.content - - assert ( - "MATCH (spans SAMPLE 100000000.0)" - in mock_raw_snql_query.call_args_list[0][0][0].serialize() - ) - def test_basic_query(self) -> None: self.login_as(user=self.user) project = self.create_project() diff --git a/tests/snuba/api/endpoints/test_organization_trace_item_attributes.py b/tests/snuba/api/endpoints/test_organization_trace_item_attributes.py index f8434be2d27ed8..7863f449a9a9eb 100644 --- a/tests/snuba/api/endpoints/test_organization_trace_item_attributes.py +++ b/tests/snuba/api/endpoints/test_organization_trace_item_attributes.py @@ -1112,6 +1112,7 @@ def matching_string_alias_count(substring: str) -> int: assert "tag.op" in keys assert "tag.op2" in keys + @pytest.mark.skip(reason="Re-enable once Snuba PR #7689 stops boolean double-writing") def test_empty_attribute_type_for_all_attribute_types(self) -> None: span1 = self.create_span(start_ts=before_now(days=0, minutes=10)) span1["data"] = { @@ -1130,12 +1131,12 @@ def test_empty_attribute_type_for_all_attribute_types(self) -> None: assert response.status_code == 200, response.content keys = {(item["key"], item["attributeType"]) for item in response.data} - # TODO: add this assert back when we stop doublewriting; - # assert len(keys) == 3 + assert len(keys) == 3 assert ("tags[tag.boolean,boolean]", "boolean") in keys - assert ("tags[tag.boolean,number]", "number") in keys assert ("tag.string", "string") in keys + assert ("tags[tag.number,number]", "number") in keys + @pytest.mark.skip(reason="Re-enable once Snuba PR #7689 stops boolean double-writing") def test_multiple_attribute_types(self) -> None: span1 = self.create_span(start_ts=before_now(days=0, minutes=10)) span1["data"] = { @@ -1155,10 +1156,9 @@ def test_multiple_attribute_types(self) -> None: assert response.status_code == 200, response.content keys = {(item["key"], item["attributeType"]) for item in response.data} - # TODO: add this assert back when we stop doublewriting; - # assert len(keys) == 2 + assert len(keys) == 2 assert ("tags[tag.boolean,boolean]", "boolean") not in keys - assert ("tags[tag.boolean,number]", "number") in keys + assert ("tags[tag.number,number]", "number") in keys assert ("tag.string", "string") in keys