Skip to content

feat(pr_filtering): Added Incident PRs Setting, handle race condition and Revert PR filter #650

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

10 changes: 9 additions & 1 deletion backend/analytics_server/mhq/api/resources/settings_resource.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from mhq.service.settings.models import (
ConfigurationSettings,
DefaultSyncDaysSetting,
IncidentSettings,
ExcludedPRsSetting,
IncidentTypesSetting,
IncidentSourcesSetting,
DefaultSyncDaysSetting,
IncidentPrsSetting,
)
from mhq.store.models import EntityType

@@ -55,6 +56,13 @@ def _add_setting_data(config_settings: ConfigurationSettings, response):
"default_sync_days": config_settings.specific_settings.default_sync_days
}

if isinstance(config_settings.specific_settings, IncidentPrsSetting):
response["setting"] = {
"include_revert_prs": config_settings.specific_settings.include_revert_prs,
"title_filters": config_settings.specific_settings.title_filters,
"head_branch_filters": config_settings.specific_settings.head_branch_filters,
}

# ADD NEW API ADAPTER HERE

return response
17 changes: 17 additions & 0 deletions backend/analytics_server/mhq/service/incidents/incident_filter.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from typing import Dict, List, Any, Optional
from mhq.store.models.incidents.enums import IncidentType
from mhq.store.models.settings.configuration_settings import SettingType
from mhq.service.settings.configuration_settings import (
get_settings_service,
IncidentSettings,
IncidentTypesSetting,
IncidentPrsSetting,
)
from mhq.store.models.incidents import IncidentFilter

@@ -106,4 +108,19 @@ def __incident_type_setting(self) -> List[str]:
incident_types = []
if setting and isinstance(setting, IncidentTypesSetting):
incident_types = setting.incident_types

if SettingType.INCIDENT_PRS_SETTING in self.setting_types:
incident_prs_setting: Optional[IncidentPrsSetting] = (
self.setting_type_to_settings_map.get(SettingType.INCIDENT_PRS_SETTING)
)
if (
isinstance(incident_prs_setting, IncidentPrsSetting)
and not incident_prs_setting.include_revert_prs
):
incident_types = [
incident_type
for incident_type in incident_types
if incident_type != IncidentType.REVERT_PR
]

