Skip to content

Conversation

@hussam789
Copy link

@hussam789 hussam789 commented Oct 30, 2025

User description

PR #10


PR Type

Enhancement


Description

  • Change evaluate() return type from list to dict keyed by group key

  • Add abstract hook build_occurrence_and_event_data() for detector-specific occurrence generation

  • Simplify duplicate detection by using dict structure instead of manual tracking

  • Update tests to use new dict-based return format and mock occurrence generation


Diagram Walkthrough

flowchart LR
  A["StatefulDetector.evaluate()"] -->|"returns dict instead of list"| B["DetectorGroupKey → Result mapping"]
  B -->|"eliminates duplicates"| C["Cleaner result handling"]
  D["New abstract method"] -->|"build_occurrence_and_event_data()"| E["Detector-specific occurrence creation"]
  E -->|"called in evaluate_group_key_value()"| F["IssueOccurrence generation"]
Loading

File Walkthrough

Relevant files
Enhancement
grouptype.py
Simplify MetricAlertDetectorHandler to use StatefulDetectorHandler

src/sentry/incidents/grouptype.py

  • Simplified MetricAlertDetectorHandler to inherit from
    StatefulDetectorHandler instead of DetectorHandler
  • Removed stub evaluate() method implementation
  • Updated imports to use StatefulDetectorHandler
+3/-8     
detector.py
Add group_type property to Detector model                               

src/sentry/workflow_engine/models/detector.py

  • Added builtins import for type hints
  • Added GroupType import from sentry.issues.grouptype
  • Added new group_type property that retrieves the GroupType from
    registry
  • Refactored detector_handler property to use the new group_type
    property
+7/-1     
detector.py
Change evaluate return type to dict and add occurrence hook

src/sentry/workflow_engine/processors/detector.py

  • Changed evaluate() return type from list[DetectorEvaluationResult] to
    dict[DetectorGroupKey, DetectorEvaluationResult]
  • Added abstract method build_occurrence_and_event_data() for subclasses
    to implement occurrence generation
  • Removed duplicate detection logic that checked for duplicate group
    keys
  • Updated process_detectors() to iterate over dict values instead of
    list
  • Modified evaluate_group_key_value() to call
    build_occurrence_and_event_data() when creating new issues
  • Added PriorityLevel import
+22/-23 
Tests
test_detector.py
Update tests for dict-based evaluate return and occurrence generation

tests/sentry/workflow_engine/processors/test_detector.py

  • Created BaseDetectorHandlerTest base class with common test setup and
    helper methods
  • Moved MockDetectorStateHandler to top of file and added
    build_occurrence_and_event_data() implementation
  • Added build_mock_occurrence_and_event() helper function for creating
    test occurrences
  • Updated all test assertions to expect dict return values instead of
    lists
  • Added mocking of produce_occurrence_to_kafka in relevant tests
  • Removed test_state_results_multi_group_dupe() test as duplicate
    detection is now handled by dict structure
  • Added @freeze_time() decorator to tests that check timestamps
  • Updated TestProcessDetectors, TestGetStateData,
    TestCommitStateUpdateData, TestEvaluate, and TestEvaluateGroupKeyValue
    classes
+217/-119

Summary by CodeRabbit

  • Refactor
    • Improved metric alert detector architecture for better scalability and reliability.
    • Enhanced workflow engine's detector system with improved grouping and event data handling capabilities.
    • Streamlined detector evaluation pipeline with updated result aggregation mechanism.

✏️ Tip: You can customize this high-level summary in your review settings.

… 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. -->
@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Action logging: New critical actions like producing occurrences and detector processing lack explicit
audit logging of actor, action, and outcome, relying only on metrics and limited error
logs.

Referred Code
def process_detectors(
    data_packet: DataPacket, detectors: list[Detector]
) -> list[tuple[Detector, dict[DetectorGroupKey, DetectorEvaluationResult]]]:
    results = []

    for detector in detectors:
        handler = detector.detector_handler

        if not handler:
            continue

        detector_results = handler.evaluate(data_packet)

        for result in detector_results.values():
            if result.result is not None:
                create_issue_occurrence_from_result(result)

        if detector_results:
            results.append((detector, detector_results))
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Edge cases: The new dict-based evaluate flow and occurrence building lack explicit handling for
empty/malformed group values and None group keys beyond tests, and errors in
build_occurrence_and_event_data are not guarded.

