Merge branch 'main' into readme-improvements

This commit is contained in:
Shaun Kirk Wong
2022-03-25 11:21:09 -07:00
committed by GitHub
6 changed files with 96 additions and 16 deletions

View File

@@ -20,7 +20,7 @@ _For more information about workflows, see [Using workflows](https://docs.github
To use the action, create a workflow that runs when issues are opened in your To use the action, create a workflow that runs when issues are opened in your
repository. Run this action in a step, optionally configuring any filters you repository. Run this action in a step, optionally configuring any filters you
may want to add, such as only adding issues with certain labels. may want to add, such as only adding issues with certain labels. If you want to match all the labels, add `label-operator` input to be `AND`.
```yaml ```yaml
name: Add bugs to bugs project name: Add bugs to bugs project
@@ -39,7 +39,8 @@ jobs:
with: with:
project-url: https://github.com/orgs/<orgName>/projects/<projectNumber> project-url: https://github.com/orgs/<orgName>/projects/<projectNumber>
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
labeled: bug labeled: bug, new
label-operator: AND
``` ```
#### Further reading and additional resources #### Further reading and additional resources
@@ -60,6 +61,7 @@ jobs:
`read:org` scopes. `read:org` scopes.
_See [Creating a PAT and adding it to your repository](creating-a-pat-and-adding-it-to-your-repository) for more details_ _See [Creating a PAT and adding it to your repository](creating-a-pat-and-adding-it-to-your-repository) for more details_
- <a name="labeled">`labeled`</a> **(optional)** is a comma-separated list of labels used to filter applicable issues. When this key is provided, an issue must have _one_ of the labels in the list to be added to the project. Omitting this key means that any issue will be added. - <a name="labeled">`labeled`</a> **(optional)** is a comma-separated list of labels used to filter applicable issues. When this key is provided, an issue must have _one_ of the labels in the list to be added to the project. Omitting this key means that any issue will be added.
- <a name="labeled">`label-operator`</a> **(optional)** is the behavior of the labels filter, either `AND` or `OR` that controls if the issue should be matched with `all` `labeled` input or any of them, default is `OR`.
## Supported Events ## Supported Events

View File

@@ -52,11 +52,11 @@ describe('addToProject', () => {
expect(outputs.itemId).toEqual('project-next-item-id') expect(outputs.itemId).toEqual('project-next-item-id')
}) })
test('adds matching issues with a label filter', async () => { test('adds matching issues with a label filter without label-operator', async () => {
mockGetInput({ mockGetInput({
'project-url': 'https://github.com/orgs/github/projects/1', 'project-url': 'https://github.com/orgs/github/projects/1',
'github-token': 'gh_token', 'github-token': 'gh_token',
labeled: 'bug' labeled: 'bug, new'
}) })
github.context.payload = { github.context.payload = {
@@ -94,7 +94,7 @@ describe('addToProject', () => {
expect(outputs.itemId).toEqual('project-next-item-id') expect(outputs.itemId).toEqual('project-next-item-id')
}) })
test('does not add un-matching issues with a label filter', async () => { test('does not add un-matching issues with a label filter without label-operator', async () => {
mockGetInput({ mockGetInput({
'project-url': 'https://github.com/orgs/github/projects/1', 'project-url': 'https://github.com/orgs/github/projects/1',
'github-token': 'gh_token', 'github-token': 'gh_token',
@@ -114,6 +114,71 @@ describe('addToProject', () => {
expect(infoSpy).toHaveBeenCalledWith(`Skipping issue 1 because it does not have one of the labels: bug`) expect(infoSpy).toHaveBeenCalledWith(`Skipping issue 1 because it does not have one of the labels: bug`)
expect(gqlMock).not.toHaveBeenCalled() expect(gqlMock).not.toHaveBeenCalled()
}) })
test('adds matching issues with labels filter with AND label-operator', async () => {
mockGetInput({
'project-url': 'https://github.com/orgs/github/projects/1',
'github-token': 'gh_token',
labeled: 'bug, new',
'label-operator': 'AND'
})
github.context.payload = {
issue: {
number: 1,
labels: [{name: 'bug'}, {name: 'new'}]
}
}
mockGraphQL(
{
test: /getProject/,
return: {
organization: {
projectNext: {
id: 'project-next-id'
}
}
}
},
{
test: /addProjectNextItem/,
return: {
addProjectNextItem: {
projectNextItem: {
id: 'project-next-item-id'
}
}
}
}
)
await addToProject()
expect(outputs.itemId).toEqual('project-next-item-id')
})
test('does not add un-matching issues with labels filter with AND label-operator', async () => {
mockGetInput({
'project-url': 'https://github.com/orgs/github/projects/1',
'github-token': 'gh_token',
labeled: 'bug, new',
'label-operator': 'AND'
})
github.context.payload = {
issue: {
number: 1,
labels: [{name: 'bug'}, {name: 'other'}]
}
}
const infoSpy = jest.spyOn(core, 'info')
const gqlMock = mockGraphQL()
await addToProject()
expect(infoSpy).toHaveBeenCalledWith(`Skipping issue 1 because it doesn't match all the labels: bug, new`)
expect(gqlMock).not.toHaveBeenCalled()
})
}) })
function mockGetInput(mocks: Record<string, string>): jest.SpyInstance { function mockGetInput(mocks: Record<string, string>): jest.SpyInstance {

View File

@@ -11,6 +11,9 @@ inputs:
labeled: labeled:
required: false required: false
description: A comma-separated list of labels to use as a filter for issue to be added description: A comma-separated list of labels to use as a filter for issue to be added
label-operator:
required: false
description: The behavior of the labels filter, AND to match all labels, OR to match any label (default is OR)
runs: runs:
using: 'node16' using: 'node16'
main: 'dist/index.js' main: 'dist/index.js'

14
dist/index.js generated vendored
View File

@@ -51,14 +51,20 @@ function addToProject() {
.split(',') .split(',')
.map(l => l.trim()) .map(l => l.trim())
.filter(l => l.length > 0)) !== null && _a !== void 0 ? _a : []; .filter(l => l.length > 0)) !== null && _a !== void 0 ? _a : [];
const labelOperator = core.getInput('label-operator').trim().toLocaleLowerCase();
const octokit = github.getOctokit(ghToken); const octokit = github.getOctokit(ghToken);
const urlMatch = projectUrl.match(urlParse); const urlMatch = projectUrl.match(urlParse);
const issue = (_b = github.context.payload.issue) !== null && _b !== void 0 ? _b : github.context.payload.pull_request; const issue = (_b = github.context.payload.issue) !== null && _b !== void 0 ? _b : github.context.payload.pull_request;
const issueLabels = ((_c = issue === null || issue === void 0 ? void 0 : issue.labels) !== null && _c !== void 0 ? _c : []).map((l) => l.name); const issueLabels = ((_c = issue === null || issue === void 0 ? void 0 : issue.labels) !== null && _c !== void 0 ? _c : []).map((l) => l.name);
// Ensure the issue matches our `labeled` filter, if provided. // Ensure the issue matches our `labeled` filter based on the label-operator.
if (labeled.length > 0) { if (labelOperator === 'and') {
const hasLabel = issueLabels.some(l => labeled.includes(l)); if (!labeled.every(l => issueLabels.includes(l))) {
if (!hasLabel) { core.info(`Skipping issue ${issue === null || issue === void 0 ? void 0 : issue.number} because it doesn't match all the labels: ${labeled.join(', ')}`);
return;
}
}
else {
if (labeled.length > 0 && !issueLabels.some(l => labeled.includes(l))) {
core.info(`Skipping issue ${issue === null || issue === void 0 ? void 0 : issue.number} because it does not have one of the labels: ${labeled.join(', ')}`); core.info(`Skipping issue ${issue === null || issue === void 0 ? void 0 : issue.number} because it does not have one of the labels: ${labeled.join(', ')}`);
return; return;
} }

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

View File

@@ -37,18 +37,22 @@ export async function addToProject(): Promise<void> {
.split(',') .split(',')
.map(l => l.trim()) .map(l => l.trim())
.filter(l => l.length > 0) ?? [] .filter(l => l.length > 0) ?? []
const labelOperator = core.getInput('label-operator').trim().toLocaleLowerCase()
const octokit = github.getOctokit(ghToken) const octokit = github.getOctokit(ghToken)
const urlMatch = projectUrl.match(urlParse) const urlMatch = projectUrl.match(urlParse)
const issue = github.context.payload.issue ?? github.context.payload.pull_request const issue = github.context.payload.issue ?? github.context.payload.pull_request
const issueLabels: string[] = (issue?.labels ?? []).map((l: {name: string}) => l.name) const issueLabels: string[] = (issue?.labels ?? []).map((l: {name: string}) => l.name)
// Ensure the issue matches our `labeled` filter, if provided. // Ensure the issue matches our `labeled` filter based on the label-operator.
if (labeled.length > 0) { if (labelOperator === 'and') {
const hasLabel = issueLabels.some(l => labeled.includes(l)) if (!labeled.every(l => issueLabels.includes(l))) {
core.info(`Skipping issue ${issue?.number} because it doesn't match all the labels: ${labeled.join(', ')}`)
if (!hasLabel) { return
}
} else {
if (labeled.length > 0 && !issueLabels.some(l => labeled.includes(l))) {
core.info(`Skipping issue ${issue?.number} because it does not have one of the labels: ${labeled.join(', ')}`) core.info(`Skipping issue ${issue?.number} because it does not have one of the labels: ${labeled.join(', ')}`)
return return
} }
} }