return incident_types
Original file line number Diff line number Diff line change
@@ -4,11 +4,12 @@
from mhq.service.settings.default_settings_data import get_default_setting_data
from mhq.service.settings.models import (
ConfigurationSettings,
DefaultSyncDaysSetting,
ExcludedPRsSetting,
IncidentSettings,
IncidentSourcesSetting,
IncidentTypesSetting,
DefaultSyncDaysSetting,
IncidentPrsSetting,
)
from mhq.store.models.core.users import Users
from mhq.store.models.incidents import IncidentSource, IncidentType
@@ -66,6 +67,15 @@ def _adapt_default_sync_days_setting_from_setting_data(self, data: Dict[str, any
default_sync_days=data.get("default_sync_days", None)
)

def _adapt_incident_prs_setting_setting_from_setting_data(
self, data: Dict[str, any]
):
return IncidentPrsSetting(
include_revert_prs=data.get("include_revert_prs", True),
title_filters=data.get("title_filters", []),
head_branch_filters=data.get("head_branch_filters", []),
)

# ADD NEW DICT TO DATACLASS ADAPTERS HERE

def _handle_config_setting_from_db_setting(
@@ -88,6 +98,11 @@ def _handle_config_setting_from_db_setting(
if setting_type == SettingType.DEFAULT_SYNC_DAYS_SETTING:
return self._adapt_default_sync_days_setting_from_setting_data(setting_data)

if setting_type == SettingType.INCIDENT_PRS_SETTING:
return self._adapt_incident_prs_setting_setting_from_setting_data(
setting_data
)

# ADD NEW HANDLE FROM DB SETTINGS HERE

raise Exception(f"Invalid Setting Type: {setting_type}")
@@ -131,7 +146,14 @@ def get_or_set_default_settings(
setting = self.get_settings(setting_type, entity_type, entity_id)

if not setting:
setting = self.save_settings(setting_type, entity_type, entity_id)
try:
setting = self.save_settings(setting_type, entity_type, entity_id)
except Exception as e:
if "UniqueViolation" in str(e) and "Settings_pkey" in str(e):
# If another concurrent request already created the settings, fetch that settings
setting = self.get_settings(setting_type, entity_type, entity_id)
else:
raise e

return setting

@@ -182,6 +204,13 @@ def _adapt_default_sync_days_setting_from_json(self, data: Dict[str, any]):
default_sync_days=data.get("default_sync_days", None)
)

def _adapt_incident_prs_setting_setting_from_json(self, data: Dict[str, any]):
return IncidentPrsSetting(
include_revert_prs=data.get("include_revert_prs", True),
title_filters=data.get("title_filters", []),
head_branch_filters=data.get("head_branch_filters", []),
)

# ADD NEW DICT TO API ADAPTERS HERE

def _handle_config_setting_from_json_data(
@@ -204,6 +233,9 @@ def _handle_config_setting_from_json_data(
if setting_type == SettingType.DEFAULT_SYNC_DAYS_SETTING:
return self._adapt_default_sync_days_setting_from_json(setting_data)

if setting_type == SettingType.INCIDENT_PRS_SETTING:
return self._adapt_incident_prs_setting_setting_from_json(setting_data)

# ADD NEW HANDLE FROM JSON DATA HERE

raise Exception(f"Invalid Setting Type: {setting_type}")
@@ -242,6 +274,15 @@ def _adapt_default_sync_days_setting_json_data(
):
return {"default_sync_days": specific_setting.default_sync_days}

def _adapt_incident_prs_setting_setting_json_data(
self, specific_setting: IncidentPrsSetting
):
return {
"include_revert_prs": specific_setting.include_revert_prs,
"title_filters": specific_setting.title_filters,
"head_branch_filters": specific_setting.head_branch_filters,
}

# ADD NEW DATACLASS TO JSON DATA ADAPTERS HERE

def _handle_config_setting_to_db_setting(
@@ -273,6 +314,11 @@ def _handle_config_setting_to_db_setting(
):
return self._adapt_default_sync_days_setting_json_data(specific_setting)

if setting_type == SettingType.INCIDENT_PRS_SETTING and isinstance(
specific_setting, IncidentPrsSetting
):
return self._adapt_incident_prs_setting_setting_json_data(specific_setting)

# ADD NEW HANDLE TO DB SETTINGS HERE

raise Exception(f"Invalid Setting Type: {setting_type}")
Original file line number Diff line number Diff line change
@@ -29,6 +29,13 @@ def get_default_setting_data(setting_type: SettingType):
if setting_type == SettingType.DEFAULT_SYNC_DAYS_SETTING:
return {"default_sync_days": 31}

if setting_type == SettingType.INCIDENT_PRS_SETTING:
return {
"include_revert_prs": True,
"title_filters": [],
"head_branch_filters": [],
}

# ADD NEW DEFAULT SETTING HERE

raise Exception(f"Invalid Setting Type: {setting_type}")
7 changes: 7 additions & 0 deletions backend/analytics_server/mhq/service/settings/models.py
Original file line number Diff line number Diff line change
@@ -46,6 +46,13 @@ class DefaultSyncDaysSetting(BaseSetting):
default_sync_days: int


@dataclass
class IncidentPrsSetting(BaseSetting):
include_revert_prs: bool
title_filters: List[str]
head_branch_filters: List[str]


# ADD NEW SETTING CLASS HERE

# Sample Future Settings
Original file line number Diff line number Diff line change
@@ -19,6 +19,9 @@ def settings_type_validator(setting_type: str):
if setting_type == SettingType.DEFAULT_SYNC_DAYS_SETTING.value:
return SettingType.DEFAULT_SYNC_DAYS_SETTING

if setting_type == SettingType.INCIDENT_PRS_SETTING.value:
return SettingType.INCIDENT_PRS_SETTING

# ADD NEW VALIDATOR HERE

raise BadRequest(f"Invalid Setting Type: {setting_type}")
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ class SettingType(Enum):
INCIDENT_TYPES_SETTING = "INCIDENT_TYPES_SETTING"
INCIDENT_SOURCES_SETTING = "INCIDENT_SOURCES_SETTING"
EXCLUDED_PRS_SETTING = "EXCLUDED_PRS_SETTING"
INCIDENT_PRS_SETTING = "INCIDENT_PRS_SETTING"
DEFAULT_SYNC_DAYS_SETTING = "DEFAULT_SYNC_DAYS_SETTING"

# ADD NEW SETTING TYPE ENUM HERE
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
from mhq.service.incidents.incident_filter import ConfigurationsIncidentFilterProcessor
from mhq.store.models.incidents import IncidentFilter
from mhq.store.models.settings.configuration_settings import SettingType
from mhq.service.settings.configuration_settings import (
IncidentTypesSetting,
IncidentPrsSetting,
)
from mhq.store.models.incidents.enums import IncidentType
from mhq.store.models.settings import EntityType


def test_get_incident_types_when_only_types_setting_present():
setting_types = [SettingType.INCIDENT_TYPES_SETTING]
setting_type_to_settings_map = {
SettingType.INCIDENT_TYPES_SETTING: IncidentTypesSetting(
incident_types=[
IncidentType.INCIDENT,
IncidentType.ALERT,
IncidentType.REVERT_PR,
]
)
}

incident_filter = ConfigurationsIncidentFilterProcessor(
incident_filter=IncidentFilter(),
entity_type=EntityType.TEAM,
entity_id="team_id",
setting_types=setting_types,
setting_type_to_settings_map=setting_type_to_settings_map,
).apply()

expected_incident_types = [
IncidentType.INCIDENT,
IncidentType.ALERT,
IncidentType.REVERT_PR,
]

assert incident_filter.incident_types == expected_incident_types


def test_get_incident_types_when_types_setting_is_empty():
setting_types = [SettingType.INCIDENT_TYPES_SETTING]
setting_type_to_settings_map = {
SettingType.INCIDENT_TYPES_SETTING: IncidentTypesSetting(incident_types=[])
}

incident_filter = ConfigurationsIncidentFilterProcessor(
incident_filter=IncidentFilter(),
entity_type=EntityType.TEAM,
entity_id="dummy_id",
setting_types=setting_types,
setting_type_to_settings_map=setting_type_to_settings_map,
).apply()

assert incident_filter.incident_types == []


def test_get_incident_types_when_only_prs_setting_present_returns_none():
setting_types = [SettingType.INCIDENT_PRS_SETTING]
incident_prs_setting = IncidentPrsSetting(
include_revert_prs=True, title_filters=[], head_branch_filters=[]
)
setting_type_to_settings_map = {
SettingType.INCIDENT_PRS_SETTING: incident_prs_setting
}

incident_filter = ConfigurationsIncidentFilterProcessor(
incident_filter=IncidentFilter(),
entity_type=EntityType.TEAM,
entity_id="team_id",
setting_types=setting_types,
setting_type_to_settings_map=setting_type_to_settings_map,
).apply()

assert incident_filter.incident_types is None


def test_get_incident_types_when_both_types_and_prs_settings_present_and_includes_revert_prs():
setting_types = [
SettingType.INCIDENT_TYPES_SETTING,
SettingType.INCIDENT_PRS_SETTING,
]
incident_prs_setting = IncidentPrsSetting(
include_revert_prs=True, title_filters=[], head_branch_filters=[]
)
setting_type_to_settings_map = {
SettingType.INCIDENT_TYPES_SETTING: IncidentTypesSetting(
incident_types=[IncidentType.INCIDENT, IncidentType.REVERT_PR]
),
SettingType.INCIDENT_PRS_SETTING: incident_prs_setting,
}

incident_filter = ConfigurationsIncidentFilterProcessor(
incident_filter=IncidentFilter(),
entity_type=EntityType.TEAM,
entity_id="team_id",
setting_types=setting_types,
setting_type_to_settings_map=setting_type_to_settings_map,
).apply()

expected_incident_types = [IncidentType.INCIDENT, IncidentType.REVERT_PR]

assert incident_filter.incident_types == expected_incident_types


def test_get_incident_types_when_both_settings_present_and_not_includes_revert_prs():
setting_types = [
SettingType.INCIDENT_TYPES_SETTING,
SettingType.INCIDENT_PRS_SETTING,
]
incident_prs_setting = IncidentPrsSetting(
include_revert_prs=False, title_filters=[], head_branch_filters=[]
)
setting_type_to_settings_map = {
SettingType.INCIDENT_TYPES_SETTING: IncidentTypesSetting(
incident_types=[IncidentType.INCIDENT, IncidentType.REVERT_PR]
),
SettingType.INCIDENT_PRS_SETTING: incident_prs_setting,
}

incident_filter = ConfigurationsIncidentFilterProcessor(
incident_filter=IncidentFilter(),
entity_type=EntityType.TEAM,
entity_id="team_id",
setting_types=setting_types,
setting_type_to_settings_map=setting_type_to_settings_map,
).apply()

expected_incident_types = [IncidentType.INCIDENT]

assert incident_filter.incident_types == expected_incident_types