Skip to content

Commit f636f2d

Browse files
committed
Add support for custom event dispatching
### Description This addresses #615 by adding the new ACTIVITY parameters. When this is specified, it overrides the default event-to-activity heuristic in #find_activity and selects the activity to run directly. This allows the end user to write their logic in the workflows when required. ### Testing I've added unit tests for these, but I haven't added integration or end-to-end tests, I was unable to get the environment set up correctly.
1 parent 1156b08 commit f636f2d

File tree

6 files changed

+91
-26
lines changed

6 files changed

+91
-26
lines changed

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,36 @@ These files include:
5959

6060
See [an example](https://github.com/py-cov-action/python-coverage-comment-action-v3-example)
6161

62+
### Determining the mode
63+
64+
By default, the action will attempt to pick the appropriate mode based on the
65+
current branch, whether or not it's in a pull request, and if that pull request
66+
is open or closed. This frequently results in the correct action taking place,
67+
but is only a heuristic. If you need more precise control, you should specify
68+
the `ACTIVITY` parameter to directly choose the mode. It may be one of:
69+
70+
- `process_pr`, to select [PR mode](#pr-mode)
71+
- `save_coverage_data_files`, to select [Default branch mode](#default-branch-mode)
72+
- `post_comment`, to select [Commenting on the PR on the `push` event](#commenting-on-the-pr-on-the-push-event)
73+
74+
Combining this with [Github's Expressions]
75+
(https://docs.github.com/en/actions/reference/workflows-and-actions/expressions) you can
76+
build out the the custom handling needed. For example:
77+
78+
```yaml
79+
- name: Coverage comment
80+
id: coverage_comment
81+
uses: py-cov-action/python-coverage-comment-action@v3
82+
with:
83+
GITHUB_TOKEN: ${{ github.token }}
84+
activity: "${{ github.event_name == 'push' && 'save_coverage_data_files' || 'process_pr' }}"
85+
86+
# or
87+
88+
with:
89+
activity: "${{ (github.event_name == 'push' && github.ref_name == 'main') && 'save_coverage_data_files' || 'process_pr' }}"
90+
```
91+
6292
## Usage
6393
6494
### Setup
@@ -469,6 +499,10 @@ Usage may look like this
469499

470500
# Deprecated, see https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/enabling-debug-logging
471501
VERBOSE: false
502+
503+
# The specific activity that should be taken on this event, see
504+
# [Determining the mode](#determining-the-mode) above.
505+
ACTIVITY: ""
472506
```
473507
474508
### Commenting on the PR on the `push` event

coverage_comment/activity.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,36 @@
88

99
from __future__ import annotations
1010

11+
from enum import Enum
12+
13+
14+
class Activity(Enum):
15+
PROCESS_PR = "process_pr"
16+
POST_COMMENT = "post_comment"
17+
SAVE_COVERAGE_DATA_FILES = "save_coverage_data_files"
1118

1219
class ActivityNotFound(Exception):
1320
pass
1421

22+
class ActivityConfigError(Exception):
23+
pass
24+
25+
def validate_activity(
26+
activity: str
27+
) -> Activity:
28+
if activity not in [a.value for a in Activity]:
29+
raise ActivityConfigError(f"Invalid activity: {activity}")
30+
return Activity(activity)
1531

1632
def find_activity(
1733
event_name: str,
1834
is_default_branch: bool,
1935
event_type: str | None,
2036
is_pr_merged: bool,
21-
) -> str:
37+
) -> Activity:
2238
"""Find the activity to perform based on the event type and payload."""
2339
if event_name == "workflow_run":
24-
return "post_comment"
40+
return Activity.POST_COMMENT
2541

2642
if (
2743
(event_name == "push" and is_default_branch)
@@ -31,9 +47,9 @@ def find_activity(
3147
):
3248
if event_name == "pull_request" and event_type == "closed" and not is_pr_merged:
3349
raise ActivityNotFound
34-
return "save_coverage_data_files"
50+
return Activity.SAVE_COVERAGE_DATA_FILES
3551

3652
if event_name not in {"pull_request", "push", "merge_group"}:
3753
raise ActivityNotFound
3854

39-
return "process_pr"
55+
return Activity.PROCESS_PR

coverage_comment/main.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -74,38 +74,43 @@ def action(
7474
github=gh, repository=config.GITHUB_REPOSITORY
7575
)
7676
try:
77-
activity = activity_module.find_activity(
78-
event_name=event_name,
79-
is_default_branch=repo_info.is_default_branch(ref=config.GITHUB_REF),
80-
event_type=config.GITHUB_EVENT_TYPE,
81-
is_pr_merged=config.IS_PR_MERGED,
82-
)
77+
if config.ACTIVITY:
78+
activity = activity_module.validate_activity(config.ACTIVITY)
79+
else:
80+
activity = activity_module.find_activity(
81+
event_name=event_name,
82+
is_default_branch=repo_info.is_default_branch(ref=config.GITHUB_REF),
83+
event_type=config.GITHUB_EVENT_TYPE,
84+
is_pr_merged=config.IS_PR_MERGED,
85+
)
8386
except activity_module.ActivityNotFound:
8487
log.error(
85-
'This action has only been designed to work for "pull_request", "push", '
86-
f'"workflow_run", "schedule" or "merge_group" actions, not "{event_name}". Because there '
87-
"are security implications. If you have a different usecase, please open an issue, "
88-
"we'll be glad to add compatibility."
88+
'This action\'s default behavior is to determine the appropriate '
89+
'mode based on the current branch, whether or not it\'s in a pull '
90+
'request, and if that pull request is open or closed. This '
91+
'frequently results in the correct action taking place, but is '
92+
'only a heuristic. If you need more precise control, you should '
93+
'specify the "ACTIVITY" parameter as described in the documentation.'
8994
)
9095
return 1
9196

92-
if activity == "save_coverage_data_files":
97+
if activity == activity_module.Activity.SAVE_COVERAGE_DATA_FILES:
9398
return save_coverage_data_files(
9499
config=config,
95100
git=git,
96101
http_session=http_session,
97102
repo_info=repo_info,
98103
)
99104

100-
elif activity == "process_pr":
105+
elif activity == activity_module.Activity.PROCESS_PR:
101106
return process_pr(
102107
config=config,
103108
gh=gh,
104109
repo_info=repo_info,
105110
)
106111

107112
else:
108-
# activity == "post_comment":
113+
# activity == activity_module.Activity.POST_COMMENT:
109114
return post_comment(
110115
config=config,
111116
gh=gh,

coverage_comment/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class Config:
6666
ANNOTATION_TYPE: str = "warning"
6767
MAX_FILES_IN_COMMENT: int = 25
6868
USE_GH_PAGES_HTML_URL: bool = False
69+
ACTIVITY: str | None = None
6970
VERBOSE: bool = False
7071
# Only for debugging, not exposed in the action:
7172
FORCE_WORKFLOW_RUN: bool = False

tests/integration/test_main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def test_action__invalid_event_name(session, push_config, in_integration_env, ge
5959
)
6060

6161
assert result == 1
62-
assert get_logs("ERROR", "This action has only been designed to work for")
62+
assert get_logs("ERROR", "This action's default behavior is to determine")
6363

6464

6565
def get_expected_output(

tests/unit/test_activity.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,20 @@
33
import pytest
44

55
from coverage_comment import activity
6+
from coverage_comment.settings import Config
67

78

89
@pytest.mark.parametrize(
910
"event_name, is_default_branch, event_type, is_pr_merged, expected_activity",
1011
[
11-
("workflow_run", True, None, False, "post_comment"),
12-
("push", True, None, False, "save_coverage_data_files"),
13-
("push", False, None, False, "process_pr"),
14-
("pull_request", True, "closed", True, "save_coverage_data_files"),
15-
("pull_request", True, None, False, "process_pr"),
16-
("pull_request", False, None, False, "process_pr"),
17-
("schedule", False, None, False, "save_coverage_data_files"),
18-
("merge_group", False, None, False, "save_coverage_data_files"),
12+
("workflow_run", True, None, False, activity.Activity.POST_COMMENT),
13+
("push", True, None, False, activity.Activity.SAVE_COVERAGE_DATA_FILES),
14+
("push", False, None, False, activity.Activity.PROCESS_PR),
15+
("pull_request", True, "closed", True, activity.Activity.SAVE_COVERAGE_DATA_FILES),
16+
("pull_request", True, None, False, activity.Activity.PROCESS_PR),
17+
("pull_request", False, None, False, activity.Activity.PROCESS_PR),
18+
("schedule", False, None, False, activity.Activity.SAVE_COVERAGE_DATA_FILES),
19+
("merge_group", False, None, False, activity.Activity.SAVE_COVERAGE_DATA_FILES),
1920
],
2021
)
2122
def test_find_activity(
@@ -48,3 +49,11 @@ def test_find_activity_pr_closed_not_merged():
4849
event_type="closed",
4950
is_pr_merged=False,
5051
)
52+
53+
def test_validate_activity__invalid():
54+
with pytest.raises(activity.ActivityConfigError):
55+
activity.validate_activity("invalid")
56+
57+
def test_validate_activity__valid():
58+
result = activity.validate_activity("process_pr")
59+
assert result == activity.Activity.PROCESS_PR

0 commit comments

Comments
 (0)