feat(close-label): automatically remove close-label when no longer closed nor locked (#334)

* feat(assignees): add new option to avoid stale for assignees

closes #271

* test: add more coverage

* docs: fix readme format issue

* docs: reorder and enhance typo

* docs(contributing): add more information about the npm scripts

* docs(readme): update the default values to reflect the real applied ones

* feat(close-label): automatically remove it when no longer closed nor locked

closes #278
This commit is contained in:
Geoffrey Testelin 2021-03-01 01:07:54 +01:00 committed by GitHub
parent ec96ff65b0
commit 836169b81a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 161 additions and 7 deletions

View File

@ -17,10 +17,10 @@ Warns and then closes issues and PRs that have had no activity for a specified a
| `stale-pr-message` | Message to post on the stale PR. | 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-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 | | `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 | | `stale-issue-label` | Label to apply on the stale issue. _Defaults to **Stale**_ | Optional |
| `close-issue-label` | Label to apply on closing issue. | 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. | 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. | 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-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-pr-labels` | Labels on the PR exempted from being marked as stale. | Optional |
| `only-labels` | Only labels checked for stale issue/PR. | Optional | | `only-labels` | Only labels checked for stale issue/PR. | Optional |

View File

@ -2112,3 +2112,111 @@ test('processing a pull request opened since 2 days and with the option "daysBef
expect(processor.staleIssues.length).toEqual(1); expect(processor.staleIssues.length).toEqual(1);
expect(processor.closedIssues.length).toEqual(0); expect(processor.closedIssues.length).toEqual(0);
}); });
test('processing a previously closed issue with a close label will remove the close label', async () => {
expect.assertions(1);
const opts: IIssuesProcessorOptions = {
...DefaultProcessorOptions,
closeIssueLabel: 'close',
staleIssueLabel: 'stale'
};
const now: Date = new Date();
const oneWeekAgo: Date = new Date(now.getDate() - 7);
const TestIssueList: Issue[] = [
generateIssue(
opts,
1,
'An opened issue with a close label',
oneWeekAgo.toDateString(),
now.toDateString(),
false,
['close'],
false,
false
)
];
const processor = new IssuesProcessor(
opts,
async () => 'abot',
async p => (p === 1 ? TestIssueList : []),
async () => [],
async () => new Date().toDateString()
);
// process our fake issue list
await processor.processIssues(1);
expect(processor.removedLabelIssues).toHaveLength(1);
});
test('processing a closed issue with a close label will not remove the close label', async () => {
expect.assertions(1);
const opts: IIssuesProcessorOptions = {
...DefaultProcessorOptions,
closeIssueLabel: 'close',
staleIssueLabel: 'stale'
};
const now: Date = new Date();
const oneWeekAgo: Date = new Date(now.getDate() - 7);
const TestIssueList: Issue[] = [
generateIssue(
opts,
1,
'A closed issue with a close label',
oneWeekAgo.toDateString(),
now.toDateString(),
false,
['close'],
true,
false
)
];
const processor = new IssuesProcessor(
opts,
async () => 'abot',
async p => (p === 1 ? TestIssueList : []),
async () => [],
async () => new Date().toDateString()
);
// process our fake issue list
await processor.processIssues(1);
expect(processor.removedLabelIssues).toHaveLength(0);
});
test('processing a locked issue with a close label will not remove the close label', async () => {
expect.assertions(1);
const opts: IIssuesProcessorOptions = {
...DefaultProcessorOptions,
closeIssueLabel: 'close',
staleIssueLabel: 'stale'
};
const now: Date = new Date();
const oneWeekAgo: Date = new Date(now.getDate() - 7);
const TestIssueList: Issue[] = [
generateIssue(
opts,
1,
'A closed issue with a close label',
oneWeekAgo.toDateString(),
now.toDateString(),
false,
['close'],
false,
true
)
];
const processor = new IssuesProcessor(
opts,
async () => 'abot',
async p => (p === 1 ? TestIssueList : []),
async () => [],
async () => new Date().toDateString()
);
// process our fake issue list
await processor.processIssues(1);
expect(processor.removedLabelIssues).toHaveLength(0);
});

