Skip to content

Commit

Permalink
Feat: add any-of-labels option (#319)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* refactor: add return type to lambda

Co-authored-by: Geoffrey Testelin <[email protected]>
  • Loading branch information
Jose Veiga and C0ZEN authored Mar 1, 2021
1 parent 8f5f223 commit 63ae8ac
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 43 deletions.
111 changes: 69 additions & 42 deletions README.md

Large diffs are not rendered by default.

107 changes: 107 additions & 0 deletions __tests__/any-of-labels.spec.ts
Original file line number Diff line number Diff line change
@@ -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()
);
}
}
1 change: 1 addition & 0 deletions __tests__/constants/default-processor-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({
onlyLabels: '',
onlyIssueLabels: '',
onlyPrLabels: '',
anyOfLabels: '',
operationsPerRun: 100,
debugOnly: true,
removeStaleWhenUpdated: false,
Expand Down
6 changes: 5 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
7 changes: 7 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`);
Expand Down Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions src/classes/issue.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('Issue', (): void => {
onlyLabels: '',
onlyIssueLabels: '',
onlyPrLabels: '',
anyOfLabels: '',
operationsPerRun: 0,
removeStaleWhenUpdated: false,
repoToken: '',
Expand Down
13 changes: 13 additions & 0 deletions src/classes/issues-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
1 change: 1 addition & 0 deletions src/interfaces/issues-processor-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface IIssuesProcessorOptions {
onlyLabels: string;
onlyIssueLabels: string;
onlyPrLabels: string;
anyOfLabels: string;
operationsPerRun: number;
removeStaleWhenUpdated: boolean;
debugOnly: boolean;
Expand Down
1 change: 1 addition & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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})
),
Expand Down

0 comments on commit 63ae8ac

Please sign in to comment.