Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ability to pass in an optional PR number as a parameter #349

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
baefe5d
Adding pr-number as an optional parameter
credfeto Apr 19, 2022
5c4971e
Updated README
credfeto Apr 19, 2022
52078ca
Tests on the pr-number parameter
credfeto Apr 19, 2022
290b11b
Added missing | to table
credfeto Jun 21, 2023
ff4b71d
re-built script
credfeto Jun 21, 2023
7e9e70f
Adding support for multiple pr-numbers
credfeto Jun 21, 2023
836757c
excluded .idea
credfeto Jun 21, 2023
b6e7208
Updated readme to reflect that there might be more than one PR
credfeto Jul 3, 2023
07ff7ea
Additional warning
credfeto Jul 3, 2023
bdefaa6
Removed unused
credfeto Jul 3, 2023
13ad946
Reformatted and re-built
credfeto Jul 3, 2023
a23ad07
Corrected message
credfeto Jul 3, 2023
24492d8
Removed required check
credfeto Jul 3, 2023
ea5dd1d
Added (s) to pull request numbers in the description
credfeto Jul 5, 2023
d64108f
Reworded PR-number parameter description
credfeto Jul 5, 2023
26e7680
adding getMultilineInput into the tests
credfeto Jul 3, 2023
c9e03c8
Fixing tests for single pr
credfeto Jul 3, 2023
5b2ecd5
Fixing tests for multiple prs
credfeto Jul 3, 2023
9df982d
Updated README.md to make it more obvious that it can take a list of PRs
credfeto Jul 3, 2023
33d0803
Added example that labels PR's 1-3
credfeto Jul 5, 2023
71ff194
Handled no pull requests better (from code review)
credfeto Jul 5, 2023
8939095
Handled no pull requests better (from code review)
credfeto Jul 5, 2023
4bad0c3
Handled missing pull request better (from code review)
credfeto Jul 5, 2023
5b72df0
Back out suggested change as it broke the tests
credfeto Jul 6, 2023
4973c4e
Rebuilt dist
credfeto Jul 6, 2023
deae017
Update src/labeler.ts
credfeto Jul 6, 2023
2cd40dd
Added Emphasis to the note
credfeto Jul 6, 2023
98b70ef
Changed mockInput for pr-number to be string[]
credfeto Jul 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.DS_Store
node_modules/
lib/
.idea
.idea
34 changes: 32 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,9 @@ Various inputs are defined in [`action.yml`](action.yml) to let you configure th
| `configuration-path` | The path to the label configuration file | `.github/labeler.yml` |
| `sync-labels` | Whether or not to remove labels when matching files are reverted or no longer changed by the PR | `false` |
| `dot` | Whether or not to auto-include paths starting with dot (e.g. `.github`) | `false` |
| `pr-number` | The number(s) of pull request to update, rather than detecting from the workflow context | N/A |

When `dot` is disabled and you want to include _all_ files in a folder:
When `dot` is disabled, and you want to include _all_ files in a folder:

```yml
label1:
Expand All @@ -142,6 +143,35 @@ label1:
- path/to/folder/**
```

##### Example workflow specifying Pull request numbers

```yml
name: "Label Previous Pull Requests"
on:
schedule:
- cron: "0 1 * * 1"

jobs:
triage:
permissions:
contents: read
pull-requests: write

runs-on: ubuntu-latest
steps:

# Label PRs 1, 2, and 3
- uses: actions/labeler@v4
with:
pr-number: |
1
2
3
```

**Note:** in normal usage the `pr-number` input is not required as the action will detect the PR number from the workflow context.


#### Outputs

Labeler provides the following outputs:
Expand All @@ -165,7 +195,7 @@ jobs:
pull-requests: write
steps:
- id: label-the-PR
uses: actions/labeler@v3
uses: actions/labeler@v4

