From 9912fa74d1c01b5d6187793d97441019cbe325d0 Mon Sep 17 00:00:00 2001 From: Geoffrey Testelin Date: Mon, 20 Sep 2021 15:37:32 +0200 Subject: [PATCH] feat(draft-pr): add new option to not process PRs which are in draft (#539) * chore(assignees): add logs * feat(draft-pr): add new option to not process PRs which are in draft * refactor(draft-pr): create a dedicated class to handle the logic * chore(index): update index file --- README.md | 9 ++ __tests__/classes/issues-processor-mock.ts | 8 +- .../constants/default-processor-options.ts | 3 +- __tests__/exempt-draft-pr.spec.ts | 139 ++++++++++++++++++ action.yml | 4 + dist/index.js | 126 ++++++++++++---- src/classes/exempt-draft-pull-request.ts | 51 +++++++ src/classes/issue.spec.ts | 3 +- src/classes/issues-processor.ts | 88 ++++++----- src/enums/option.ts | 3 +- src/interfaces/issues-processor-options.ts | 1 + src/interfaces/pull-request.ts | 1 + src/main.ts | 3 +- 13 files changed, 367 insertions(+), 72 deletions(-) create mode 100644 __tests__/exempt-draft-pr.spec.ts create mode 100644 src/classes/exempt-draft-pull-request.ts diff --git a/README.md b/README.md index a1c183a02..61bc3a1f5 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ Every argument is optional. | [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 | | @@ -472,6 +473,14 @@ Override [exempt-all-assignees](#exempt-all-assignees) but only to exempt the pu Default value: unset +#### exempt-draft-pr + +If set to `true`, the pull requests currently in draft will not be marked as stale automatically. +⚠️ This option consume one operation per pull request to process because we need to fetch the pull request with the GitHub API to know if it's a draft one or not. + +Default value: `false` +Required Permission: `pull-requests: read` + #### enable-statistics Collects and display statistics at the end of the stale workflow logs to get a summary of what happened during the run. diff --git a/__tests__/classes/issues-processor-mock.ts b/__tests__/classes/issues-processor-mock.ts index c1b210031..1d1a4b9f6 100644 --- a/__tests__/classes/issues-processor-mock.ts +++ b/__tests__/classes/issues-processor-mock.ts @@ -2,6 +2,7 @@ import {Issue} from '../../src/classes/issue'; import {IssuesProcessor} from '../../src/classes/issues-processor'; import {IComment} from '../../src/interfaces/comment'; import {IIssuesProcessorOptions} from '../../src/interfaces/issues-processor-options'; +import {IPullRequest} from '../../src/interfaces/pull-request'; export class IssuesProcessorMock extends IssuesProcessor { constructor( @@ -14,7 +15,8 @@ export class IssuesProcessorMock extends IssuesProcessor { getLabelCreationDate?: ( issue: Issue, label: string - ) => Promise + ) => Promise, + getPullRequest?: (issue: Issue) => Promise ) { super(options); @@ -29,5 +31,9 @@ export class IssuesProcessorMock extends IssuesProcessor { if (getLabelCreationDate) { this.getLabelCreationDate = getLabelCreationDate; } + + if (getPullRequest) { + this.getPullRequest = getPullRequest; + } } } diff --git a/__tests__/constants/default-processor-options.ts b/__tests__/constants/default-processor-options.ts index 2e3518c16..b526d12aa 100644 --- a/__tests__/constants/default-processor-options.ts +++ b/__tests__/constants/default-processor-options.ts @@ -49,5 +49,6 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({ labelsToAddWhenUnstale: '', ignoreUpdates: false, ignoreIssueUpdates: undefined, - ignorePrUpdates: undefined + ignorePrUpdates: undefined, + exemptDraftPr: false }); diff --git a/__tests__/exempt-draft-pr.spec.ts b/__tests__/exempt-draft-pr.spec.ts new file mode 100644 index 000000000..f3ffcd2bf --- /dev/null +++ b/__tests__/exempt-draft-pr.spec.ts @@ -0,0 +1,139 @@ +import {Issue} from '../src/classes/issue'; +import {IIssue} from '../src/interfaces/issue'; +import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options'; +import {IPullRequest} from '../src/interfaces/pull-request'; +import {IssuesProcessorMock} from './classes/issues-processor-mock'; +import {DefaultProcessorOptions} from './constants/default-processor-options'; +import {generateIssue} from './functions/generate-issue'; + +let issuesProcessorBuilder: IssuesProcessorBuilder; +let issuesProcessor: IssuesProcessorMock; + +describe('exempt-draft-pr option', (): void => { + beforeEach((): void => { + issuesProcessorBuilder = new IssuesProcessorBuilder(); + }); + + describe('when the option "exempt-draft-pr" is disabled', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.processDraftPr(); + }); + + test('should stale the pull request', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .toStalePrs([ + { + number: 10 + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(1); + }); + }); + + describe('when the option "exempt-draft-pr" is enabled', (): void => { + beforeEach((): void => { + issuesProcessorBuilder.exemptDraftPr(); + }); + + test('should not stale the pull request', async (): Promise => { + expect.assertions(1); + issuesProcessor = issuesProcessorBuilder + .toStalePrs([ + { + number: 20 + } + ]) + .build(); + + await issuesProcessor.processIssues(); + + expect(issuesProcessor.staleIssues).toHaveLength(0); + }); + }); +}); + +class IssuesProcessorBuilder { + private _options: IIssuesProcessorOptions = { + ...DefaultProcessorOptions + }; + private _issues: Issue[] = []; + + processDraftPr(): IssuesProcessorBuilder { + this._options.exemptDraftPr = false; + + return this; + } + + exemptDraftPr(): IssuesProcessorBuilder { + this._options.exemptDraftPr = true; + + return this; + } + + issuesOrPrs(issues: Partial[]): IssuesProcessorBuilder { + this._issues = issues.map( + (issue: Readonly>, index: Readonly): Issue => + generateIssue( + this._options, + issue.number ?? index, + issue.title ?? 'dummy-title', + issue.updated_at ?? new Date().toDateString(), + issue.created_at ?? new Date().toDateString(), + !!issue.pull_request, + issue.labels ? issue.labels.map(label => label.name) : [] + ) + ); + + return this; + } + + prs(issues: Partial[]): IssuesProcessorBuilder { + this.issuesOrPrs( + issues.map((issue: Readonly>): Partial => { + return { + ...issue, + pull_request: {key: 'value'} + }; + }) + ); + + return this; + } + + toStalePrs(issues: Partial[]): IssuesProcessorBuilder { + this.prs( + issues.map((issue: Readonly>): Partial => { + return { + ...issue, + updated_at: '2020-01-01T17:00:00Z', + created_at: '2020-01-01T17:00:00Z' + }; + }) + ); + + return this; + } + + build(): IssuesProcessorMock { + return new IssuesProcessorMock( + this._options, + async p => (p === 1 ? this._issues : []), + async () => [], + async () => new Date().toDateString(), + async (): Promise => { + return Promise.resolve({ + number: 0, + draft: true, + head: { + ref: 'ref' + } + }); + } + ); + } +} diff --git a/action.yml b/action.yml index 8e4b53bfc..5cf52fc17 100644 --- a/action.yml +++ b/action.yml @@ -164,6 +164,10 @@ inputs: description: 'Exempt all pull requests with assignees from being marked as stale. Override "exempt-all-assignees" option regarding only the pull requests.' default: '' required: false + exempt-draft-pr: + description: 'Exempt draft pull requests from being marked as stale. Default to false.' + default: 'false' + required: false enable-statistics: description: 'Display some statistics at the end regarding the stale workflow (only when the logs are enabled).' default: 'true' diff --git a/dist/index.js b/dist/index.js index 68fa14e18..0b7acc691 100644 --- a/dist/index.js +++ b/dist/index.js @@ -141,6 +141,55 @@ class Assignees { exports.Assignees = Assignees; +/***/ }), + +/***/ 854: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.ExemptDraftPullRequest = void 0; +const option_1 = __nccwpck_require__(5931); +const logger_service_1 = __nccwpck_require__(1973); +const issue_logger_1 = __nccwpck_require__(2984); +class ExemptDraftPullRequest { + constructor(options, issue) { + this._options = options; + this._issue = issue; + this._issueLogger = new issue_logger_1.IssueLogger(issue); + } + shouldExemptDraftPullRequest(pullRequestCallback) { + return __awaiter(this, void 0, void 0, function* () { + if (this._issue.isPullRequest) { + if (this._options.exemptDraftPr) { + this._issueLogger.info(`The option ${this._issueLogger.createOptionLink(option_1.Option.ExemptDraftPr)} is enabled`); + const pullRequest = yield pullRequestCallback(); + if ((pullRequest === null || pullRequest === void 0 ? void 0 : pullRequest.draft) === true) { + this._issueLogger.info(logger_service_1.LoggerService.white('└──'), `Skip the $$type draft checks`); + return true; + } + else { + this._issueLogger.info(logger_service_1.LoggerService.white('└──'), `Continuing the process for this $$type because it is not a draft`); + } + } + } + return false; + }); + } +} +exports.ExemptDraftPullRequest = ExemptDraftPullRequest; + + /***/ }), /***/ 2935: @@ -298,6 +347,7 @@ const should_mark_when_stale_1 = __nccwpck_require__(2461); const words_to_list_1 = __nccwpck_require__(1883); const assignees_1 = __nccwpck_require__(7236); const ignore_updates_1 = __nccwpck_require__(2935); +const exempt_draft_pull_request_1 = __nccwpck_require__(854); const issue_1 = __nccwpck_require__(4783); const issue_logger_1 = __nccwpck_require__(2984); const logger_1 = __nccwpck_require__(6212); @@ -404,6 +454,16 @@ class IssuesProcessor { const daysBeforeStale = issue.isPullRequest ? this._getDaysBeforePrStale() : this._getDaysBeforeIssueStale(); + if (issue.state === 'closed') { + issueLogger.info(`Skipping this $$type because it is closed`); + IssuesProcessor._endIssueProcessing(issue); + return; // Don't process closed issues + } + if (issue.locked) { + issueLogger.info(`Skipping this $$type because it is locked`); + IssuesProcessor._endIssueProcessing(issue); + return; // Don't process locked issues + } const onlyLabels = 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)})`); @@ -426,16 +486,6 @@ class IssuesProcessor { } issueLogger.info(`Days before $$type stale: ${logger_service_1.LoggerService.cyan(daysBeforeStale)}`); const shouldMarkAsStale = should_mark_when_stale_1.shouldMarkWhenStale(daysBeforeStale); - if (issue.state === 'closed') { - issueLogger.info(`Skipping this $$type because it is closed`); - IssuesProcessor._endIssueProcessing(issue); - return; // Don't process closed issues - } - if (issue.locked) { - issueLogger.info(`Skipping this $$type because it is locked`); - IssuesProcessor._endIssueProcessing(issue); - return; // 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) { @@ -503,6 +553,16 @@ class IssuesProcessor { IssuesProcessor._endIssueProcessing(issue); return; // Don't process exempt assignees } + // Ignore draft PR + // Note that this check is so far below because it cost one read operation + // So it's simply better to do all the stale checks which don't cost more operation before this one + const exemptDraftPullRequest = new exempt_draft_pull_request_1.ExemptDraftPullRequest(this.options, issue); + if (yield exemptDraftPullRequest.shouldExemptDraftPullRequest(() => __awaiter(this, void 0, void 0, function* () { + return this.getPullRequest(issue); + }))) { + IssuesProcessor._endIssueProcessing(issue); + return; // Don't process draft PR + } // Determine if this issue needs to be marked stale first if (!issue.isStale) { issueLogger.info(`This $$type is not stale`); @@ -624,6 +684,25 @@ class IssuesProcessor { return staleLabeledEvent.created_at; }); } + getPullRequest(issue) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const issueLogger = new issue_logger_1.IssueLogger(issue); + try { + this._consumeIssueOperation(issue); + (_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementFetchedPullRequestsCount(); + const pullRequest = yield this.client.pulls.get({ + owner: github_1.context.repo.owner, + repo: github_1.context.repo.repo, + pull_number: issue.number + }); + return pullRequest.data; + } + catch (error) { + issueLogger.error(`Error when getting this $$type: ${error.message}`); + } + }); + } // handle all of the stale issue logic when we find a stale issue _processStaleIssue(issue, staleLabel, staleMessage, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale, closeMessage, closeLabel) { return __awaiter(this, void 0, void 0, function* () { @@ -664,7 +743,7 @@ class IssuesProcessor { issueLogger.info(`Closing $$type because it was last updated on: ${logger_service_1.LoggerService.cyan(issue.updated_at)}`); yield this._closeIssue(issue, closeMessage, closeLabel); if (this.options.deleteBranch && issue.pull_request) { - issueLogger.info(`Deleting the branch since the option ${issueLogger.createOptionLink(option_1.Option.DeleteBranch)} was specified`); + issueLogger.info(`Deleting the branch since the option ${issueLogger.createOptionLink(option_1.Option.DeleteBranch)} is enabled`); yield this._deleteBranch(issue); this.deletedBranchIssues.push(issue); } @@ -795,32 +874,13 @@ class IssuesProcessor { } }); } - _getPullRequest(issue) { - var _a; - return __awaiter(this, void 0, void 0, function* () { - const issueLogger = new issue_logger_1.IssueLogger(issue); - try { - this._consumeIssueOperation(issue); - (_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementFetchedPullRequestsCount(); - const pullRequest = yield this.client.pulls.get({ - owner: github_1.context.repo.owner, - repo: github_1.context.repo.repo, - pull_number: issue.number - }); - return pullRequest.data; - } - catch (error) { - issueLogger.error(`Error when getting this $$type: ${error.message}`); - } - }); - } // Delete the branch on closed pull request _deleteBranch(issue) { var _a; return __awaiter(this, void 0, void 0, function* () { const issueLogger = new issue_logger_1.IssueLogger(issue); issueLogger.info(`Delete branch from closed $$type - ${issue.title}`); - const pullRequest = yield this._getPullRequest(issue); + const pullRequest = yield this.getPullRequest(issue); if (!pullRequest) { issueLogger.info(`Not deleting this branch as no pull request was found for this $$type`); return; @@ -1807,6 +1867,7 @@ var Option; Option["IgnoreUpdates"] = "ignore-updates"; Option["IgnoreIssueUpdates"] = "ignore-issue-updates"; Option["IgnorePrUpdates"] = "ignore-pr-updates"; + Option["ExemptDraftPr"] = "exempt-draft-pr"; })(Option = exports.Option || (exports.Option = {})); @@ -2116,7 +2177,8 @@ function _getAndValidateArgs() { labelsToAddWhenUnstale: core.getInput('labels-to-add-when-unstale'), ignoreUpdates: core.getInput('ignore-updates') === 'true', ignoreIssueUpdates: _toOptionalBoolean('ignore-issue-updates'), - ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates') + ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'), + exemptDraftPr: core.getInput('exempt-draft-pr') === 'true' }; for (const numberInput of [ 'days-before-stale', diff --git a/src/classes/exempt-draft-pull-request.ts b/src/classes/exempt-draft-pull-request.ts new file mode 100644 index 000000000..ae5e979ac --- /dev/null +++ b/src/classes/exempt-draft-pull-request.ts @@ -0,0 +1,51 @@ +import {Option} from '../enums/option'; +import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options'; +import {IPullRequest} from '../interfaces/pull-request'; +import {LoggerService} from '../services/logger.service'; +import {Issue} from './issue'; +import {IssueLogger} from './loggers/issue-logger'; + +export class ExemptDraftPullRequest { + private readonly _options: IIssuesProcessorOptions; + private readonly _issue: Issue; + private readonly _issueLogger: IssueLogger; + + constructor(options: Readonly, issue: Issue) { + this._options = options; + this._issue = issue; + this._issueLogger = new IssueLogger(issue); + } + + async shouldExemptDraftPullRequest( + pullRequestCallback: () => Promise + ): Promise { + if (this._issue.isPullRequest) { + if (this._options.exemptDraftPr) { + this._issueLogger.info( + `The option ${this._issueLogger.createOptionLink( + Option.ExemptDraftPr + )} is enabled` + ); + + const pullRequest: IPullRequest | undefined | void = + await pullRequestCallback(); + + if (pullRequest?.draft === true) { + this._issueLogger.info( + LoggerService.white('└──'), + `Skip the $$type draft checks` + ); + + return true; + } else { + this._issueLogger.info( + LoggerService.white('└──'), + `Continuing the process for this $$type because it is not a draft` + ); + } + } + } + + return false; + } +} diff --git a/src/classes/issue.spec.ts b/src/classes/issue.spec.ts index 0be0e7188..ccd32d51a 100644 --- a/src/classes/issue.spec.ts +++ b/src/classes/issue.spec.ts @@ -60,7 +60,8 @@ describe('Issue', (): void => { labelsToAddWhenUnstale: '', ignoreUpdates: false, ignoreIssueUpdates: undefined, - ignorePrUpdates: undefined + ignorePrUpdates: undefined, + exemptDraftPr: false }; issueInterface = { title: 'dummy-title', diff --git a/src/classes/issues-processor.ts b/src/classes/issues-processor.ts index 64a42522a..3b0710fe8 100644 --- a/src/classes/issues-processor.ts +++ b/src/classes/issues-processor.ts @@ -17,6 +17,7 @@ import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options'; import {IPullRequest} from '../interfaces/pull-request'; import {Assignees} from './assignees'; import {IgnoreUpdates} from './ignore-updates'; +import {ExemptDraftPullRequest} from './exempt-draft-pull-request'; import {Issue} from './issue'; import {IssueLogger} from './loggers/issue-logger'; import {Logger} from './loggers/logger'; @@ -207,6 +208,19 @@ export class IssuesProcessor { const daysBeforeStale: number = issue.isPullRequest ? this._getDaysBeforePrStale() : this._getDaysBeforeIssueStale(); + + if (issue.state === 'closed') { + issueLogger.info(`Skipping this $$type because it is closed`); + IssuesProcessor._endIssueProcessing(issue); + return; // Don't process closed issues + } + + if (issue.locked) { + issueLogger.info(`Skipping this $$type because it is locked`); + IssuesProcessor._endIssueProcessing(issue); + return; // Don't process locked issues + } + const onlyLabels: string[] = wordsToList(this._getOnlyLabels(issue)); if (onlyLabels.length > 0) { @@ -260,18 +274,6 @@ export class IssuesProcessor { const shouldMarkAsStale: boolean = shouldMarkWhenStale(daysBeforeStale); - if (issue.state === 'closed') { - issueLogger.info(`Skipping this $$type because it is closed`); - IssuesProcessor._endIssueProcessing(issue); - return; // Don't process closed issues - } - - if (issue.locked) { - issueLogger.info(`Skipping this $$type because it is locked`); - IssuesProcessor._endIssueProcessing(issue); - return; // Don't process locked issues - } - // Try to remove the close label when not close/locked issue or PR await this._removeCloseLabel(issue, closeLabel); @@ -397,6 +399,23 @@ export class IssuesProcessor { return; // Don't process exempt assignees } + // Ignore draft PR + // Note that this check is so far below because it cost one read operation + // So it's simply better to do all the stale checks which don't cost more operation before this one + const exemptDraftPullRequest: ExemptDraftPullRequest = + new ExemptDraftPullRequest(this.options, issue); + + if ( + await exemptDraftPullRequest.shouldExemptDraftPullRequest( + async (): Promise => { + return this.getPullRequest(issue); + } + ) + ) { + IssuesProcessor._endIssueProcessing(issue); + return; // Don't process draft PR + } + // Determine if this issue needs to be marked stale first if (!issue.isStale) { issueLogger.info(`This $$type is not stale`); @@ -574,6 +593,25 @@ export class IssuesProcessor { return staleLabeledEvent.created_at; } + async getPullRequest(issue: Issue): Promise { + const issueLogger: IssueLogger = new IssueLogger(issue); + + try { + this._consumeIssueOperation(issue); + this._statistics?.incrementFetchedPullRequestsCount(); + + const pullRequest = await this.client.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: issue.number + }); + + return pullRequest.data; + } catch (error) { + issueLogger.error(`Error when getting this $$type: ${error.message}`); + } + } + // handle all of the stale issue logic when we find a stale issue private async _processStaleIssue( issue: Issue, @@ -666,7 +704,7 @@ export class IssuesProcessor { issueLogger.info( `Deleting the branch since the option ${issueLogger.createOptionLink( Option.DeleteBranch - )} was specified` + )} is enabled` ); await this._deleteBranch(issue); this.deletedBranchIssues.push(issue); @@ -830,34 +868,14 @@ export class IssuesProcessor { } } - private async _getPullRequest( - issue: Issue - ): Promise { - const issueLogger: IssueLogger = new IssueLogger(issue); - - try { - this._consumeIssueOperation(issue); - this._statistics?.incrementFetchedPullRequestsCount(); - - const pullRequest = await this.client.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: issue.number - }); - - return pullRequest.data; - } catch (error) { - issueLogger.error(`Error when getting this $$type: ${error.message}`); - } - } - // Delete the branch on closed pull request private async _deleteBranch(issue: Issue): Promise { const issueLogger: IssueLogger = new IssueLogger(issue); issueLogger.info(`Delete branch from closed $$type - ${issue.title}`); - const pullRequest = await this._getPullRequest(issue); + const pullRequest: IPullRequest | undefined | void = + await this.getPullRequest(issue); if (!pullRequest) { issueLogger.info( diff --git a/src/enums/option.ts b/src/enums/option.ts index 648bac4e8..f7167c725 100644 --- a/src/enums/option.ts +++ b/src/enums/option.ts @@ -45,5 +45,6 @@ export enum Option { LabelsToAddWhenUnstale = 'labels-to-add-when-unstale', IgnoreUpdates = 'ignore-updates', IgnoreIssueUpdates = 'ignore-issue-updates', - IgnorePrUpdates = 'ignore-pr-updates' + IgnorePrUpdates = 'ignore-pr-updates', + ExemptDraftPr = 'exempt-draft-pr' } diff --git a/src/interfaces/issues-processor-options.ts b/src/interfaces/issues-processor-options.ts index 619bc2c6a..8adbfb4a5 100644 --- a/src/interfaces/issues-processor-options.ts +++ b/src/interfaces/issues-processor-options.ts @@ -50,4 +50,5 @@ export interface IIssuesProcessorOptions { ignoreUpdates: boolean; ignoreIssueUpdates: boolean | undefined; ignorePrUpdates: boolean | undefined; + exemptDraftPr: boolean; } diff --git a/src/interfaces/pull-request.ts b/src/interfaces/pull-request.ts index 9f13005d5..22f6183ef 100644 --- a/src/interfaces/pull-request.ts +++ b/src/interfaces/pull-request.ts @@ -3,4 +3,5 @@ export interface IPullRequest { head: { ref: string; }; + draft: boolean; } diff --git a/src/main.ts b/src/main.ts index df15fc47a..af96654c5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -86,7 +86,8 @@ function _getAndValidateArgs(): IIssuesProcessorOptions { labelsToAddWhenUnstale: core.getInput('labels-to-add-when-unstale'), ignoreUpdates: core.getInput('ignore-updates') === 'true', ignoreIssueUpdates: _toOptionalBoolean('ignore-issue-updates'), - ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates') + ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'), + exemptDraftPr: core.getInput('exempt-draft-pr') === 'true' }; for (const numberInput of [