Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
94 changes: 10 additions & 84 deletions src/sentry/preprod/size_analysis/grouptype.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import logging
from collections.abc import Mapping
from dataclasses import dataclass
from datetime import datetime
from datetime import timezone as dt_timezone
Expand All @@ -13,25 +12,18 @@
from sentry.issues.issue_occurrence import IssueEvidence
from sentry.preprod.artifact_search import artifact_matches_query
from sentry.types.group import PriorityLevel
from sentry.utils import metrics
from sentry.workflow_engine.endpoints.validators.base import BaseDetectorTypeValidator
from sentry.workflow_engine.handlers.detector.base import (
DetectorEvaluations,
DetectorGroupValues,
DetectorOccurrence,
EventData,
)
from sentry.workflow_engine.handlers.detector.condition import DetectorHandler
from sentry.workflow_engine.models import DataPacket
from sentry.workflow_engine.processors import DataConditionGroupEvaluation, DetectorEvaluation
from sentry.workflow_engine.processors.data_condition_group import (
process_data_condition_group,
)
from sentry.workflow_engine.processors.evaluations import DetectorEvaluationData
from sentry.workflow_engine.registry import detector_settings_registry
from sentry.workflow_engine.types import (
DetectorGroupKey,
DetectorPriorityLevel,
DetectorSettings,
)

Expand Down Expand Up @@ -222,66 +214,13 @@ def _matches_query(self, data_packet: SizeAnalysisDataPacket) -> bool:
return False

@override
def evaluate(
self,
data_packet: SizeAnalysisDataPacket,
values: Mapping[DetectorGroupKey, SizeAnalysisEvaluation],
) -> DetectorEvaluations:
evaluation, priority = self._evaluate_conditions(values[None])
if evaluation is None or priority is None:
return DetectorEvaluations(result={}, tainted=False)

detector_evaluation = DetectorEvaluation(
result=None,
data=DetectorEvaluationData(
group_key=None,
trigger_group_evaluation=evaluation,
event_data=None,
),
triggered=True,
priority=priority,
)
detector_occurrence, event_data = self.create_occurrence(detector_evaluation, data_packet)
occurrence = detector_occurrence.to_issue_occurrence(
occurrence_id=event_data["event_id"],
event_id=event_data["event_id"],
project_id=self.detector.project_id,
status=priority,
additional_evidence_data={},
fingerprint=[uuid4().hex],
)
result = DetectorEvaluation(
result=occurrence,
data=DetectorEvaluationData(
group_key=None,
trigger_group_evaluation=evaluation,
event_data=event_data,
),
triggered=True,
priority=priority,
)
return DetectorEvaluations(result={None: result}, tainted=False)

def _evaluate_conditions(
self, value: SizeAnalysisEvaluation
) -> tuple[DataConditionGroupEvaluation | None, DetectorPriorityLevel | None]:
if not self.condition_group:
metrics.incr("workflow_engine.detector.skipping_invalid_condition_group")
return None, None
def get_issue_fingerprint(self, group_key: DetectorGroupKey = None) -> list[str]:
# Every regression is its own issue
return [uuid4().hex]

group_evaluation, _ = process_data_condition_group(self.condition_group, value)
if not group_evaluation.triggered:
return None, None

priorities = [
condition_evaluation.result
for condition_evaluation in group_evaluation.data["condition_evaluations"]
if isinstance(condition_evaluation.result, DetectorPriorityLevel)
]
if not priorities:
return None, None

return group_evaluation, max(priorities)
@override
def get_occurrence_id(self, group_key: DetectorGroupKey, event_id: str) -> str:
return event_id

def _extract_head(self, data_packet: SizeAnalysisDataPacket) -> int:
measurement = self.detector.config["measurement"]
Expand Down Expand Up @@ -312,9 +251,7 @@ def extract_value(
# Skip evaluating artifacts the detector's query filters out
if not self._matches_query(data_packet):
return DetectorGroupValues()
return self._extract_measurement(data_packet)

def _extract_measurement(self, data_packet: SizeAnalysisDataPacket) -> SizeAnalysisEvaluation:
threshold_type = self.detector.config["threshold_type"]
match threshold_type:
case "absolute":
Expand All @@ -333,7 +270,6 @@ def create_occurrence(
data_packet: SizeAnalysisDataPacket,
) -> tuple[DetectorOccurrence, EventData]:
trigger_evaluation = evaluation.data["trigger_group_evaluation"]
priority = evaluation.priority
current_timestamp = datetime.now(dt_timezone.utc)
metadata = data_packet.packet.get("metadata")

Expand All @@ -348,18 +284,8 @@ def create_occurrence(

platform = metadata["platform"] if metadata else "unknown"

condition_evaluations = (
trigger_evaluation.data["condition_evaluations"] if trigger_evaluation else []
)
evidence_data: dict[str, Any] = {
"detector_id": self.detector.id,
"value": self._extract_measurement(data_packet),
"conditions": [
condition_evaluation.condition.get_snapshot()
for condition_evaluation in condition_evaluations
],
"config": self.detector.config,
}
# The platform adds the detector id, value, triggered conditions, and config
evidence_data: dict[str, Any] = {}
if metadata:
evidence_data["head_artifact_id"] = metadata["head_artifact_id"]
if "base_artifact_id" in metadata:
Expand Down Expand Up @@ -410,10 +336,10 @@ def create_occurrence(
type=PreprodSizeAnalysisGroupType,
level="warning",
culprit="",
priority=priority,
priority=evaluation.priority,
)

event_data = {
event_data: EventData = {
"event_id": uuid4().hex,
"project_id": self.detector.project_id,
"platform": platform,
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/workflow_engine/docs/adding-detectors.md
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,7 @@ shared references are:
| [`UptimeDetectorHandler`](../../uptime/grouptype.py) | Stateful trigger/recovery thresholds, source data, evidence, and API config |
| [`MetricIssueDetectorHandler`](../../incidents/grouptype.py) | Stateful metric thresholds, anomaly payloads, and metric evidence |
| Processing error handlers in [`processing_errors/grouptype.py`](../../processing_errors/grouptype.py) | Customized state transitions and asymmetric resolution |
| [`PreprodSizeAnalysisDetectorHandler`](../../preprod/size_analysis/grouptype.py) | Custom `DetectorHandler.evaluate` override |
| [`PreprodSizeAnalysisDetectorHandler`](../../preprod/size_analysis/grouptype.py) | Stateless `DetectorHandler` with only hooks: query filtering, fingerprints |

Copy architecture, not product assumptions. Each example has specialized producer,
state, and lifecycle behavior.
Loading