- id: run-frontend-tests
if: contains(fromJson(steps.label-the-PR.outputs.all-labels), 'frontend')
Expand Down
6 changes: 5 additions & 1 deletion __mocks__/@actions/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ const mockApi = {
setLabels: jest.fn()
},
pulls: {
get: jest.fn().mockResolvedValue({}),
get: jest.fn().mockResolvedValue({
data: {
labels: []
}
}),
listFiles: {
endpoint: {
merge: jest.fn().mockReturnValue({})
Expand Down
86 changes: 86 additions & 0 deletions __tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ const configureInput = (
'configuration-path': string;
'sync-labels': boolean;
dot: boolean;
'pr-number': string[];
}>
) => {
jest
.spyOn(core, 'getInput')
.mockImplementation((name: string, ...opts) => mockInput[name]);
jest
.spyOn(core, 'getMultilineInput')
.mockImplementation((name: string, ...opts) => mockInput[name]);
jest
.spyOn(core, 'getBooleanInput')
.mockImplementation((name: string, ...opts) => mockInput[name]);
Expand Down Expand Up @@ -209,6 +213,88 @@ describe('run', () => {
expect(setOutputSpy).toHaveBeenCalledWith('new-labels', '');
expect(setOutputSpy).toHaveBeenCalledWith('all-labels', allLabels);
});

it('(with pr-number: array of one item, uses the PR number specified in the parameters', async () => {
configureInput({
'repo-token': 'foo',
'configuration-path': 'bar',
'pr-number': ['104']
});

usingLabelerConfigYaml('only_pdfs.yml');
mockGitHubResponseChangedFiles('foo.pdf');

getPullMock.mockResolvedValue(<any>{
data: {
labels: [{name: 'manually-added'}]
}
});

await run();

expect(setLabelsMock).toHaveBeenCalledTimes(1);
expect(setLabelsMock).toHaveBeenCalledWith({
owner: 'monalisa',
repo: 'helloworld',
issue_number: 104,
labels: ['manually-added', 'touched-a-pdf-file']
});
expect(setOutputSpy).toHaveBeenCalledWith(
'new-labels',
'touched-a-pdf-file'
);
expect(setOutputSpy).toHaveBeenCalledWith(
'all-labels',
'manually-added,touched-a-pdf-file'
);
});

it('(with pr-number: array of two items, uses the PR number specified in the parameters', async () => {
configureInput({
'repo-token': 'foo',
'configuration-path': 'bar',
'pr-number': ['104', '150']
});

usingLabelerConfigYaml('only_pdfs.yml');
mockGitHubResponseChangedFiles('foo.pdf');

getPullMock.mockResolvedValueOnce(<any>{
data: {
labels: [{name: 'manually-added'}]
}
});

getPullMock.mockResolvedValueOnce(<any>{
data: {
labels: []
}
});

await run();

expect(setLabelsMock).toHaveBeenCalledTimes(2);
expect(setLabelsMock).toHaveBeenCalledWith({
owner: 'monalisa',
repo: 'helloworld',
issue_number: 104,
labels: ['manually-added', 'touched-a-pdf-file']
});
expect(setLabelsMock).toHaveBeenCalledWith({
owner: 'monalisa',
repo: 'helloworld',
issue_number: 150,
labels: ['touched-a-pdf-file']
});
expect(setOutputSpy).toHaveBeenCalledWith(
'new-labels',
'touched-a-pdf-file'
);
expect(setOutputSpy).toHaveBeenCalledWith(
'all-labels',
'manually-added,touched-a-pdf-file'
);
});
});

function usingLabelerConfigYaml(fixtureName: keyof typeof yamlFixtures): void {
Expand Down
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ inputs:
description: 'Whether or not to auto-include paths starting with dot (e.g. `.github`)'
default: false
required: false
pr-number:
description: 'The pull request number(s)'
required: false

outputs:
new-labels:
Expand Down
115 changes: 70 additions & 45 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,55 +54,66 @@ function run() {
const configPath = core.getInput('configuration-path', { required: true });
const syncLabels = !!core.getInput('sync-labels');
const dot = core.getBooleanInput('dot');
const prNumber = getPrNumber();
if (!prNumber) {
core.info('Could not get pull request number from context, exiting');
const prNumbers = getPrNumbers();
if (!prNumbers.length) {
core.warning('Could not get pull request number(s), exiting');
return;
}
const client = github.getOctokit(token, {}, pluginRetry.retry);
const { data: pullRequest } = yield client.rest.pulls.get({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
pull_number: prNumber
});
core.debug(`fetching changed files for pr #${prNumber}`);
const changedFiles = yield getChangedFiles(client, prNumber);
const labelGlobs = yield getLabelGlobs(client, configPath);
const preexistingLabels = pullRequest.labels.map(l => l.name);
const allLabels = new Set(preexistingLabels);
for (const [label, globs] of labelGlobs.entries()) {
core.debug(`processing ${label}`);
if (checkGlobs(changedFiles, globs, dot)) {
allLabels.add(label);
}
else if (syncLabels) {
allLabels.delete(label);
for (const prNumber of prNumbers) {
core.debug(`looking for pr #${prNumber}`);
let pullRequest;
try {
const result = yield client.rest.pulls.get({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
pull_number: prNumber
});
pullRequest = result.data;
}
}
const labelsToAdd = [...allLabels].slice(0, GITHUB_MAX_LABELS);
const excessLabels = [...allLabels].slice(GITHUB_MAX_LABELS);
try {
let newLabels = [];
if (!isListEqual(labelsToAdd, preexistingLabels)) {
yield setLabels(client, prNumber, labelsToAdd);
newLabels = labelsToAdd.filter(l => !preexistingLabels.includes(l));
catch (error) {
core.warning(`Could not find pull request #${prNumber}, skipping`);
continue;
}
core.setOutput('new-labels', newLabels.join(','));
core.setOutput('all-labels', labelsToAdd.join(','));
if (excessLabels.length) {
core.warning(`Maximum of ${GITHUB_MAX_LABELS} labels allowed. Excess labels: ${excessLabels.join(', ')}`, { title: 'Label limit for a PR exceeded' });
core.debug(`fetching changed files for pr #${prNumber}`);
const changedFiles = yield getChangedFiles(client, prNumber);
const labelGlobs = yield getLabelGlobs(client, configPath);
const preexistingLabels = pullRequest.labels.map(l => l.name);
const allLabels = new Set(preexistingLabels);
for (const [label, globs] of labelGlobs.entries()) {
core.debug(`processing ${label}`);
if (checkGlobs(changedFiles, globs, dot)) {
allLabels.add(label);
}
else if (syncLabels) {
allLabels.delete(label);
}
}
}
catch (error) {
if (error.name === 'HttpError' &&
error.message === 'Resource not accessible by integration') {
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`, {
title: `${process.env['GITHUB_ACTION_REPOSITORY']} running under '${github.context.eventName}' is misconfigured`
});
core.setFailed(error.message);
const labelsToAdd = [...allLabels].slice(0, GITHUB_MAX_LABELS);
const excessLabels = [...allLabels].slice(GITHUB_MAX_LABELS);
try {
let newLabels = [];
if (!isListEqual(labelsToAdd, preexistingLabels)) {
yield setLabels(client, prNumber, labelsToAdd);
newLabels = labelsToAdd.filter(l => !preexistingLabels.includes(l));
}
core.setOutput('new-labels', newLabels.join(','));
core.setOutput('all-labels', labelsToAdd.join(','));
if (excessLabels.length) {
core.warning(`Maximum of ${GITHUB_MAX_LABELS} labels allowed. Excess labels: ${excessLabels.join(', ')}`, { title: 'Label limit for a PR exceeded' });
}
}
else {
throw error;
catch (error) {
if (error.name === 'HttpError' &&
error.message === 'Resource not accessible by integration') {
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`, {
title: `${process.env['GITHUB_ACTION_REPOSITORY']} running under '${github.context.eventName}' is misconfigured`
});
core.setFailed(error.message);
}
else {
throw error;
}
}
}
}
Expand All @@ -113,12 +124,26 @@ function run() {
});
}
exports.run = run;
function getPrNumber() {
function getPrNumbers() {
const pullRequestNumbers = core.getMultilineInput('pr-number');
if (pullRequestNumbers && pullRequestNumbers.length) {
const prNumbers = [];
for (const prNumber of pullRequestNumbers) {
const prNumberInt = parseInt(prNumber, 10);
if (isNaN(prNumberInt) || prNumberInt <= 0) {
core.warning(`'${prNumber}' is not a valid pull request number`);
}
else {
prNumbers.push(prNumberInt);
}
}
return prNumbers;
}
const pullRequest = github.context.payload.pull_request;
if (!pullRequest) {
return undefined;
return [];
}
return pullRequest.number;
return [pullRequest.number];
}
function getChangedFiles(client, prNumber) {
return __awaiter(this, void 0, void 0, function* () {
Expand Down