18
dist/index.js vendored
View File

@ -299,6 +299,8 @@ class IssuesProcessor {
issueLogger.info(`Skipping $$type because it is locked`); issueLogger.info(`Skipping $$type because it is locked`);
continue; // don't process locked issues continue; // don't process locked issues
} }
// Try to remove the close label when not close/locked issue or PR
yield this._removeCloseLabel(issue, closeLabel);
if (this.options.startDate) { if (this.options.startDate) {
const startDate = new Date(this.options.startDate); const startDate = new Date(this.options.startDate);
const createdAt = new Date(issue.created_at); const createdAt = new Date(issue.created_at);
@ -677,10 +679,24 @@ class IssuesProcessor {
_removeStaleLabel(issue, staleLabel) { _removeStaleLabel(issue, staleLabel) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const issueLogger = new issue_logger_1.IssueLogger(issue); const issueLogger = new issue_logger_1.IssueLogger(issue);
issueLogger.info(`$$type is no longer stale. Removing stale label.`); issueLogger.info(`The $$type is no longer stale. Removing the stale label...`);
return this._removeLabel(issue, staleLabel); return this._removeLabel(issue, staleLabel);
}); });
} }
_removeCloseLabel(issue, closeLabel) {
return __awaiter(this, void 0, void 0, function* () {
const issueLogger = new issue_logger_1.IssueLogger(issue);
issueLogger.info(`The $$type is not closed nor locked. Trying to remove the close label...`);
if (!closeLabel) {
issueLogger.info(`There is no close label on this $$type. Skip`);
return Promise.resolve();
}
if (is_labeled_1.isLabeled(issue, closeLabel)) {
issueLogger.info(`The $$type has a close label "${closeLabel}". Removing the close label...`);
return this._removeLabel(issue, closeLabel);
}
});
}
} }
exports.IssuesProcessor = IssuesProcessor; exports.IssuesProcessor = IssuesProcessor;

View File

@ -15,7 +15,7 @@ export class Issue implements IIssue {
updated_at: IsoDateString; updated_at: IsoDateString;
readonly labels: ILabel[]; readonly labels: ILabel[];
readonly pull_request: Object | null | undefined; readonly pull_request: Object | null | undefined;
readonly state: string; readonly state: string | 'closed' | 'open';
readonly locked: boolean; readonly locked: boolean;
readonly milestone: IMilestone | undefined; readonly milestone: IMilestone | undefined;
readonly assignees: IAssignee[]; readonly assignees: IAssignee[];

View File

@ -140,6 +140,9 @@ export class IssuesProcessor {
continue; // don't process locked issues continue; // don't process locked issues
} }
// Try to remove the close label when not close/locked issue or PR
await this._removeCloseLabel(issue, closeLabel);
if (this.options.startDate) { if (this.options.startDate) {
const startDate: Date = new Date(this.options.startDate); const startDate: Date = new Date(this.options.startDate);
const createdAt: Date = new Date(issue.created_at); const createdAt: Date = new Date(issue.created_at);
@ -663,8 +666,35 @@ export class IssuesProcessor {
): Promise<void> { ): Promise<void> {
const issueLogger: IssueLogger = new IssueLogger(issue); const issueLogger: IssueLogger = new IssueLogger(issue);
issueLogger.info(`$$type is no longer stale. Removing stale label.`); issueLogger.info(
`The $$type is no longer stale. Removing the stale label...`
);
return this._removeLabel(issue, staleLabel); return this._removeLabel(issue, staleLabel);
} }
private async _removeCloseLabel(
issue: Issue,
closeLabel: Readonly<string | undefined>
): Promise<void> {
const issueLogger: IssueLogger = new IssueLogger(issue);
issueLogger.info(
`The $$type is not closed nor locked. Trying to remove the close label...`
);
if (!closeLabel) {
issueLogger.info(`There is no close label on this $$type. Skip`);
return Promise.resolve();
}
if (isLabeled(issue, closeLabel)) {
issueLogger.info(
`The $$type has a close label "${closeLabel}". Removing the close label...`
);
return this._removeLabel(issue, closeLabel);
}
}
} }