Skip to content
This repository was archived by the owner on May 5, 2025. It is now read-only.

Commit 25efc25

Browse files
committed
build out types for status and checks, for passing information back to other notifiers
1 parent eabddaf commit 25efc25

File tree

7 files changed

+169
-71
lines changed

7 files changed

+169
-71
lines changed

services/notification/__init__.py

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,7 @@
3737
ChecksWithFallback,
3838
)
3939
from services.notification.notifiers.codecov_slack_app import CodecovSlackAppNotifier
40-
from services.notification.notifiers.mixins.status import (
41-
StatusState,
42-
custom_target_helper_text,
43-
)
40+
from services.notification.notifiers.mixins.status import StatusState
4441
from services.yaml import read_yaml_field
4542
from services.yaml.reader import get_components_from_yaml
4643

@@ -261,7 +258,7 @@ def notify(self, comparison: ComparisonProxy) -> list[IndividualResult]:
261258
)
262259

263260
results = []
264-
add_custom_target_helper_text_to_pr_comment = False
261+
status_or_checks_helper_text = {}
265262
notifiers_to_notify = [
266263
notifier
267264
for notifier in self.get_notifiers_instances()
@@ -280,34 +277,33 @@ def notify(self, comparison: ComparisonProxy) -> list[IndividualResult]:
280277
if notifier.notification_type not in notification_type_status_or_checks
281278
]
282279

283-
if status_or_checks_notifiers and all_other_notifiers:
280+
status_or_checks_results = [
281+
self.notify_individual_notifier(notifier, comparison)
282+
for notifier in status_or_checks_notifiers
283+
]
284+
285+
if status_or_checks_results and all_other_notifiers:
284286
# if the status/check fails, sometimes we want to add helper text to the message of the other notifications,
285287
# to better surface that the status/check failed.
286288
# so if there are status_and_checks_notifiers and all_other_notifiers, do the status_and_checks_notifiers first,
287289
# look at the results of the checks, if any failed AND they are the type we have helper text for,
288290
# add that text onto the other notifiers messages.
289-
status_or_checks_results = [
290-
self.notify_individual_notifier(notifier, comparison)
291-
for notifier in status_or_checks_notifiers
292-
]
293-
294291
for result in status_or_checks_results:
295292
notification_result = result["result"]
296293
if (
297294
notification_result is not None
298295
and notification_result.data_sent.get("state")
299296
== StatusState.failure.value
300-
):
301-
if custom_target_helper_text in notification_result.data_sent.get(
302-
"message"
303-
):
304-
add_custom_target_helper_text_to_pr_comment = True
305-
results = results + status_or_checks_results
297+
) and notification_result.data_sent.get("included_helper_text"):
298+
status_or_checks_helper_text.update(
299+
notification_result.data_sent["included_helper_text"]
300+
)
301+
# TODO: pass status_or_checks_helper_text to all_other_notifiers,
302+
# where they can integrate the helper text into their messages
303+
results = results + status_or_checks_results
306304

307305
results = results + [
308-
self.notify_individual_notifier(
309-
notifier, comparison, add_custom_target_helper_text_to_pr_comment
310-
)
306+
self.notify_individual_notifier(notifier, comparison)
311307
for notifier in all_other_notifiers
312308
]
313309
return results
@@ -316,7 +312,6 @@ def notify_individual_notifier(
316312
self,
317313
notifier: AbstractBaseNotifier,
318314
comparison: ComparisonProxy,
319-
add_custom_target_helper_text_to_pr_comment: bool = False,
320315
) -> IndividualResult:
321316
commit = comparison.head.commit
322317
base_commit = comparison.project_coverage_base.commit
@@ -337,9 +332,7 @@ def notify_individual_notifier(
337332
notifier=notifier.name, title=notifier.title, result=None
338333
)
339334
try:
340-
res = notifier.notify(
341-
comparison, add_custom_target_helper_text_to_pr_comment
342-
)
335+
res = notifier.notify(comparison)
343336
individual_result["result"] = res
344337

345338
notifier.store_results(comparison, res)
Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,34 @@
1+
from typing import Any, TypedDict
2+
13
from database.enums import Notification
24
from services.comparison import ComparisonProxy, FilteredComparison
35
from services.notification.notifiers.checks.base import ChecksNotifier
4-
from services.notification.notifiers.mixins.status import StatusPatchMixin
6+
from services.notification.notifiers.mixins.status import StatusPatchMixin, StatusState
57
from services.yaml import read_yaml_field
68

79

10+
class CheckOutput(TypedDict):
11+
title: str
12+
summary: str
13+
annotations: list[Any]
14+
15+
16+
class CheckResult(TypedDict):
17+
state: StatusState
18+
output: CheckOutput
19+
included_helper_text: dict[str, str]
20+
21+
822
class PatchChecksNotifier(StatusPatchMixin, ChecksNotifier):
923
context = "patch"
1024

