Skip to content

feat(workflow_engine): Add in hook for producing occurrences from the stateful detector#6

Open
akshayutture-augment wants to merge 1 commit into
workflow-engine-stateful-detector-beforefrom
workflow-engine-stateful-detector-after
Open

feat(workflow_engine): Add in hook for producing occurrences from the stateful detector#6
akshayutture-augment wants to merge 1 commit into
workflow-engine-stateful-detector-beforefrom
workflow-engine-stateful-detector-after

Conversation

@akshayutture-augment

@akshayutture-augment akshayutture-augment commented Nov 14, 2025

Copy link
Copy Markdown

Test 10

Summary by CodeRabbit

  • Refactor

    • Restructured detector result collection to improve consistency across incident detection workflows.
    • Enhanced detector handlers to generate comprehensive event data and occurrence information when incidents are detected.
    • Improved state management for more reliable incident tracking and deduplication.
  • Tests

    • Updated test suite to verify detector state behavior, result collection, and event data generation.

Replicated from ai-code-review-evaluation/sentry-coderabbit#10

… stateful detector (#80168)

This adds a hook that can be implemented to produce an occurrence
specific to the detector that is subclassing the StatefulDetector.

Also change the signature of evaluate to return a dict keyed by groupkey
instead of a list. This helps avoid the chance of duplicate results for
the same group key.

<!-- Describe your PR here. -->
@akshayutture-augment

Copy link
Copy Markdown
Author

@coderabbitai review

@akshayutture-augment

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 18, 2025

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Nov 18, 2025

Copy link
Copy Markdown

Walkthrough

The PR refactors detector evaluation results from list-based to dictionary-based structures keyed by group identifiers. MetricAlertDetectorHandler transitions to inherit from StatefulDetectorHandler, which gains a new abstract method for building occurrences and event data. The Detector model adds a group_type property for cleaner group type registry lookups.

Changes

Cohort / File(s) Summary
GroupType registration
src/sentry/incidents/grouptype.py, src/sentry/workflow_engine/models/detector.py
MetricAlertDetectorHandler switched from DetectorHandler to StatefulDetectorHandler base class; evaluate method removed. Detector model adds group_type property returning GroupType from registry based on detector type slug.
Detector processor core refactor
src/sentry/workflow_engine/processors/detector.py
process_detectors return type changed from list to dict keyed by DetectorGroupKey. DetectorHandler.evaluate signature updated to return dict instead of list. StatefulDetectorHandler gains abstract method build_occurrence_and_event_data for generating IssueOccurrence and event data. Result accumulation refactored to use dict-based storage.
Detector processor tests
tests/sentry/workflow_engine/processors/test_detector.py
Test expectations updated to assert dict-based results. Added MockDetectorStateHandler and build_mock_occurrence_and_event helper. Updated mock handlers to return dicts. Added produce_occurrence_to_kafka call assertions with PayloadType.OCCURRENCE.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant Detector
    participant Handler as StatefulDetectorHandler
    participant GroupRegistry as GroupType Registry
    
    Caller->>Detector: process_detectors(data_packet, detectors)
    Detector->>Handler: evaluate(data_packet)
    Handler->>Handler: iterate groups
    alt non-OK status
        Handler->>Handler: build_occurrence_and_event_data(group_key, value, status)
        Handler->>Handler: create DetectorEvaluationResult
    else OK status
        Handler->>Handler: create StatusChangeMessage
    end
    Handler-->>Detector: dict[DetectorGroupKey, DetectorEvaluationResult]
    Detector-->>Caller: list[(Detector, dict[DetectorGroupKey, DetectorEvaluationResult])]
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Data structure overhaul: Core refactor from list-based to dict-based results across multiple layers requires careful verification of all access patterns and edge cases
  • New abstract method: StatefulDetectorHandler.build_occurrence_and_event_data introduces new contract that must be validated across all implementations
  • Test complexity: Comprehensive test rewrites with new mocks and assertions spanning multiple test scenarios (single/multi-group, deduplication, kafka production)
  • Interconnected changes: Modifications span three distinct files with dependencies between model, processor, and grouptype layers

Poem

🐰 Dictionaries bloom where lists once grew,
Each group key guides results through,
Occurrences craft'd with stateful care,
Detector dreams reorganized fair!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a hook mechanism for producing occurrences from the stateful detector by introducing build_occurrence_and_event_data method in StatefulDetectorHandler.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch workflow-engine-stateful-detector-after

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/sentry/incidents/grouptype.py (1)

10-27: MetricAlertDetectorHandler is now abstract but still wired as a concrete handler

The verification confirms the review comment is correct. StatefulDetectorHandler inherits from abc.ABC and declares four abstract methods that MetricAlertDetectorHandler does not implement: counter_names, get_dedupe_value, get_group_key_values, and build_occurrence_and_event_data. The instantiation path at src/sentry/workflow_engine/models/detector.py:86 (return group_type.detector_handler(self)) will attempt to construct MetricAlertDetectorHandler, triggering TypeError: Can't instantiate abstract class if a Detector is created with this GroupType.

Either implement all required abstract methods or revert MetricAlertDetectorHandler to inherit from the non-stateful DetectorHandler base until the stateful behavior is ready.

🧹 Nitpick comments (5)
src/sentry/workflow_engine/processors/detector.py (2)

166-171: New build_occurrence_and_event_data hook is solid, but enum conversion is a bit brittle

The abstract build_occurrence_and_event_data hook is a good place to centralize IssueOccurrence + event_data construction for non‑OK statuses, and the way evaluate_group_key_value calls it ensures every non‑OK transition has concrete payloads.

The only concern is the enum conversion:

result, event_data = self.build_occurrence_and_event_data(
    group_key, value, PriorityLevel(new_status)
)

This assumes DetectorPriorityLevel and PriorityLevel share compatible underlying values. If they ever diverge, PriorityLevel(new_status) will raise or map to the wrong priority. Consider instead:

  • Changing the hook signature to accept DetectorPriorityLevel and letting implementations convert to PriorityLevel if/when they need it, or
  • Introducing an explicit mapping function from DetectorPriorityLevelPriorityLevel so the coupling is visible and testable.

Either option would make this more robust against future enum changes.

Also applies to: 285-301


226-245: Update StatefulDetectorHandler.evaluate docstring to mention dict results

The method now returns dict[DetectorGroupKey, DetectorEvaluationResult], but the docstring still says “returns a list of DetectorEvaluationResult.” Updating the wording to “returns a dict keyed by group_key” will avoid confusion for future implementers.

tests/sentry/workflow_engine/processors/test_detector.py (3)

27-29: Annotate counter_names as a ClassVar to satisfy Ruff and clarify intent

counter_names is a mutable class attribute used as shared configuration, and Ruff flags this (RUF012). You can make the intent explicit and silence the warning by annotating it as a ClassVar:

-from typing import Any
+from typing import Any, ClassVar
@@
-class MockDetectorStateHandler(StatefulDetectorHandler[dict]):
-    counter_names = ["test1", "test2"]
+class MockDetectorStateHandler(StatefulDetectorHandler[dict]):
+    counter_names: ClassVar[list[str]] = ["test1", "test2"]

58-63: Rename unused data_packet parameter in MockDetectorHandler.evaluate

Ruff’s ARG002 warning here is valid: the data_packet argument isn’t used in this simple test double. Easiest fix is to rename it to _data_packet to signal intentional non‑use and satisfy the linter:

-        class MockDetectorHandler(DetectorHandler[dict]):
-            def evaluate(
-                self, data_packet: DataPacket[dict]
-            ) -> dict[DetectorGroupKey, DetectorEvaluationResult]:
-                return {None: DetectorEvaluationResult(None, True, DetectorPriorityLevel.HIGH)}
+        class MockDetectorHandler(DetectorHandler[dict]):
+            def evaluate(
+                self, _data_packet: DataPacket[dict]
+            ) -> dict[DetectorGroupKey, DetectorEvaluationResult]:
+                return {None: DetectorEvaluationResult(None, True, DetectorPriorityLevel.HIGH)}

244-275: Tidy up unused value parameter in build_mock_occurrence_and_event (and align test usage)

Ruff’s ARG001 warning is correct: value isn’t used inside build_mock_occurrence_and_event, and in test_state_results_multi_group you even pass value=6 for "group_2" while the underlying group_vals is 10, which could become confusing once value starts influencing the payload.

Two minimal cleanups that keep behavior identical today:

  1. Mark the parameter as intentionally unused:
-def build_mock_occurrence_and_event(
-    handler: StatefulDetectorHandler,
-    group_key: DetectorGroupKey,
-    value: int,
-    new_status: PriorityLevel,
+def build_mock_occurrence_and_event(
+    handler: StatefulDetectorHandler,
+    group_key: DetectorGroupKey,
+    _value: int,
+    new_status: PriorityLevel,
 ) -> tuple[IssueOccurrence, dict[str, Any]]:
  1. (Optional) In test_state_results_multi_group, pass the actual group value for readability:
-        occurrence_2, event_data_2 = build_mock_occurrence_and_event(
-            detector.detector_handler, "group_2", 6, PriorityLevel.HIGH
-        )
+        occurrence_2, event_data_2 = build_mock_occurrence_and_event(
+            detector.detector_handler, "group_2", 10, PriorityLevel.HIGH
+        )

That keeps tests semantically aligned if you later decide to use value when building occurrences.

Also applies to: 175-192

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7249010 and de60b7f.

📒 Files selected for processing (4)
  • src/sentry/incidents/grouptype.py (1 hunks)
  • src/sentry/workflow_engine/models/detector.py (3 hunks)
  • src/sentry/workflow_engine/processors/detector.py (9 hunks)
  • tests/sentry/workflow_engine/processors/test_detector.py (11 hunks)
🧰 Additional context used
🪛 Ruff (0.14.5)
tests/sentry/workflow_engine/processors/test_detector.py

28-28: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


60-60: Unused method argument: data_packet

(ARG002)


247-247: Unused function argument: value

(ARG001)

🔇 Additional comments (3)
src/sentry/workflow_engine/models/detector.py (1)

58-86: group_type property and updated detector_handler lookup look good

Centralizing the grouptype registry lookup behind Detector.group_type and reusing it in detector_handler keeps the error handling in one place and makes the tests’ handler.detector.group_type is not None assertions clearer. No functional issues spotted here.

tests/sentry/workflow_engine/processors/test_detector.py (1)

123-223: New stateful-detector tests give strong coverage of the dict-based and Kafka flows

The added tests around MockDetectorStateHandler (state data fetching, commit behavior, dict-shaped evaluate results, per-group evaluation, and Kafka emission via produce_occurrence_to_kafka) exercise the new StatefulDetectorHandler contract thoroughly. They validate:

  • Dict-based results from both process_detectors and evaluate.
  • Correct interaction with Redis and DetectorState persistence.
  • Emission behavior for single and multi-group cases, including any_order verification.

This is a solid safety net for the new hook and stateful logic.

Also applies to: 327-419, 422-619

src/sentry/workflow_engine/processors/detector.py (1)

47-70: Verification complete: no double-emission risk with current detector implementations

The concern about existing detectors calling produce_occurrence_to_kafka directly is not an issue. The only StatefulDetectorHandler implementation is MetricAlertDetectorHandler, which is a stub (empty class with no methods). The workflow engine detector pipeline is not yet in production, and when new detectors are added, the architecture correctly centralizes Kafka emission through process_detectorscreate_issue_occurrence_from_result. The code change is sound.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants