-
Notifications
You must be signed in to change notification settings - Fork 76
138 lines (118 loc) · 5.27 KB
/
info-needed-closer.yml
File metadata and controls
138 lines (118 loc) · 5.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
name: Info Needed Closer
on:
schedule:
- cron: 30 5 * * * # 10:30pm PT
workflow_dispatch:
workflow_call:
inputs:
days:
description: "Number of days without activity before closing (default: 14)"
required: false
type: string
default: "14"
permissions:
issues: write
contents: read
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Close Info Needed Issues
uses: actions/github-script@v8
env:
DAYS: ${{ inputs.days || '14' }}
with:
script: |
const days = parseInt(process.env.DAYS);
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - days);
core.info(`Closing issues with 'info-needed' label inactive for more than ${days} days (cutoff: ${cutoffDate.toISOString()})`);
// Cache of permission lookups to avoid redundant API calls for the same user
const permissionCache = {};
async function hasWriteAccess(username) {
if (permissionCache[username] !== undefined) {
return permissionCache[username];
}
try {
const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username,
});
// 'admin', 'maintain', and 'write' all grant write access
const result = data.permission === 'admin' || data.permission === 'maintain' || data.permission === 'write';
permissionCache[username] = result;
return result;
} catch (err) {
// User is not a collaborator or permission lookup failed
permissionCache[username] = false;
return false;
}
}
// Fetch all open issues with the 'info-needed' label
const issues = await github.paginate(github.rest.issues.listForRepo, {
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'info-needed',
per_page: 100,
});
core.info(`Found ${issues.length} open issue(s) with 'info-needed' label`);
let closedCount = 0;
for (const issue of issues) {
// Skip locked issues and pull requests
if (issue.locked) {
core.info(`Issue #${issue.number}: locked, skipping`);
continue;
}
if (issue.pull_request) {
continue;
}
// The listComments endpoint returns comments in ascending chronological
// order and does not support sort/direction params. Use the issue's
// comment count to jump directly to the last page.
if (issue.comments === 0) {
core.info(`Issue #${issue.number}: no comments, skipping`);
continue;
}
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
per_page: 1,
page: issue.comments, // Jump to the last page (with per_page=1), to get the last comment directly
});
if (comments.length === 0) {
core.info(`Issue #${issue.number}: could not fetch last comment, skipping`);
continue;
}
const lastComment = comments[0];
const lastCommentDate = new Date(lastComment.created_at);
// Skip issues with recent comment activity
if (lastCommentDate >= cutoffDate) {
core.info(`Issue #${issue.number}: last comment on ${lastComment.created_at} is within the activity window, skipping`);
continue;
}
// Only close if the last comment was made by someone with write access
const commenter = lastComment.user?.login;
if (!commenter || !(await hasWriteAccess(commenter))) {
core.info(`Issue #${issue.number}: last comment by ${commenter} does not have write access, skipping`);
continue;
}
core.info(`Closing issue #${issue.number}: last write-access comment by ${commenter} on ${lastComment.created_at}`);
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: 'This issue has been closed automatically because it needs more information and has not had recent activity. See also our <a href="https://aka.ms/azcodeissuereporting">issue reporting</a> guidelines.\n\nHappy Coding!',
});
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: 'closed',
state_reason: 'not_planned',
});
closedCount++;
}
core.info(`Done. Closed ${closedCount} issue(s).`);