Skip to content

PR Incidents Filtering for better CFR and MTTR Reporting #656

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

Merged
merged 7 commits into from
May 23, 2025

Conversation

Kamlesh72
Copy link
Contributor

@Kamlesh72 Kamlesh72 commented May 16, 2025

Linked Issue(s)

Ref #557

Acceptance Criteria fulfillment

  • Incident PRs Setting (Regex Filters)
  • Option to exclude REVERT_PR from Incident types if Incident PRs Setting has set include_revert_prs to false
  • Method to get Incident PRs
  • Tests for Incident Types setting with Incident PRs setting

Summary by CodeRabbit

  • New Features

    • Added support for detecting and filtering incidents based on pull requests, including configurable inclusion of revert PRs and custom PR filters.
    • Incident analytics endpoints now support advanced PR-based filtering parameters.
    • New settings for incident PR filtering introduced and integrated into configuration options.
    • Combined traditional and PR-based incidents in incident retrieval and metrics workflows.
  • Tests

    • Added comprehensive tests for incident type filtering and PR-based incident detection to ensure correct behavior with new settings.
  • Chores

    • Updated internal service interfaces and constructors to support new PR-based incident features and settings.

Copy link

coderabbitai bot commented May 16, 2025

Walkthrough

These changes introduce support for incident pull request (PR) filtering and configuration throughout the analytics backend. New models, settings, and adapters enable teams to include or exclude certain PRs as incidents, with customizable filters. The incident service and related APIs are updated to retrieve, process, and merge PR-based incidents alongside traditional incidents. New test cases validate these behaviors.

Changes

File(s) Change Summary
backend/analytics_server/mhq/api/incidents.py Added optional pr_filter parameter to multiple API endpoints, parsing and applying it to incident and deployment service calls for PR-based incident filtering.
backend/analytics_server/mhq/api/resources/settings_resource.py Added support for IncidentPRsSetting in configuration settings response, including relevant fields in the output.
backend/analytics_server/mhq/service/code/pr_filter.py Extended PR filter processor to handle IncidentPRsSetting and apply incident PR filters.
backend/analytics_server/mhq/service/incidents/incident_filter.py Updated incident type filtering logic to respect include_revert_prs from IncidentPRsSetting, excluding REVERT_PR incidents if needed.
backend/analytics_server/mhq/service/incidents/incidents.py Enhanced IncidentService to integrate PR-based incidents, added logic to fetch and adapt PRs as incidents, and updated method signatures to accept PR filters.
backend/analytics_server/mhq/service/incidents/models/adapter.py Added function adaptIncidentPR to convert PR data into incident models.
backend/analytics_server/mhq/service/settings/configuration_settings.py Added support for INCIDENT_PRS_SETTING in settings service, with adapters for serialization and deserialization.
backend/analytics_server/mhq/service/settings/default_settings_data.py Added default data for INCIDENT_PRS_SETTING type.
backend/analytics_server/mhq/service/settings/models.py Added IncidentPRFilter and IncidentPRsSetting dataclasses for incident PR configuration.
backend/analytics_server/mhq/service/settings/setting_type_validator.py Extended validator to recognize INCIDENT_PRS_SETTING.
backend/analytics_server/mhq/store/models/code/filter.py Added incident_pr_filters to PRFilter dataclass and query logic for applying regex-based PR filters.
backend/analytics_server/mhq/store/models/settings/configuration_settings.py Added INCIDENT_PRS_SETTING to SettingType enum.
backend/analytics_server/mhq/store/repos/code.py Added method to fetch PRs merged in interval filtered by PR numbers with optional PR filter.
backend/analytics_server/tests/service/Incidents/test_incident_types_setting.py Added new tests for incident type and PR settings filtering logic.
backend/analytics_server/tests/service/Incidents/test_mean_time_to_recovery.py Updated tests to match new IncidentService constructor signature.
backend/analytics_server/tests/service/Incidents/test_team_pr_incidents.py Added comprehensive tests for get_team_pr_incidents method covering various filtering scenarios.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API
    participant IncidentService
    participant CodeRepoService
    participant SettingsService

    Client->>API: Request incidents (with pr_filter)
    API->>IncidentService: get_team_incidents(team_id, interval, pr_filter)
    IncidentService->>SettingsService: Fetch PR incident settings
    IncidentService->>CodeRepoService: Fetch PRs matching filters
    IncidentService->>IncidentService: Adapt PRs to incidents
    IncidentService->>IncidentService: Merge PR-based and traditional incidents
    IncidentService-->>API: Return combined incidents
    API-->>Client: Respond with filtered incidents
Loading

Possibly related PRs

Suggested reviewers

  • adnanhashmi09

Poem

In the warren where code and carrots meet,
PRs and incidents now share a seat.
With filters and settings, oh what a delight—
Rabbits can now debug late into the night!
Hop, hop, hooray for PRs in the mix,
Our incident magic just learned new tricks! 🐇✨

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 10

🧹 Nitpick comments (1)
backend/analytics_server/mhq/service/incidents/incidents.py (1)

135-147: Avoid shadowing built-ins & improve loop efficiency

Using for filter in … shadows Python’s built-in filter, and scanning the entire filter list for every resolution PR is O(n²).

