Skip to content

Commit b5c9275

Browse files
authored
feat(pr_filtering): Added Incident PRs Setting, handle race condition and Revert PR filter (#650)
* feat(pr_filtering): Added Incident PRs Setting * feat(pr_filtering): Handle race condition when creating default settings in db * feat(pr_filtering): Exclude Revert PR from Incident types based on IncidentPrsSetting * feat(pr_filtering): tests for incident types setting * feat(pr_filtering): updated a test method name * feat(pr_filtering): remove pr_mapping_field and pr_mapping_pattern from settings
1 parent 055bb47 commit b5c9275

File tree

8 files changed

+223
-3
lines changed

8 files changed

+223
-3
lines changed

backend/analytics_server/mhq/api/resources/settings_resource.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from mhq.service.settings.models import (
22
ConfigurationSettings,
3-
DefaultSyncDaysSetting,
43
IncidentSettings,
54
ExcludedPRsSetting,
65
IncidentTypesSetting,
76
IncidentSourcesSetting,
7+
DefaultSyncDaysSetting,
8+
IncidentPrsSetting,
89
)
910
from mhq.store.models import EntityType
1011

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

59+
if isinstance(config_settings.specific_settings, IncidentPrsSetting):
60+
response["setting"] = {
61+
"include_revert_prs": config_settings.specific_settings.include_revert_prs,
62+
"title_filters": config_settings.specific_settings.title_filters,
63+
"head_branch_filters": config_settings.specific_settings.head_branch_filters,
64+
}
65+
5866
# ADD NEW API ADAPTER HERE
5967

6068
return response

backend/analytics_server/mhq/service/incidents/incident_filter.py

+17
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from typing import Dict, List, Any, Optional
2+
from mhq.store.models.incidents.enums import IncidentType
23
from mhq.store.models.settings.configuration_settings import SettingType
34
from mhq.service.settings.configuration_settings import (
45
get_settings_service,
56
IncidentSettings,
67
IncidentTypesSetting,
8+
IncidentPrsSetting,
79
)
810
from mhq.store.models.incidents import IncidentFilter
911

@@ -106,4 +108,19 @@ def __incident_type_setting(self) -> List[str]:
106108
incident_types = []
107109
if setting and isinstance(setting, IncidentTypesSetting):
108110
incident_types = setting.incident_types
111+
112+
if SettingType.INCIDENT_PRS_SETTING in self.setting_types:
113+
incident_prs_setting: Optional[IncidentPrsSetting] = (
114+
self.setting_type_to_settings_map.get(SettingType.INCIDENT_PRS_SETTING)
115+
)
116+
if (
117+
isinstance(incident_prs_setting, IncidentPrsSetting)
118+
and not incident_prs_setting.include_revert_prs
119+
):
120+
incident_types = [
121+
incident_type
122+
for incident_type in incident_types
123+
if incident_type != IncidentType.REVERT_PR
124+
]
125+
109126
return incident_types

backend/analytics_server/mhq/service/settings/configuration_settings.py

+48-2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
from mhq.service.settings.default_settings_data import get_default_setting_data
55
from mhq.service.settings.models import (
66
ConfigurationSettings,
7-
DefaultSyncDaysSetting,
87
ExcludedPRsSetting,
98
IncidentSettings,
109
IncidentSourcesSetting,
1110
IncidentTypesSetting,
11+
DefaultSyncDaysSetting,
12+
IncidentPrsSetting,
1213
)
1314
from mhq.store.models.core.users import Users
1415
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
6667
default_sync_days=data.get("default_sync_days", None)
6768
)
6869

70+
def _adapt_incident_prs_setting_setting_from_setting_data(
71+
self, data: Dict[str, any]
72+
):
73+
return IncidentPrsSetting(
74+
include_revert_prs=data.get("include_revert_prs", True),
75+
title_filters=data.get("title_filters", []),
76+
head_branch_filters=data.get("head_branch_filters", []),
77+
)
78+
6979
# ADD NEW DICT TO DATACLASS ADAPTERS HERE
7080

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

101+
if setting_type == SettingType.INCIDENT_PRS_SETTING:
102+
return self._adapt_incident_prs_setting_setting_from_setting_data(
103+
setting_data
104+
)
105+
91106
# ADD NEW HANDLE FROM DB SETTINGS HERE
92107

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

133148
if not setting:
134-
setting = self.save_settings(setting_type, entity_type, entity_id)
149+
try:
150+
setting = self.save_settings(setting_type, entity_type, entity_id)
151+
except Exception as e:
152+
if "UniqueViolation" in str(e) and "Settings_pkey" in str(e):
153+
# If another concurrent request already created the settings, fetch that settings
154+
setting = self.get_settings(setting_type, entity_type, entity_id)
155+
else:
156+
raise e
135157

136158
return setting
137159

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

