Skip to content

Commit 65b1873

Browse files
authored
PR Incidents Filtering for better CFR and MTTR Reporting (#656) -> Backend
* Added Incident PRs Setting * Exclude Revert PR from Incident types based on IncidentPRsSetting and added tests * Core logic for Incident PR filtering * Updated Incident types setting tests * modify core logic for efficiency and add tests for incident prs * Set Include revert PRs default to True * Improved type safety and better variable naming
1 parent 6920e76 commit 65b1873

File tree

16 files changed

+696
-30
lines changed

16 files changed

+696
-30
lines changed

backend/analytics_server/mhq/api/incidents.py

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import json
2-
from typing import Dict, List
2+
from typing import Dict, List, Optional as typeOptional
33

44
from datetime import datetime
55

@@ -23,10 +23,10 @@
2323
adapt_mean_time_to_recovery_metrics,
2424
)
2525
from mhq.store.models.incidents import Incident
26-
2726
from mhq.api.request_utils import coerce_workflow_filter, queryschema
2827
from mhq.service.query_validator import get_query_validator
2928

29+
3030
app = Blueprint("incidents", __name__)
3131

3232

@@ -36,19 +36,29 @@
3636
{
3737
Required("from_time"): All(str, Coerce(datetime.fromisoformat)),
3838
Required("to_time"): All(str, Coerce(datetime.fromisoformat)),
39+
Optional("pr_filter"): All(str, Coerce(json.loads)),
3940
}
4041
),
4142
)
42-
def get_resolved_incidents(team_id: str, from_time: datetime, to_time: datetime):
43+
def get_resolved_incidents(
44+
team_id: str,
45+
from_time: datetime,
46+
to_time: datetime,
47+
pr_filter: typeOptional[Dict] = None,
48+
):
4349

4450
query_validator = get_query_validator()
4551
interval = query_validator.interval_validator(from_time, to_time)
4652
query_validator.team_validator(team_id)
4753

54+
pr_filter: PRFilter = apply_pr_filter(
55+
pr_filter, EntityType.TEAM, team_id, [SettingType.EXCLUDED_PRS_SETTING]
56+
)
57+
4858
incident_service = get_incident_service()
4959

5060
resolved_incidents: List[Incident] = incident_service.get_resolved_team_incidents(
51-
team_id, interval
61+
team_id, interval, pr_filter
5262
)
5363

5464
# ToDo: Generate a user map
@@ -71,7 +81,7 @@ def get_deployments_with_related_incidents(
7181
team_id: str,
7282
from_time: datetime,
7383
to_time: datetime,
74-
pr_filter: dict = None,
84+
pr_filter: typeOptional[Dict] = None,
7585
workflow_filter: WorkflowFilter = None,
7686
):
7787
query_validator = get_query_validator()
@@ -90,7 +100,9 @@ def get_deployments_with_related_incidents(
90100

91101
incident_service = get_incident_service()
92102

93-
incidents: List[Incident] = incident_service.get_team_incidents(team_id, interval)
103+
incidents: List[Incident] = incident_service.get_team_incidents(
104+
team_id, interval, pr_filter
105+
)
94106

95107
deployment_incidents_map: Dict[Deployment, List[Incident]] = (
96108
incident_service.get_deployment_incidents_map(deployments, incidents)
@@ -112,18 +124,28 @@ def get_deployments_with_related_incidents(
112124
{
113125
Required("from_time"): All(str, Coerce(datetime.fromisoformat)),
114126
Required("to_time"): All(str, Coerce(datetime.fromisoformat)),
127+
Optional("pr_filter"): All(str, Coerce(json.loads)),
115128
}
116129
),
117130
)
118-
def get_team_mttr(team_id: str, from_time: datetime, to_time: datetime):
131+
def get_team_mttr(
132+
team_id: str,
133+
from_time: datetime,
134+
to_time: datetime,
135+
pr_filter: typeOptional[Dict] = None,
136+
):
119137
query_validator = get_query_validator()
120138
interval = query_validator.interval_validator(from_time, to_time)
121139
query_validator.team_validator(team_id)
122140

141+
pr_filter: PRFilter = apply_pr_filter(
142+
pr_filter, EntityType.TEAM, team_id, [SettingType.EXCLUDED_PRS_SETTING]
143+
)
144+
123145
incident_service = get_incident_service()
124146

125147
team_mean_time_to_recovery_metrics = (
126-
incident_service.get_team_mean_time_to_recovery(team_id, interval)
148+
incident_service.get_team_mean_time_to_recovery(team_id, interval, pr_filter)
127149
)
128150

129151
return adapt_mean_time_to_recovery_metrics(team_mean_time_to_recovery_metrics)
@@ -135,18 +157,30 @@ def get_team_mttr(team_id: str, from_time: datetime, to_time: datetime):
135157
{
136158
Required("from_time"): All(str, Coerce(datetime.fromisoformat)),
137159
Required("to_time"): All(str, Coerce(datetime.fromisoformat)),
160+
Optional("pr_filter"): All(str, Coerce(json.loads)),
138161
}
139162
),
140163
)
141-
def get_team_mttr_trends(team_id: str, from_time: datetime, to_time: datetime):
164+
def get_team_mttr_trends(
165+
team_id: str,
166+
from_time: datetime,
167+
to_time: datetime,
168+
pr_filter: typeOptional[Dict] = None,
169+
):
142170
query_validator = get_query_validator()
143171
interval = query_validator.interval_validator(from_time, to_time)
144172
query_validator.team_validator(team_id)
145173