1125
@property
1226
def notification_type(self) -> Notification:
1327
return Notification.checks_patch
1428

15-
def build_payload(self, comparison: ComparisonProxy | FilteredComparison) -> dict:
29+
def build_payload(
30+
self, comparison: ComparisonProxy | FilteredComparison
31+
) -> CheckResult:
1632
"""
1733
This method build the paylod of the patch github checks.
1834
@@ -21,17 +37,21 @@ def build_payload(self, comparison: ComparisonProxy | FilteredComparison) -> dic
2137
"""
2238
if self.is_empty_upload():
2339
state, message = self.get_status_check_for_empty_upload()
24-
return {
25-
"state": state,
26-
"output": {
27-
"title": "Empty Upload",
28-
"summary": message,
29-
},
30-
}
31-
state, message = self.get_patch_status(comparison)
40+
result = CheckResult(
41+
state=state,
42+
output=CheckOutput(
43+
title="Empty Upload",
44+
summary=message,
45+
annotations=[],
46+
),
47+
included_helper_text={},
48+
)
49+
return result
50+
status_result = self.get_patch_status(comparison)
3251
codecov_link = self.get_codecov_pr_link(comparison)
3352

34-
title = message
53+
title = status_result["message"]
54+
message = status_result["message"]
3555

3656
should_use_upgrade = self.should_use_upgrade_decoration()
3757
if should_use_upgrade:
@@ -54,23 +74,27 @@ def build_payload(self, comparison: ComparisonProxy | FilteredComparison) -> dic
5474
or should_use_upgrade
5575
or should_annotate is False
5676
):
57-
return {
58-
"state": state,
59-
"output": {
60-
"title": f"{title}",
61-
"summary": "\n\n".join([codecov_link, message]),
62-
},
63-
}
77+
result = CheckResult(
78+
state=status_result["state"],
79+
output=CheckOutput(
80+
title=title,
81+
summary="\n\n".join([codecov_link, message]),
82+
annotations=[],
83+
),
84+
included_helper_text=status_result["included_helper_text"],
85+
)
86+
return result
6487
diff = comparison.get_diff(use_original_base=True)
6588
# TODO: Look into why the apply diff in get_patch_status is not saving state at this point
6689
comparison.head.report.apply_diff(diff)
6790
annotations = self.create_annotations(comparison, diff)
68-
69-
return {
70-
"state": state,
71-
"output": {
72-
"title": f"{title}",
73-
"summary": "\n\n".join([codecov_link, message]),
74-
"annotations": annotations,
75-
},
76-
}
91+
result = CheckResult(
92+
state=status_result["state"],
93+
output=CheckOutput(
94+
title=title,
95+
summary="\n\n".join([codecov_link, message]),
96+
annotations=annotations,
97+
),
98+
included_helper_text=status_result["included_helper_text"],
99+
)
100+
return result

services/notification/notifiers/mixins/status.py

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
from decimal import Decimal, InvalidOperation
33
from enum import Enum
4+
from typing import Literal, TypedDict
45

56
from services.comparison import ComparisonProxy, FilteredComparison
67
from services.yaml.reader import round_number
@@ -13,10 +14,35 @@ class StatusState(Enum):
1314
failure = "failure"
1415

1516

16-
custom_target_helper_text = "Your project check has failed because the patch coverage is below the target coverage. You can increase the patch coverage or adjust the [target](https://docs.codecov.com/docs/commit-status#target) coverage."
17+
class StatusResult(TypedDict):
18+
"""
19+
The mixins in this file do the calculations and decide the SuccessState for all Status and Checks Notifiers.
20+
Checks have different fields than Statuses, so Checks are converted to the CheckResult type later.
21+
"""
22+
23+
state: Literal["success", "failure"] # StatusState values
24+
message: str
25+
included_helper_text: dict[str, str]
26+
27+
28+
CUSTOM_TARGET_TEXT_PATCH_KEY = "custom_target_helper_text_patch"
29+
CUSTOM_TARGET_TEXT_PROJECT_KEY = "custom_target_helper_text_project"
30+
CUSTOM_TARGET_TEXT_VALUE = (
31+
"Your {context} check has failed because the patch coverage is below the target coverage. "
32+
"You can increase the patch coverage or adjust the "
33+
"[target](https://docs.codecov.com/docs/commit-status#target) coverage."
34+
)
35+
36+
37+
HELPER_TEXT_MAP = {
38+
CUSTOM_TARGET_TEXT_PATCH_KEY: CUSTOM_TARGET_TEXT_VALUE,
39+
CUSTOM_TARGET_TEXT_PROJECT_KEY: CUSTOM_TARGET_TEXT_VALUE,
40+
}
1741

