-
Notifications
You must be signed in to change notification settings - Fork 0
chore(aci milestone 3): open period -> incident serializer #123
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,95 +1,132 @@ | ||||||||||||||||||||||
| from collections import defaultdict | ||||||||||||||||||||||
| from collections.abc import Mapping, Sequence | ||||||||||||||||||||||
| from typing import Any, DefaultDict | ||||||||||||||||||||||
| from datetime import datetime | ||||||||||||||||||||||
| from typing import Any, ClassVar, DefaultDict | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| from django.contrib.auth.models import AnonymousUser | ||||||||||||||||||||||
| from django.db.models import Subquery | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| from sentry.api.serializers import Serializer, serialize | ||||||||||||||||||||||
| from sentry.incidents.endpoints.serializers.incident import ( | ||||||||||||||||||||||
| DetailedIncidentSerializerResponse, | ||||||||||||||||||||||
| IncidentSerializer, | ||||||||||||||||||||||
| IncidentSerializerResponse, | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| from sentry.incidents.models.incident import Incident | ||||||||||||||||||||||
| from sentry.incidents.endpoints.serializers.utils import get_fake_id_from_object_id | ||||||||||||||||||||||
| from sentry.incidents.models.incident import IncidentStatus, IncidentStatusMethod, IncidentType | ||||||||||||||||||||||
| from sentry.models.groupopenperiod import GroupOpenPeriod | ||||||||||||||||||||||
| from sentry.models.groupopenperiodactivity import GroupOpenPeriodActivity | ||||||||||||||||||||||
| from sentry.snuba.entity_subscription import apply_dataset_query_conditions | ||||||||||||||||||||||
| from sentry.snuba.models import QuerySubscription, SnubaQuery | ||||||||||||||||||||||
| from sentry.types.group import PriorityLevel | ||||||||||||||||||||||
| from sentry.users.models.user import User | ||||||||||||||||||||||
| from sentry.users.services.user.model import RpcUser | ||||||||||||||||||||||
| from sentry.workflow_engine.endpoints.serializers.group_open_period_serializer import ( | ||||||||||||||||||||||
| GroupOpenPeriodActivitySerializer, | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| from sentry.workflow_engine.models import ( | ||||||||||||||||||||||
| Action, | ||||||||||||||||||||||
| DataCondition, | ||||||||||||||||||||||
| DataConditionGroupAction, | ||||||||||||||||||||||
| AlertRuleDetector, | ||||||||||||||||||||||
| DataSourceDetector, | ||||||||||||||||||||||
| Detector, | ||||||||||||||||||||||
| DetectorWorkflow, | ||||||||||||||||||||||
| DetectorGroup, | ||||||||||||||||||||||
| IncidentGroupOpenPeriod, | ||||||||||||||||||||||
| WorkflowDataConditionGroup, | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| from sentry.workflow_engine.models.workflow_action_group_status import WorkflowActionGroupStatus | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| class WorkflowEngineIncidentSerializer(Serializer): | ||||||||||||||||||||||
| def __init__(self, expand=None): | ||||||||||||||||||||||
| self.expand = expand or [] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| priority_to_incident_status: ClassVar[dict[int, int]] = { | ||||||||||||||||||||||
| PriorityLevel.HIGH.value: IncidentStatus.CRITICAL.value, | ||||||||||||||||||||||
| PriorityLevel.MEDIUM.value: IncidentStatus.WARNING.value, | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def get_incident_status(self, priority: int | None, date_ended: datetime | None) -> int: | ||||||||||||||||||||||
| if priority is None: | ||||||||||||||||||||||
| raise ValueError("Priority is required to get an incident status") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if date_ended: | ||||||||||||||||||||||
| return IncidentStatus.CLOSED.value | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return self.priority_to_incident_status[priority] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def get_attrs( | ||||||||||||||||||||||
| self, | ||||||||||||||||||||||
| item_list: Sequence[GroupOpenPeriod], | ||||||||||||||||||||||
| user: User | RpcUser | AnonymousUser, | ||||||||||||||||||||||
| **kwargs: Any, | ||||||||||||||||||||||
| ) -> defaultdict[GroupOpenPeriod, dict[str, Any]]: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| from sentry.incidents.endpoints.serializers.workflow_engine_detector import ( | ||||||||||||||||||||||
| WorkflowEngineDetectorSerializer, | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| results: DefaultDict[GroupOpenPeriod, dict[str, Any]] = defaultdict() | ||||||||||||||||||||||
| open_periods_to_detectors = self.get_open_periods_to_detectors(item_list) | ||||||||||||||||||||||
| alert_rules = { | ||||||||||||||||||||||
| alert_rule["id"]: alert_rule # we are serializing detectors to look like alert rules | ||||||||||||||||||||||
| for alert_rule in serialize( | ||||||||||||||||||||||
| list(open_periods_to_detectors.values()), | ||||||||||||||||||||||
| user, | ||||||||||||||||||||||
| WorkflowEngineDetectorSerializer(expand=self.expand), | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| alert_rule_detectors = AlertRuleDetector.objects.filter( | ||||||||||||||||||||||
| detector__in=list(open_periods_to_detectors.values()) | ||||||||||||||||||||||
| ).values_list("alert_rule_id", "detector_id") | ||||||||||||||||||||||
| detector_ids_to_alert_rule_ids = {} | ||||||||||||||||||||||
| for alert_rule_id, detector_id in alert_rule_detectors: | ||||||||||||||||||||||
| detector_ids_to_alert_rule_ids[detector_id] = alert_rule_id | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| for open_period in item_list: | ||||||||||||||||||||||
| detector_id = open_periods_to_detectors[open_period].id | ||||||||||||||||||||||
| if detector_id in detector_ids_to_alert_rule_ids: | ||||||||||||||||||||||
| alert_rule_id = detector_ids_to_alert_rule_ids[detector_id] | ||||||||||||||||||||||
| else: | ||||||||||||||||||||||
| alert_rule_id = get_fake_id_from_object_id(detector_id) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| results[open_period] = {"projects": [open_period.project.slug]} | ||||||||||||||||||||||
| results[open_period]["alert_rule"] = alert_rules.get(str(alert_rule_id)) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if "activities" in self.expand: | ||||||||||||||||||||||
| gopas = list( | ||||||||||||||||||||||
| GroupOpenPeriodActivity.objects.filter(group_open_period__in=item_list)[:1000] | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| open_period_activities = defaultdict(list) | ||||||||||||||||||||||
| # XXX: the incident endpoint is undocumented, so we aren' on the hook for supporting | ||||||||||||||||||||||
| # any specific payloads. Since this isn't used on the Sentry side for notification charts, | ||||||||||||||||||||||
| # I've opted to just use the GroupOpenPeriodActivity serializer. | ||||||||||||||||||||||
| for gopa, serialized_activity in zip( | ||||||||||||||||||||||
| gopas, | ||||||||||||||||||||||
| serialize(gopas, user=user, serializer=GroupOpenPeriodActivitySerializer()), | ||||||||||||||||||||||
| ): | ||||||||||||||||||||||
| open_period_activities[gopa.group_open_period_id].append(serialized_activity) | ||||||||||||||||||||||
| for open_period in item_list: | ||||||||||||||||||||||
| results[open_period]["activities"] = open_period_activities[open_period.id] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return results | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def get_open_periods_to_detectors( | ||||||||||||||||||||||
| self, open_periods: Sequence[GroupOpenPeriod] | ||||||||||||||||||||||
| ) -> dict[GroupOpenPeriod, Detector]: | ||||||||||||||||||||||
| wf_action_group_statuses = WorkflowActionGroupStatus.objects.filter( | ||||||||||||||||||||||
| group__in=[open_period.group for open_period in open_periods] | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| open_periods_to_actions: DefaultDict[GroupOpenPeriod, Action] = defaultdict() | ||||||||||||||||||||||
| for open_period in open_periods: | ||||||||||||||||||||||
| for wf_action_group_status in wf_action_group_statuses: | ||||||||||||||||||||||
| if wf_action_group_status.group == open_period.group: | ||||||||||||||||||||||
| open_periods_to_actions[open_period] = wf_action_group_status.action | ||||||||||||||||||||||
| break | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| dcgas = DataConditionGroupAction.objects.filter( | ||||||||||||||||||||||
| action__in=list(open_periods_to_actions.values()) | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| open_periods_to_condition_group: DefaultDict[GroupOpenPeriod, DataConditionGroupAction] = ( | ||||||||||||||||||||||
| defaultdict() | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| for open_period, action in open_periods_to_actions.items(): | ||||||||||||||||||||||
| for dcga in dcgas: | ||||||||||||||||||||||
| if dcga.action == action: | ||||||||||||||||||||||
| open_periods_to_condition_group[open_period] = dcga | ||||||||||||||||||||||
| break | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| action_filters = DataCondition.objects.filter( | ||||||||||||||||||||||
| condition_group__in=[dcga.condition_group for dcga in dcgas] | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| open_period_to_action_filters: DefaultDict[GroupOpenPeriod, DataCondition] = defaultdict() | ||||||||||||||||||||||
| for open_period, dcga in open_periods_to_condition_group.items(): | ||||||||||||||||||||||
| for action_filter in action_filters: | ||||||||||||||||||||||
| if action_filter.condition_group == dcga.condition_group: | ||||||||||||||||||||||
| open_period_to_action_filters[open_period] = action_filter | ||||||||||||||||||||||
| break | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| workflow_dcgs = WorkflowDataConditionGroup.objects.filter( | ||||||||||||||||||||||
| condition_group__in=Subquery(action_filters.values("condition_group")) | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| # open period -> group -> detector via detectorgroup | ||||||||||||||||||||||
| groups = [op.group for op in open_periods] | ||||||||||||||||||||||
| group_to_open_periods = defaultdict(list) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| open_periods_to_workflow_dcgs: DefaultDict[GroupOpenPeriod, WorkflowDataConditionGroup] = ( | ||||||||||||||||||||||
| defaultdict() | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| for open_period, action_filter in open_period_to_action_filters.items(): | ||||||||||||||||||||||
| for workflow_dcg in workflow_dcgs: | ||||||||||||||||||||||
| if workflow_dcg.condition_group == action_filter.condition_group: | ||||||||||||||||||||||
| open_periods_to_workflow_dcgs[open_period] = workflow_dcg | ||||||||||||||||||||||
| for op in open_periods: | ||||||||||||||||||||||
| group_to_open_periods[op.group].append(op) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| detector_workflows = DetectorWorkflow.objects.filter( | ||||||||||||||||||||||
| workflow__in=Subquery(workflow_dcgs.values("workflow")) | ||||||||||||||||||||||
| detector_groups = DetectorGroup.objects.filter(group__in=groups).select_related( | ||||||||||||||||||||||
| "group", "detector" | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| open_periods_to_detectors: DefaultDict[GroupOpenPeriod, Detector] = defaultdict() | ||||||||||||||||||||||
| for open_period, workflow_dcg in open_periods_to_workflow_dcgs.items(): | ||||||||||||||||||||||
| for detector_workflow in detector_workflows: | ||||||||||||||||||||||
| if detector_workflow.workflow == workflow_dcg.workflow: | ||||||||||||||||||||||
| open_periods_to_detectors[open_period] = detector_workflow.detector | ||||||||||||||||||||||
| break | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| groups_to_detectors = {} | ||||||||||||||||||||||
| for dg in detector_groups: | ||||||||||||||||||||||
| groups_to_detectors[dg.group] = dg.detector | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| open_periods_to_detectors = {} | ||||||||||||||||||||||
| for group in group_to_open_periods: | ||||||||||||||||||||||
| for op in group_to_open_periods[group]: | ||||||||||||||||||||||
| open_periods_to_detectors[op] = groups_to_detectors[group] | ||||||||||||||||||||||
|
Comment on lines
+126
to
+129
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [CriticalError] Potential KeyError when accessing missing group detector: Line 129 accesses Suggested Change
Suggested change
⚡ Committable suggestion Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Context for Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return open_periods_to_detectors | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -103,10 +140,36 @@ def serialize( | |||||||||||||||||||||
| """ | ||||||||||||||||||||||
| Temporary serializer to take a GroupOpenPeriod and serialize it for the old incident endpoint | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| incident = Incident.objects.get( | ||||||||||||||||||||||
| id=IncidentGroupOpenPeriod.objects.get(group_open_period=obj).incident_id | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| return serialize(incident, user=user, serializer=IncidentSerializer(expand=self.expand)) | ||||||||||||||||||||||
| try: | ||||||||||||||||||||||
| igop = IncidentGroupOpenPeriod.objects.get(group_open_period=obj) | ||||||||||||||||||||||
| incident_id = igop.incident_id | ||||||||||||||||||||||
| incident_identifier = igop.incident_identifier | ||||||||||||||||||||||
| except IncidentGroupOpenPeriod.DoesNotExist: | ||||||||||||||||||||||
| incident_id = get_fake_id_from_object_id(obj.id) | ||||||||||||||||||||||
| incident_identifier = incident_id | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # TODO: get event time using offset | ||||||||||||||||||||||
| date_closed = obj.date_ended.replace(second=0, microsecond=0) if obj.date_ended else None | ||||||||||||||||||||||
| return { | ||||||||||||||||||||||
| "id": str(incident_id), | ||||||||||||||||||||||
| "identifier": str(incident_identifier), | ||||||||||||||||||||||
| "organizationId": str(obj.project.organization.id), | ||||||||||||||||||||||
| "projects": attrs["projects"], | ||||||||||||||||||||||
| "alertRule": attrs["alert_rule"], | ||||||||||||||||||||||
| "activities": attrs["activities"] if "activities" in self.expand else None, | ||||||||||||||||||||||
| "status": self.get_incident_status(obj.group.priority, obj.date_ended), | ||||||||||||||||||||||
| "statusMethod": ( | ||||||||||||||||||||||
| IncidentStatusMethod.RULE_TRIGGERED.value | ||||||||||||||||||||||
| if not date_closed | ||||||||||||||||||||||
| else IncidentStatusMethod.RULE_UPDATED.value | ||||||||||||||||||||||
| ), | ||||||||||||||||||||||
| "type": IncidentType.ALERT_TRIGGERED.value, | ||||||||||||||||||||||
| "title": obj.group.title, | ||||||||||||||||||||||
| "dateStarted": obj.date_started, | ||||||||||||||||||||||
| "dateDetected": obj.date_started, | ||||||||||||||||||||||
| "dateCreated": obj.date_added, | ||||||||||||||||||||||
| "dateClosed": date_closed, | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| class WorkflowEngineDetailedIncidentSerializer(WorkflowEngineIncidentSerializer): | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[CriticalError]
Potential KeyError when priority is not in mapping: The
get_incident_statusmethod will raise aKeyErrorifpriorityis notHIGHorMEDIUM(e.g.,LOWpriority). This could cause runtime failures.Suggested Change
⚡ Committable suggestion
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.
Context for Agents