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

Commit 2df1369

Browse files
committed
Refactor and simplify notification logic a bit
1 parent e932541 commit 2df1369

4 files changed

Lines changed: 83 additions & 111 deletions

File tree

services/notification/__init__.py

Lines changed: 60 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@
2121
from services.comparison import ComparisonProxy
2222
from services.decoration import Decoration
2323
from services.license import is_properly_licensed
24-
from services.notification.commit_notifications import (
25-
create_or_update_commit_notification_from_notification_result,
26-
)
24+
from services.notification.commit_notifications import store_notification_results
2725
from services.notification.notifiers import (
2826
StatusType,
2927
get_all_notifier_classes_mapping,
@@ -162,6 +160,8 @@ def get_notifiers_instances(self) -> Iterator[AbstractBaseNotifier]:
162160
if yaml_field is not None:
163161
for instance_type, instance_configs in yaml_field.items():
164162
class_to_use = mapping.get(instance_type)
163+
if not class_to_use:
164+
continue
165165
for title, individual_config in instance_configs.items():
166166
yield class_to_use(
167167
repository=self.repository,
@@ -269,127 +269,101 @@ def notify(self, comparison: ComparisonProxy) -> list[IndividualResult]:
269269
),
270270
)
271271

272-
results = []
273-
status_or_checks_helper_text = {}
274-
notifiers_to_notify = [
272+
status_or_checks_notifiers, all_other_notifiers = split_notifiers(
275273
notifier
276274
for notifier in self.get_notifiers_instances()
277275
if notifier.is_enabled()
278-
]
279-
280-
status_or_checks_notifiers = [
281-
notifier
282-
for notifier in notifiers_to_notify
283-
if notifier.notification_type in notification_type_status_or_checks
284-
]
285-
286-
all_other_notifiers = [
287-
notifier
288-
for notifier in notifiers_to_notify
289-
if notifier.notification_type not in notification_type_status_or_checks
290-
]
276+
)
291277

292-
status_or_checks_results = [
278+
results = [
293279
self.notify_individual_notifier(notifier, comparison)
294280
for notifier in status_or_checks_notifiers
295281
]
296282

297-
if status_or_checks_results and all_other_notifiers:
283+
status_or_checks_helper_text = {}
284+
if results and all_other_notifiers:
298285
# if the status/check fails, sometimes we want to add helper text to the message of the other notifications,
299286
# to better surface that the status/check failed.
300287
# so if there are status_and_checks_notifiers and all_other_notifiers, do the status_and_checks_notifiers first,
301288
# look at the results of the checks, if any failed AND they are the type we have helper text for,
302289
# add that text onto the other notifiers messages through status_or_checks_helper_text.
303-
for result in status_or_checks_results:
304-
notification_result = result["result"]
305-
if (
306-
notification_result is not None
307-
and notification_result.data_sent is not None
308-
):
290+
for notifier, result in results:
291+
if result is not None and result.data_sent is not None:
309292
if (
310-
notification_result.data_sent.get("state")
311-
== StatusState.failure.value
312-
) and notification_result.data_sent.get("included_helper_text"):
293+
result.data_sent.get("state") == StatusState.failure.value
294+
) and result.data_sent.get("included_helper_text"):
313295
status_or_checks_helper_text.update(
314-
notification_result.data_sent["included_helper_text"]
296+
result.data_sent["included_helper_text"]
315297
)
316-
results = results + status_or_checks_results
317298

