Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 076a3b9

Browse files
authored
✨ Add support for managing PRs and remove support for HTML comments to avoid rate limits (#12)
* 🔥 Remove support for HTML comments to avoid rate limits and because that feature was not used much after having support for labels * 🔊 Add not closing logging * 📌 Pin ranges of PyGitHub and pydantic better * ✨ Enable processing PRs * ✨ Add support for reading PR event data * 📝 Update docs supporting PRs * 📝 Update README with new version
1 parent b038c0e commit 076a3b9

3 files changed

Lines changed: 74 additions & 87 deletions

File tree

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
FROM python:3.7
22

3-
RUN pip install PyGithub "pydantic==1.5.1"
3+
RUN pip install "PyGithub>=1.55,<2.0" "pydantic>=v1.8.2,<2.0"
44

55
COPY ./app /app
66

README.md

Lines changed: 50 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Issue Manager
22

3-
Automatically close issues that have a **label**, after a **custom delay**, if no one replies back.
3+
Automatically close issues or Pull Requests that have a **label**, after a **custom delay**, if no one replies back.
44

55
## How to use
66

@@ -17,32 +17,35 @@ on:
1717
issue_comment:
1818
types:
1919
- created
20-
- edited
2120
issues:
2221
types:
2322
- labeled
23+
pull_request_target:
24+
types:
25+
- labeled
26+
workflow_dispatch:
2427

2528
jobs:
2629
issue-manager:
2730
runs-on: ubuntu-latest
2831
steps:
29-
- uses: tiangolo/issue-manager@0.3.0
32+
- uses: tiangolo/issue-manager@0.4.0
3033
with:
3134
token: ${{ secrets.GITHUB_TOKEN }}
3235
config: '{"answered": {}}'
3336
```
3437
35-
Then, you can answer an issue and add the label from the config, in this case, `answered`.
38+
Then, you can answer an issue or PR and add the label from the config, in this case, `answered`.
3639

3740
After 10 days, if no one has added a new comment, the GitHub action will write:
3841

3942
```markdown
40-
Assuming the original issue was solved, it will be automatically closed now.
43+
Assuming the original need was handled, this will be automatically closed now.
4144
```
4245

4346
And then it will close the issue.
4447

45-
But if someone adds a comment _after_ you added the label, it will remove the label.
48+
But if someone adds a comment _after_ you added the label, this GitHub Action will remove the label so that you can come back and check it instead of closing it.
4649

4750
## Config
4851

@@ -77,6 +80,10 @@ Imagine this JSON config:
7780
"waiting": {
7881
"delay": 691200,
7982
"message": "Closing after 8 days of waiting for the additional info requested."
83+
},
84+
"needs-tests": {
85+
"delay": 691200,
86+
"message": "This PR will be closed after waiting 8 days for tests to be added. Please create a new one with tests."
8087
}
8188
}
8289
```
@@ -113,11 +120,11 @@ And also, if there was a new comment created _after_ the label was added, by def
113120

114121
---
115122

116-
And in the last case, if:
123+
Then, if:
117124

118125
* the issue has a label `waiting`
119126
* the label was added _after_ the last comment
120-
* the last comment was addded more than `691200` seconds (10 days) ago
127+
* the last comment was addded more than `691200` seconds (8 days) ago
121128

122129
...the GitHub action would close the issue with:
123130

@@ -127,41 +134,31 @@ Closing after 10 days of waiting for the additional info requested.
127134

128135
And again, by default, removing the label if there was a new comment written after adding the label.
129136

130-
### Delay
131-
132-
The delay can be configured using [anything supported by Pydantic's `datetime`](https://pydantic-docs.helpmanual.io/usage/types/#datetime-types).
133-
134-
So, it can be an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) period format (like `P3DT12H30M5S`), or the amount of seconds between the two dates (like `691200`, or 10 days) plus other options.
135-
136-
### Users and HTML comments
137-
138-
Before supporting labels, this GitHub action used HTML comments, so, you would write a comment like:
137+
---
139138

140-
```markdown
141-
Ah, you have to use a JSON string in the config.
139+
And finally, if:
142140

143-
<!-- issue-manager: answered -->
144-
```
141+
* a PR has a label `needs-tests`
142+
* the label was added _after_ the last comment
143+
* the last comment was addded more than `691200` seconds (8 days) ago
145144

146-
Then the comment would only show:
145+
...the GitHub action would close the PR with:
147146

148147
```markdown
149-
Ah, you have to use a JSON string in the config.
148+
This PR will be closed after waiting 8 days for tests to be added. Please create a new one with tests.
150149
```
151150

152-
And the GitHub action would read the label/keyword from that HTML comment.
151+
**Note**: in this last example the process is applied to a PR instead of an issue. The same logic applies to both issues and PRs. If you want a label to only apply to issues, you should use that label only with issues, and the same with PRs.
153152

154-
To support external users adding these comments (even if they can't add labels to your repo), you can add a config `users` with a list of usernames allowed to add these HTML keyword comments.
153+
### Delay
155154

156-
In this case, the GitHub action will only close the issue if:
155+
The delay can be configured using [anything supported by Pydantic's `datetime`](https://pydantic-docs.helpmanual.io/usage/types/#datetime-types).
157156

158-
* the _last_ comment has the keyword/label
159-
* it was written by a user in the `users` list in the `config` (or the owner of the repo)
160-
* the time delay since the last comment is enough
157+
So, it can be an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) period format (like `P3DT12H30M5S`), or the number of seconds between the two dates (like `691200`, or 10 days) plus other options.
161158

162159
### Remove label on comment
163160

164-
You can also pass a config `remove_label_on_comment` per keyword. By default it's `true`.
161+
You can also pass a config `remove_label_on_comment` per keyword. By default, it's `true`.
165162

166163
When someone adds a comment _after_ the label was added, then this GitHub action won't close the issue.
167164

@@ -177,7 +174,6 @@ By default it is false, and doesn't remove the label from the issue.
177174

178175
By default, any config has:
179176

180-
* `users`: No users, only the repository owner (only applies to HTML comments).
181177
* `delay`: A delay of 10 days.
182178
* `message`: A message of:
183179

@@ -211,12 +207,16 @@ on:
211207
issues:
212208
types:
213209
- labeled
210+
pull_request_target:
211+
types:
212+
- labeled
213+
workflow_dispatch:
214214
215215
jobs:
216216
issue-manager:
217217
runs-on: ubuntu-latest
218218
steps:
219-
- uses: tiangolo/issue-manager@0.3.0
219+
- uses: tiangolo/issue-manager@0.4.0
220220
with:
221221
token: ${{ secrets.GITHUB_TOKEN }}
222222
config: >
@@ -238,7 +238,7 @@ jobs:
238238

239239
### Edit your own config
240240

241-
If you have [Visual Studio Code](https://code.visualstudio.com) or other modern editor, you can create your JSON config by creating a JSON file, e.g. `config.json`.
241+
If you have [Visual Studio Code](https://code.visualstudio.com) or another modern editor, you can create your JSON config by creating a JSON file, e.g. `config.json`.
242242

243243
Then writing the contents of your config in that file, and then copying the results.
244244

@@ -252,13 +252,13 @@ You can start your JSON config file with:
252252
}
253253
```
254254

255-
And then after you write a keyword and start its config, like `"answered": {}`, it will autocomplete the internal config keys, like `delay`, `users`, `message`. And will validate its contents.
255+
And then after you write a keyword and start its config, like `"answered": {}`, it will autocomplete the internal config keys, like `delay`, `message`. And will validate its contents.
256256

257257
It's fine to leave the `$schema` in the `config` on the `.yml` file, it will be discarded and won't be used as a label.
258258

259259
### A complete example
260260

261-
**Note**: you probably don't need all the configs, the examples above should suffice for most cases. But if you want to allow other users to use keywords/labels in HTML comments, or want to make the GitHub action _not_ remove the labels if someone adds a new comment, this can help as an example:
261+
**Note**: you probably don't need all the configs, the examples above should suffice for most cases. But if you want to make the GitHub action _not_ remove the labels if someone adds a new comment, this can help as an example:
262262

263263
```yml
264264
name: Issue Manager
@@ -273,42 +273,34 @@ on:
273273
issues:
274274
types:
275275
- labeled
276+
pull_request_target:
277+
types:
278+
- labeled
279+
workflow_dispatch:
276280
277281
jobs:
278282
issue-manager:
279283
runs-on: ubuntu-latest
280284
steps:
281-
- uses: tiangolo/issue-manager@0.3.0
285+
- uses: tiangolo/issue-manager@0.4.0
282286
with:
283287
token: ${{ secrets.GITHUB_TOKEN }}
284288
config: >
285289
{
286290
"$schema": "https://raw.githubusercontent.com/tiangolo/issue-manager/master/schema.json",
287291
"answered": {
288-
"users": [
289-
"tiangolo",
290-
"dmontagu"
291-
],
292292
"delay": "P3DT12H30M5S",
293293
"message": "It seems the issue was answered, closing this now.",
294294
"remove_label_on_comment": false,
295295
"remove_label_on_close": false
296296
},
297297
"validated": {
298-
"users": [
299-
"tiangolo",
300-
"samuelcolvin"
301-
],
302298
"delay": 300,
303299
"message": "The issue could not be validated after 5 minutes. Closing now.",
304300
"remove_label_on_comment": true,
305301
"remove_label_on_close": false
306302
},
307303
"waiting": {
308-
"users": [
309-
"tomchristie",
310-
"dmontagu"
311-
],
312304
"delay": 691200,
313305
"message": "Closing after 8 days of waiting for the additional info requested.",
314306
"remove_label_on_comment": true,
@@ -332,21 +324,28 @@ on:
332324
issues:
333325
types:
334326
- labeled
327+
pull_request_target:
328+
types:
329+
- labeled
330+
workflow_dispatch:
335331
```
336332

337333
* The `cron` option means that the GitHub action will be run every day at 00:00 UTC.
338334
* The `issue_comment` option means that it will be run with a specific issue when a comment is added.
339335
* This way, if there's a new comment, it can immediately remove any label that was added before the new comment.
340336
* The `issues` option with a type of `label` will run it with each specific issue when you add a label.
341337
* This way you can add a label to an issue that was answered long ago, and if the configured delay since the last comment is enough the GitHub action will close the issue right away.
338+
* The `pull_request_target` option with a type of `label` will run it with each specific Pull Request made to your repo when you add a label.
339+
* This way you can add a label to a PR that was answered long ago, or that was waiting for more comments from the author, etc. And if the configured delay since the last comment is enough the GitHub action will close the issue right away.
340+
* The `workflow_dispatch` option allows you to run the action manually from the GitHub Actions tab for your repo.
342341

343342
## Motivation
344343

345344
### Closing early
346345

347-
When I answer an issue, I like to give the original user some time to respond, and give them the chance to close the issue before doing it myself.
346+
When I answer an issue, I like to give the original user some time to respond and give them the chance to close the issue before doing it myself.
348347

349-
Or some times, I have to request additional info.
348+
Or sometimes, I have to request additional info.
350349

351350
Sometimes, my answer didn't respond the real question/problem, and if I closed the issue immediately, it would end up feeling "impolite" to the user.
352351

@@ -364,7 +363,7 @@ But that requires me going through all the open issues again, one by one, check
364363

365364
One option would be to use a tool that closes stale issues, like [probot/stale](https://github.com/probot/stale), or the [Close Stale Issues Action](https://github.com/marketplace/actions/close-stale-issues).
366365

367-
But if the user came back explaining that my answer didn't respond to his/her problem, or giving the extra info requested, but I couldn't respond on time, the issue would still go "stale" and be closed.
366+
But if the user came back explaining that my answer didn't respond to his/her problem or giving the extra info requested, but I couldn't respond on time, the issue would still go "stale" and be closed.
368367

369368
## What Issue Manager does
370369

@@ -379,8 +378,6 @@ Then, this action, by running every night (or however you configure it) will, fo
379378
* Then, if all that matches, it will add a comment with a message (configurable).
380379
* And then it will close the issue.
381380

382-
Also, all that with the optional alternative using HTML comments.
383-
384381
It will also run after each comment or label added, with the specific issue that has the new comment or label (if you used the example configurations from above).
385382

386383
## Release Notes

app/main.py

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@
77
from github.Issue import Issue
88
from github.IssueComment import IssueComment
99
from github.IssueEvent import IssueEvent
10-
from github.NamedUser import NamedUser
1110
from pydantic import BaseModel, BaseSettings, SecretStr, validator
1211

1312

1413
class KeywordMeta(BaseModel):
1514
delay: timedelta = timedelta(days=10)
16-
users: List[str] = []
17-
message: str = "Assuming the original issue was solved, it will be automatically closed now."
15+
message: str = "Assuming the original need was handled, this will be automatically closed now."
1816
remove_label_on_comment: bool = True
1917
remove_label_on_close: bool = False
2018

@@ -39,6 +37,7 @@ class PartialGitHubEventIssue(BaseModel):
3937

4038
class PartialGitHubEvent(BaseModel):
4139
issue: Optional[PartialGitHubEventIssue] = None
40+
pull_request: Optional[PartialGitHubEventIssue] = None
4241

4342

4443
def get_last_comment(issue: Issue) -> Optional[IssueComment]:
@@ -87,20 +86,18 @@ def close_issue(
8786
issue.remove_from_labels(keyword)
8887

8988

90-
def process_issue(*, issue: Issue, settings: Settings, owner: NamedUser) -> None:
89+
def process_issue(*, issue: Issue, settings: Settings) -> None:
9190
logging.info(f"Processing issue: #{issue.number}")
9291
label_strs = set([label.name for label in issue.get_labels()])
9392
events = list(issue.get_events())
9493
labeled_events = get_labeled_events(events)
9594
last_comment = get_last_comment(issue)
9695
for keyword, keyword_meta in settings.input_config.items():
9796
# Check closable delay, if enough time passed and the issue could be closed
98-
closable_delay = False
99-
if (
97+
closable_delay = (
10098
last_comment is None
10199
or (datetime.utcnow() - keyword_meta.delay) > last_comment.created_at
102-
):
103-
closable_delay = True
100+
)
104101
# Check label, optionally removing it if there's a comment after adding it
105102
if keyword in label_strs:
106103
logging.info(f'Keyword: "{keyword}" in issue labels')
@@ -127,24 +124,10 @@ def process_issue(*, issue: Issue, settings: Settings, owner: NamedUser) -> None
127124
label_strs=label_strs,
128125
)
129126
break
130-
# Check HTML comments by allowed users
131-
if (
132-
last_comment
133-
and f"<!-- issue-manager: {keyword} -->" in last_comment.body
134-
and closable_delay
135-
and last_comment.user.login in keyword_meta.users + [owner.login]
136-
):
137-
logging.info(
138-
f'Last comment by user: "{last_comment.user.login}" had HTML keyword '
139-
f'comment: "{keyword}" and there\'s a closable delay.'
140-
)
141-
close_issue(
142-
issue=issue,
143-
keyword_meta=keyword_meta,
144-
keyword=keyword,
145-
label_strs=label_strs,
146-
)
147-
break
127+
else:
128+
logging.info(
129+
f"Not clossing issue: #{issue.number} as the delay hasn't been reached: {keyword_meta.delay}"
130+
)
148131

149132

150133
if __name__ == "__main__":
@@ -153,20 +136,27 @@ def process_issue(*, issue: Issue, settings: Settings, owner: NamedUser) -> None
153136
logging.info(f"Using config: {settings.json()}")
154137
g = Github(settings.input_token.get_secret_value())
155138
repo = g.get_repo(settings.github_repository)
156-
owner: NamedUser = repo.owner
157139
github_event: Optional[PartialGitHubEvent] = None
158140
if settings.github_event_path.is_file():
159141
contents = settings.github_event_path.read_text()
160142
github_event = PartialGitHubEvent.parse_raw(contents)
161143
if (
162144
settings.github_event_name == "issues"
145+
or settings.github_event_name == "pull_request_target"
163146
or settings.github_event_name == "issue_comment"
164147
):
165-
if github_event and github_event.issue:
166-
issue = repo.get_issue(github_event.issue.number)
167-
if issue.state == "open":
168-
process_issue(issue=issue, settings=settings, owner=owner)
148+
if github_event:
149+
issue_number: Optional[int] = None
150+
if github_event.issue:
151+
issue_number = github_event.issue.number
152+
elif github_event.pull_request:
153+
issue_number = github_event.pull_request.number
154+
if issue_number is not None:
155+
issue = repo.get_issue(issue_number)
156+
if issue.state == "open":
157+
process_issue(issue=issue, settings=settings)
169158
else:
170-
for issue in repo.get_issues(state="open"):
171-
process_issue(issue=issue, settings=settings, owner=owner)
159+
for keyword, keyword_meta in settings.input_config.items():
160+
for issue in repo.get_issues(state="open", labels=[keyword]):
161+
process_issue(issue=issue, settings=settings)
172162
logging.info("Finished")

0 commit comments

Comments
 (0)