Feat: add any-of-labels option (#319)
* feat: add any-of-labels option * chore: run pack script * fix: error in milestones spec * chore: update readme * chore: fix default value in action.yml * chore: add some unit tests * docs: update README.md Co-authored-by: Geoffrey Testelin <geoffrey.testelin@gmail.com> * refactor: add return type to lambda Co-authored-by: Geoffrey Testelin <geoffrey.testelin@gmail.com>
This commit is contained in:
parent
8f5f223d0c
commit
63ae8ac024
111
README.md
111
README.md
|
@ -4,48 +4,56 @@ Warns and then closes issues and PRs that have had no activity for a specified a
|
|||
|
||||
### Arguments
|
||||
|
||||
| Input | Description | Usage |
|
||||
| ----------------------------- | --------------------------------------------------------------------------------------------------------------- | -------- |
|
||||
| `repo-token` | PAT(Personal Access Token) for authorizing repository. _Defaults to **${{ github.token }}**_ | Optional |
|
||||
| `days-before-stale` | Idle number of days before marking an issue/PR as stale. _Defaults to **60**_ | Optional |
|
||||
| `days-before-issue-stale` | Idle number of days before marking an issue as stale (override `days-before-stale`). | Optional |
|
||||
| `days-before-pr-stale` | Idle number of days before marking an PR as stale (override `days-before-stale`). | Optional |
|
||||
| `days-before-close` | Idle number of days before closing an stale issue/PR. _Defaults to **7**_ | Optional |
|
||||
| `days-before-issue-close` | Idle number of days before closing an stale issue (override `days-before-close`). | Optional |
|
||||
| `days-before-pr-close` | Idle number of days before closing an stale PR (override `days-before-close`). | Optional |
|
||||
| `stale-issue-message` | Message to post on the stale issue. | Optional |
|
||||
| `stale-pr-message` | Message to post on the stale PR. | Optional |
|
||||
| `close-issue-message` | Message to post on the stale issue while closing it. | Optional |
|
||||
| `close-pr-message` | Message to post on the stale PR while closing it. | Optional |
|
||||
| `stale-issue-label` | Label to apply on the stale issue. _Defaults to **Stale**_ | Optional |
|
||||
| `close-issue-label` | Label to apply on closing issue (automatically removed if no longer closed nor locked). | Optional |
|
||||
| `stale-pr-label` | Label to apply on the stale PR. _Defaults to **Stale**_ | Optional |
|
||||
| `close-pr-label` | Label to apply on the closing PR (automatically removed if no longer closed nor locked). | Optional |
|
||||
| `exempt-issue-labels` | Labels on an issue exempted from being marked as stale. | Optional |
|
||||
| `exempt-pr-labels` | Labels on the PR exempted from being marked as stale. | Optional |
|
||||
| `only-labels` | Only labels checked for stale issue/PR. | Optional |
|
||||
| `only-issue-labels` | Only labels checked for stale issue (override `only-labels`). | Optional |
|
||||
| `only-pr-labels` | Only labels checked for stale PR (override `only-labels`). | Optional |
|
||||
| `operations-per-run` | Maximum number of operations per run (GitHub API CRUD related). _Defaults to **30**_ | Optional |
|
||||
| `remove-stale-when-updated` | Remove stale label from issue/PR on updates or comments. _Defaults to **true**_ | Optional |
|
||||
| `debug-only` | Dry-run on action. _Defaults to **false**_ | Optional |
|
||||
| `ascending` | Order to get issues/PR. _Defaults to **false**_ | Optional |
|
||||
| `skip-stale-issue-message` | Skip adding stale message on stale issue. _Defaults to **false**_ | Optional |
|
||||
| `skip-stale-pr-message` | Skip adding stale message on stale PR. _Defaults to **false**_ | Optional |
|
||||
| `start-date` | The date used to skip the stale action on issue/PR created before it (ISO 8601 or RFC 2822). | Optional |
|
||||
| `delete-branch` | Delete the git branch after closing a stale pull request. _Defaults to **false**_ | Optional |
|
||||
| `exempt-milestones` | Milestones on an issue or a PR exempted from being marked as stale. | Optional |
|
||||
| `exempt-issue-milestones` | Milestones on an issue exempted from being marked as stale (override `exempt-milestones`). | Optional |
|
||||
| `exempt-pr-milestones` | Milestones on the PR exempted from being marked as stale (override `exempt-milestones`). | Optional |
|
||||
| `exempt-all-milestones` | Exempt all issues and PRs with milestones from being marked as stale. (priority over `exempt-milestones` rules) | Optional |
|
||||
| `exempt-all-issue-milestones` | Exempt all issues with milestones from being marked as stale. (override `exempt-all-milestones`). | Optional |
|
||||
| `exempt-all-pr-milestones` | Exempt all PRs with milestones from being marked as stale. (override `exempt-all-milestones`). | Optional |
|
||||
| `exempt-assignees` | Assignees on an issue or a PR exempted from being marked as stale. | Optional |
|
||||
| `exempt-issue-assignees` | Assignees on an issue exempted from being marked as stale (override `exempt-assignees`). | Optional |
|
||||
| `exempt-pr-assignees` | Assignees on the PR exempted from being marked as stale (override `exempt-assignees`). | Optional |
|
||||
| `exempt-all-assignees` | Exempt all issues and PRs with assignees from being marked as stale. (priority over `exempt-assignees` rules) | Optional |
|
||||
| `exempt-all-issue-assignees` | Exempt all issues with assignees from being marked as stale. (override `exempt-all-assignees`). | Optional |
|
||||
| `exempt-all-pr-assignees` | Exempt all PRs with assignees from being marked as stale. (override `exempt-all-assignees`). | Optional |
|
||||
| Input | Description | Usage |
|
||||
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -------- |
|
||||
| `repo-token` | PAT(Personal Access Token) for authorizing repository. _Defaults to **${{ github.token }}**_ | Optional |
|
||||
| `days-before-stale` | Idle number of days before marking an issue/PR as stale. _Defaults to **60**_ | Optional |
|
||||
| `days-before-issue-stale` | Idle number of days before marking an issue as stale (override `days-before-stale`). | Optional |
|
||||
| `days-before-pr-stale` | Idle number of days before marking an PR as stale (override `days-before-stale`). | Optional |
|
||||
| `days-before-close` | Idle number of days before closing an stale issue/PR. _Defaults to **7**_ | Optional |
|
||||
| `days-before-issue-close` | Idle number of days before closing an stale issue (override `days-before-close`). | Optional |
|
||||
| `days-before-pr-close` | Idle number of days before closing an stale PR (override `days-before-close`). | Optional |
|
||||
| `stale-issue-message` | Message to post on the stale issue. | Optional |
|
||||
| `stale-pr-message` | Message to post on the stale PR. | Optional |
|
||||
| `close-issue-message` | Message to post on the stale issue while closing it. | Optional |
|
||||
| `close-pr-message` | Message to post on the stale PR while closing it. | Optional |
|
||||
| `stale-issue-label` | Label to apply on the stale issue. _Defaults to **Stale**_ | Optional |
|
||||
| `close-issue-label` | Label to apply on closing issue (automatically removed if no longer closed nor locked). | Optional |
|
||||
| `stale-pr-label` | Label to apply on the stale PR. _Defaults to **Stale**_ | Optional |
|
||||
| `close-pr-label` | Label to apply on the closing PR (automatically removed if no longer closed nor locked). | Optional |
|
||||
| `exempt-issue-labels` | Labels on an issue exempted from being marked as stale. | Optional |
|
||||
| `exempt-pr-labels` | Labels on the PR exempted from being marked as stale. | Optional |
|
||||
| `exempt-milestones` | Milestones on an issue or a PR exempted from being marked as stale. | Optional |
|
||||
| `exempt-issue-milestones` | Milestones on an issue exempted from being marked as stale (override `exempt-milestones`). | Optional |
|
||||
| `exempt-pr-milestones` | Milestones on the PR exempted from being marked as stale (override `exempt-milestones`). | Optional |
|
||||
| `exempt-all-milestones` | Exempt all issues and PRs with milestones from being marked as stale. (priority over `exempt-milestones` rules) | Optional |
|
||||
| `exempt-all-issue-milestones` | Exempt all issues with milestones from being marked as stale. (override `exempt-all-milestones`). | Optional |
|
||||
| `exempt-all-pr-milestones` | Exempt all PRs with milestones from being marked as stale. (override `exempt-all-milestones`). | Optional |
|
||||
| `only-labels` | Only issues and PRs with ALL these labels are checked. Separate multiple labels with commas (eg. "question,answered"). | Optional |
|
||||
| `only-labels` | Only labels checked for stale issue/PR. | Optional |
|
||||
| `only-issue-labels` | Only labels checked for stale issue (override `only-labels`). | Optional |
|
||||
| `only-pr-labels` | Only labels checked for stale PR (override `only-labels`). | Optional |
|
||||
| `any-of-labels` | Only issues and PRs with ANY of these labels are checked. Separate multiple labels with commas (eg. "incomplete,waiting-feedback"). | Optional |
|
||||
| `operations-per-run` | Maximum number of operations per run (GitHub API CRUD related). _Defaults to **30**_ | Optional |
|
||||
| `remove-stale-when-updated` | Remove stale label from issue/PR on updates or comments. _Defaults to **true**_ | Optional |
|
||||
| `debug-only` | Dry-run on action. _Defaults to **false**_ | Optional |
|
||||
| `ascending` | Order to get issues/PR. _Defaults to **false**_ | Optional |
|
||||
| `skip-stale-issue-message` | Skip adding stale message on stale issue. _Defaults to **false**_ | Optional |
|
||||
| `skip-stale-pr-message` | Skip adding stale message on stale PR. _Defaults to **false**_ | Optional |
|
||||
| `start-date` | The date used to skip the stale action on issue/PR created before it (ISO 8601 or RFC 2822). | Optional |
|
||||
| `delete-branch` | Delete the git branch after closing a stale pull request. _Defaults to **false**_ | Optional |
|
||||
| `exempt-milestones` | Milestones on an issue or a PR exempted from being marked as stale. | Optional |
|
||||
| `exempt-issue-milestones` | Milestones on an issue exempted from being marked as stale (override `exempt-milestones`). | Optional |
|
||||
| `exempt-pr-milestones` | Milestones on the PR exempted from being marked as stale (override `exempt-milestones`). | Optional |
|
||||
| `exempt-all-milestones` | Exempt all issues and PRs with milestones from being marked as stale. (priority over `exempt-milestones` rules) | Optional |
|
||||
| `exempt-all-issue-milestones` | Exempt all issues with milestones from being marked as stale. (override `exempt-all-milestones`). | Optional |
|
||||
| `exempt-all-pr-milestones` | Exempt all PRs with milestones from being marked as stale. (override `exempt-all-milestones`). | Optional |
|
||||
| `exempt-assignees` | Assignees on an issue or a PR exempted from being marked as stale. | Optional |
|
||||
| `exempt-issue-assignees` | Assignees on an issue exempted from being marked as stale (override `exempt-assignees`). | Optional |
|
||||
| `exempt-pr-assignees` | Assignees on the PR exempted from being marked as stale (override `exempt-assignees`). | Optional |
|
||||
| `exempt-all-assignees` | Exempt all issues and PRs with assignees from being marked as stale. (priority over `exempt-assignees` rules) | Optional |
|
||||
| `exempt-all-issue-assignees` | Exempt all issues with assignees from being marked as stale. (override `exempt-all-assignees`). | Optional |
|
||||
| `exempt-all-pr-assignees` | Exempt all PRs with assignees from being marked as stale. (override `exempt-all-assignees`). | Optional |
|
||||
|
||||
### Usage
|
||||
|
||||
|
@ -211,6 +219,25 @@ jobs:
|
|||
exempt-all-pr-milestones: true
|
||||
```
|
||||
|
||||
Avoid stale for specific labels:
|
||||
|
||||
```yaml
|
||||
name: 'Close stale issues and PRs'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
with:
|
||||
any-of-labels: 'needs-more-info,needs-demo'
|
||||
# You can opt for 'only-labels' instead if your usecase requires all labels
|
||||
# to be present in the issue/PR
|
||||
```
|
||||
|
||||
Avoid stale for specific assignees:
|
||||
|
||||
```yaml
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
import {Issue} from '../src/classes/issue';
|
||||
import {IssuesProcessor} from '../src/classes/issues-processor';
|
||||
import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options';
|
||||
import {DefaultProcessorOptions} from './constants/default-processor-options';
|
||||
import {generateIssue} from './functions/generate-issue';
|
||||
|
||||
describe('any-of-labels option', () => {
|
||||
test('should do nothing when not set', async () => {
|
||||
const sut = new IssuesProcessorBuilder()
|
||||
.emptyAnyOfLabels()
|
||||
.issues([{labels: [{name: 'some-label'}]}])
|
||||
.build();
|
||||
|
||||
await sut.processIssues();
|
||||
|
||||
expect(sut.staleIssues).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should skip it when none of the issue labels match', async () => {
|
||||
const sut = new IssuesProcessorBuilder()
|
||||
.anyOfLabels('skip-this-issue,and-this-one')
|
||||
.issues([{labels: [{name: 'some-label'}, {name: 'some-other-label'}]}])
|
||||
.build();
|
||||
|
||||
await sut.processIssues();
|
||||
|
||||
expect(sut.staleIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should skip it when the issue has no labels', async () => {
|
||||
const sut = new IssuesProcessorBuilder()
|
||||
.anyOfLabels('skip-this-issue,and-this-one')
|
||||
.issues([{labels: []}])
|
||||
.build();
|
||||
|
||||
await sut.processIssues();
|
||||
|
||||
expect(sut.staleIssues).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should process it when one of the issue labels match', async () => {
|
||||
const sut = new IssuesProcessorBuilder()
|
||||
.anyOfLabels('skip-this-issue,and-this-one')
|
||||
.issues([{labels: [{name: 'some-label'}, {name: 'skip-this-issue'}]}])
|
||||
.build();
|
||||
|
||||
await sut.processIssues();
|
||||
|
||||
expect(sut.staleIssues).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should process it when all the issue labels match', async () => {
|
||||
const sut = new IssuesProcessorBuilder()
|
||||
.anyOfLabels('skip-this-issue,and-this-one')
|
||||
.issues([{labels: [{name: 'and-this-one'}, {name: 'skip-this-issue'}]}])
|
||||
.build();
|
||||
|
||||
await sut.processIssues();
|
||||
|
||||
expect(sut.staleIssues).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
class IssuesProcessorBuilder {
|
||||
private _options: IIssuesProcessorOptions;
|
||||
private _issues: Issue[];
|
||||
|
||||
constructor() {
|
||||
this._options = {...DefaultProcessorOptions};
|
||||
this._issues = [];
|
||||
}
|
||||
|
||||
anyOfLabels(labels: string): IssuesProcessorBuilder {
|
||||
this._options.anyOfLabels = labels;
|
||||
return this;
|
||||
}
|
||||
|
||||
emptyAnyOfLabels(): IssuesProcessorBuilder {
|
||||
return this.anyOfLabels('');
|
||||
}
|
||||
|
||||
issues(issues: Partial<Issue>[]): IssuesProcessorBuilder {
|
||||
this._issues = issues.map(
|
||||
(issue, index): Issue =>
|
||||
generateIssue(
|
||||
this._options,
|
||||
index,
|
||||
issue.title || 'Issue title',
|
||||
issue.updated_at || '2000-01-01T00:00:00Z', // we only care about stale/expired issues here
|
||||
issue.created_at || '2000-01-01T00:00:00Z',
|
||||
issue.isPullRequest || false,
|
||||
issue.labels ? issue.labels.map(label => label.name) : []
|
||||
)
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
build(): IssuesProcessor {
|
||||
return new IssuesProcessor(
|
||||
this._options,
|
||||
async () => 'abot',
|
||||
async p => (p === 1 ? this._issues : []),
|
||||
async () => [],
|
||||
async () => new Date().toDateString()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({
|
|||
onlyLabels: '',
|
||||
onlyIssueLabels: '',
|
||||
onlyPrLabels: '',
|
||||
anyOfLabels: '',
|
||||
operationsPerRun: 100,
|
||||
debugOnly: true,
|
||||
removeStaleWhenUpdated: false,
|
||||
|
|
|
@ -85,7 +85,11 @@ inputs:
|
|||
default: ''
|
||||
required: false
|
||||
only-labels:
|
||||
description: 'Only issues or pull requests with all of these labels are checked if stale. Defaults to `[]` (disabled) and can be a comma-separated list of labels.'
|
||||
description: 'Only issues or pull requests with all of these labels are checked if stale. Defaults to `` (disabled) and can be a comma-separated list of labels.'
|
||||
default: ''
|
||||
required: false
|
||||
any-of-labels:
|
||||
description: 'Only issues or pull requests with at least one of these labels are checked if stale. Defaults to `` (disabled) and can be a comma-separated list of labels.'
|
||||
default: ''
|
||||
required: false
|
||||
only-issue-labels:
|
||||
|
|
|
@ -350,6 +350,12 @@ class IssuesProcessor {
|
|||
issueLogger.info(`Skipping $$type because it has an exempt label`);
|
||||
continue; // don't process exempt issues
|
||||
}
|
||||
const anyOfLabels = words_to_list_1.wordsToList(this.options.anyOfLabels);
|
||||
if (anyOfLabels.length &&
|
||||
!anyOfLabels.some((label) => is_labeled_1.isLabeled(issue, label))) {
|
||||
issueLogger.info(`Skipping ${issueType} because it does not have any of the required labels`);
|
||||
continue; // don't process issues without any of the required labels
|
||||
}
|
||||
const milestones = new milestones_1.Milestones(this.options, issue);
|
||||
if (milestones.shouldExemptMilestones()) {
|
||||
issueLogger.info(`Skipping $$type because it has an exempted milestone`);
|
||||
|
@ -1201,6 +1207,7 @@ function _getAndValidateArgs() {
|
|||
onlyLabels: core.getInput('only-labels'),
|
||||
onlyIssueLabels: core.getInput('only-issue-labels'),
|
||||
onlyPrLabels: core.getInput('only-pr-labels'),
|
||||
anyOfLabels: core.getInput('any-of-labels'),
|
||||
operationsPerRun: parseInt(core.getInput('operations-per-run', { required: true })),
|
||||
removeStaleWhenUpdated: !(core.getInput('remove-stale-when-updated') === 'false'),
|
||||
debugOnly: core.getInput('debug-only') === 'true',
|
||||
|
|
|
@ -30,6 +30,7 @@ describe('Issue', (): void => {
|
|||
onlyLabels: '',
|
||||
onlyIssueLabels: '',
|
||||
onlyPrLabels: '',
|
||||
anyOfLabels: '',
|
||||
operationsPerRun: 0,
|
||||
removeStaleWhenUpdated: false,
|
||||
repoToken: '',
|
||||
|
|
|
@ -230,6 +230,19 @@ export class IssuesProcessor {
|
|||
continue; // don't process exempt issues
|
||||
}
|
||||
|
||||
const anyOfLabels: string[] = wordsToList(this.options.anyOfLabels);
|
||||
if (
|
||||
anyOfLabels.length &&
|
||||
!anyOfLabels.some((label: Readonly<string>): boolean =>
|
||||
isLabeled(issue, label)
|
||||
)
|
||||
) {
|
||||
issueLogger.info(
|
||||
`Skipping ${issueType} because it does not have any of the required labels`
|
||||
);
|
||||
continue; // don't process issues without any of the required labels
|
||||
}
|
||||
|
||||
const milestones: Milestones = new Milestones(this.options, issue);
|
||||
|
||||
if (milestones.shouldExemptMilestones()) {
|
||||
|
|
|
@ -21,6 +21,7 @@ export interface IIssuesProcessorOptions {
|
|||
onlyLabels: string;
|
||||
onlyIssueLabels: string;
|
||||
onlyPrLabels: string;
|
||||
anyOfLabels: string;
|
||||
operationsPerRun: number;
|
||||
removeStaleWhenUpdated: boolean;
|
||||
debugOnly: boolean;
|
||||
|
|
|
@ -41,6 +41,7 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
|
|||
onlyLabels: core.getInput('only-labels'),
|
||||
onlyIssueLabels: core.getInput('only-issue-labels'),
|
||||
onlyPrLabels: core.getInput('only-pr-labels'),
|
||||
anyOfLabels: core.getInput('any-of-labels'),
|
||||
operationsPerRun: parseInt(
|
||||
core.getInput('operations-per-run', {required: true})
|
||||
),
|
||||
|
|
Loading…
Reference in New Issue