-        for pr in resolution_prs:
-            for filter in incident_prs_setting.filters:
-                pr_number = self._extract_pr_number_from_regex(
-                    getattr(pr, filter["field"]), filter["value"]
+        for pr in resolution_prs:
+            for rule in incident_prs_setting.filters:
+                pr_number = self._extract_pr_number_from_regex(
+                    getattr(pr, rule.field), rule.value   # once adapters return objects
                 )

Consider pre-compiling the regex patterns once and/or indexing repo_id_to_pr_number_to_pr_map by repo & number to keep the lookup O(1) (you already do this).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between ef856e6 and 1b0fd74.

📒 Files selected for processing (14)
  • backend/analytics_server/mhq/api/incidents.py (7 hunks)
  • backend/analytics_server/mhq/api/resources/settings_resource.py (2 hunks)
  • backend/analytics_server/mhq/service/code/pr_filter.py (3 hunks)
  • backend/analytics_server/mhq/service/incidents/incident_filter.py (2 hunks)
  • backend/analytics_server/mhq/service/incidents/incidents.py (4 hunks)
  • backend/analytics_server/mhq/service/incidents/models/adapter.py (1 hunks)
  • backend/analytics_server/mhq/service/settings/configuration_settings.py (7 hunks)
  • backend/analytics_server/mhq/service/settings/default_settings_data.py (1 hunks)
  • backend/analytics_server/mhq/service/settings/models.py (1 hunks)
  • backend/analytics_server/mhq/service/settings/setting_type_validator.py (1 hunks)
  • backend/analytics_server/mhq/store/models/code/filter.py (2 hunks)
  • backend/analytics_server/mhq/store/models/settings/configuration_settings.py (1 hunks)
  • backend/analytics_server/tests/service/Incidents/test_incident_types_setting.py (1 hunks)
  • backend/analytics_server/tests/service/Incidents/test_mean_time_to_recovery.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (10)
backend/analytics_server/tests/service/Incidents/test_mean_time_to_recovery.py (2)
backend/analytics_server/mhq/service/incidents/incidents.py (1)
  • IncidentService (36-322)
backend/analytics_server/tests/factories/models/incidents.py (1)
  • get_mean_time_to_recovery_metrics (101-104)
backend/analytics_server/mhq/service/settings/setting_type_validator.py (1)
backend/analytics_server/mhq/store/models/settings/configuration_settings.py (1)
  • SettingType (14-22)
backend/analytics_server/mhq/service/settings/default_settings_data.py (1)
backend/analytics_server/mhq/store/models/settings/configuration_settings.py (1)
  • SettingType (14-22)
backend/analytics_server/mhq/service/code/pr_filter.py (2)
backend/analytics_server/mhq/service/settings/models.py (2)
  • ExcludedPRsSetting (30-31)
  • IncidentPRsSetting (56-58)
backend/analytics_server/mhq/store/models/settings/configuration_settings.py (1)
  • SettingType (14-22)
backend/analytics_server/mhq/store/models/code/filter.py (1)
backend/analytics_server/mhq/store/models/code/pull_requests.py (1)
  • PullRequest (12-76)
backend/analytics_server/mhq/api/resources/settings_resource.py (1)
backend/analytics_server/mhq/service/settings/models.py (1)
  • IncidentPRsSetting (56-58)
backend/analytics_server/mhq/service/incidents/incident_filter.py (3)
backend/analytics_server/mhq/service/settings/models.py (1)
  • IncidentPRsSetting (56-58)
backend/analytics_server/mhq/store/models/incidents/enums.py (1)
  • IncidentType (29-32)
backend/analytics_server/mhq/store/models/settings/configuration_settings.py (1)
  • SettingType (14-22)
backend/analytics_server/tests/service/Incidents/test_incident_types_setting.py (4)
backend/analytics_server/mhq/service/incidents/incident_filter.py (3)
  • ConfigurationsIncidentFilterProcessor (65-126)
  • apply (32-42)
  • apply (80-89)
backend/analytics_server/mhq/store/models/settings/configuration_settings.py (1)
  • SettingType (14-22)
backend/analytics_server/mhq/service/settings/models.py (1)
  • IncidentTypesSetting (35-36)
backend/analytics_server/mhq/store/models/incidents/enums.py (1)
  • IncidentType (29-32)
backend/analytics_server/mhq/service/settings/configuration_settings.py (2)
backend/analytics_server/mhq/service/settings/models.py (1)
  • IncidentPRsSetting (56-58)
backend/analytics_server/mhq/store/models/settings/configuration_settings.py (1)
  • SettingType (14-22)
backend/analytics_server/mhq/service/incidents/incidents.py (11)
backend/analytics_server/mhq/service/settings/models.py (1)
  • IncidentPRsSetting (56-58)
backend/analytics_server/mhq/store/models/code/filter.py (1)
  • PRFilter (10-108)
backend/analytics_server/mhq/store/models/code/pull_requests.py (1)
  • PullRequest (12-76)
backend/analytics_server/mhq/store/repos/code.py (2)
  • get_active_team_repos_by_team_id (263-268)
  • get_prs_merged_in_interval (287-308)
backend/analytics_server/mhq/utils/time.py (4)
  • time_now (10-11)
  • Interval (14-96)
  • from_time (26-27)
  • to_time (30-31)
backend/analytics_server/mhq/service/settings/configuration_settings.py (3)
  • SettingsService (22-490)
  • get_settings_service (493-494)
  • get_settings (119-131)
backend/analytics_server/mhq/store/repos/incidents.py (1)
  • IncidentsRepoService (23-211)
backend/analytics_server/mhq/service/incidents/models/adapter.py (2)
  • IncidentPRAdapter (6-24)
  • adapt (8-24)
backend/analytics_server/mhq/service/code/pr_filter.py (1)
  • apply_pr_filter (11-34)
backend/analytics_server/mhq/service/incidents/incident_filter.py (1)
  • apply_incident_filter (45-62)
backend/analytics_server/mhq/store/models/settings/configuration_settings.py (1)
  • SettingType (14-22)
🪛 GitHub Actions: Unit Tests
backend/analytics_server/tests/service/Incidents/test_incident_types_setting.py

[error] 4-4: ImportError: cannot import name 'IncidentPrsSetting' from 'mhq.service.settings.configuration_settings'. This caused test collection to fail.

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: All file linting
🔇 Additional comments (27)
backend/analytics_server/tests/service/Incidents/test_mean_time_to_recovery.py (2)

19-19: Constructor updated correctly to match new signature

The instantiation of IncidentService has been properly updated to include the new required code_repo_service parameter (set to None for testing purposes), matching the updated constructor signature.


27-27: Constructor updated correctly to match new signature

The instantiation of IncidentService has been properly updated to include the new required code_repo_service parameter (set to None for testing purposes), matching the updated constructor signature.

backend/analytics_server/mhq/store/models/settings/configuration_settings.py (1)

20-20: New setting type added correctly

The addition of INCIDENT_PRS_SETTING to the SettingType enum follows the existing pattern and is placed in the appropriate location per the code comment guidance.

backend/analytics_server/mhq/service/settings/setting_type_validator.py (1)

22-24: Validator properly extended for new setting type

The conditional check for the new INCIDENT_PRS_SETTING value has been implemented correctly following the existing pattern in the validator function.

backend/analytics_server/mhq/service/settings/default_settings_data.py (1)

32-37: Default configuration for incident PRs setting looks good

The default settings provided for the new INCIDENT_PRS_SETTING type are sensible and aligned with the PR objectives:

  • include_revert_prs defaults to False, which matches the requirement to exclude REVERT_PR from incident types
  • filters is initialized as an empty array, ready to store regex filters for Incident PRs

The implementation follows the existing pattern in the codebase.

backend/analytics_server/mhq/service/settings/models.py (2)

49-53: Implementation looks good for the IncidentPRFilter dataclass.

The dataclass is well-designed with a clear purpose - to define key-value pairs for PR filtering where:

  • field specifies which PR attribute to match against
  • value contains the regex pattern to use for matching

This simple structure will be flexible enough to support filtering against various PR attributes.


56-58: Well-structured IncidentPRsSetting class with appropriate properties.

This setting class provides:

  1. A boolean flag include_revert_prs to control the inclusion of revert PRs in incident reports
  2. A list of filter criteria via the filters property

This implementation satisfies the PR objectives by creating a setting that allows for regex-based filtering of incident PRs.

backend/analytics_server/mhq/service/incidents/incident_filter.py (3)

7-7: Import statement correctly updated for the new setting class.

The import statement has been updated to include IncidentPRsSetting, which is used in the modified code below.


10-10: Good practice importing the specific enum needed.

The import statement for IncidentType is correctly added to support the comparison operation with IncidentType.REVERT_PR.


112-125: The implementation correctly handles excluding REVERT_PR incidents.

This code effectively:

  1. Checks if INCIDENT_PRS_SETTING is present in the setting types
  2. Retrieves the corresponding setting
  3. When include_revert_prs is set to False, filters out REVERT_PR from incident types

This satisfies the PR objective to "add an option to exclude REVERT_PR from incident types when the Incident PRs setting has the include_revert_prs flag set to false."

One small optimization could be to early return if incident_types is empty, but the current implementation is fine.

backend/analytics_server/mhq/service/code/pr_filter.py (3)

4-4: The import statement correctly includes the new setting class.

Importing IncidentPRsSetting alongside ExcludedPRsSetting is appropriate as both are used in this file.


106-107: Good implementation of the condition for applying incident PR filters.

This code follows the same pattern as the existing condition for excluded PR IDs, making it consistent with the codebase's style.


117-120: The method correctly applies incident PR filters.

This implementation:

  1. Initializes incident_pr_filters as an empty list if it's None
  2. Appends the filters from the settings object

The approach matches the pattern used in _apply_excluded_pr_ids_setting, maintaining code consistency.

backend/analytics_server/mhq/store/models/code/filter.py (2)

16-16: New attribute correctly added to the PRFilter dataclass.

The incident_pr_filters attribute is appropriately typed as List[Dict] and set as optional with a default value of None.


102-102: Correctly added the new query to the conditions dictionary.

The incident PR filters query is properly integrated into the existing conditions dictionary.

backend/analytics_server/mhq/api/resources/settings_resource.py (2)

8-8: Good addition of the IncidentPRsSetting import.

This import aligns with the PR objective to implement settings for Incident PRs and is used appropriately in the code below.


59-63: Implementation looks good.

The code correctly adds support for the new IncidentPRsSetting type in the response, exposing both the include_revert_prs flag and filters fields. This approach is consistent with how other setting types are handled in this module.

backend/analytics_server/mhq/service/incidents/models/adapter.py (1)

6-24: Well-implemented adapter pattern.

The IncidentPRAdapter follows a clean design pattern with a static method that transforms PR data into an incident model. The implementation correctly maps all necessary fields from both the incident PR and resolution PR.

Two observations:

  1. The adapter ensures the incident type is set to IncidentType.REVERT_PR, which aligns with the PR objectives
  2. The implementation properly sets the status to resolved and uses appropriate timestamps from the PRs
backend/analytics_server/tests/service/Incidents/test_incident_types_setting.py (2)

12-38: Test for incident types settings looks good.

This test verifies that when only INCIDENT_TYPES_SETTING is present, all incident types are properly included in the filter. The test is comprehensive and includes all incident types.


41-55: Edge case test for empty incident types is well implemented.

Good test case that verifies behavior when the incident types setting exists but contains an empty list. This is an important edge case to cover.

backend/analytics_server/mhq/api/incidents.py (7)

39-39: Good addition of the optional pr_filter parameter.

The pr_filter parameter is correctly defined as optional in both the schema and function signature. The type conversion from JSON string to dictionary is handled appropriately.

Also applies to: 44-45


51-53: Filter propagation well implemented.

The code correctly processes the PR filter through apply_pr_filter and passes it to the incident service method. This ensures consistent filtering behavior across all endpoints.

Also applies to: 58-58


100-102: PR filter properly integrated with get_team_incidents.

The implementation correctly passes the PR filter to the incident service's get_team_incidents method, aligning with the PR objectives for improved incident filtering.


124-124: Consistent implementation for MTTR endpoint.

The code follows the same pattern as other endpoints, adding the optional PR filter parameter and properly processing it for the MTTR calculations.

Also applies to: 129-130, 135-137


154-154: Consistent implementation for MTTR trends endpoint.

The code follows the same pattern as other endpoints, adding the optional PR filter parameter and properly processing it for the MTTR trend calculations.

Also applies to: 159-160, 165-167


220-222: PR filter properly integrated with CFR calculations.

The implementation correctly passes the PR filter to the incident service's method for fetching incidents used in CFR calculations.


266-268: PR filter properly integrated with CFR trends calculations.

The implementation correctly passes the PR filter to the incident service's method for fetching incidents used in CFR trend calculations.

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: 0

♻️ Duplicate comments (4)
backend/analytics_server/mhq/service/incidents/incidents.py (4)

47-67: ⚠️ Potential issue

Fix potential serialization issue with dict_values view

The method returns a dict_values view rather than a concrete list, which could cause issues with JSON serialization or when callers expect list semantics like indexing.

Apply this fix:

-        return {incident.key: incident for incident in total_incidents}.values()
+        return list({incident.key: incident for incident in total_incidents}.values())

69-91: ⚠️ Potential issue

Fix potential serialization issue with dict_values view

The method returns a dict_values view rather than a concrete list, which could cause issues with JSON serialization or when callers expect list semantics like indexing.

Apply this fix:

-        return {incident.key: incident for incident in total_incidents}.values()
+        return list({incident.key: incident for incident in total_incidents}.values())

97-105: ⚠️ Potential issue

Fix potential AttributeError when setting is absent

The code calls get_settings() which can return None, but then accesses .specific_settings unconditionally, risking an AttributeError for teams that have never saved this setting.

Apply this fix:

-        incident_prs_setting: IncidentPRsSetting = self._settings_service.get_settings(
+        setting = self._settings_service.get_settings(
             setting_type=SettingType.INCIDENT_PRS_SETTING,
             entity_type=EntityType.TEAM,
             entity_id=team_id,
-        ).specific_settings
+        )
+        incident_prs_setting: IncidentPRsSetting = setting.specific_settings if setting else None

113-118: ⚠️ Potential issue

Fix TypeError with asdict and avoid parameter shadowing

There are two issues in this code:

  1. asdict() only works on dataclass instances, but PRFilter is not a dataclass
  2. Reusing the variable name pr_filter shadows the input parameter

Apply this fix:

-        resolution_prs_filter: PRFilter = apply_pr_filter(
-            asdict(pr_filter),
+        processed_pr_filter: PRFilter = apply_pr_filter(
+            vars(pr_filter),  # or another appropriate dict conversion method
             EntityType.TEAM,
             team_id,
             [SettingType.EXCLUDED_PRS_SETTING, SettingType.INCIDENT_PRS_SETTING],
         )
 
         resolution_prs = self._code_repo_service.get_prs_merged_in_interval(
-            team_repo_ids, resolution_prs_interval, resolution_prs_filter
+            team_repo_ids, resolution_prs_interval, processed_pr_filter
         )
🧹 Nitpick comments (1)
backend/analytics_server/mhq/service/incidents/incidents.py (1)

126-142: Consider adding logging and optimizing nested loops

The nested loops in this section could become inefficient with a large number of PRs and filters. Consider adding debug logging to help diagnose issues and optimize the structure.

+        # Map to track PRs that have already matched a filter
+        matched_prs = set()
 
         for pr in resolution_prs:
+            if pr.id in matched_prs:
+                continue
+                
             for filter in incident_prs_setting.filters:
                 incident_pr_number = self._extract_pr_number_from_regex(
                     getattr(pr, filter["field"]), filter["value"]
                 )
 
                 if incident_pr_number:
+                    matched_prs.add(pr.id)
                     pr_numbers.append(incident_pr_number)
                     if str(pr.repo_id) not in repo_id_to_pr_number_to_pr_map:
                         repo_id_to_pr_number_to_pr_map[str(pr.repo_id)] = {}
                     repo_id_to_pr_number_to_pr_map[str(pr.repo_id)][
                         incident_pr_number
                     ] = pr
                     break
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between a7fb8bf and 5e32205.

📒 Files selected for processing (3)
  • backend/analytics_server/mhq/service/incidents/incidents.py (4 hunks)
  • backend/analytics_server/mhq/store/repos/code.py (1 hunks)
  • backend/analytics_server/tests/service/Incidents/test_team_pr_incidents.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
backend/analytics_server/mhq/store/repos/code.py (4)
backend/analytics_server/tests/service/Incidents/test_team_pr_incidents.py (1)
  • get_prs_merged_in_interval_by_numbers (78-79)
backend/analytics_server/mhq/utils/time.py (1)
  • Interval (14-96)
backend/analytics_server/mhq/store/models/code/filter.py (1)
  • PRFilter (10-108)
backend/analytics_server/mhq/store/models/code/pull_requests.py (1)
  • PullRequest (12-76)
backend/analytics_server/mhq/service/incidents/incidents.py (10)
backend/analytics_server/mhq/service/settings/models.py (1)
  • IncidentPRsSetting (56-58)
backend/analytics_server/mhq/store/models/code/filter.py (1)
  • PRFilter (10-108)
backend/analytics_server/mhq/store/repos/code.py (3)
  • get_active_team_repos_by_team_id (263-268)
  • get_prs_merged_in_interval (287-308)
  • get_prs_merged_in_interval_by_numbers (311-327)
backend/analytics_server/mhq/utils/time.py (4)
  • time_now (10-11)
  • Interval (14-96)
  • from_time (26-27)
  • to_time (30-31)
backend/analytics_server/mhq/service/settings/configuration_settings.py (2)
  • get_settings_service (493-494)
  • get_settings (119-131)
backend/analytics_server/mhq/store/repos/incidents.py (1)
  • IncidentsRepoService (23-211)
backend/analytics_server/mhq/service/incidents/models/adapter.py (2)
  • IncidentPRAdapter (6-24)
  • adapt (8-24)
backend/analytics_server/mhq/service/code/pr_filter.py (1)
  • apply_pr_filter (11-34)
backend/analytics_server/mhq/service/incidents/incident_filter.py (1)
  • apply_incident_filter (45-62)
backend/analytics_server/tests/service/Incidents/test_team_pr_incidents.py (4)
  • get_settings (30-50)
  • get_active_team_repos_by_team_id (63-73)
  • get_prs_merged_in_interval (75-76)
  • get_prs_merged_in_interval_by_numbers (78-79)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: All file linting
🔇 Additional comments (13)
backend/analytics_server/mhq/store/repos/code.py (1)

310-327: Well-implemented repository method for PR filtering by numbers

The implementation follows the established pattern of other query methods in this service, with appropriate deferred loading of the data field, filtering by repos, interval, PR filter, and PR numbers, and proper ordering of results.

backend/analytics_server/tests/service/Incidents/test_team_pr_incidents.py (8)

16-19: LGTM: Simple and effective test interval setup

The test interval is set appropriately with realistic time values for testing.


22-26: LGTM: Good use of fixture with patching

The mock_apply_pr_filter fixture effectively isolates the tests from the actual filter application logic.


29-55: LGTM: Well-structured fake settings service

The fake service provides realistic test data with appropriate regex patterns for extracting PR numbers from branches and titles.


58-79: LGTM: Complete fake repository service implementation

The implementation properly handles both filtering methods for testing different PR retrieval approaches.


86-101: LGTM: Good edge case test for empty filters

This test verifies that the service returns an empty list when there are no incident PR filters.


104-151: LGTM: Comprehensive test for PR filtering

The test thoroughly checks that the correct PRs are identified as incidents based on the revert patterns.


154-187: LGTM: Good test for multiple repos edge case

This test verifies behavior when PRs match the filter pattern but no original PRs are found.


190-236: LGTM: Thorough test for the complete multi-repo scenario

This test effectively verifies that the service can identify and return incidents from multiple repositories.

backend/analytics_server/mhq/service/incidents/incidents.py (4)

3-8: LGTM: Well-organized imports

The imports are properly organized by category (typing, core services, models, utilities) which improves code readability.

Also applies to: 22-34


37-45: LGTM: Proper constructor extension

Good addition of the CodeRepoService dependency to the constructor, following the dependency injection pattern.


160-169: LGTM: Well-implemented regex extraction method

The method properly validates both inputs, checks the regex pattern, and safely extracts the first capture group. Good defensive programming.


331-334: LGTM: Updated factory method with new dependency

The factory method has been properly updated to include the new CodeRepoService dependency.

@Kamlesh72 Kamlesh72 force-pushed the feat/pr-filtering-backend branch from 5e32205 to 9536dd5 Compare May 19, 2025 19:22
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

♻️ Duplicate comments (1)
backend/analytics_server/mhq/service/incidents/incidents.py (1)

118-123: ⚠️ Potential issue

Fix the use of asdict() on a non-dataclass instance

The PRFilter class is not a dataclass, so calling asdict(pr_filter) will raise a TypeError. This was mentioned in a previous review.

-        resolution_prs_filter: PRFilter = apply_pr_filter(
-            asdict(pr_filter),
+        resolution_prs_filter: PRFilter = apply_pr_filter(
+            vars(pr_filter) if pr_filter else None,
             EntityType.TEAM,
             team_id,
             [SettingType.EXCLUDED_PRS_SETTING, SettingType.INCIDENT_PRS_SETTING],
         )
🧹 Nitpick comments (1)
backend/analytics_server/mhq/service/incidents/incidents.py (1)

116-117: Consider limiting the time range for resolution PRs

Using time_now() for to_time in the resolution interval could result in scanning an unnecessarily large time range if the original interval is far in the past.

Consider adding a reasonable upper bound to the interval, perhaps a few months beyond the original interval's end time.

-        resolution_prs_interval = Interval(
-            from_time=interval.from_time, to_time=time_now()
-        )
+        # Limit the resolution interval to a reasonable range (e.g., 3 months after interval end)
+        max_resolution_time = max(interval.to_time, time_now())
+        resolution_prs_interval = Interval(
+            from_time=interval.from_time, to_time=max_resolution_time
+        )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 5e32205 and 9536dd5.

📒 Files selected for processing (3)
  • backend/analytics_server/mhq/service/incidents/incidents.py (4 hunks)
  • backend/analytics_server/mhq/store/repos/code.py (1 hunks)
  • backend/analytics_server/tests/service/Incidents/test_team_pr_incidents.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/analytics_server/mhq/store/repos/code.py
  • backend/analytics_server/tests/service/Incidents/test_team_pr_incidents.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/analytics_server/mhq/service/incidents/incidents.py (10)
backend/analytics_server/mhq/service/settings/models.py (1)
  • IncidentPRsSetting (56-58)
backend/analytics_server/mhq/store/models/code/filter.py (1)
  • PRFilter (10-108)
backend/analytics_server/mhq/store/repos/code.py (4)
  • CodeRepoService (27-513)
  • get_active_team_repos_by_team_id (263-268)
  • get_prs_merged_in_interval (287-308)
  • get_prs_merged_in_interval_by_numbers (311-327)
backend/analytics_server/mhq/utils/time.py (2)
  • time_now (10-11)
  • Interval (14-96)
backend/analytics_server/mhq/service/settings/configuration_settings.py (3)
  • SettingsService (22-490)
  • get_settings_service (493-494)
  • get_settings (119-131)
backend/analytics_server/mhq/service/incidents/models/adapter.py (2)
  • IncidentPRAdapter (6-24)
  • adapt (8-24)
backend/analytics_server/mhq/service/code/pr_filter.py (1)
  • apply_pr_filter (11-34)
backend/analytics_server/mhq/service/incidents/incident_filter.py (1)
  • apply_incident_filter (45-62)
backend/analytics_server/mhq/store/models/settings/configuration_settings.py (1)
  • SettingType (14-22)
backend/analytics_server/tests/service/Incidents/test_team_pr_incidents.py (4)
  • get_settings (30-50)
  • get_active_team_repos_by_team_id (63-73)
  • get_prs_merged_in_interval (75-76)
  • get_prs_merged_in_interval_by_numbers (78-79)
🔇 Additional comments (6)
backend/analytics_server/mhq/service/incidents/incidents.py (6)

93-163: **Well-implemented PR incidents retrieval! **

The new get_team_pr_incidents method effectively implements the core functionality described in the PR objectives. It appropriately fetches incident PRs based on regex filters defined in the settings, processes them, and adapts them into incident objects.


103-106: Good null-check implementation

I appreciate the defensive coding here, properly checking if settings exist before trying to access specific_settings.


67-67: Thanks for addressing the previous feedback

Good job converting the dict_values view to a list, as recommended in the previous review comments. This will prevent potential issues with callers expecting list semantics.


91-91: Consistent implementation of the dict_values fix

The same fix has been properly applied here as well.


165-174: Well-implemented regex extraction helper

The _extract_pr_number_from_regex method is cleanly implemented with proper error handling and validations.


336-339: Factory function properly updated

The get_incident_service function has been correctly updated to pass the CodeRepoService to the IncidentService constructor.

Copy link
Contributor

@adnanhashmi09 adnanhashmi09 left a comment

Choose a reason for hiding this comment

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

quite a few of these misses could have been avoided if the linter and typechecks were setup properly in your IDE.

}
),
)
def get_resolved_incidents(team_id: str, from_time: datetime, to_time: datetime):
def get_resolved_incidents(
team_id: str, from_time: datetime, to_time: datetime, pr_filter: dict = None
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
team_id: str, from_time: datetime, to_time: datetime, pr_filter: dict = None
team_id: str, from_time: datetime, to_time: datetime, pr_filter: typeOptional[dict] = None

Type of Dict cannot be assigned to None. Use correct type. Import the correct optional from typing and ensure it is not conflicting with voluptuous.Optional

Comment on lines 6 to 24
class IncidentPRAdapter:
@staticmethod
def adapt(incident_pr: PullRequest, resolution_pr: PullRequest) -> Incident:
return Incident(
id=incident_pr.id,
provider=incident_pr.provider,
key=str(incident_pr.id),
title=incident_pr.title,
incident_number=int(incident_pr.number),
status=IncidentStatus.RESOLVED.value,
creation_date=incident_pr.state_changed_at,
acknowledged_date=resolution_pr.created_at,
resolved_date=resolution_pr.state_changed_at,
assigned_to=resolution_pr.author,
assignees=[resolution_pr.author],
url=incident_pr.url,
meta={},
incident_type=IncidentType.REVERT_PR,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this a class? Can it not be simply a function?

Comment on lines 118 to 123
resolution_prs_filter: PRFilter = apply_pr_filter(
asdict(pr_filter),
EntityType.TEAM,
team_id,
[SettingType.EXCLUDED_PRS_SETTING, SettingType.INCIDENT_PRS_SETTING],
)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Use named arguments when there are multiple parameters accepted by the function. Helps avoid any misplaced arguments, which become super difficult to debug later on. (Speaking from experience 🥲)

Comment on lines 140 to 146
pr_numbers.append(incident_pr_number)
if str(pr.repo_id) not in repo_id_to_pr_number_to_pr_map:
repo_id_to_pr_number_to_pr_map[str(pr.repo_id)] = {}
repo_id_to_pr_number_to_pr_map[str(pr.repo_id)][
incident_pr_number
] = pr
break
Copy link
Contributor

Choose a reason for hiding this comment

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

Can be made cleaner using defaultdicts(dict). Then you won't have to the extra conditional check to see if str(pr.repo_id) not in repo_id_to_pr_number_to_pr_map.

for pr in resolution_prs:
for filter in incident_prs_setting.filters:
incident_pr_number = self._extract_pr_number_from_regex(
getattr(pr, filter["field"]), filter["value"]
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't get this. If filter is a dataclass how are you accessing the property using __getitem__. This would fail no?

Copy link
Contributor

Choose a reason for hiding this comment

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

is filter a json object or a dataclass?

Copy link
Contributor

Choose a reason for hiding this comment

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

From what i can see from the rest of the code, filters is a json object and not a dataclass. I think you wanted to define the structure of the filters, but ended up using the wrong tool for the job. You can use typedDicts in python to define the structure of the python dicts. They work like typescript's interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@adnanhashmi09 I defined filter as dataclass but wasn't converting it from dict to dataclass. That's why maybe no error as it is still a dictionary.

I will handle through frontend that field inside filter is either title or head_branch as in UI it will be displayed as List of Options.
On backend side we will rely only on type checking. works?

class IncidentPRFilter(TypedDict):
    field: Literal["title", "head_branch"]
    value: str

Copy link
Contributor

@adnanhashmi09 adnanhashmi09 May 23, 2025

Choose a reason for hiding this comment

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

i am not sure what you mean. If you want type safety using dataclasses then you need to adapt the json filter adapter to a dataclass first and then access the properties using the dot notation.
but if you don't want to go that route and have the filters remain as dicts then it would be nice to have them use TypedDicts if you want the syntactic sugar of having the types. Do remember, that typedDicts don't provide runtime typesafety. TypedDicts just help your intellisense work well.


def test_get_team_pr_incidents_with_multiple_repos_and_incidents():

prs_using_filters = [
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
prs_using_filters = [
resolution_prs = [

can be named better to signify what exactly those prs are
similar for other variables in the test

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My bad. This should have been named resolution_prs as they are fetched using our PR Incident filters.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also I shouldn't rename prs_using_numbers right ?
Because this PRs are Incident PRs is not confirmed yet.

Ex: Resolution PR revert 123 and we have 3 Repos.
Then we try to fetch PR number 123 from each Repo.
So len(prs_using_numbers) = 3 but only one of these will be Incident PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

sure

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: 0

♻️ Duplicate comments (3)
backend/analytics_server/mhq/service/incidents/incidents.py (3)

134-151: Optimize with defaultdict to eliminate conditional checks.

As suggested in past review comments, using defaultdict(dict) eliminates the need for the conditional check when accessing the nested dictionary.

The current implementation is already using defaultdict(dict) correctly at line 136-138, so this optimization is already in place.


118-126: ⚠️ Potential issue

Critical: Fix the asdict usage on non-dataclass.

This is the same issue flagged in past review comments. PRFilter is not a dataclass, so asdict(pr_filter) will raise a TypeError. Also, the variable name shadows the input parameter.

-        resolution_prs_filter: PRFilter = apply_pr_filter(
-            pr_filter=asdict(pr_filter),
+        resolution_prs_filter: PRFilter = apply_pr_filter(
+            pr_filter=vars(pr_filter) if pr_filter else None,
             entity_type=EntityType.TEAM,
             entity_id=team_id,
             setting_types=[
                 SettingType.EXCLUDED_PRS_SETTING,
                 SettingType.INCIDENT_PRS_SETTING,
             ],
         )

140-151: ⚠️ Potential issue

Critical: Add field validation to prevent AttributeError.

This is the same issue flagged in past review comments. The code could raise an AttributeError if filter["field"] is not a valid attribute of the PR object.

                 incident_pr_number = self._extract_pr_number_from_regex(
-                    getattr(pr, filter["field"]), filter["value"]
+                    getattr(pr, filter["field"], "") if hasattr(pr, filter["field"]) else "", 
+                    filter["value"]
                 )
🧹 Nitpick comments (1)
backend/analytics_server/mhq/service/incidents/incidents.py (1)

33-33: Remove unused import.

The asdict import from dataclasses is imported but not properly used in the code. Based on past review comments, there's an issue with its usage on line 119 where it's called on a PRFilter instance that isn't a dataclass.

-from dataclasses import asdict
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 930bd74 and c9be3e8.

📒 Files selected for processing (6)
  • backend/analytics_server/mhq/api/incidents.py (11 hunks)
  • backend/analytics_server/mhq/service/incidents/incidents.py (4 hunks)
  • backend/analytics_server/mhq/service/incidents/models/adapter.py (1 hunks)
  • backend/analytics_server/mhq/service/settings/models.py (2 hunks)
  • backend/analytics_server/mhq/store/models/code/filter.py (3 hunks)
  • backend/analytics_server/tests/service/Incidents/test_team_pr_incidents.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • backend/analytics_server/mhq/service/settings/models.py
  • backend/analytics_server/mhq/store/models/code/filter.py
  • backend/analytics_server/tests/service/Incidents/test_team_pr_incidents.py
  • backend/analytics_server/mhq/service/incidents/models/adapter.py
  • backend/analytics_server/mhq/api/incidents.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/analytics_server/mhq/service/incidents/incidents.py (10)
backend/analytics_server/mhq/service/settings/models.py (1)
  • IncidentPRsSetting (55-57)
backend/analytics_server/mhq/store/models/code/filter.py (1)
  • PRFilter (10-108)
backend/analytics_server/mhq/store/repos/code.py (3)
  • get_active_team_repos_by_team_id (263-268)
  • get_prs_merged_in_interval (287-308)
  • get_prs_merged_in_interval_by_numbers (311-327)
backend/analytics_server/mhq/utils/time.py (2)
  • time_now (10-11)
  • Interval (14-96)
backend/analytics_server/mhq/service/settings/configuration_settings.py (3)
  • SettingsService (22-490)
  • get_settings_service (493-494)
  • get_settings (119-131)
backend/analytics_server/mhq/service/incidents/models/adapter.py (1)
  • adaptIncidentPR (6-22)
backend/analytics_server/mhq/service/code/pr_filter.py (1)
  • apply_pr_filter (11-34)
backend/analytics_server/mhq/service/incidents/incident_filter.py (1)
  • apply_incident_filter (45-62)
backend/analytics_server/mhq/store/models/settings/configuration_settings.py (1)
  • SettingType (14-22)
backend/analytics_server/tests/service/Incidents/test_team_pr_incidents.py (4)
  • get_settings (30-50)
  • get_active_team_repos_by_team_id (63-73)
  • get_prs_merged_in_interval (75-76)
  • get_prs_merged_in_interval_by_numbers (78-79)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: All file linting
🔇 Additional comments (8)
backend/analytics_server/mhq/service/incidents/incidents.py (8)

3-8: LGTM! Imports are well-organized and necessary.

The new imports support the PR incident functionality correctly. All imports appear to be used appropriately within the code.


41-45: LGTM! Constructor properly updated with new dependency.

The addition of CodeRepoService dependency is correctly implemented and stored as an instance variable for use in the new PR incident functionality.


48-67: LGTM! Method correctly enhanced to include PR incidents.

The method properly combines traditional incidents with PR-based incidents, sorts by creation date, and deduplicates by incident key. The implementation addresses the previous issue of returning a proper list instead of dict_values.


69-91: LGTM! Consistent implementation with resolved incidents method.

The method follows the same pattern as get_resolved_team_incidents, correctly combining and deduplicating incidents from both sources.


153-167: LGTM! PR filtering and incident adaptation logic is sound.

The code correctly:

  • Queries PRs by numbers within the interval
  • Applies the PR filter
  • Maps PRs to their resolution PRs
  • Adapts them to incidents using the adapter function

170-179: LGTM! Safe regex extraction with proper validation.

The helper method correctly:

  • Validates input parameters
  • Uses the regex validation utility
  • Safely extracts the first capture group
  • Returns None for invalid patterns or no matches

219-239: LGTM! MTTR methods correctly updated to accept PR filter.

Both MTTR methods properly pass the pr_filter parameter to the underlying incident retrieval methods, maintaining consistency across the API.


342-344: LGTM! Factory function correctly updated with new dependency.

The factory function properly instantiates IncidentService with all required dependencies including the new CodeRepoService.

@adnanhashmi09 adnanhashmi09 merged commit 65b1873 into middlewarehq:main May 23, 2025
6 checks passed
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