1842

1943
class StatusPatchMixin(object):
44+
context = "patch"
45+
2046
def _get_threshold(self) -> Decimal:
2147
"""
2248
Threshold can be configured by user, default is 0.0
@@ -55,10 +81,11 @@ def _get_target(
5581

5682
def get_patch_status(
5783
self, comparison: ComparisonProxy | FilteredComparison
58-
) -> tuple[str, str]:
84+
) -> StatusResult:
5985
threshold = self._get_threshold()
6086
target_coverage, is_custom_target = self._get_target(comparison)
6187
totals = comparison.get_patch_totals()
88+
included_helper_text = {}
6289

6390
# coverage affected
6491
if totals and totals.lines > 0:
@@ -89,9 +116,16 @@ def get_patch_status(
89116
message = (
90117
f"{coverage_rounded}% of diff hit (target {target_rounded}%)"
91118
)
92-
if state == StatusState.failure.value and is_custom_target:
93-
message = message + " - " + custom_target_helper_text
94-
return state, message
119+
# TODO:
120+
# if state == StatusState.failure.value and is_custom_target:
121+
# helper_text = HELPER_TEXT_MAP[CUSTOM_TARGET_TEXT_PATCH_KEY].format(
122+
# context=self.context
123+
# )
124+
# included_helper_text[CUSTOM_TARGET_TEXT_PATCH_KEY] = helper_text
125+
# message = message + " - " + helper_text
126+
return StatusResult(
127+
state=state, message=message, included_helper_text=included_helper_text
128+
)
95129

96130
# coverage not affected
97131
if comparison.project_coverage_base.commit:
@@ -101,10 +135,16 @@ def get_patch_status(
101135
)
102136
else:
103137
description = "Coverage not affected"
104-
return StatusState.success.value, description
138+
return StatusResult(
139+
state=StatusState.success.value,
140+
message=description,
141+
included_helper_text=included_helper_text,
142+
)
105143

106144

107145
class StatusChangesMixin(object):
146+
context = "changes"
147+
108148
def is_a_change_worth_noting(self, change) -> bool:
109149
if not change.new and not change.deleted:
110150
# has totals and not -10m => 10h
@@ -151,6 +191,7 @@ def get_changes_status(
151191

152192
class StatusProjectMixin(object):
153193
DEFAULT_REMOVED_CODE_BEHAVIOR = "adjust_base"
194+
context = "project"
154195

155196
def _apply_removals_only_behavior(
156197
self, comparison: ComparisonProxy | FilteredComparison
@@ -427,8 +468,10 @@ def _get_project_status(
427468
# use rounded numbers for messages
428469
target_rounded = round_number(self.current_yaml, target_coverage)
429470
message = f"{head_coverage_rounded}% (target {target_rounded}%)"
430-
if state == StatusState.failure.value:
431-
message = message + " - " + custom_target_helper_text
471+
# TODO:
472+
# helper_text = HELPER_TEXT_MAP[CUSTOM_TARGET_TEXT].format(context=self.context)
473+
# included_helper_text[CUSTOM_TARGET_TEXT] = helper_text
474+
# message = message + " - " + helper_text
432475
return state, message
433476

434477
# use rounded numbers for messages

services/notification/notifiers/status/patch.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from database.enums import Notification
22
from services.comparison import ComparisonProxy, FilteredComparison
3-
from services.notification.notifiers.mixins.status import StatusPatchMixin
3+
from services.notification.notifiers.mixins.status import StatusPatchMixin, StatusResult
44
from services.notification.notifiers.status.base import StatusNotifier
55

66

@@ -22,13 +22,16 @@ class PatchStatusNotifier(StatusPatchMixin, StatusNotifier):
2222
def notification_type(self) -> Notification:
2323
return Notification.status_patch
2424

25-
def build_payload(self, comparison: ComparisonProxy | FilteredComparison) -> dict:
25+
def build_payload(
26+
self, comparison: ComparisonProxy | FilteredComparison
27+
) -> StatusResult:
2628
if self.is_empty_upload():
2729
state, message = self.get_status_check_for_empty_upload()
28-
return {"state": state, "message": message}
30+
result = StatusResult(state=state, message=message, included_helper_text={})
31+
return result
2932

30-
state, message = self.get_patch_status(comparison)
33+
result = self.get_patch_status(comparison)
3134
if self.should_use_upgrade_decoration():
32-
message = self.get_upgrade_message()
35+
result["message"] = self.get_upgrade_message()
3336

34-
return {"state": state, "message": message}
37+
return result

0 commit comments

Comments
 (0)