207+
def _adapt_incident_prs_setting_setting_from_json(self, data: Dict[str, any]):
208+
return IncidentPrsSetting(
209+
include_revert_prs=data.get("include_revert_prs", True),
210+
title_filters=data.get("title_filters", []),
211+
head_branch_filters=data.get("head_branch_filters", []),
212+
)
213+
185214
# ADD NEW DICT TO API ADAPTERS HERE
186215

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

236+
if setting_type == SettingType.INCIDENT_PRS_SETTING:
237+
return self._adapt_incident_prs_setting_setting_from_json(setting_data)
238+
207239
# ADD NEW HANDLE FROM JSON DATA HERE
208240

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

277+
def _adapt_incident_prs_setting_setting_json_data(
278+
self, specific_setting: IncidentPrsSetting
279+
):
280+
return {
281+
"include_revert_prs": specific_setting.include_revert_prs,
282+
"title_filters": specific_setting.title_filters,
283+
"head_branch_filters": specific_setting.head_branch_filters,
284+
}
285+
245286
# ADD NEW DATACLASS TO JSON DATA ADAPTERS HERE
246287

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

317+
if setting_type == SettingType.INCIDENT_PRS_SETTING and isinstance(
318+
specific_setting, IncidentPrsSetting
319+
):
320+
return self._adapt_incident_prs_setting_setting_json_data(specific_setting)
321+
276322
# ADD NEW HANDLE TO DB SETTINGS HERE
277323

278324
raise Exception(f"Invalid Setting Type: {setting_type}")

backend/analytics_server/mhq/service/settings/default_settings_data.py

+7
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ def get_default_setting_data(setting_type: SettingType):
2929
if setting_type == SettingType.DEFAULT_SYNC_DAYS_SETTING:
3030
return {"default_sync_days": 31}
3131

32+
if setting_type == SettingType.INCIDENT_PRS_SETTING:
33+
return {
34+
"include_revert_prs": True,
35+
"title_filters": [],
36+
"head_branch_filters": [],
37+
}
38+
3239
# ADD NEW DEFAULT SETTING HERE
3340

3441
raise Exception(f"Invalid Setting Type: {setting_type}")

backend/analytics_server/mhq/service/settings/models.py

+7
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ class DefaultSyncDaysSetting(BaseSetting):
4646
default_sync_days: int
4747

4848

49+
@dataclass
50+
class IncidentPrsSetting(BaseSetting):
51+
include_revert_prs: bool
52+
title_filters: List[str]
53+
head_branch_filters: List[str]
54+
55+
4956
# ADD NEW SETTING CLASS HERE
5057

5158
# Sample Future Settings

backend/analytics_server/mhq/service/settings/setting_type_validator.py

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ def settings_type_validator(setting_type: str):
1919
if setting_type == SettingType.DEFAULT_SYNC_DAYS_SETTING.value:
2020
return SettingType.DEFAULT_SYNC_DAYS_SETTING
2121

22+
if setting_type == SettingType.INCIDENT_PRS_SETTING.value:
23+
return SettingType.INCIDENT_PRS_SETTING
24+
2225
# ADD NEW VALIDATOR HERE
2326

2427
raise BadRequest(f"Invalid Setting Type: {setting_type}")

backend/analytics_server/mhq/store/models/settings/configuration_settings.py

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class SettingType(Enum):
1616
INCIDENT_TYPES_SETTING = "INCIDENT_TYPES_SETTING"
1717
INCIDENT_SOURCES_SETTING = "INCIDENT_SOURCES_SETTING"
1818
EXCLUDED_PRS_SETTING = "EXCLUDED_PRS_SETTING"
19+
INCIDENT_PRS_SETTING = "INCIDENT_PRS_SETTING"
1920
DEFAULT_SYNC_DAYS_SETTING = "DEFAULT_SYNC_DAYS_SETTING"
2021

