From 4eac6e9c911d95778c469be2be18c2c8b8e6fd38 Mon Sep 17 00:00:00 2001 From: bherrmann2 Date: Tue, 9 Jul 2024 13:31:45 -0500 Subject: [PATCH] feat(remove-pr-from-merge-queue): remove non-pending and stale PRs (#629) * remove successful and stale PRs * remove all PRs * update test cases * update test cases * only remove non-pending checks * fix * bun package * fix * updates --------- Co-authored-by: Dan Adajian Co-authored-by: Dan Adajian --- dist/974.index.js | 48 ++++++++++--------- dist/974.index.js.map | 2 +- src/helpers/remove-pr-from-merge-queue.ts | 6 ++- .../remove-pr-from-merge-queue.test.ts | 14 ++---- 4 files changed, 34 insertions(+), 36 deletions(-) diff --git a/dist/974.index.js b/dist/974.index.js index d2ff41fa..f77d9219 100644 --- a/dist/974.index.js +++ b/dist/974.index.js @@ -154,14 +154,16 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ }); /* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2186); /* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_actions_core__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9042); -/* harmony import */ var _types_generated__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(3476); -/* harmony import */ var _actions_github__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5438); -/* harmony import */ var _actions_github__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_actions_github__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _octokit__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6161); -/* harmony import */ var _remove_label__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(61); -/* harmony import */ var bluebird__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(8710); -/* harmony import */ var bluebird__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(bluebird__WEBPACK_IMPORTED_MODULE_5__); +/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(250); +/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(lodash__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9042); +/* harmony import */ var _types_generated__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(3476); +/* harmony import */ var _actions_github__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5438); +/* harmony import */ var _actions_github__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_actions_github__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var _octokit__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6161); +/* harmony import */ var _remove_label__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(61); +/* harmony import */ var bluebird__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(8710); +/* harmony import */ var bluebird__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(bluebird__WEBPACK_IMPORTED_MODULE_6__); /* Copyright 2021 Expedia, Inc. Licensed under the Apache License, Version 2.0 (the "License"); @@ -181,42 +183,44 @@ limitations under the License. -class RemovePrFromMergeQueue extends _types_generated__WEBPACK_IMPORTED_MODULE_6__/* .HelperInputs */ .s { + +class RemovePrFromMergeQueue extends _types_generated__WEBPACK_IMPORTED_MODULE_7__/* .HelperInputs */ .s { constructor() { super(...arguments); this.seconds = ''; } } const removePrFromMergeQueue = async ({ seconds }) => { - const { data: pullRequests } = await _octokit__WEBPACK_IMPORTED_MODULE_3__/* .octokit.pulls.list */ .K.pulls.list({ + const { data: pullRequests } = await _octokit__WEBPACK_IMPORTED_MODULE_4__/* .octokit.pulls.list */ .K.pulls.list({ state: 'open', per_page: 100, - ..._actions_github__WEBPACK_IMPORTED_MODULE_2__.context.repo + ..._actions_github__WEBPACK_IMPORTED_MODULE_3__.context.repo }); - const firstQueuedPr = pullRequests.find(pr => pr.labels.some(label => label.name === _constants__WEBPACK_IMPORTED_MODULE_1__/* .FIRST_QUEUED_PR_LABEL */ .IH)); + const firstQueuedPr = pullRequests.find(pr => pr.labels.some(label => label.name === _constants__WEBPACK_IMPORTED_MODULE_2__/* .FIRST_QUEUED_PR_LABEL */ .IH)); if (!firstQueuedPr) { _actions_core__WEBPACK_IMPORTED_MODULE_0__.info('No PR is first in the merge queue.'); - return (0,bluebird__WEBPACK_IMPORTED_MODULE_5__.map)(pullRequests, async (pr) => { - const readyForMergeLabel = pr.labels.find(label => label.name.startsWith(_constants__WEBPACK_IMPORTED_MODULE_1__/* .READY_FOR_MERGE_PR_LABEL */ .Ak)); - const queueLabel = pr.labels.find(label => label.name.startsWith(_constants__WEBPACK_IMPORTED_MODULE_1__/* .QUEUED_FOR_MERGE_PREFIX */ .Ee)); + return (0,bluebird__WEBPACK_IMPORTED_MODULE_6__.map)(pullRequests, async (pr) => { + const readyForMergeLabel = pr.labels.find(label => label.name.startsWith(_constants__WEBPACK_IMPORTED_MODULE_2__/* .READY_FOR_MERGE_PR_LABEL */ .Ak)); + const queueLabel = pr.labels.find(label => label.name.startsWith(_constants__WEBPACK_IMPORTED_MODULE_2__/* .QUEUED_FOR_MERGE_PREFIX */ .Ee)); if (readyForMergeLabel || queueLabel) { _actions_core__WEBPACK_IMPORTED_MODULE_0__.info(`Cleaning up queued PR #${pr.number}...`); - await (0,_remove_label__WEBPACK_IMPORTED_MODULE_4__.removeLabelIfExists)(_constants__WEBPACK_IMPORTED_MODULE_1__/* .READY_FOR_MERGE_PR_LABEL */ .Ak, pr.number); + await (0,_remove_label__WEBPACK_IMPORTED_MODULE_5__.removeLabelIfExists)(_constants__WEBPACK_IMPORTED_MODULE_2__/* .READY_FOR_MERGE_PR_LABEL */ .Ak, pr.number); if (queueLabel) { - await (0,_remove_label__WEBPACK_IMPORTED_MODULE_4__.removeLabelIfExists)(queueLabel.name, pr.number); + await (0,_remove_label__WEBPACK_IMPORTED_MODULE_5__.removeLabelIfExists)(queueLabel.name, pr.number); } } }); } const { number, head: { sha } } = firstQueuedPr; - const { data } = await _octokit__WEBPACK_IMPORTED_MODULE_3__/* .octokit.repos.listCommitStatusesForRef */ .K.repos.listCommitStatusesForRef({ + const { data } = await _octokit__WEBPACK_IMPORTED_MODULE_4__/* .octokit.repos.listCommitStatusesForRef */ .K.repos.listCommitStatusesForRef({ ref: sha, - ..._actions_github__WEBPACK_IMPORTED_MODULE_2__.context.repo + ..._actions_github__WEBPACK_IMPORTED_MODULE_3__.context.repo }); - const failingStatus = data.find(status => status.state === 'failure'); - if (failingStatus && timestampIsStale(failingStatus.created_at, seconds)) { + const mostRecentStatus = (0,lodash__WEBPACK_IMPORTED_MODULE_1__.orderBy)(data, 'created_at', 'desc')[0]; + const noPendingStatus = data.find(status => status.state !== 'pending'); + if (noPendingStatus && mostRecentStatus && timestampIsStale(mostRecentStatus.created_at, seconds)) { _actions_core__WEBPACK_IMPORTED_MODULE_0__.info('Removing stale PR from first queued position...'); - return Promise.all([(0,_remove_label__WEBPACK_IMPORTED_MODULE_4__.removeLabelIfExists)(_constants__WEBPACK_IMPORTED_MODULE_1__/* .READY_FOR_MERGE_PR_LABEL */ .Ak, number), (0,_remove_label__WEBPACK_IMPORTED_MODULE_4__.removeLabelIfExists)(_constants__WEBPACK_IMPORTED_MODULE_1__/* .FIRST_QUEUED_PR_LABEL */ .IH, number)]); + return Promise.all([(0,_remove_label__WEBPACK_IMPORTED_MODULE_5__.removeLabelIfExists)(_constants__WEBPACK_IMPORTED_MODULE_2__/* .READY_FOR_MERGE_PR_LABEL */ .Ak, number), (0,_remove_label__WEBPACK_IMPORTED_MODULE_5__.removeLabelIfExists)(_constants__WEBPACK_IMPORTED_MODULE_2__/* .FIRST_QUEUED_PR_LABEL */ .IH, number)]); } }; const timestampIsStale = (timestamp, seconds) => { diff --git a/dist/974.index.js.map b/dist/974.index.js.map index 815aa1a1..50cf708a 100644 --- a/dist/974.index.js.map +++ b/dist/974.index.js.map @@ -1 +1 @@ -{"version":3,"file":"974.index.js","mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;AAWA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;AAWA;;;;;;;;;;;;;;;;;;;;AC3DA;;;;;;;;;;;AAWA;AAEA;AAEA;AACA;AACA;AAEA;AAAA;;AACA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;ACrCA;;;;;;;;;;;AAWA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;ACnEA;;;;;;;;;;;AAWA;AAEA;AACA;AACA;AAEA;AACA;;;;;;;;;;;AClBA;;;;;;;;;;;AAWA;AAEA;AAiDA","sources":[".././src/constants.ts",".././src/helpers/remove-label.ts",".././src/helpers/remove-pr-from-merge-queue.ts",".././src/octokit.ts",".././src/types/generated.ts"],"sourcesContent":["/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// These extra headers are for experimental API features on Github Enterprise. See https://docs.github.com/en/enterprise-server@3.0/rest/overview/api-previews for details.\nconst PREVIEWS = ['ant-man', 'flash', 'groot', 'inertia', 'starfox'];\nexport const GITHUB_OPTIONS = {\n headers: {\n accept: PREVIEWS.map(preview => `application/vnd.github.${preview}-preview+json`).join()\n }\n};\n\nexport const SECONDS_IN_A_DAY = 86400000;\nexport const DEFAULT_EXEMPT_DESCRIPTION = 'Passed in case the check is exempt.';\nexport const DEFAULT_PIPELINE_STATUS = 'Pipeline Status';\nexport const DEFAULT_PIPELINE_DESCRIPTION = 'Pipeline clear.';\nexport const PRODUCTION_ENVIRONMENT = 'production';\nexport const LATE_REVIEW = 'Late Review';\nexport const OVERDUE_ISSUE = 'Overdue';\nexport const ALMOST_OVERDUE_ISSUE = 'Due Soon';\nexport const PRIORITY_1 = 'Priority: Critical';\nexport const PRIORITY_2 = 'Priority: High';\nexport const PRIORITY_3 = 'Priority: Medium';\nexport const PRIORITY_4 = 'Priority: Low';\nexport const PRIORITY_LABELS = [PRIORITY_1, PRIORITY_2, PRIORITY_3, PRIORITY_4] as const;\nexport const PRIORITY_TO_DAYS_MAP = {\n [PRIORITY_1]: 2,\n [PRIORITY_2]: 14,\n [PRIORITY_3]: 45,\n [PRIORITY_4]: 90\n};\nexport const CORE_APPROVED_PR_LABEL = 'CORE APPROVED';\nexport const PEER_APPROVED_PR_LABEL = 'PEER APPROVED';\nexport const READY_FOR_MERGE_PR_LABEL = 'READY FOR MERGE';\nexport const MERGE_QUEUE_STATUS = 'QUEUE CHECKER';\nexport const QUEUED_FOR_MERGE_PREFIX = 'QUEUED FOR MERGE';\nexport const FIRST_QUEUED_PR_LABEL = `${QUEUED_FOR_MERGE_PREFIX} #1`;\nexport const JUMP_THE_QUEUE_PR_LABEL = 'JUMP THE QUEUE';\nexport const DEFAULT_PR_TITLE_REGEX = '^(build|ci|chore|docs|feat|fix|perf|refactor|style|test|revert|Revert|BREAKING CHANGE)((.*))?: .+$';\nexport const COPYRIGHT_HEADER = `/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/`;\n","/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport * as core from '@actions/core';\nimport { GithubError } from '../types/github';\nimport { HelperInputs } from '../types/generated';\nimport { context } from '@actions/github';\nimport { octokit } from '../octokit';\n\nexport class RemoveLabel extends HelperInputs {\n label = '';\n}\n\nexport const removeLabel = async ({ label }: RemoveLabel) => removeLabelIfExists(label, context.issue.number);\n\nexport const removeLabelIfExists = async (labelName: string, issue_number: number) => {\n try {\n await octokit.issues.removeLabel({\n name: labelName,\n issue_number,\n ...context.repo\n });\n } catch (error) {\n if ((error as GithubError).status === 404) {\n core.info('Label is not present on PR.');\n }\n }\n};\n","/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport * as core from '@actions/core';\nimport { FIRST_QUEUED_PR_LABEL, QUEUED_FOR_MERGE_PREFIX, READY_FOR_MERGE_PR_LABEL } from '../constants';\nimport { HelperInputs } from '../types/generated';\nimport { context } from '@actions/github';\nimport { octokit } from '../octokit';\nimport { removeLabelIfExists } from './remove-label';\nimport { map } from 'bluebird';\n\nexport class RemovePrFromMergeQueue extends HelperInputs {\n seconds = '';\n}\n\nexport const removePrFromMergeQueue = async ({ seconds }: RemovePrFromMergeQueue) => {\n const { data: pullRequests } = await octokit.pulls.list({\n state: 'open',\n per_page: 100,\n ...context.repo\n });\n const firstQueuedPr = pullRequests.find(pr => pr.labels.some(label => label.name === FIRST_QUEUED_PR_LABEL));\n if (!firstQueuedPr) {\n core.info('No PR is first in the merge queue.');\n\n return map(pullRequests, async pr => {\n const readyForMergeLabel = pr.labels.find(label => label.name.startsWith(READY_FOR_MERGE_PR_LABEL));\n const queueLabel = pr.labels.find(label => label.name.startsWith(QUEUED_FOR_MERGE_PREFIX));\n if (readyForMergeLabel || queueLabel) {\n core.info(`Cleaning up queued PR #${pr.number}...`);\n await removeLabelIfExists(READY_FOR_MERGE_PR_LABEL, pr.number);\n if (queueLabel) {\n await removeLabelIfExists(queueLabel.name, pr.number);\n }\n }\n });\n }\n\n const {\n number,\n head: { sha }\n } = firstQueuedPr;\n const { data } = await octokit.repos.listCommitStatusesForRef({\n ref: sha,\n ...context.repo\n });\n const failingStatus = data.find(status => status.state === 'failure');\n if (failingStatus && timestampIsStale(failingStatus.created_at, seconds)) {\n core.info('Removing stale PR from first queued position...');\n return Promise.all([removeLabelIfExists(READY_FOR_MERGE_PR_LABEL, number), removeLabelIfExists(FIRST_QUEUED_PR_LABEL, number)]);\n }\n};\n\nconst timestampIsStale = (timestamp: string, seconds: string) => {\n const ageOfTimestampInMiliseconds = Date.now() - new Date(timestamp).getTime();\n const milisecondsConsideredStale = Number(seconds) * 1000;\n return ageOfTimestampInMiliseconds > milisecondsConsideredStale;\n};\n","/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport * as core from '@actions/core';\nimport * as fetch from '@adobe/node-fetch-retry';\nimport { getOctokit } from '@actions/github';\n\nconst githubToken = core.getInput('github_token', { required: true });\nexport const { rest: octokit, graphql: octokitGraphql } = getOctokit(githubToken, { request: { fetch } });\n","/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nexport class HelperInputs {\n helper?: string;\n github_token?: string;\n body?: string;\n project_name?: string;\n project_destination_column_name?: string;\n note?: string;\n project_origin_column_name?: string;\n sha?: string;\n context?: string;\n state?: string;\n description?: string;\n target_url?: string;\n environment?: string;\n environment_url?: string;\n label?: string;\n labels?: string;\n paths?: string;\n ignore_globs?: string;\n extensions?: string;\n override_filter_paths?: string;\n batches?: string;\n pattern?: string;\n teams?: string;\n users?: string;\n login?: string;\n paths_no_filter?: string;\n slack_webhook_url?: string;\n number_of_assignees?: string;\n number_of_reviewers?: string;\n globs?: string;\n override_filter_globs?: string;\n title?: string;\n seconds?: string;\n pull_number?: string;\n base?: string;\n head?: string;\n days?: string;\n no_evict_upon_conflict?: string;\n skip_if_already_set?: string;\n delimiter?: string;\n team?: string;\n ignore_deleted?: string;\n return_full_payload?: string;\n skip_auto_merge?: string;\n repo_name?: string;\n repo_owner_name?: string;\n load_balancing_sizes?: string;\n required_review_overrides?: string;\n}\n"],"names":[],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"974.index.js","mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;AAWA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;AAWA;;;;;;;;;;;;;;;;;;;;AC3DA;;;;;;;;;;;AAWA;AAEA;AAEA;AACA;AACA;AAEA;AAAA;;AACA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;ACrCA;;;;;;;;;;;AAWA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;ACrEA;;;;;;;;;;;AAWA;AAEA;AACA;AACA;AAEA;AACA;;;;;;;;;;;AClBA;;;;;;;;;;;AAWA;AAEA;AAiDA","sources":[".././src/constants.ts",".././src/helpers/remove-label.ts",".././src/helpers/remove-pr-from-merge-queue.ts",".././src/octokit.ts",".././src/types/generated.ts"],"sourcesContent":["/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// These extra headers are for experimental API features on Github Enterprise. See https://docs.github.com/en/enterprise-server@3.0/rest/overview/api-previews for details.\nconst PREVIEWS = ['ant-man', 'flash', 'groot', 'inertia', 'starfox'];\nexport const GITHUB_OPTIONS = {\n headers: {\n accept: PREVIEWS.map(preview => `application/vnd.github.${preview}-preview+json`).join()\n }\n};\n\nexport const SECONDS_IN_A_DAY = 86400000;\nexport const DEFAULT_EXEMPT_DESCRIPTION = 'Passed in case the check is exempt.';\nexport const DEFAULT_PIPELINE_STATUS = 'Pipeline Status';\nexport const DEFAULT_PIPELINE_DESCRIPTION = 'Pipeline clear.';\nexport const PRODUCTION_ENVIRONMENT = 'production';\nexport const LATE_REVIEW = 'Late Review';\nexport const OVERDUE_ISSUE = 'Overdue';\nexport const ALMOST_OVERDUE_ISSUE = 'Due Soon';\nexport const PRIORITY_1 = 'Priority: Critical';\nexport const PRIORITY_2 = 'Priority: High';\nexport const PRIORITY_3 = 'Priority: Medium';\nexport const PRIORITY_4 = 'Priority: Low';\nexport const PRIORITY_LABELS = [PRIORITY_1, PRIORITY_2, PRIORITY_3, PRIORITY_4] as const;\nexport const PRIORITY_TO_DAYS_MAP = {\n [PRIORITY_1]: 2,\n [PRIORITY_2]: 14,\n [PRIORITY_3]: 45,\n [PRIORITY_4]: 90\n};\nexport const CORE_APPROVED_PR_LABEL = 'CORE APPROVED';\nexport const PEER_APPROVED_PR_LABEL = 'PEER APPROVED';\nexport const READY_FOR_MERGE_PR_LABEL = 'READY FOR MERGE';\nexport const MERGE_QUEUE_STATUS = 'QUEUE CHECKER';\nexport const QUEUED_FOR_MERGE_PREFIX = 'QUEUED FOR MERGE';\nexport const FIRST_QUEUED_PR_LABEL = `${QUEUED_FOR_MERGE_PREFIX} #1`;\nexport const JUMP_THE_QUEUE_PR_LABEL = 'JUMP THE QUEUE';\nexport const DEFAULT_PR_TITLE_REGEX = '^(build|ci|chore|docs|feat|fix|perf|refactor|style|test|revert|Revert|BREAKING CHANGE)((.*))?: .+$';\nexport const COPYRIGHT_HEADER = `/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/`;\n","/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport * as core from '@actions/core';\nimport { GithubError } from '../types/github';\nimport { HelperInputs } from '../types/generated';\nimport { context } from '@actions/github';\nimport { octokit } from '../octokit';\n\nexport class RemoveLabel extends HelperInputs {\n label = '';\n}\n\nexport const removeLabel = async ({ label }: RemoveLabel) => removeLabelIfExists(label, context.issue.number);\n\nexport const removeLabelIfExists = async (labelName: string, issue_number: number) => {\n try {\n await octokit.issues.removeLabel({\n name: labelName,\n issue_number,\n ...context.repo\n });\n } catch (error) {\n if ((error as GithubError).status === 404) {\n core.info('Label is not present on PR.');\n }\n }\n};\n","/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport * as core from '@actions/core';\nimport { orderBy } from 'lodash';\nimport { FIRST_QUEUED_PR_LABEL, QUEUED_FOR_MERGE_PREFIX, READY_FOR_MERGE_PR_LABEL } from '../constants';\nimport { HelperInputs } from '../types/generated';\nimport { context } from '@actions/github';\nimport { octokit } from '../octokit';\nimport { removeLabelIfExists } from './remove-label';\nimport { map } from 'bluebird';\n\nexport class RemovePrFromMergeQueue extends HelperInputs {\n seconds = '';\n}\n\nexport const removePrFromMergeQueue = async ({ seconds }: RemovePrFromMergeQueue) => {\n const { data: pullRequests } = await octokit.pulls.list({\n state: 'open',\n per_page: 100,\n ...context.repo\n });\n const firstQueuedPr = pullRequests.find(pr => pr.labels.some(label => label.name === FIRST_QUEUED_PR_LABEL));\n if (!firstQueuedPr) {\n core.info('No PR is first in the merge queue.');\n\n return map(pullRequests, async pr => {\n const readyForMergeLabel = pr.labels.find(label => label.name.startsWith(READY_FOR_MERGE_PR_LABEL));\n const queueLabel = pr.labels.find(label => label.name.startsWith(QUEUED_FOR_MERGE_PREFIX));\n if (readyForMergeLabel || queueLabel) {\n core.info(`Cleaning up queued PR #${pr.number}...`);\n await removeLabelIfExists(READY_FOR_MERGE_PR_LABEL, pr.number);\n if (queueLabel) {\n await removeLabelIfExists(queueLabel.name, pr.number);\n }\n }\n });\n }\n\n const {\n number,\n head: { sha }\n } = firstQueuedPr;\n const { data } = await octokit.repos.listCommitStatusesForRef({\n ref: sha,\n ...context.repo\n });\n const mostRecentStatus = orderBy(data, 'created_at', 'desc')[0];\n const noPendingStatus = data.find(status => status.state !== 'pending');\n if (noPendingStatus && mostRecentStatus && timestampIsStale(mostRecentStatus.created_at, seconds)) {\n core.info('Removing stale PR from first queued position...');\n return Promise.all([removeLabelIfExists(READY_FOR_MERGE_PR_LABEL, number), removeLabelIfExists(FIRST_QUEUED_PR_LABEL, number)]);\n }\n};\n\nconst timestampIsStale = (timestamp: string, seconds: string) => {\n const ageOfTimestampInMiliseconds = Date.now() - new Date(timestamp).getTime();\n const milisecondsConsideredStale = Number(seconds) * 1000;\n return ageOfTimestampInMiliseconds > milisecondsConsideredStale;\n};\n","/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport * as core from '@actions/core';\nimport * as fetch from '@adobe/node-fetch-retry';\nimport { getOctokit } from '@actions/github';\n\nconst githubToken = core.getInput('github_token', { required: true });\nexport const { rest: octokit, graphql: octokitGraphql } = getOctokit(githubToken, { request: { fetch } });\n","/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nexport class HelperInputs {\n helper?: string;\n github_token?: string;\n body?: string;\n project_name?: string;\n project_destination_column_name?: string;\n note?: string;\n project_origin_column_name?: string;\n sha?: string;\n context?: string;\n state?: string;\n description?: string;\n target_url?: string;\n environment?: string;\n environment_url?: string;\n label?: string;\n labels?: string;\n paths?: string;\n ignore_globs?: string;\n extensions?: string;\n override_filter_paths?: string;\n batches?: string;\n pattern?: string;\n teams?: string;\n users?: string;\n login?: string;\n paths_no_filter?: string;\n slack_webhook_url?: string;\n number_of_assignees?: string;\n number_of_reviewers?: string;\n globs?: string;\n override_filter_globs?: string;\n title?: string;\n seconds?: string;\n pull_number?: string;\n base?: string;\n head?: string;\n days?: string;\n no_evict_upon_conflict?: string;\n skip_if_already_set?: string;\n delimiter?: string;\n team?: string;\n ignore_deleted?: string;\n return_full_payload?: string;\n skip_auto_merge?: string;\n repo_name?: string;\n repo_owner_name?: string;\n load_balancing_sizes?: string;\n required_review_overrides?: string;\n}\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/src/helpers/remove-pr-from-merge-queue.ts b/src/helpers/remove-pr-from-merge-queue.ts index 75384614..a123eaef 100644 --- a/src/helpers/remove-pr-from-merge-queue.ts +++ b/src/helpers/remove-pr-from-merge-queue.ts @@ -12,6 +12,7 @@ limitations under the License. */ import * as core from '@actions/core'; +import { orderBy } from 'lodash'; import { FIRST_QUEUED_PR_LABEL, QUEUED_FOR_MERGE_PREFIX, READY_FOR_MERGE_PR_LABEL } from '../constants'; import { HelperInputs } from '../types/generated'; import { context } from '@actions/github'; @@ -54,8 +55,9 @@ export const removePrFromMergeQueue = async ({ seconds }: RemovePrFromMergeQueue ref: sha, ...context.repo }); - const failingStatus = data.find(status => status.state === 'failure'); - if (failingStatus && timestampIsStale(failingStatus.created_at, seconds)) { + const mostRecentStatus = orderBy(data, 'created_at', 'desc')[0]; + const noPendingStatus = data.find(status => status.state !== 'pending'); + if (noPendingStatus && mostRecentStatus && timestampIsStale(mostRecentStatus.created_at, seconds)) { core.info('Removing stale PR from first queued position...'); return Promise.all([removeLabelIfExists(READY_FOR_MERGE_PR_LABEL, number), removeLabelIfExists(FIRST_QUEUED_PR_LABEL, number)]); } diff --git a/test/helpers/remove-pr-from-merge-queue.test.ts b/test/helpers/remove-pr-from-merge-queue.test.ts index f4ff9cbc..52eeb738 100644 --- a/test/helpers/remove-pr-from-merge-queue.test.ts +++ b/test/helpers/remove-pr-from-merge-queue.test.ts @@ -57,16 +57,12 @@ describe('removePrFromMergeQueue', () => { beforeEach(() => { (octokit.repos.listCommitStatusesForRef as unknown as Mocktokit).mockImplementation(async () => ({ data: [ - { - created_at: '2022-01-01T10:00:00Z', - state: 'success' - }, { created_at: '2022-01-01T08:59:00Z', state: 'failure' }, { - created_at: '2022-01-01T10:00:00Z', + created_at: '2022-01-01T08:00:00Z', state: 'success' } ] @@ -99,10 +95,6 @@ describe('removePrFromMergeQueue', () => { beforeEach(() => { (octokit.repos.listCommitStatusesForRef as unknown as Mocktokit).mockImplementation(async () => ({ data: [ - { - created_at: '2022-01-01T10:00:00Z', - state: 'success' - }, { created_at: '2022-01-01T09:01:00Z', state: 'failure' @@ -136,13 +128,13 @@ describe('removePrFromMergeQueue', () => { }); }); - describe('should not remove pr case with no failure status', () => { + describe('should not remove pr case with pending status', () => { beforeEach(async () => { (octokit.repos.listCommitStatusesForRef as unknown as Mocktokit).mockImplementation(async () => ({ data: [ { created_at: '2022-01-01T10:00:00Z', - state: 'success' + state: 'failure' }, { created_at: '2022-01-01T09:01:00Z',