174+
pr_filter: PRFilter = apply_pr_filter(
175+
pr_filter, EntityType.TEAM, team_id, [SettingType.EXCLUDED_PRS_SETTING]
176+
)
177+
146178
incident_service = get_incident_service()
147179

148180
weekly_mean_time_to_recovery_metrics = (
149-
incident_service.get_team_mean_time_to_recovery_trends(team_id, interval)
181+
incident_service.get_team_mean_time_to_recovery_trends(
182+
team_id, interval, pr_filter
183+
)
150184
)
151185

152186
return {
@@ -172,7 +206,7 @@ def get_team_cfr(
172206
team_id: str,
173207
from_time: datetime,
174208
to_time: datetime,
175-
pr_filter: dict = None,
209+
pr_filter: typeOptional[Dict] = None,
176210
workflow_filter: WorkflowFilter = None,
177211
):
178212

@@ -192,7 +226,9 @@ def get_team_cfr(
192226

193227
incident_service = get_incident_service()
194228

195-
incidents: List[Incident] = incident_service.get_team_incidents(team_id, interval)
229+
incidents: List[Incident] = incident_service.get_team_incidents(
230+
team_id, interval, pr_filter
231+
)
196232

197233
team_change_failure_rate: ChangeFailureRateMetrics = (
198234
incident_service.get_change_failure_rate_metrics(deployments, incidents)
@@ -216,7 +252,7 @@ def get_team_cfr_trends(
216252
team_id: str,
217253
from_time: datetime,
218254
to_time: datetime,
219-
pr_filter: dict = None,
255+
pr_filter: typeOptional[Dict] = None,
220256
workflow_filter: WorkflowFilter = None,
221257
):
222258

@@ -236,7 +272,9 @@ def get_team_cfr_trends(
236272

237273
incident_service = get_incident_service()
238274

239-
incidents: List[Incident] = incident_service.get_team_incidents(team_id, interval)
275+
incidents: List[Incident] = incident_service.get_team_incidents(
276+
team_id, interval, pr_filter
277+
)
240278

241279
team_weekly_change_failure_rate: Dict[datetime, ChangeFailureRateMetrics] = (
242280
incident_service.get_weekly_change_failure_rate(

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
ExcludedPRsSetting,
66
IncidentTypesSetting,
77
IncidentSourcesSetting,
8+
IncidentPRsSetting,
89
)
910
from mhq.store.models import EntityType
1011

@@ -55,6 +56,12 @@ 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+
"filters": config_settings.specific_settings.filters,
63+
}
64+
5865
# ADD NEW API ADAPTER HERE
5966

6067
return response

backend/analytics_server/mhq/service/code/pr_filter.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from typing import List, Dict, Any
22

33
from mhq.service.settings.configuration_settings import get_settings_service
4-
from mhq.service.settings.models import ExcludedPRsSetting
4+
from mhq.service.settings.models import ExcludedPRsSetting, IncidentPRsSetting
55
from mhq.store.models.code import PRFilter
66
from mhq.store.models.settings.configuration_settings import SettingType
77
from mhq.store.models.settings.enums import EntityType
@@ -103,6 +103,8 @@ def apply(self) -> PRFilter:
103103
)
104104
if setting_type == SettingType.EXCLUDED_PRS_SETTING:
105105
self._apply_excluded_pr_ids_setting(setting=setting)
106+
if setting_type == SettingType.INCIDENT_PRS_SETTING:
107+
self._apply_incident_prs_setting(setting=setting)
106108

107109
return self.pr_filter
108110

@@ -111,3 +113,8 @@ def _apply_excluded_pr_ids_setting(self, setting: ExcludedPRsSetting):
111113
self.pr_filter.excluded_pr_ids = (
112114
self.pr_filter.excluded_pr_ids or []
113115
) + setting.excluded_pr_ids
116+
117+
def _apply_incident_prs_setting(self, setting: IncidentPRsSetting):
118+
self.pr_filter.incident_pr_filters = (
119+
self.pr_filter.incident_pr_filters or []
120+
) + setting.filters

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
get_settings_service,
55
IncidentSettings,
66
IncidentTypesSetting,
7+
IncidentPRsSetting,
78
)
89
from mhq.store.models.incidents import IncidentFilter
10+
from mhq.store.models.incidents.enums import IncidentType
911

1012
from mhq.store.models.settings import EntityType
1113

@@ -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

0 commit comments

Comments
 (0)