Skip to content

Commit 327d35f

Browse files
Added ability to pass in an optional PR number as a parameter (#349)
* Adding pr-number as an optional parameter * Updated README * Tests on the pr-number parameter * Added missing | to table * re-built script * Adding support for multiple pr-numbers * excluded .idea * Updated readme to reflect that there might be more than one PR * Additional warning * Removed unused * Reformatted and re-built * Corrected message * Removed required check * Added (s) to pull request numbers in the description Co-authored-by: MaksimZhukov <[email protected]> * Reworded PR-number parameter description Co-authored-by: MaksimZhukov <[email protected]> * adding getMultilineInput into the tests * Fixing tests for single pr * Fixing tests for multiple prs * Updated README.md to make it more obvious that it can take a list of PRs * Added example that labels PR's 1-3 * Handled no pull requests better (from code review) * Handled no pull requests better (from code review) * Handled missing pull request better (from code review) * Back out suggested change as it broke the tests * Rebuilt dist * Update src/labeler.ts Co-authored-by: MaksimZhukov <[email protected]> * Added Emphasis to the note Co-authored-by: MaksimZhukov <[email protected]> * Changed mockInput for pr-number to be string[] --------- Co-authored-by: MaksimZhukov <[email protected]>
1 parent 65f306b commit 327d35f

File tree

7 files changed

+283
-111
lines changed

7 files changed

+283
-111
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
.DS_Store
22
node_modules/
33
lib/
4-
.idea
4+
.idea

README.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,9 @@ Various inputs are defined in [`action.yml`](action.yml) to let you configure th
126126
| `configuration-path` | The path to the label configuration file | `.github/labeler.yml` |
127127
| `sync-labels` | Whether or not to remove labels when matching files are reverted or no longer changed by the PR | `false` |
128128
| `dot` | Whether or not to auto-include paths starting with dot (e.g. `.github`) | `false` |
129+
| `pr-number` | The number(s) of pull request to update, rather than detecting from the workflow context | N/A |
129130

130-
When `dot` is disabled and you want to include _all_ files in a folder:
131+
When `dot` is disabled, and you want to include _all_ files in a folder:
131132

132133
```yml
133134
label1:
@@ -142,6 +143,35 @@ label1:
142143
- path/to/folder/**
143144
```
144145

146+
##### Example workflow specifying Pull request numbers
147+
148+
```yml
149+
name: "Label Previous Pull Requests"
150+
on:
151+
schedule:
152+
- cron: "0 1 * * 1"
153+
154+
jobs:
155+
triage:
156+
permissions:
157+
contents: read
158+
pull-requests: write
159+
160+
runs-on: ubuntu-latest
161+
steps:
162+
163+
# Label PRs 1, 2, and 3
164+
- uses: actions/labeler@v4
165+
with:
166+
pr-number: |
167+
1
168+
2
169+
3
170+
```
171+
172+
**Note:** in normal usage the `pr-number` input is not required as the action will detect the PR number from the workflow context.
173+
174+
145175
#### Outputs
146176

147177
Labeler provides the following outputs:

__mocks__/@actions/github.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ const mockApi = {
1616
setLabels: jest.fn()
1717
},
1818
pulls: {
19-
get: jest.fn().mockResolvedValue({}),
19+
get: jest.fn().mockResolvedValue({
20+
data: {
21+
labels: []
22+
}
23+
}),
2024
listFiles: {
2125
endpoint: {
2226
merge: jest.fn().mockReturnValue({})

__tests__/main.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,15 @@ const configureInput = (
2525
'configuration-path': string;
2626
'sync-labels': boolean;
2727
dot: boolean;
28+
'pr-number': string[];
2829
}>
2930
) => {
3031
jest
3132
.spyOn(core, 'getInput')
3233
.mockImplementation((name: string, ...opts) => mockInput[name]);
34+
jest
35+
.spyOn(core, 'getMultilineInput')
36+
.mockImplementation((name: string, ...opts) => mockInput[name]);
3337
jest
3438
.spyOn(core, 'getBooleanInput')
3539
.mockImplementation((name: string, ...opts) => mockInput[name]);
@@ -209,6 +213,88 @@ describe('run', () => {
209213
expect(setOutputSpy).toHaveBeenCalledWith('new-labels', '');
210214
expect(setOutputSpy).toHaveBeenCalledWith('all-labels', allLabels);
211215
});
216+
217+
it('(with pr-number: array of one item, uses the PR number specified in the parameters', async () => {
218+
configureInput({
219+
'repo-token': 'foo',
220+
'configuration-path': 'bar',
221+
'pr-number': ['104']
222+
});
223+
224+
usingLabelerConfigYaml('only_pdfs.yml');
225+
mockGitHubResponseChangedFiles('foo.pdf');
226+
227+
getPullMock.mockResolvedValue(<any>{
228+
data: {
229+
labels: [{name: 'manually-added'}]
230+
}
231+
});
232+
233+
await run();
234+
235+
expect(setLabelsMock).toHaveBeenCalledTimes(1);
236+
expect(setLabelsMock).toHaveBeenCalledWith({
237+
owner: 'monalisa',
238+
repo: 'helloworld',
239+
issue_number: 104,
240+
labels: ['manually-added', 'touched-a-pdf-file']
241+
});
242+
expect(setOutputSpy).toHaveBeenCalledWith(
243+
'new-labels',
244+
'touched-a-pdf-file'
245+
);
246+
expect(setOutputSpy).toHaveBeenCalledWith(
247+
'all-labels',
248+
'manually-added,touched-a-pdf-file'
249+
);
250+
});
251+
252+
it('(with pr-number: array of two items, uses the PR number specified in the parameters', async () => {
253+
configureInput({
254+
'repo-token': 'foo',
255+
'configuration-path': 'bar',
256+
'pr-number': ['104', '150']
257+
});
258+
259+
usingLabelerConfigYaml('only_pdfs.yml');
260+
mockGitHubResponseChangedFiles('foo.pdf');
261+
262+
getPullMock.mockResolvedValueOnce(<any>{
263+
data: {
264+
labels: [{name: 'manually-added'}]
265+
}
266+
});
267+
268+
getPullMock.mockResolvedValueOnce(<any>{
269+
data: {
270+
labels: []
271+
}
272+
});
273+
274+
await run();
275+
276+
expect(setLabelsMock).toHaveBeenCalledTimes(2);
277+
expect(setLabelsMock).toHaveBeenCalledWith({
278+
owner: 'monalisa',
279+
repo: 'helloworld',
280+
issue_number: 104,
281+
labels: ['manually-added', 'touched-a-pdf-file']
282+
});
283+
expect(setLabelsMock).toHaveBeenCalledWith({
284+
owner: 'monalisa',
285+
repo: 'helloworld',
286+
issue_number: 150,
287+
labels: ['touched-a-pdf-file']
288+
});
289+
expect(setOutputSpy).toHaveBeenCalledWith(
290+
'new-labels',
291+
'touched-a-pdf-file'
292+
);
293+
expect(setOutputSpy).toHaveBeenCalledWith(
294+
'all-labels',
295+
'manually-added,touched-a-pdf-file'
296+
);
297+
});
212298
});
213299

214300
function usingLabelerConfigYaml(fixtureName: keyof typeof yamlFixtures): void {

action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ inputs:
1818
description: 'Whether or not to auto-include paths starting with dot (e.g. `.github`)'
1919
default: false
2020
required: false
21+
pr-number:
22+
description: 'The pull request number(s)'
23+
required: false
2124

2225
outputs:
2326
new-labels:

dist/index.js

Lines changed: 70 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -54,55 +54,66 @@ function run() {
5454
const configPath = core.getInput('configuration-path', { required: true });
5555
const syncLabels = !!core.getInput('sync-labels');
5656
const dot = core.getBooleanInput('dot');
57-
const prNumber = getPrNumber();
58-
if (!prNumber) {
59-
core.info('Could not get pull request number from context, exiting');
57+
const prNumbers = getPrNumbers();
58+
if (!prNumbers.length) {
59+
core.warning('Could not get pull request number(s), exiting');
6060
return;
6161
}
6262
const client = github.getOctokit(token, {}, pluginRetry.retry);
63-
const { data: pullRequest } = yield client.rest.pulls.get({
64-
owner: github.context.repo.owner,
65-
repo: github.context.repo.repo,
66-
pull_number: prNumber
67-
});
68-
core.debug(`fetching changed files for pr #${prNumber}`);
69-
const changedFiles = yield getChangedFiles(client, prNumber);
70-
const labelGlobs = yield getLabelGlobs(client, configPath);
71-
const preexistingLabels = pullRequest.labels.map(l => l.name);
72-
const allLabels = new Set(preexistingLabels);
73-
for (const [label, globs] of labelGlobs.entries()) {
74-
core.debug(`processing ${label}`);
75-
if (checkGlobs(changedFiles, globs, dot)) {
76-
allLabels.add(label);
77-
}
78-
else if (syncLabels) {
79-
allLabels.delete(label);
63+
for (const prNumber of prNumbers) {
64+
core.debug(`looking for pr #${prNumber}`);
65+
let pullRequest;
66+
try {
67+
const result = yield client.rest.pulls.get({
68+
owner: github.context.repo.owner,
69+
repo: github.context.repo.repo,
70+
pull_number: prNumber
71+
});
72+
pullRequest = result.data;
8073
}
81-
}
82-
const labelsToAdd = [...allLabels].slice(0, GITHUB_MAX_LABELS);
83-
const excessLabels = [...allLabels].slice(GITHUB_MAX_LABELS);
84-
try {
85-
let newLabels = [];
86-
if (!isListEqual(labelsToAdd, preexistingLabels)) {
87-
yield setLabels(client, prNumber, labelsToAdd);
88-
newLabels = labelsToAdd.filter(l => !preexistingLabels.includes(l));
74+
catch (error) {
75+
core.warning(`Could not find pull request #${prNumber}, skipping`);
76+
continue;
8977
}
90-
core.setOutput('new-labels', newLabels.join(','));
91-
core.setOutput('all-labels', labelsToAdd.join(','));
92-
if (excessLabels.length) {
93-
core.warning(`Maximum of ${GITHUB_MAX_LABELS} labels allowed. Excess labels: ${excessLabels.join(', ')}`, { title: 'Label limit for a PR exceeded' });
78+
core.debug(`fetching changed files for pr #${prNumber}`);
79+
const changedFiles = yield getChangedFiles(client, prNumber);
80+
const labelGlobs = yield getLabelGlobs(client, configPath);
81+
const preexistingLabels = pullRequest.labels.map(l => l.name);
82+
const allLabels = new Set(preexistingLabels);
83+
for (const [label, globs] of labelGlobs.entries()) {
84+
core.debug(`processing ${label}`);
85+
if (checkGlobs(changedFiles, globs, dot)) {
86+
allLabels.add(label);
87+
}
88+
else if (syncLabels) {
89+
allLabels.delete(label);
90+
}
9491
}
95-
}
96-
catch (error) {
97-
if (error.name === 'HttpError' &&
98-
error.message === 'Resource not accessible by integration') {
99-
core.warning(`The action requires write permission to add labels to pull requests. For more information please refer to the action documentation: https://github.com/actions/labeler#permissions`, {
100-
title: `${process.env['GITHUB_ACTION_REPOSITORY']} running under '${github.context.eventName}' is misconfigured`
101-
});
102-
core.setFailed(error.message);
92+
const labelsToAdd = [...allLabels].slice(0, GITHUB_MAX_LABELS);
93+
const excessLabels = [...allLabels].slice(GITHUB_MAX_LABELS);
94+
try {
95+
let newLabels = [];
96+
if (!isListEqual(labelsToAdd, preexistingLabels)) {
97+
yield setLabels(client, prNumber, labelsToAdd);
98+
newLabels = labelsToAdd.filter(l => !preexistingLabels.includes(l));
99+
}
100+
core.setOutput('new-labels', newLabels.join(','));
101+
core.setOutput('all-labels', labelsToAdd.join(','));
102+
if (excessLabels.length) {
103+
core.warning(`Maximum of ${GITHUB_MAX_LABELS} labels allowed. Excess labels: ${excessLabels.join(', ')}`, { title: 'Label limit for a PR exceeded' });
104+
}
103105
}
104-
else {
105-
throw error;
106+
catch (error) {
107+
if (error.name === 'HttpError' &&
108+
error.message === 'Resource not accessible by integration') {
109+
core.warning(`The action requires write permission to add labels to pull requests. For more information please refer to the action documentation: https://github.com/actions/labeler#permissions`, {
110+
title: `${process.env['GITHUB_ACTION_REPOSITORY']} running under '${github.context.eventName}' is misconfigured`
111+
});
112+
core.setFailed(error.message);
113+
}
114+
else {
115+
throw error;
116+
}
106117
}
107118
}
108119
}
@@ -113,12 +124,26 @@ function run() {
113124
});
114125
}
115126
exports.run = run;
116-
function getPrNumber() {
127+
function getPrNumbers() {
128+
const pullRequestNumbers = core.getMultilineInput('pr-number');
129+
if (pullRequestNumbers && pullRequestNumbers.length) {
130+
const prNumbers = [];
131+
for (const prNumber of pullRequestNumbers) {
132+
const prNumberInt = parseInt(prNumber, 10);
133+
if (isNaN(prNumberInt) || prNumberInt <= 0) {
134+
core.warning(`'${prNumber}' is not a valid pull request number`);
135+
}
136+
else {
137+
prNumbers.push(prNumberInt);
138+
}
139+
}
140+
return prNumbers;
141+
}
117142
const pullRequest = github.context.payload.pull_request;
118143
if (!pullRequest) {
119-
return undefined;
144+
return [];
120145
}
121-
return pullRequest.number;
146+
return [pullRequest.number];
122147
}
123148
function getChangedFiles(client, prNumber) {
124149
return __awaiter(this, void 0, void 0, function* () {

0 commit comments

Comments
 (0)