Merge pull request #41 from actions/add-lable-operator

Add label operator input support
This commit is contained in:
Abdelhamid Attaby
2022-03-25 13:50:27 +02:00
committed by GitHub
6 changed files with 97 additions and 17 deletions

View File

@@ -10,7 +10,7 @@ not the original GitHub Projects.
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
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
name: Add bugs to bugs project
@@ -32,7 +32,8 @@ jobs:
with:
project-url: https://github.com/orgs/<orgName>/projects/<projectNumber>
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
labeled: bug
labeled: bug, new
label-operator: AND
```
## Inputs
@@ -42,8 +43,9 @@ jobs:
token](https://github.com/settings/tokens/new) with the `repo`, `write:org` and
`read:org` scopes.
- `labeled` is a comma-separated list of labels. For an issue to be added to the
project, it must have _one_ of the labels in the list. Omitting this key means
project, it must have _one_ of the labels in the list if the `label-operator` doesn't exist or not `AND`. Omitting this key means
that all issues will be added.
- `label-operator` 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`.
## Development

View File

@@ -52,11 +52,11 @@ describe('addToProject', () => {
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({
'project-url': 'https://github.com/orgs/github/projects/1',
'github-token': 'gh_token',
labeled: 'bug'
labeled: 'bug, new'
})
github.context.payload = {
@@ -94,7 +94,7 @@ describe('addToProject', () => {
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({
'project-url': 'https://github.com/orgs/github/projects/1',
'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(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 {

View File

@@ -11,6 +11,9 @@ inputs:
labeled:
required: false
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:
using: 'node16'
main: 'dist/index.js'

14
dist/index.js generated vendored
View File

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