318-
results = results + [
299+
results.extend(
319300
self.notify_individual_notifier(
320301
notifier,
321302
comparison,
322303
status_or_checks_helper_text=status_or_checks_helper_text,
323304
)
324305
for notifier in all_other_notifiers
306+
)
307+
308+
store_notification_results(comparison, results)
309+
310+
return [
311+
IndividualResult(
312+
notifier=notifier.name, title=notifier.title, result=result
313+
)
314+
for notifier, result in results
325315
]
326-
return results
327316

328317
def notify_individual_notifier(
329318
self,
330319
notifier: AbstractBaseNotifier,
331320
comparison: ComparisonProxy,
332321
status_or_checks_helper_text: Optional[dict[str, str]] = None,
333-
) -> IndividualResult:
322+
) -> tuple[AbstractBaseNotifier, NotificationResult | None]:
334323
commit = comparison.head.commit
335324
base_commit = comparison.project_coverage_base.commit
336-
log.info(
337-
"Attempting individual notification",
338-
extra=dict(
339-
commit=commit.commitid,
340-
base_commit=(
341-
base_commit.commitid if base_commit is not None else "NO_BASE"
342-
),
343-
repoid=commit.repoid,
344-
notifier=notifier.name,
345-
notifier_title=notifier.title,
346-
),
347-
)
348-
# individual_result.result is updated in case of success
349-
individual_result = IndividualResult(
350-
notifier=notifier.name, title=notifier.title, result=None
351-
)
325+
log_extra = {
326+
"commit": commit.commitid,
327+
"base_commit": base_commit.commitid
328+
if base_commit is not None
329+
else "NO_BASE",
330+
"repoid": commit.repoid,
331+
"notifier": notifier.name,
332+
"notifier_title": notifier.title,
333+
}
334+
335+
log.info("Attempting individual notification", extra=log_extra)
352336
try:
353337
res = notifier.notify(
354338
comparison, status_or_checks_helper_text=status_or_checks_helper_text
355339
)
356-
individual_result["result"] = res
340+
log_extra["result"] = res
357341

342+
# TODO: The `CommentNotifier` is the only one implementing this method,
343+
# so maybe we should just move it there
358344
notifier.store_results(comparison, res)
359-
log.info(
360-
"Individual notification done",
361-
extra=dict(
362-
individual_result=individual_result,
363-
commit=commit.commitid,
364-
base_commit=(
365-
base_commit.commitid if base_commit is not None else "NO_BASE"
366-
),
367-
repoid=commit.repoid,
368-
),
369-
)
370-
return individual_result
345+
346+
log.info("Individual notification done", extra=log_extra)
347+
return notifier, res
371348
except (CeleryError, SoftTimeLimitExceeded):
372349
raise
373350
except Exception:
374-
log.exception(
375-
"Individual notifier failed",
376-
extra=dict(
377-
repoid=commit.repoid,
378-
commit=commit.commitid,
379-
individual_result=individual_result,
380-
base_commit=(
381-
base_commit.commitid if base_commit is not None else "NO_BASE"
382-
),
383-
),
384-
)
385-
return individual_result
386-
finally:
387-
if (
388-
individual_result["result"] is None
389-
or individual_result["result"].notification_attempted
390-
):
391-
# only running if there is no result (indicating some exception)
392-
# or there was an actual attempt
393-
create_or_update_commit_notification_from_notification_result(
394-
comparison, notifier, individual_result["result"]
395-
)
351+
log.exception("Individual notifier failed", extra=log_extra)
352+
return notifier, None
353+
354+
355+
def split_notifiers(
356+
notifiers: Iterator[AbstractBaseNotifier],
357+
) -> tuple[list[AbstractBaseNotifier], list[AbstractBaseNotifier]]:
358+
"Splits the input notifiers based on whether they are a status/check, or other notifier."
359+
360+
status_or_checks_notifiers = []
361+
all_other_notifiers = []
362+
363+
for notifier in notifiers:
364+
if notifier.notification_type in notification_type_status_or_checks:
365+
status_or_checks_notifiers.append(notifier)
366+
else:
367+
all_other_notifiers.append(notifier)
368+
369+
return status_or_checks_notifiers, all_other_notifiers

services/notification/commit_notifications.py

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import logging
22

3+
import sentry_sdk
34
from sqlalchemy.orm.session import Session
45