Referred Code
    """
    Evaluates a given data packet and returns a list of `DetectorEvaluationResult`.
    There will be one result for each group key result in the packet, unless the
    evaluation is skipped due to various rules.
    """
    dedupe_value = self.get_dedupe_value(data_packet)
    group_values = self.get_group_key_values(data_packet)
    all_state_data = self.get_state_data(list(group_values.keys()))
    results = {}
    for group_key, group_value in group_values.items():
        result = self.evaluate_group_key_value(
            group_key, group_value, all_state_data[group_key], dedupe_value
        )
        if result:
            results[result.group_key] = result
    return results

def evaluate_group_key_value(
Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Input validation: The evaluate path consumes external packet data and forwards occurrence/event_data without
visible sanitization or validation of tags and fields, which may risk unsafe data
handling.

Referred Code
    """
    Evaluates a given data packet and returns a list of `DetectorEvaluationResult`.
    There will be one result for each group key result in the packet, unless the
    evaluation is skipped due to various rules.
    """
    dedupe_value = self.get_dedupe_value(data_packet)
    group_values = self.get_group_key_values(data_packet)
    all_state_data = self.get_state_data(list(group_values.keys()))
    results = {}
    for group_key, group_value in group_values.items():
        result = self.evaluate_group_key_value(
            group_key, group_value, all_state_data[group_key], dedupe_value
        )
        if result:
            results[result.group_key] = result
    return results

def evaluate_group_key_value(
    self,
    group_key: DetectorGroupKey,
    value: int,


 ... (clipped 52 lines)
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Implement abstract methods in handler

Implement the abstract methods from StatefulDetectorHandler in
MetricAlertDetectorHandler, raising NotImplementedError if they are not yet
functional, to prevent a runtime TypeError.

src/sentry/incidents/grouptype.py [11-12]

 class MetricAlertDetectorHandler(StatefulDetectorHandler[QuerySubscriptionUpdate]):
-    pass
+    @property
+    def counter_names(self) -> list[str]:
+        raise NotImplementedError
 
+    def get_dedupe_value(self, data_packet: DataPacket[QuerySubscriptionUpdate]) -> int:
+        raise NotImplementedError
+
+    def get_group_key_values(
+        self, data_packet: DataPacket[QuerySubscriptionUpdate]
+    ) -> dict[str, int]:
+        raise NotImplementedError
+
+    def build_occurrence_and_event_data(
+        self, group_key: DetectorGroupKey, value: int, new_status: PriorityLevel
+    ) -> tuple[IssueOccurrence, dict[str, Any]]:
+        raise NotImplementedError
+
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that MetricAlertDetectorHandler fails to implement abstract methods from its parent StatefulDetectorHandler, which will cause a TypeError on instantiation. This is a valid correctness issue.

Medium
Use dynamic UUIDs in test helper

In the test helper function build_mock_occurrence_and_event, replace the
hardcoded id and event_id with dynamically generated UUIDs using
uuid.uuid4().hex to ensure uniqueness and prevent test flakiness.

tests/sentry/workflow_engine/processors/test_detector.py [244-275]

+import uuid
+
+
 def build_mock_occurrence_and_event(
     handler: StatefulDetectorHandler,
     group_key: DetectorGroupKey,
     value: int,
     new_status: PriorityLevel,
 ) -> tuple[IssueOccurrence, dict[str, Any]]:
     assert handler.detector.group_type is not None
     occurrence = IssueOccurrence(
-        id="eb4b0acffadb4d098d48cb14165ab578",
+        id=uuid.uuid4().hex,
         project_id=handler.detector.project_id,
-        event_id="43878ab4419f4ab181f6379ac376d5aa",
+        event_id=uuid.uuid4().hex,
         fingerprint=handler.build_fingerprint(group_key),
         issue_title="Some Issue",
         subtitle="Some subtitle",
         resource_id=None,
         evidence_data={},
         evidence_display=[],
         type=handler.detector.group_type,
         detection_time=datetime.now(timezone.utc),
         level="error",
         culprit="Some culprit",
         initial_issue_priority=new_status.value,
     )
     event_data = {
         "timestamp": occurrence.detection_time,
         "project_id": occurrence.project_id,
         "event_id": occurrence.event_id,
         "platform": "python",
         "received": occurrence.detection_time,
         "tags": {},
     }
     return occurrence, event_data
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that using hardcoded IDs in the test helper build_mock_occurrence_and_event can lead to issues when multiple occurrences are created. Using dynamic UUIDs is a best practice for test correctness and isolation.

Medium
  • More

@bar-qodo
Copy link

bar-qodo commented Nov 4, 2025

/agentic_review

1 similar comment
@bar-qodo
Copy link

bar-qodo commented Nov 4, 2025

/agentic_review

@bar-qodo
Copy link

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Nov 23, 2025

✅ 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
Copy link

coderabbitai bot commented Nov 23, 2025

Walkthrough

This pull request refactors detector result collection from lists to dicts keyed by DetectorGroupKey, updating process_detectors and evaluate method signatures across DetectorHandler and StatefulDetectorHandler. MetricAlertDetectorHandler now inherits from StatefulDetectorHandler, and a new abstract hook build_occurrence_and_event_data was introduced for generating occurrence and event data during non-OK status scenarios.

Changes

Cohort / File(s) Summary
Detector inheritance update
src/sentry/incidents/grouptype.py
Changed MetricAlertDetectorHandler base class from DetectorHandler to StatefulDetectorHandler; removed DataPacket and DetectorEvaluationResult imports; removed evaluate method implementation, leaving class body with pass.
Detector model enhancement
src/sentry/workflow_engine/models/detector.py
Added new group_type property that resolves GroupType from detector's type slug; updated detector_handler to use self.group_type for lookup; added GroupType and builtins imports.
Core detector processing refactor
src/sentry/workflow_engine/processors/detector.py
Changed return types from lists to dicts keyed by DetectorGroupKey for process_detectors, DetectorHandler.evaluate, and StatefulDetectorHandler.evaluate; introduced build_occurrence_and_event_data abstract method in StatefulDetectorHandler; updated DetectorEvaluationResult to include optional occurrence and event_data fields; simplified deduplication logic.
Test updates
tests/sentry/workflow_engine/processors/test_detector.py
Updated tests to align with new dict-based result collection and DetectorGroupKey mapping; added occurrence and event_data field population in DetectorEvaluationResult instances; reflected DetectorHandler.evaluate return type changes.

Sequence Diagram(s)

sequenceDiagram
    participant Caller as Caller / Pipeline
    participant ProcDet as process_detectors
    participant DetHandler as DetectorHandler.evaluate
    participant StateDetHandler as StatefulDetectorHandler.evaluate
    participant Hook as build_occurrence_and_event_data

    Caller->>ProcDet: process_detectors(data_packet, detectors)
    
    loop for each detector
        ProcDet->>DetHandler: evaluate(data_packet)
        
        alt StatefulDetectorHandler subclass
            DetHandler->>StateDetHandler: evaluate(data_packet)
            
            loop for each group_key
                StateDetHandler->>StateDetHandler: evaluate_group_key_value()
                
                alt status != OK/RESOLVED
                    StateDetHandler->>Hook: build_occurrence_and_event_data(group_key, value, status)
                    Hook-->>StateDetHandler: (occurrence, event_data)
                    StateDetHandler->>StateDetHandler: create DetectorEvaluationResult(occurrence, event_data)
                else status == OK/RESOLVED
                    StateDetHandler->>StateDetHandler: create DetectorEvaluationResult(StatusChangeMessage)
                end
            end
            
            StateDetHandler-->>DetHandler: dict[DetectorGroupKey, DetectorEvaluationResult]
        end
        
        DetHandler-->>ProcDet: dict[DetectorGroupKey, DetectorEvaluationResult]
        ProcDet->>ProcDet: collect as (Detector, dict[...])
    end
    
    ProcDet-->>Caller: list[tuple[Detector, dict[DetectorGroupKey, DetectorEvaluationResult]]]
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • API surface changes: Multiple public method signatures updated (process_detectors, evaluate on both DetectorHandler and StatefulDetectorHandler); return types shifted from list[DetectorEvaluationResult] to dict[DetectorGroupKey, DetectorEvaluationResult], affecting all callers.
  • New abstract method: build_occurrence_and_event_data introduced in StatefulDetectorHandler; requires careful review of how occurrences and event data are constructed and propagated.
  • Heterogeneous changes: Multiple files with distinct change patterns (inheritance hierarchy, model properties, core processing logic, tests), each requiring separate reasoning.
  • Integration complexity: MetricAlertDetectorHandler migration to StatefulDetectorHandler and its interaction with the new hook method needs verification.

Areas requiring extra attention:

  • The dict-based result collection and how downstream callers handle the new return type structure
  • Deduplication logic simplification and its correctness with the new dict approach
  • Implementation requirements for build_occurrence_and_event_data across all StatefulDetectorHandler subclasses
  • Event data field propagation through the new control flow

Poem

🐰 A Stateful Hop Forward

From lists we leap to dicts of might,
Where group keys map results just right,
A hook appears to build with care,
Occurrences float through the air,
The MetricAlert now stands up tall,
StatefulDetectorHandler's call! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main objective of the PR: adding a hook (build_occurrence_and_event_data) to the stateful detector for producing occurrences.
✨ 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

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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 registered as detector_handler

MetricAlertDetectorHandler now subclasses StatefulDetectorHandler but doesn’t implement any of its abstract methods (counter_names, get_dedupe_value, get_group_key_values, build_occurrence_and_event_data). With MetricAlertFire.detector_handler = MetricAlertDetectorHandler, any attempt to resolve detector.detector_handler for this group type will raise a TypeError when instantiating the handler.

If this handler truly isn’t used yet, this won’t surface immediately, but it’s a sharp edge for anyone wiring metric alerts later. Consider one of:

  • Implementing minimal concrete overrides that raise NotImplementedError from within the methods, so instantiation works and failures occur only if the path is actually exercised; or
  • Keeping MetricAlertDetectorHandler as a DetectorHandler[...] stub until you’re ready to implement the stateful version; or
  • Removing it from MetricAlertFire.detector_handler until an implementation exists.
🧹 Nitpick comments (3)
tests/sentry/workflow_engine/processors/test_detector.py (3)

27-35: counter_names implementation vs abstract property and Ruff warning

Here counter_names overrides an abstract @property with a mutable class attribute list. That works at runtime, but it both hides the property and triggers Ruff’s RUF012 about mutable class attributes.

If you want to appease tooling and keep intent clear, consider either:

  • Making it a proper property:
-class MockDetectorStateHandler(StatefulDetectorHandler[dict]):
-    counter_names = ["test1", "test2"]
+class MockDetectorStateHandler(StatefulDetectorHandler[dict]):
+    @property
+    def counter_names(self) -> list[str]:
+        return ["test1", "test2"]

or

  • Annotating as a ClassVar[list[str]] if you prefer the constant-attribute style.

58-63: Unused data_packet parameter in MockDetectorHandler.evaluate

MockDetectorHandler.evaluate ignores its data_packet argument, which is what Ruff’s ARG002 is flagging. Since this is a test stub, it’s harmless, but you can quiet the warning and make intent clearer by renaming it to _data_packet:

-            def evaluate(
-                self, data_packet: DataPacket[dict]
-            ) -> dict[DetectorGroupKey, DetectorEvaluationResult]:
+            def evaluate(
+                self, _data_packet: DataPacket[dict]
+            ) -> dict[DetectorGroupKey, DetectorEvaluationResult]:

244-275: Unused value parameter in build_mock_occurrence_and_event

The value argument isn’t used inside build_mock_occurrence_and_event, which is what ARG001 is reporting. Either drop it from the signature or incorporate it into the constructed IssueOccurrence (e.g., in evidence_data or subtitle) so the parameter meaning is explicit:

-def build_mock_occurrence_and_event(
-    handler: StatefulDetectorHandler,
-    group_key: DetectorGroupKey,
-    value: int,
-    new_status: PriorityLevel,
-) -> tuple[IssueOccurrence, dict[str, Any]]:
+def build_mock_occurrence_and_event(
+    handler: StatefulDetectorHandler,
+    group_key: DetectorGroupKey,
+    new_status: PriorityLevel,
+) -> tuple[IssueOccurrence, dict[str, Any]]:

(and update call sites), or start actually using value inside the helper.

📜 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
🧬 Code graph analysis (3)
src/sentry/workflow_engine/models/detector.py (1)
src/sentry/issues/grouptype.py (2)
  • GroupType (141-223)
  • get_by_slug (104-107)
src/sentry/incidents/grouptype.py (1)
src/sentry/workflow_engine/processors/detector.py (1)
  • StatefulDetectorHandler (135-407)
src/sentry/workflow_engine/processors/detector.py (3)
src/sentry/types/group.py (1)
  • PriorityLevel (63-80)
src/sentry/workflow_engine/models/detector.py (1)
  • Detector (23-86)
src/sentry/workflow_engine/models/data_source.py (1)
  • DataPacket (19-21)
🪛 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-65: Detector.group_type helper and handler resolution look good

Centralizing the slug lookup in group_type and reusing it in detector_handler keeps behavior the same while making the group type reusable (e.g. in tests and occurrence construction). No issues from this change.

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

123-222: New tests thoroughly exercise dict-based results and occurrence emission

The updated tests around process_detectors, StatefulDetectorHandler.evaluate, and evaluate_group_key_value (including the freeze_time usage and Kafka producer mocks) give good coverage of:

  • dict-structured DetectorEvaluationResults keyed by DetectorGroupKey
  • both IssueOccurrence and StatusChangeMessage paths
  • dedupe behavior and state transitions

This looks like solid protection for the new API and behavior.

Also applies to: 422-580

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

34-45: Dict-based DetectorEvaluationResult API and process_detectors loop look consistent

Switching evaluate and process_detectors to use dict[DetectorGroupKey, DetectorEvaluationResult] and then iterating detector_results.values() to produce Kafka messages is internally consistent:

  • Callers now get a per-detector mapping keyed by DetectorGroupKey.
  • Only entries with a non-None result trigger create_issue_occurrence_from_result, so “no-op” evaluations don’t accidentally emit.
  • State commits remain per-detector via handler.commit_state_updates().

Assuming all existing handlers have been updated to return dicts (tests suggest so), this refactor looks sound.

Also applies to: 47-70, 114-133

Comment on lines +166 to +171
@abc.abstractmethod
def build_occurrence_and_event_data(
self, group_key: DetectorGroupKey, value: int, new_status: PriorityLevel
) -> tuple[IssueOccurrence, dict[str, Any]]:
pass

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

New build_occurrence_and_event_data hook and PriorityLevel cast — verify enum alignment and subclasses

The new build_occurrence_and_event_data abstract method plus the updated evaluate/evaluate_group_key_value flow give a clear extension point for detectors to construct IssueOccurrence + event payloads, which is a nice separation of concerns.

Two follow-ups are worth tightening:

  1. Enum cast correctness
result, event_data = self.build_occurrence_and_event_data(
    group_key, value, PriorityLevel(new_status)
)

Here new_status is a DetectorPriorityLevel. This relies on the underlying integer values lining up with PriorityLevel so the cast is valid. If they ever diverge, you’ll get ValueError or incorrect priorities on the occurrences.

It might be safer long term to provide an explicit converter (e.g. a helper that maps DetectorPriorityLevelPriorityLevel) rather than relying on raw IntEnum value equality.

  1. Subclass completeness

Adding build_occurrence_and_event_data as an abstract method means every concrete StatefulDetectorHandler subclass must now implement it; otherwise those classes remain abstract and can’t be instantiated. Tests cover MockDetectorStateHandler, but it’s worth checking for any production subclasses that might now be missing an implementation.

You can scan for subclasses to verify they all override this hook:

Also applies to: 226-245, 285-308


🏁 Script executed:

#!/bin/bash
# Find classes that subclass StatefulDetectorHandler and show their definitions
ast-grep --pattern $'class $C(StatefulDetectorHandler[$T]) {\n  $$$\n}' -n -C3

Length of output: 177


I'll correct the script and verify the concerns from the review comment.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find classes that subclass StatefulDetectorHandler (corrected ast-grep syntax)
ast-grep --pattern 'class $C(StatefulDetectorHandler[$T]) { $$$ }'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Find all classes that inherit from StatefulDetectorHandler using rg
rg --type-add 'py:*.py' -n 'class\s+\w+.*StatefulDetectorHandler' -A 2

Length of output: 490


🏁 Script executed:

#!/bin/bash
# Search for DetectorPriorityLevel and PriorityLevel definitions and usage
rg --type-add 'py:*.py' -n 'DetectorPriorityLevel|PriorityLevel' src/sentry/workflow_engine/ -B 2 -A 2

Length of output: 10376


🏁 Script executed:

#!/bin/bash
# Examine the detector.py file around specified lines
cat src/sentry/workflow_engine/processors/detector.py | head -320 | tail -160

Length of output: 6948


🏁 Script executed:

#!/bin/bash
# Search for build_occurrence_and_event_data implementations
rg --type-add 'py:*.py' -n 'build_occurrence_and_event_data' -B 2 -A 5

Length of output: 2241


I need to check the full implementation of MetricAlertDetectorHandler and verify the PriorityLevel enum values.


🏁 Script executed:

#!/bin/bash
# Get full MetricAlertDetectorHandler class definition
cat src/sentry/incidents/grouptype.py

Length of output: 1073


🏁 Script executed:

#!/bin/bash
# Find PriorityLevel enum definition
rg --type-add 'py:*.py' -n 'class PriorityLevel' -A 10

Length of output: 2316


🏁 Script executed:

#!/bin/bash
# Check if MetricAlertDetectorHandler inherits any abstract methods or if it's instantiated anywhere
rg --type-add 'py:*.py' -n 'MetricAlertDetectorHandler' -B 2 -A 5

Length of output: 865


Enum cast will fail on DetectorPriorityLevel.OK; MetricAlertDetectorHandler missing required implementation

The PriorityLevel(new_status) cast at line 300 has a real alignment problem. PriorityLevel enum only defines LOW (25), MEDIUM (50), and HIGH (75), but DetectorPriorityLevel.OK has value 0. When new_status == DetectorPriorityLevel.OK, the cast will raise ValueError.

Additionally, MetricAlertDetectorHandler in src/sentry/incidents/grouptype.py:11 is a concrete subclass but only contains pass—it never implements the required abstract method build_occurrence_and_event_data. Although marked with a TODO comment suggesting future work, it's currently referenced as detector_handler in MetricAlertFire and cannot be instantiated.

Recommended fixes:

  1. Add an explicit converter from DetectorPriorityLevelPriorityLevel that handles OK (e.g., map it to a safe default or raise a clearer error).
  2. Either implement build_occurrence_and_event_data in MetricAlertDetectorHandler or make it explicitly abstract (or stub it with NotImplementedError).
🤖 Prompt for AI Agents
In src/sentry/workflow_engine/processors/detector.py around lines 166–171 (and
where PriorityLevel(new_status) is used around line 300), stop directly casting
DetectorPriorityLevel into PriorityLevel; add a small converter function that
maps DetectorPriorityLevel.OK (0) to a safe PriorityLevel (e.g.,
PriorityLevel.LOW) or returns None/raises a clear ValueError with context, and
use that converter instead of direct enum construction to avoid ValueError on
OK. In src/sentry/incidents/grouptype.py (MetricAlertDetectorHandler) implement
the required abstract method build_occurrence_and_event_data (or explicitly make
the class abstract / raise NotImplementedError) so MetricAlertDetectorHandler
cannot be instantiated without a concrete implementation; if you choose a stub,
raise NotImplementedError with a clear message referencing required behavior.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants