Repository navigation
139 lines (124 loc) · 7.07 KB
/
Copy pathself-assign.yml
File metadata and controls
139 lines (124 loc) · 7.07 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
139
# Let a contributor claim an issue themselves by commenting `/take`.
#
# GitHub's UI only offers collaborators in the assignee dropdown, and the "can this user be
# assigned" API returns 404 for everyone else, so assigning an outside contributor looks impossible
# from the web interface. The REST API allows it once the person has commented on the issue, which
# this workflow uses. That removes a round trip: nobody has to wait for a maintainer to be awake.
#
# The second job exists because the first one was silently useless to the people it was for. Four
# contributors in a row asked to be assigned in ordinary English, the exact-command check ignored
# every one, and they waited a day for a human. The command was fine. Nobody could discover it, and
# the failure mode was silence.
name: Self-assign
on:
issue_comment:
types: [created]
permissions:
issues: write
# One claim at a time per issue. The job reads the assignee list and then adds to it, so two
# comments arriving together could both see it empty and both assign, leaving two people who each
# think the issue is theirs, which is the exact outcome this workflow exists to prevent.
concurrency:
group: self-assign-${{ github.repository }}-${{ github.event.issue.number }}
cancel-in-progress: false
jobs:
take:
# Issues only. This is a cheap pre-filter; the script below checks the command exactly, because
# `startsWith` alone would also fire on `/takeover` and quietly assign someone who was arguing
# against the idea.
if: >
!github.event.issue.pull_request &&
(startsWith(github.event.comment.body, '/take') || startsWith(github.event.comment.body, '.take'))
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo;
const issue_number = context.issue.number;
const who = context.payload.comment.user.login;
// The first token must be exactly the command. Without this, `/takeover this is a bad
// idea` reads as a claim and assigns the person objecting to it.
const firstToken = (context.payload.comment.body || '').trim().split(/\s+/)[0];
if (firstToken !== '/take' && firstToken !== '.take') {
core.info(`Not a claim command (${firstToken}); ignoring.`);
return;
}
const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number });
const current = issue.assignees.map(a => a.login);
if (current.includes(who)) {
core.info(`${who} already has it; nothing to do.`);
return;
}
// Someone else is already on it. Say so rather than silently piling on. Two people
// discovering they duplicated a weekend's work is the outcome this is meant to prevent.
if (current.length > 0) {
await github.rest.issues.createComment({
owner, repo, issue_number,
body: `@${who} this one is already with @${current.join(', @')}. ` +
`Worth checking in with them before starting. They may be glad of the help, ` +
`or it may be further along than it looks. There are other issues going spare ` +
`under [help wanted](https://github.com/${owner}/${repo}/issues?q=is%3Aopen+label%3A%22help+wanted%22).`,
});
return;
}
await github.rest.issues.addAssignees({ owner, repo, issue_number, assignees: [who] });
await github.rest.issues.createComment({
owner, repo, issue_number,
body: `Assigned to @${who}. It's yours. 🎉\n\n` +
`No deadline, and no hard feelings if you change your mind; just say so and ` +
`someone else can pick it up. A draft PR early is very welcome if you'd like ` +
`eyes on the direction before polishing it.\n\n` +
`CLA Assistant will ask you to sign on your first pull request. It takes about ` +
`thirty seconds and you keep copyright of your work.`,
});
# Somebody asked for the issue in plain English. Tell them the command exists rather than leaving
# them waiting on a maintainer who may be asleep.
#
# A nudge rather than an assignment. Matching intent from prose is guesswork, and the negations
# are the problem: "I don't think I'd like to work on this" contains "like to work on this". A
# wrong assignment has to be noticed and undone by a human; a wrong hint costs one comment.
hint:
if: >
!github.event.issue.pull_request &&
!startsWith(github.event.comment.body, '/take') &&
!startsWith(github.event.comment.body, '.take')
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo;
const issue_number = context.issue.number;
const who = context.payload.comment.user.login;
const body = context.payload.comment.body || '';
// Bots asking to be assigned is not a thing we need to answer.
if (context.payload.comment.user.type === 'Bot') return;
const asking = /(assign\s+(me|it|this|to\s+me)|can\s+(i|u|you)\s+(assign|work|take)|could\s+(i|you)\s+(assign|work|take)|(i|I)('d|\s+would)?\s*(like|want)\s+to\s+(work|take)|may\s+i\s+(work|take)|let\s+me\s+(work|take|handle)|i'?ll\s+take\s+(this|it)|i\s+can\s+take\s+(this|it)|please\s+assign|working\s+on\s+this\??$)/i;
if (!asking.test(body)) {
core.info('Not an assignment request; ignoring.');
return;
}
const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number });
const current = issue.assignees.map(a => a.login);
if (current.length > 0) {
core.info(`Already assigned to ${current.join(', ')}; the take job handles that case.`);
return;
}
// Post once per issue. Without this, a thread where somebody keeps saying they want to
// work on it collects a hint under every message, and the hint becomes the noise it is
// meant to remove. The marker is an HTML comment so it does not render.
const MARKER = '<!-- self-assign-hint -->';
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number, per_page: 100,
});
if (comments.some(c => (c.body || '').includes(MARKER))) {
core.info('Already hinted on this issue.');
return;
}
await github.rest.issues.createComment({
owner, repo, issue_number,
body: `${MARKER}@${who} you can claim this yourself, no need to wait for anyone. ` +
`Comment \`/take\` on its own and it will be assigned to you within a minute.\n\n` +
`Doing it that way means two people cannot start the same work without noticing.`,
});