56
from database.enums import NotificationState
67
from database.models import CommitNotification, Pull
7-
from helpers.metrics import metrics
88
from services.comparison import ComparisonProxy
99
from services.notification.notifiers.base import (
1010
AbstractBaseNotifier,
@@ -14,30 +14,27 @@
1414
log = logging.getLogger(__name__)
1515

1616

17-
def _get_notification_state_from_result(
18-
notification_result: NotificationResult | None,
19-
) -> NotificationState:
20-
"""
21-
Take notification result from notification service and convert to
22-
the proper NotificationState enum
23-
"""
24-
if notification_result is None:
25-
return NotificationState.error
26-
27-
successful = notification_result.notification_successful
28-
29-
if successful:
30-
return NotificationState.success
31-
else:
32-
return NotificationState.error
17+
@sentry_sdk.trace
18+
def store_notification_results(
19+
comparison: ComparisonProxy,
20+
results: list[tuple[AbstractBaseNotifier, NotificationResult | None]],
21+
):
22+
# TODO: we should change this to a batch "INSERT ON CONFLICT UPDATE" eventually,
23+
# as this currently results in quite some inefficient query patterns.
24+
for notifier, result in results:
25+
if result is None or result.notification_attempted:
26+
# only running if there is no result (indicating some exception)
27+
# or there was an actual attempt
28+
create_or_update_commit_notification_from_notification_result(
29+
comparison, notifier, result
30+
)
3331

3432

35-
@metrics.timer("internal.services.notification.store_notification_result")
3633
def create_or_update_commit_notification_from_notification_result(
3734
comparison: ComparisonProxy,
3835
notifier: AbstractBaseNotifier,
3936
notification_result: NotificationResult | None,
40-
) -> CommitNotification:
37+
) -> CommitNotification | None:
4138
"""Saves a CommitNotification entry in the database.
4239
We save an entry in the following scenarios:
4340
- We save all notification attempts for commits that are part of a PullRequest
@@ -54,12 +51,12 @@ def create_or_update_commit_notification_from_notification_result(
5451
or notification_result.notification_successful == False
5552
)
5653
if not_pull and (not_head_commit or not_github_app_info or failed):
57-
return
54+
return None
5855

5956
commit = pull.get_head_commit() if pull else comparison.head.commit
6057
if not commit:
6158
log.warning("Head commit not found for pull", extra=dict(pull=pull))
62-
return
59+
return None
6360

6461
db_session: Session = commit.get_db_session()
6562

@@ -72,7 +69,9 @@ def create_or_update_commit_notification_from_notification_result(
7269
.first()
7370
)
7471

75-
notification_state = _get_notification_state_from_result(notification_result)
72+
notification_state = (
73+
NotificationState.error if failed else NotificationState.success
74+
)
7675
github_app_used = (
7776
notification_result.github_app_used if notification_result else None
7877
)

services/notification/notifiers/comment/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242

4343

4444
class CommentNotifier(MessageMixin, AbstractBaseNotifier):
45-
notify_conditions: list[NotifyCondition] = [
45+
notify_conditions: list[type[NotifyCondition]] = [
4646
ComparisonHasPull,
4747
PullRequestInProvider,
4848
PullHeadMatchesComparisonHead,

services/notification/notifiers/mixins/message/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ def create_message(
4949
pull = comparison.pull
5050

5151
settings = yaml_settings
52-
5352
current_yaml = self.current_yaml
5453

5554
links = {
@@ -63,7 +62,7 @@ def create_message(
6362
}
6463

6564
# bool: show complexity
66-
if read_yaml_field(self.current_yaml, ("codecov", "ui", "hide_complexity")):
65+
if read_yaml_field(current_yaml, ("codecov", "ui", "hide_complexity")):
6766
show_complexity = False
6867
else:
6968
show_complexity = bool(

0 commit comments

Comments
 (0)