2122
# ADD NEW SETTING TYPE ENUM HERE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
from mhq.service.incidents.incident_filter import ConfigurationsIncidentFilterProcessor
2+
from mhq.store.models.incidents import IncidentFilter
3+
from mhq.store.models.settings.configuration_settings import SettingType
4+
from mhq.service.settings.configuration_settings import (
5+
IncidentTypesSetting,
6+
IncidentPrsSetting,
7+
)
8+
from mhq.store.models.incidents.enums import IncidentType
9+
from mhq.store.models.settings import EntityType
10+
11+
12+
def test_get_incident_types_when_only_types_setting_present():
13+
setting_types = [SettingType.INCIDENT_TYPES_SETTING]
14+
setting_type_to_settings_map = {
15+
SettingType.INCIDENT_TYPES_SETTING: IncidentTypesSetting(
16+
incident_types=[
17+
IncidentType.INCIDENT,
18+
IncidentType.ALERT,
19+
IncidentType.REVERT_PR,
20+
]
21+
)
22+
}
23+
24+
incident_filter = ConfigurationsIncidentFilterProcessor(
25+
incident_filter=IncidentFilter(),
26+
entity_type=EntityType.TEAM,
27+
entity_id="team_id",
28+
setting_types=setting_types,
29+
setting_type_to_settings_map=setting_type_to_settings_map,
30+
).apply()
31+
32+
expected_incident_types = [
33+
IncidentType.INCIDENT,
34+
IncidentType.ALERT,
35+
IncidentType.REVERT_PR,
36+
]
37+
38+
assert incident_filter.incident_types == expected_incident_types
39+
40+
41+
def test_get_incident_types_when_types_setting_is_empty():
42+
setting_types = [SettingType.INCIDENT_TYPES_SETTING]
43+
setting_type_to_settings_map = {
44+
SettingType.INCIDENT_TYPES_SETTING: IncidentTypesSetting(incident_types=[])
45+
}
46+
47+
incident_filter = ConfigurationsIncidentFilterProcessor(
48+
incident_filter=IncidentFilter(),
49+
entity_type=EntityType.TEAM,
50+
entity_id="dummy_id",
51+
setting_types=setting_types,
52+
setting_type_to_settings_map=setting_type_to_settings_map,
53+
).apply()
54+
55+
assert incident_filter.incident_types == []
56+
57+
58+
def test_get_incident_types_when_only_prs_setting_present_returns_none():
59+
setting_types = [SettingType.INCIDENT_PRS_SETTING]
60+
incident_prs_setting = IncidentPrsSetting(
61+
include_revert_prs=True, title_filters=[], head_branch_filters=[]
62+
)
63+
setting_type_to_settings_map = {
64+
SettingType.INCIDENT_PRS_SETTING: incident_prs_setting
65+
}
66+
67+
incident_filter = ConfigurationsIncidentFilterProcessor(
68+
incident_filter=IncidentFilter(),
69+
entity_type=EntityType.TEAM,
70+
entity_id="team_id",
71+
setting_types=setting_types,
72+
setting_type_to_settings_map=setting_type_to_settings_map,
73+
).apply()
74+
75+
assert incident_filter.incident_types is None
76+
77+
78+
def test_get_incident_types_when_both_types_and_prs_settings_present_and_includes_revert_prs():
79+
setting_types = [
80+
SettingType.INCIDENT_TYPES_SETTING,
81+
SettingType.INCIDENT_PRS_SETTING,
82+
]
83+
incident_prs_setting = IncidentPrsSetting(
84+
include_revert_prs=True, title_filters=[], head_branch_filters=[]
85+
)
86+
setting_type_to_settings_map = {
87+
SettingType.INCIDENT_TYPES_SETTING: IncidentTypesSetting(
88+
incident_types=[IncidentType.INCIDENT, IncidentType.REVERT_PR]
89+
),
90+
SettingType.INCIDENT_PRS_SETTING: incident_prs_setting,
91+
}
92+
93+
incident_filter = ConfigurationsIncidentFilterProcessor(
94+
incident_filter=IncidentFilter(),
95+
entity_type=EntityType.TEAM,
96+
entity_id="team_id",
97+
setting_types=setting_types,
98+
setting_type_to_settings_map=setting_type_to_settings_map,
99+
).apply()
100+
101+
expected_incident_types = [IncidentType.INCIDENT, IncidentType.REVERT_PR]
102+
103+
assert incident_filter.incident_types == expected_incident_types
104+
105+
106+
def test_get_incident_types_when_both_settings_present_and_not_includes_revert_prs():
107+
setting_types = [
108+
SettingType.INCIDENT_TYPES_SETTING,
109+
SettingType.INCIDENT_PRS_SETTING,
110+
]
111+
incident_prs_setting = IncidentPrsSetting(
112+
include_revert_prs=False, title_filters=[], head_branch_filters=[]
113+
)
114+
setting_type_to_settings_map = {
115+
SettingType.INCIDENT_TYPES_SETTING: IncidentTypesSetting(
116+
incident_types=[IncidentType.INCIDENT, IncidentType.REVERT_PR]
117+
),
118+
SettingType.INCIDENT_PRS_SETTING: incident_prs_setting,
119+
}
120+
121+
incident_filter = ConfigurationsIncidentFilterProcessor(
122+
incident_filter=IncidentFilter(),
123+
entity_type=EntityType.TEAM,
124+
entity_id="team_id",
125+
setting_types=setting_types,
126+
setting_type_to_settings_map=setting_type_to_settings_map,
127+
).apply()
128+
129+
expected_incident_types = [IncidentType.INCIDENT]
130+
131+
assert incident_filter.incident_types == expected_incident_types

0 commit comments

Comments
 (0)