Merge acfe4b9de04eadf6071caa3a071a63e28eb7c00e into 3f3b0175e8c66fb49b9a6d5a0cd1f8436d4c3ab6
This commit is contained in:
commit
b9ffbcdb43
109
README.md
109
README.md
@ -42,61 +42,62 @@ configured for the repo.
|
||||
|
||||
Every argument is optional.
|
||||
|
||||
| Input | Description | Default |
|
||||
| ------------------------------------------------------------------- | --------------------------------------------------------------------------- | --------------------- |
|
||||
| [repo-token](#repo-token) | PAT for GitHub API authentication | `${{ github.token }}` |
|
||||
| [days-before-stale](#days-before-stale) | Idle number of days before marking issues/PRs stale | `60` |
|
||||
| [days-before-issue-stale](#days-before-issue-stale) | Override [days-before-stale](#days-before-stale) for issues only | |
|
||||
| [days-before-pr-stale](#days-before-pr-stale) | Override [days-before-stale](#days-before-stale) for PRs only | |
|
||||
| [days-before-close](#days-before-close) | Idle number of days before closing stale issues/PRs | `7` |
|
||||
| [days-before-issue-close](#days-before-issue-close) | Override [days-before-close](#days-before-close) for issues only | |
|
||||
| [days-before-pr-close](#days-before-pr-close) | Override [days-before-close](#days-before-close) for PRs only | |
|
||||
| [stale-issue-message](#stale-issue-message) | Comment on the staled issues | |
|
||||
| [stale-pr-message](#stale-pr-message) | Comment on the staled PRs | |
|
||||
| [close-issue-message](#close-issue-message) | Comment on the staled issues while closed | |
|
||||
| [close-pr-message](#close-pr-message) | Comment on the staled PRs while closed | |
|
||||
| [stale-issue-label](#stale-issue-label) | Label to apply on staled issues | `Stale` |
|
||||
| [close-issue-label](#close-issue-label) | Label to apply on closed issues | |
|
||||
| [close-issue-reason](#close-issue-reason) | Reason to use when closing issues | `not_planned` |
|
||||
| [stale-pr-label](#stale-pr-label) | Label to apply on staled PRs | `Stale` |
|
||||
| [close-pr-label](#close-pr-label) | Label to apply on closed PRs | |
|
||||
| [exempt-issue-labels](#exempt-issue-labels) | Labels on issues exempted from stale | |
|
||||
| [exempt-pr-labels](#exempt-pr-labels) | Labels on PRs exempted from stale | |
|
||||
| [only-labels](#only-labels) | Only issues/PRs with ALL these labels are checked | |
|
||||
| [only-issue-labels](#only-issue-labels) | Override [only-labels](#only-labels) for issues only | |
|
||||
| [only-pr-labels](#only-pr-labels) | Override [only-labels](#only-labels) for PRs only | |
|
||||
| [any-of-labels](#any-of-labels) | Only issues/PRs with ANY of these labels are checked | |
|
||||
| [any-of-issue-labels](#any-of-issue-labels) | Override [any-of-labels](#any-of-labels) for issues only | |
|
||||
| [any-of-pr-labels](#any-of-pr-labels) | Override [any-of-labels](#any-of-labels) for PRs only | |
|
||||
| [operations-per-run](#operations-per-run) | Max number of operations per run | `30` |
|
||||
| [remove-stale-when-updated](#remove-stale-when-updated) | Remove stale label from issues/PRs on updates | `true` |
|
||||
| [remove-issue-stale-when-updated](#remove-issue-stale-when-updated) | Remove stale label from issues on updates/comments | |
|
||||
| [remove-pr-stale-when-updated](#remove-pr-stale-when-updated) | Remove stale label from PRs on updates/comments | |
|
||||
| [labels-to-add-when-unstale](#labels-to-add-when-unstale) | Add specified labels from issues/PRs when they become unstale | |
|
||||
| [labels-to-remove-when-stale](#labels-to-remove-when-stale) | Remove specified labels from issues/PRs when they become stale | |
|
||||
| [labels-to-remove-when-unstale](#labels-to-remove-when-unstale) | Remove specified labels from issues/PRs when they become unstale | |
|
||||
| [debug-only](#debug-only) | Dry-run | `false` |
|
||||
| [ascending](#ascending) | Order to get issues/PRs | `false` |
|
||||
| [start-date](#start-date) | Skip stale action for issues/PRs created before it | |
|
||||
| [delete-branch](#delete-branch) | Delete branch after closing a stale PR | `false` |
|
||||
| [exempt-milestones](#exempt-milestones) | Milestones on issues/PRs exempted from stale | |
|
||||
| [exempt-issue-milestones](#exempt-issue-milestones) | Override [exempt-milestones](#exempt-milestones) for issues only | |
|
||||
| [exempt-pr-milestones](#exempt-pr-milestones) | Override [exempt-milestones](#exempt-milestones) for PRs only | |
|
||||
| [exempt-all-milestones](#exempt-all-milestones) | Exempt all issues/PRs with milestones from stale | `false` |
|
||||
| [exempt-all-issue-milestones](#exempt-all-issue-milestones) | Override [exempt-all-milestones](#exempt-all-milestones) for issues only | |
|
||||
| [exempt-all-pr-milestones](#exempt-all-pr-milestones) | Override [exempt-all-milestones](#exempt-all-milestones) for PRs only | |
|
||||
| [exempt-assignees](#exempt-assignees) | Assignees on issues/PRs exempted from stale | |
|
||||
| [exempt-issue-assignees](#exempt-issue-assignees) | Override [exempt-assignees](#exempt-assignees) for issues only | |
|
||||
| [exempt-pr-assignees](#exempt-pr-assignees) | Override [exempt-assignees](#exempt-assignees) for PRs only | |
|
||||
| [exempt-all-assignees](#exempt-all-assignees) | Exempt all issues/PRs with assignees from stale | `false` |
|
||||
| [exempt-all-issue-assignees](#exempt-all-issue-assignees) | Override [exempt-all-assignees](#exempt-all-assignees) for issues only | |
|
||||
| [exempt-all-pr-assignees](#exempt-all-pr-assignees) | Override [exempt-all-assignees](#exempt-all-assignees) for PRs only | |
|
||||
| [exempt-draft-pr](#exempt-draft-pr) | Skip the stale action for draft PRs | `false` |
|
||||
| [enable-statistics](#enable-statistics) | Display statistics in the logs | `true` |
|
||||
| Input | Description | Default |
|
||||
|---------------------------------------------------------------------|----------------------------------------------------------------------------|-----------------------|
|
||||
| [repo-token](#repo-token) | PAT for GitHub API authentication | `${{ github.token }}` |
|
||||
| [days-before-stale](#days-before-stale) | Idle number of days before marking issues/PRs stale | `60` |
|
||||
| [days-before-issue-stale](#days-before-issue-stale) | Override [days-before-stale](#days-before-stale) for issues only | |
|
||||
| [days-before-pr-stale](#days-before-pr-stale) | Override [days-before-stale](#days-before-stale) for PRs only | |
|
||||
| [days-before-close](#days-before-close) | Idle number of days before closing stale issues/PRs | `7` |
|
||||
| [days-before-issue-close](#days-before-issue-close) | Override [days-before-close](#days-before-close) for issues only | |
|
||||
| [days-before-pr-close](#days-before-pr-close) | Override [days-before-close](#days-before-close) for PRs only | |
|
||||
| [stale-issue-message](#stale-issue-message) | Comment on the staled issues | |
|
||||
| [stale-pr-message](#stale-pr-message) | Comment on the staled PRs | |
|
||||
| [close-issue-message](#close-issue-message) | Comment on the staled issues while closed | |
|
||||
| [close-pr-message](#close-pr-message) | Comment on the staled PRs while closed | |
|
||||
| [stale-issue-label](#stale-issue-label) | Label to apply on staled issues | `Stale` |
|
||||
| [close-issue-label](#close-issue-label) | Label to apply on closed issues | |
|
||||
| [close-issue-reason](#close-issue-reason) | Reason to use when closing issues | `not_planned` |
|
||||
| [stale-pr-label](#stale-pr-label) | Label to apply on staled PRs | `Stale` |
|
||||
| [close-pr-label](#close-pr-label) | Label to apply on closed PRs | |
|
||||
| [exempt-issue-labels](#exempt-issue-labels) | Labels on issues exempted from stale | |
|
||||
| [exempt-pr-labels](#exempt-pr-labels) | Labels on PRs exempted from stale | |
|
||||
| [only-labels](#only-labels) | Only issues/PRs with ALL these labels are checked | |
|
||||
| [only-issue-labels](#only-issue-labels) | Override [only-labels](#only-labels) for issues only | |
|
||||
| [only-pr-labels](#only-pr-labels) | Override [only-labels](#only-labels) for PRs only | |
|
||||
| [any-of-labels](#any-of-labels) | Only issues/PRs with ANY of these labels are checked | |
|
||||
| [any-of-issue-labels](#any-of-issue-labels) | Override [any-of-labels](#any-of-labels) for issues only | |
|
||||
| [any-of-pr-labels](#any-of-pr-labels) | Override [any-of-labels](#any-of-labels) for PRs only | |
|
||||
| [operations-per-run](#operations-per-run) | Max number of operations per run | `30` |
|
||||
| [remove-stale-when-updated](#remove-stale-when-updated) | Remove stale label from issues/PRs on updates | `true` |
|
||||
| [remove-issue-stale-when-updated](#remove-issue-stale-when-updated) | Remove stale label from issues on updates/comments | |
|
||||
| [remove-pr-stale-when-updated](#remove-pr-stale-when-updated) | Remove stale label from PRs on updates/comments | |
|
||||
| [labels-to-add-when-unstale](#labels-to-add-when-unstale) | Add specified labels from issues/PRs when they become unstale | |
|
||||
| [labels-to-remove-when-stale](#labels-to-remove-when-stale) | Remove specified labels from issues/PRs when they become stale | |
|
||||
| [labels-to-remove-when-unstale](#labels-to-remove-when-unstale) | Remove specified labels from issues/PRs when they become unstale | |
|
||||
| [debug-only](#debug-only) | Dry-run | `false` |
|
||||
| [ascending](#ascending) | Order to get issues/PRs | `false` |
|
||||
| [start-date](#start-date) | Skip stale action for issues/PRs created before it | |
|
||||
| [delete-branch](#delete-branch) | Delete branch after closing a stale PR | `false` |
|
||||
| [exempt-milestones](#exempt-milestones) | Milestones on issues/PRs exempted from stale | |
|
||||
| [exempt-issue-milestones](#exempt-issue-milestones) | Override [exempt-milestones](#exempt-milestones) for issues only | |
|
||||
| [exempt-pr-milestones](#exempt-pr-milestones) | Override [exempt-milestones](#exempt-milestones) for PRs only | |
|
||||
| [exempt-all-milestones](#exempt-all-milestones) | Exempt all issues/PRs with milestones from stale | `false` |
|
||||
| [exempt-all-issue-milestones](#exempt-all-issue-milestones) | Override [exempt-all-milestones](#exempt-all-milestones) for issues only | |
|
||||
| [exempt-all-pr-milestones](#exempt-all-pr-milestones) | Override [exempt-all-milestones](#exempt-all-milestones) for PRs only | |
|
||||
| [exempt-assignees](#exempt-assignees) | Assignees on issues/PRs exempted from stale | |
|
||||
| [exempt-issue-assignees](#exempt-issue-assignees) | Override [exempt-assignees](#exempt-assignees) for issues only | |
|
||||
| [exempt-pr-assignees](#exempt-pr-assignees) | Override [exempt-assignees](#exempt-assignees) for PRs only | |
|
||||
| [exempt-pinned-issues](#exempt-pinned-issues) | Exempt pinned issues from stale | `false` |
|
||||
| [exempt-all-assignees](#exempt-all-assignees) | Exempt all issues/PRs with assignees from stale | `false` |
|
||||
| [exempt-all-issue-assignees](#exempt-all-issue-assignees) | Override [exempt-all-assignees](#exempt-all-assignees) for issues only | |
|
||||
| [exempt-all-pr-assignees](#exempt-all-pr-assignees) | Override [exempt-all-assignees](#exempt-all-assignees) for PRs only | |
|
||||
| [exempt-draft-pr](#exempt-draft-pr) | Skip the stale action for draft PRs | `false` |
|
||||
| [enable-statistics](#enable-statistics) | Display statistics in the logs | `true` |
|
||||
| [ignore-updates](#ignore-updates) | Any update (update/comment) can reset the stale idle time on the issues/PRs | `false` |
|
||||
| [ignore-issue-updates](#ignore-issue-updates) | Override [ignore-updates](#ignore-updates) for issues only | |
|
||||
| [ignore-pr-updates](#ignore-pr-updates) | Override [ignore-updates](#ignore-updates) for PRs only | |
|
||||
| [include-only-assigned](#include-only-assigned) | Process only assigned issues | `false` |
|
||||
| [ignore-issue-updates](#ignore-issue-updates) | Override [ignore-updates](#ignore-updates) for issues only | |
|
||||
| [ignore-pr-updates](#ignore-pr-updates) | Override [ignore-updates](#ignore-updates) for PRs only | |
|
||||
| [include-only-assigned](#include-only-assigned) | Process only assigned issues | `false` |
|
||||
|
||||
### List of output options
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import {IComment} from '../../src/interfaces/comment';
|
||||
import {IIssuesProcessorOptions} from '../../src/interfaces/issues-processor-options';
|
||||
import {IPullRequest} from '../../src/interfaces/pull-request';
|
||||
import {IState} from '../../src/interfaces/state/state';
|
||||
import {IIssueEvent} from '../../src/interfaces/issue-event';
|
||||
|
||||
export class IssuesProcessorMock extends IssuesProcessor {
|
||||
constructor(
|
||||
@ -18,7 +19,8 @@ export class IssuesProcessorMock extends IssuesProcessor {
|
||||
issue: Issue,
|
||||
label: string
|
||||
) => Promise<string | undefined>,
|
||||
getPullRequest?: (issue: Issue) => Promise<IPullRequest | undefined | void>
|
||||
getPullRequest?: (issue: Issue) => Promise<IPullRequest | undefined | void>,
|
||||
getIssueEvents?: (issue: Issue) => Promise<IIssueEvent[]>
|
||||
) {
|
||||
super(options, state);
|
||||
|
||||
@ -37,5 +39,9 @@ export class IssuesProcessorMock extends IssuesProcessor {
|
||||
if (getPullRequest) {
|
||||
this.getPullRequest = getPullRequest;
|
||||
}
|
||||
|
||||
if (getIssueEvents) {
|
||||
this.getIssueEvents = getIssueEvents;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({
|
||||
staleIssueLabel: 'Stale',
|
||||
closeIssueLabel: '',
|
||||
exemptIssueLabels: '',
|
||||
exemptPinnedIssues: false,
|
||||
stalePrLabel: 'Stale',
|
||||
closePrLabel: '',
|
||||
exemptPrLabels: '',
|
||||
|
||||
89
__tests__/pinned.spec.ts
Normal file
89
__tests__/pinned.spec.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options';
|
||||
import {DefaultProcessorOptions} from './constants/default-processor-options';
|
||||
import {Issue} from '../src/classes/issue';
|
||||
import {generateIssue} from './functions/generate-issue';
|
||||
import {IssuesProcessorMock} from './classes/issues-processor-mock';
|
||||
import {alwaysFalseStateMock} from './classes/state-mock';
|
||||
import {IIssueEvent} from '../src/interfaces/issue-event';
|
||||
|
||||
const opts: IIssuesProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeClose: 0
|
||||
};
|
||||
const testIssueList = async (page: number): Promise<Issue[]> => {
|
||||
return page == 1
|
||||
? [
|
||||
generateIssue(opts, 1, 'Issue 1', '2020-01-01T17:00:00Z'),
|
||||
generateIssue(opts, 2, 'Issue 2', '2020-01-01T17:00:00Z')
|
||||
]
|
||||
: [];
|
||||
};
|
||||
|
||||
const pinnedEvent: IIssueEvent = {
|
||||
created_at: '2020-01-01T17:00:00Z',
|
||||
event: 'pinned',
|
||||
label: {}
|
||||
};
|
||||
const unpinnedEvent: IIssueEvent = {
|
||||
created_at: '2020-01-01T17:00:00Z',
|
||||
event: 'unpinned',
|
||||
label: {}
|
||||
};
|
||||
describe('exempt-pinned-issues options', (): void => {
|
||||
it('pinned issues should be skipped if exemptPinnedIssues true', async () => {
|
||||
const processor = new IssuesProcessorMock(
|
||||
{...opts, exemptPinnedIssues: true},
|
||||
alwaysFalseStateMock,
|
||||
testIssueList,
|
||||
async () => [],
|
||||
async () => new Date().toDateString(),
|
||||
async (issue: Issue) => undefined,
|
||||
async (issue: Issue) => (issue.number === 1 ? [pinnedEvent] : [])
|
||||
);
|
||||
await processor.processIssues(1);
|
||||
expect(processor.staleIssues).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('pinned issues should not be skipped if exemptPinnedIssues false', async () => {
|
||||
const processor = new IssuesProcessorMock(
|
||||
{...opts, exemptPinnedIssues: false},
|
||||
alwaysFalseStateMock,
|
||||
testIssueList,
|
||||
async () => [],
|
||||
async () => new Date().toDateString(),
|
||||
async (issue: Issue) => undefined,
|
||||
async (issue: Issue) => (issue.number === 1 ? [pinnedEvent] : [])
|
||||
);
|
||||
await processor.processIssues(1);
|
||||
expect(processor.staleIssues).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('pinned issues should not be skipped if exemptPinnedIssues true but it was unpinned', async () => {
|
||||
const processor = new IssuesProcessorMock(
|
||||
{...opts, exemptPinnedIssues: true},
|
||||
alwaysFalseStateMock,
|
||||
testIssueList,
|
||||
async () => [],
|
||||
async () => new Date().toDateString(),
|
||||
async (issue: Issue) => undefined,
|
||||
async (issue: Issue) =>
|
||||
issue.number === 1 ? [unpinnedEvent, pinnedEvent] : []
|
||||
);
|
||||
await processor.processIssues(1);
|
||||
expect(processor.staleIssues).toHaveLength(2);
|
||||
});
|
||||
it('pinned issues should not be skipped if exemptPinnedIssues true and it was unpinned and pinned', async () => {
|
||||
const processor = new IssuesProcessorMock(
|
||||
{...opts, exemptPinnedIssues: true},
|
||||
alwaysFalseStateMock,
|
||||
testIssueList,
|
||||
async () => [],
|
||||
async () => new Date().toDateString(),
|
||||
async (issue: Issue) => undefined,
|
||||
async (issue: Issue) =>
|
||||
issue.number === 1 ? [pinnedEvent, unpinnedEvent, pinnedEvent] : []
|
||||
);
|
||||
await processor.processIssues(1);
|
||||
expect(processor.staleIssues).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@ -164,6 +164,10 @@ inputs:
|
||||
description: 'Exempt all issues with assignees from being marked as stale. Override "exempt-all-assignees" option regarding only the issues.'
|
||||
default: ''
|
||||
required: false
|
||||
exempt-pinned-issues:
|
||||
description: 'Exempt pinned issues from being marked as stale. Default to false.'
|
||||
default: 'false'
|
||||
required: false
|
||||
exempt-all-pr-assignees:
|
||||
description: 'Exempt all pull requests with assignees from being marked as stale. Override "exempt-all-assignees" option regarding only the pull requests.'
|
||||
default: ''
|
||||
|
||||
52
dist/index.js
vendored
52
dist/index.js
vendored
@ -409,6 +409,8 @@ class IssuesProcessor {
|
||||
this.addedLabelIssues = [];
|
||||
this.addedCloseCommentIssues = [];
|
||||
this._logger = new logger_1.Logger();
|
||||
this._lastIssueEvents = [];
|
||||
this._lastIssueEventsIssueId = -1;
|
||||
this.options = options;
|
||||
this.state = state;
|
||||
this.client = (0, github_1.getOctokit)(this.options.repoToken, undefined, plugin_retry_1.retry);
|
||||
@ -465,6 +467,34 @@ class IssuesProcessor {
|
||||
return this.processIssues(page + 1);
|
||||
});
|
||||
}
|
||||
getIssueEvents(issue) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (issue.number !== this._lastIssueEventsIssueId) {
|
||||
const options = this.client.rest.issues.listEvents.endpoint.merge({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
per_page: 100,
|
||||
issue_number: issue.number
|
||||
});
|
||||
const events = yield this.client.paginate(options);
|
||||
this._lastIssueEvents = events.reverse();
|
||||
this._lastIssueEventsIssueId = issue.number;
|
||||
}
|
||||
return this._lastIssueEvents;
|
||||
});
|
||||
}
|
||||
getPinnedStatus(issue) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const events = yield this.getIssueEvents(issue);
|
||||
const pinnedEvent = events.findIndex(event => event.event === 'pinned');
|
||||
if (pinnedEvent == -1)
|
||||
return false;
|
||||
const unpinnedEvent = events.findIndex(event => event.event === 'unpinned');
|
||||
if (unpinnedEvent == -1)
|
||||
return true;
|
||||
return pinnedEvent < unpinnedEvent;
|
||||
});
|
||||
}
|
||||
processIssue(issue, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale, labelsToRemoveWhenStale) {
|
||||
var _a;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
@ -505,6 +535,11 @@ class IssuesProcessor {
|
||||
IssuesProcessor._endIssueProcessing(issue);
|
||||
return; // If the issue has an 'include-only-assigned' option set, process only issues with nonempty assignees list
|
||||
}
|
||||
if (yield this._isSkipPinned(issue)) {
|
||||
issueLogger.info('Skipping this issue because it is pinned');
|
||||
IssuesProcessor._endIssueProcessing(issue);
|
||||
return; // Don't process pinned issues
|
||||
}
|
||||
const onlyLabels = (0, words_to_list_1.wordsToList)(this._getOnlyLabels(issue));
|
||||
if (onlyLabels.length > 0) {
|
||||
issueLogger.info(`The option ${issueLogger.createOptionLink(option_1.Option.OnlyLabels)} was specified to only process issues and pull requests with all those labels (${logger_service_1.LoggerService.cyan(onlyLabels.length)})`);
|
||||
@ -703,15 +738,8 @@ class IssuesProcessor {
|
||||
issueLogger.info(`Checking for label on this $$type`);
|
||||
this._consumeIssueOperation(issue);
|
||||
(_a = this.statistics) === null || _a === void 0 ? void 0 : _a.incrementFetchedItemsEventsCount();
|
||||
const options = this.client.rest.issues.listEvents.endpoint.merge({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
per_page: 100,
|
||||
issue_number: issue.number
|
||||
});
|
||||
const events = yield this.client.paginate(options);
|
||||
const reversedEvents = events.reverse();
|
||||
const staleLabeledEvent = reversedEvents.find(event => event.event === 'labeled' &&
|
||||
const events = yield this.getIssueEvents(issue);
|
||||
const staleLabeledEvent = events.find(event => event.event === 'labeled' &&
|
||||
(0, clean_label_1.cleanLabel)(event.label.name) === (0, clean_label_1.cleanLabel)(label));
|
||||
if (!staleLabeledEvent) {
|
||||
// Must be old rather than labeled
|
||||
@ -1038,6 +1066,11 @@ class IssuesProcessor {
|
||||
_isIncludeOnlyAssigned(issue) {
|
||||
return this.options.includeOnlyAssigned && !issue.hasAssignees;
|
||||
}
|
||||
_isSkipPinned(issue) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
return (this.options.exemptPinnedIssues && (yield this.getPinnedStatus(issue)));
|
||||
});
|
||||
}
|
||||
_getAnyOfLabels(issue) {
|
||||
if (issue.isPullRequest) {
|
||||
if (this.options.anyOfPrLabels !== '') {
|
||||
@ -2527,6 +2560,7 @@ function _getAndValidateArgs() {
|
||||
staleIssueLabel: core.getInput('stale-issue-label', { required: true }),
|
||||
closeIssueLabel: core.getInput('close-issue-label'),
|
||||
exemptIssueLabels: core.getInput('exempt-issue-labels'),
|
||||
exemptPinnedIssues: core.getInput('exempt-pinned-issues') === 'true',
|
||||
stalePrLabel: core.getInput('stale-pr-label', { required: true }),
|
||||
closePrLabel: core.getInput('close-pr-label'),
|
||||
exemptPrLabels: core.getInput('exempt-pr-labels'),
|
||||
|
||||
@ -26,6 +26,7 @@ describe('Issue', (): void => {
|
||||
debugOnly: false,
|
||||
deleteBranch: false,
|
||||
exemptIssueLabels: '',
|
||||
exemptPinnedIssues: false,
|
||||
exemptPrLabels: '',
|
||||
onlyLabels: '',
|
||||
onlyIssueLabels: '',
|
||||
|
||||
@ -196,6 +196,35 @@ export class IssuesProcessor {
|
||||
return this.processIssues(page + 1);
|
||||
}
|
||||
|
||||
private _lastIssueEvents: IIssueEvent[] = [];
|
||||
private _lastIssueEventsIssueId = -1;
|
||||
async getIssueEvents(issue: Issue): Promise<IIssueEvent[]> {
|
||||
if (issue.number !== this._lastIssueEventsIssueId) {
|
||||
const options = this.client.rest.issues.listEvents.endpoint.merge({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
per_page: 100,
|
||||
issue_number: issue.number
|
||||
});
|
||||
const events: IIssueEvent[] = await this.client.paginate(options);
|
||||
this._lastIssueEvents = events.reverse();
|
||||
this._lastIssueEventsIssueId = issue.number;
|
||||
}
|
||||
return this._lastIssueEvents;
|
||||
}
|
||||
|
||||
async getPinnedStatus(issue: Issue): Promise<boolean> {
|
||||
const events = await this.getIssueEvents(issue);
|
||||
const pinnedEvent = events.findIndex(event => event.event === 'pinned');
|
||||
|
||||
if (pinnedEvent == -1) return false;
|
||||
|
||||
const unpinnedEvent = events.findIndex(event => event.event === 'unpinned');
|
||||
if (unpinnedEvent == -1) return true;
|
||||
|
||||
return pinnedEvent < unpinnedEvent;
|
||||
}
|
||||
|
||||
async processIssue(
|
||||
issue: Issue,
|
||||
labelsToAddWhenUnstale: Readonly<string>[],
|
||||
@ -251,6 +280,12 @@ export class IssuesProcessor {
|
||||
return; // If the issue has an 'include-only-assigned' option set, process only issues with nonempty assignees list
|
||||
}
|
||||
|
||||
if (await this._isSkipPinned(issue)) {
|
||||
issueLogger.info('Skipping this issue because it is pinned');
|
||||
IssuesProcessor._endIssueProcessing(issue);
|
||||
return; // Don't process pinned issues
|
||||
}
|
||||
|
||||
const onlyLabels: string[] = wordsToList(this._getOnlyLabels(issue));
|
||||
|
||||
if (onlyLabels.length > 0) {
|
||||
@ -596,17 +631,9 @@ export class IssuesProcessor {
|
||||
|
||||
this._consumeIssueOperation(issue);
|
||||
this.statistics?.incrementFetchedItemsEventsCount();
|
||||
const options = this.client.rest.issues.listEvents.endpoint.merge({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
per_page: 100,
|
||||
issue_number: issue.number
|
||||
});
|
||||
|
||||
const events: IIssueEvent[] = await this.client.paginate(options);
|
||||
const reversedEvents = events.reverse();
|
||||
|
||||
const staleLabeledEvent = reversedEvents.find(
|
||||
const events = await this.getIssueEvents(issue);
|
||||
const staleLabeledEvent = events.find(
|
||||
event =>
|
||||
event.event === 'labeled' &&
|
||||
cleanLabel(event.label.name) === cleanLabel(label)
|
||||
@ -1087,6 +1114,12 @@ export class IssuesProcessor {
|
||||
return this.options.includeOnlyAssigned && !issue.hasAssignees;
|
||||
}
|
||||
|
||||
private async _isSkipPinned(issue: Issue): Promise<boolean> {
|
||||
return (
|
||||
this.options.exemptPinnedIssues && (await this.getPinnedStatus(issue))
|
||||
);
|
||||
}
|
||||
|
||||
private _getAnyOfLabels(issue: Issue): string {
|
||||
if (issue.isPullRequest) {
|
||||
if (this.options.anyOfPrLabels !== '') {
|
||||
|
||||
@ -15,6 +15,7 @@ export interface IIssuesProcessorOptions {
|
||||
staleIssueLabel: string;
|
||||
closeIssueLabel: string;
|
||||
exemptIssueLabels: string;
|
||||
exemptPinnedIssues: boolean;
|
||||
stalePrLabel: string;
|
||||
closePrLabel: string;
|
||||
exemptPrLabels: string;
|
||||
|
||||
@ -74,6 +74,7 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
|
||||
staleIssueLabel: core.getInput('stale-issue-label', {required: true}),
|
||||
closeIssueLabel: core.getInput('close-issue-label'),
|
||||
exemptIssueLabels: core.getInput('exempt-issue-labels'),
|
||||
exemptPinnedIssues: core.getInput('exempt-pinned-issues') === 'true',
|
||||
stalePrLabel: core.getInput('stale-pr-label', {required: true}),
|
||||
closePrLabel: core.getInput('close-pr-label'),
|
||||
exemptPrLabels: core.getInput('exempt-pr-labels'),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user