From 0d83db2846ee328f1a778bb85443a4aec95dfbda Mon Sep 17 00:00:00 2001 From: Pooja Donekal Date: Wed, 14 Feb 2024 21:01:47 -0600 Subject: [PATCH] do not allow ready for merge when PR is not core approved --- dist/676.index.js | 4 ++ dist/676.index.js.map | 2 +- src/helpers/manage-merge-queue.ts | 5 +++ test/helpers/manage-merge-queue.test.ts | 56 +++++++++++++++++++------ 4 files changed, 54 insertions(+), 13 deletions(-) diff --git a/dist/676.index.js b/dist/676.index.js index 26c370611..fab075a6f 100644 --- a/dist/676.index.js +++ b/dist/676.index.js @@ -248,6 +248,10 @@ const manageMergeQueue = ({ login, slack_webhook_url } = {}) => manage_merge_que core.info('This PR is not in the merge queue.'); return removePrFromQueue(pullRequest); } + if (!pullRequest.labels.find(label => label.name === constants/* CORE_APPROVED_PR_LABEL */._d)) { + core.info('This PR is not core approved.'); + return removePrFromQueue(pullRequest); + } const queuedPrs = yield getQueuedPullRequests(); const queuePosition = queuedPrs.length; if (pullRequest.labels.find(label => label.name === constants/* JUMP_THE_QUEUE_PR_LABEL */.nJ)) { diff --git a/dist/676.index.js.map b/dist/676.index.js.map index 41fbefbf1..70e2b8a65 100644 --- a/dist/676.index.js.map +++ b/dist/676.index.js.map @@ -1 +1 @@ -{"version":3,"file":"676.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;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;ACvFA;;;;;;;;;;;AAWA;;;;;;;;;;AAEA;AACA;AAOA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;;;;AAIA;AACA;AAAA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;ACtHA;;;;;;;;;;;AAWA;;;;;;;;;;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AAEA;;AACA;AACA;AAEA;AAGA;;AACA;AACA;AACA;AACA;AAIA;AAAA;AACA;AACA;AACA;;AAAA;AACA;AACA;AACA;AACA;AAKA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;;AAAA;AACA;AACA;;;;;;;;;;;;;;;;;;;;AClEA;;;;;;;;;;;AAWA;;;;;;;;;;AAEA;AAEA;AACA;AACA;AAEA;AAAA;;AACA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;ACrCA;;;;;;;;;;;AAWA;;;;;;;;;;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAAA;;AACA;AACA;AACA;AAIA;AAAA;AAEA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAGA;AAGA;AACA;;;;;;;;;;;;;;;;;;ACrDA;;;;;;;;;;;AAWA;AAEA;AACA;AACA;AAEA;AACA;;;;;;;;;;;AClBA;;;;;;;;;;;AAWA;AAEA;AA2CA;;;;;;;;;;;;;;;;;ACxDA;;;;;;;;;;;AAWA;;;;;;;;;;AAEA;AACA;AACA;AACA;AAQA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;AChDA;;;;;;;;;;;AAWA;;;;;;;;;;AAGA;AACA;AAEA;AACA;AAQA;AACA;AACA;AACA;AACA","sources":[".././src/constants.ts",".././src/utils/update-merge-queue.ts",".././src/helpers/manage-merge-queue.ts",".././src/helpers/prepare-queued-pr-for-merge.ts",".././src/helpers/remove-label.ts",".././src/helpers/set-commit-status.ts",".././src/octokit.ts",".././src/types/generated.ts",".././src/utils/notify-user.ts",".././src/utils/paginate-open-pull-requests.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 { JUMP_THE_QUEUE_PR_LABEL, MERGE_QUEUE_STATUS, QUEUED_FOR_MERGE_PREFIX } from '../constants';\nimport { PullRequestList } from '../types/github';\nimport { context } from '@actions/github';\nimport { map } from 'bluebird';\nimport { octokit } from '../octokit';\nimport { removeLabelIfExists } from '../helpers/remove-label';\nimport { updatePrWithDefaultBranch } from '../helpers/prepare-queued-pr-for-merge';\nimport { setCommitStatus } from '../helpers/set-commit-status';\n\nexport const updateMergeQueue = (queuedPrs: PullRequestList) => {\n const sortedPrs = sortPrsByQueuePosition(queuedPrs);\n return map(sortedPrs, updateQueuePosition);\n};\n\nconst sortPrsByQueuePosition = (queuedPrs: PullRequestList) =>\n queuedPrs\n .map(pr => {\n const label = pr.labels.find(label => label.name?.startsWith(QUEUED_FOR_MERGE_PREFIX))?.name;\n const isJumpingTheQueue = Boolean(pr.labels.find(label => label.name === JUMP_THE_QUEUE_PR_LABEL));\n const queuePosition = isJumpingTheQueue ? 0 : Number(label?.split('#')?.[1]);\n return {\n number: pr.number,\n label,\n queuePosition,\n sha: pr.head.sha\n };\n })\n .sort((pr1, pr2) => pr1.queuePosition - pr2.queuePosition);\n\nconst updateQueuePosition = async (pr: ReturnType[number], index: number) => {\n const { number, label, queuePosition, sha } = pr;\n const newQueuePosition = index + 1;\n if (!label || isNaN(queuePosition) || queuePosition === newQueuePosition) {\n return;\n }\n const prIsNowFirstInQueue = newQueuePosition === 1;\n if (prIsNowFirstInQueue) {\n const { data: firstPrInQueue } = await octokit.pulls.get({ pull_number: number, ...context.repo });\n await Promise.all([removeLabelIfExists(JUMP_THE_QUEUE_PR_LABEL, number), updatePrWithDefaultBranch(firstPrInQueue)]);\n const {\n data: {\n head: { sha: updatedHeadSha }\n }\n } = await octokit.pulls.get({ pull_number: number, ...context.repo });\n return Promise.all([\n octokit.issues.addLabels({\n labels: [`${QUEUED_FOR_MERGE_PREFIX} #${newQueuePosition}`],\n issue_number: number,\n ...context.repo\n }),\n removeLabelIfExists(label, number),\n setCommitStatus({\n sha: updatedHeadSha,\n context: MERGE_QUEUE_STATUS,\n state: 'success',\n description: 'This PR is next to merge.'\n })\n ]);\n }\n\n return Promise.all([\n octokit.issues.addLabels({\n labels: [`${QUEUED_FOR_MERGE_PREFIX} #${newQueuePosition}`],\n issue_number: number,\n ...context.repo\n }),\n removeLabelIfExists(label, number),\n setCommitStatus({\n sha,\n context: MERGE_QUEUE_STATUS,\n state: 'pending',\n description: 'This PR is in line to merge.'\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 {\n FIRST_QUEUED_PR_LABEL,\n JUMP_THE_QUEUE_PR_LABEL,\n MERGE_QUEUE_STATUS,\n QUEUED_FOR_MERGE_PREFIX,\n READY_FOR_MERGE_PR_LABEL\n} from '../constants';\nimport { HelperInputs } from '../types/generated';\nimport { PullRequest, PullRequestList } from '../types/github';\nimport { context } from '@actions/github';\nimport { map } from 'bluebird';\nimport { notifyUser } from '../utils/notify-user';\nimport { octokit, octokitGraphql } from '../octokit';\nimport { removeLabelIfExists } from './remove-label';\nimport { setCommitStatus } from './set-commit-status';\nimport { updateMergeQueue } from '../utils/update-merge-queue';\nimport { paginateAllOpenPullRequests } from '../utils/paginate-open-pull-requests';\nimport { updatePrWithDefaultBranch } from './prepare-queued-pr-for-merge';\n\nexport class ManageMergeQueue extends HelperInputs {\n login?: string;\n slack_webhook_url?: string;\n}\n\nexport const manageMergeQueue = async ({ login, slack_webhook_url }: ManageMergeQueue = {}) => {\n const { data: pullRequest } = await octokit.pulls.get({ pull_number: context.issue.number, ...context.repo });\n if (pullRequest.merged || !pullRequest.labels.find(label => label.name === READY_FOR_MERGE_PR_LABEL)) {\n core.info('This PR is not in the merge queue.');\n return removePrFromQueue(pullRequest);\n }\n const queuedPrs = await getQueuedPullRequests();\n const queuePosition = queuedPrs.length;\n if (pullRequest.labels.find(label => label.name === JUMP_THE_QUEUE_PR_LABEL)) {\n return updateMergeQueue(queuedPrs);\n }\n if (!pullRequest.labels.find(label => label.name?.startsWith(QUEUED_FOR_MERGE_PREFIX))) {\n await addPrToQueue(pullRequest, queuePosition);\n }\n\n const isFirstQueuePosition = queuePosition === 1 || pullRequest.labels.find(label => label.name === FIRST_QUEUED_PR_LABEL);\n\n if (isFirstQueuePosition) {\n await updatePrWithDefaultBranch(pullRequest);\n }\n\n await setCommitStatus({\n sha: pullRequest.head.sha,\n context: MERGE_QUEUE_STATUS,\n state: isFirstQueuePosition ? 'success' : 'pending',\n description: isFirstQueuePosition ? 'This PR is next to merge.' : 'This PR is in line to merge.'\n });\n\n if (isFirstQueuePosition && slack_webhook_url && login) {\n await notifyUser({\n login,\n pull_number: context.issue.number,\n slack_webhook_url\n });\n }\n};\n\nexport const removePrFromQueue = async (pullRequest: PullRequest) => {\n const queueLabel = pullRequest.labels.find(label => label.name?.startsWith(QUEUED_FOR_MERGE_PREFIX))?.name;\n if (queueLabel) {\n await map([READY_FOR_MERGE_PR_LABEL, queueLabel], async label => removeLabelIfExists(label, pullRequest.number));\n await setCommitStatus({\n sha: pullRequest.head.sha,\n context: MERGE_QUEUE_STATUS,\n state: 'pending',\n description: 'This PR is not in the merge queue.'\n });\n const queuedPrs = await getQueuedPullRequests();\n await updateMergeQueue(queuedPrs);\n }\n};\n\nconst addPrToQueue = async (pullRequest: PullRequest, queuePosition: number) =>\n Promise.all([\n octokit.issues.addLabels({\n labels: [`${QUEUED_FOR_MERGE_PREFIX} #${queuePosition}`],\n issue_number: context.issue.number,\n ...context.repo\n }),\n enableAutoMerge(pullRequest.node_id)\n ]);\n\nconst getQueuedPullRequests = async (): Promise => {\n const openPullRequests = await paginateAllOpenPullRequests();\n return openPullRequests.filter(pr => pr.labels.some(label => label.name === READY_FOR_MERGE_PR_LABEL));\n};\n\nexport const enableAutoMerge = async (pullRequestId: string, mergeMethod = 'SQUASH') => {\n try {\n return await octokitGraphql(`\n mutation {\n enablePullRequestAutoMerge(input: { pullRequestId: \"${pullRequestId}\", mergeMethod: ${mergeMethod} }) {\n clientMutationId\n }\n }\n `);\n } catch (error) {\n core.warning('Auto merge could not be enabled. Perhaps you need to enable auto-merge on your repo?');\n core.warning(error as Error);\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, JUMP_THE_QUEUE_PR_LABEL, READY_FOR_MERGE_PR_LABEL } from '../constants';\nimport { GithubError, PullRequest, PullRequestList, SinglePullRequest } from '../types/github';\nimport { context } from '@actions/github';\nimport { octokit } from '../octokit';\nimport { removePrFromQueue } from './manage-merge-queue';\n\nexport const prepareQueuedPrForMerge = async () => {\n const { data } = await octokit.pulls.list({\n state: 'open',\n per_page: 100,\n ...context.repo\n });\n const pullRequest = findNextPrToMerge(data);\n if (pullRequest) {\n return updatePrWithDefaultBranch(pullRequest as PullRequest);\n }\n};\n\nconst findNextPrToMerge = (pullRequests: PullRequestList) =>\n pullRequests.find(pr => hasRequiredLabels(pr, [READY_FOR_MERGE_PR_LABEL, JUMP_THE_QUEUE_PR_LABEL])) ??\n pullRequests.find(pr => hasRequiredLabels(pr, [READY_FOR_MERGE_PR_LABEL, FIRST_QUEUED_PR_LABEL]));\n\nconst hasRequiredLabels = (pr: SinglePullRequest, requiredLabels: string[]) =>\n requiredLabels.every(mergeQueueLabel => pr.labels.some(label => label.name === mergeQueueLabel));\n\nexport const updatePrWithDefaultBranch = async (pullRequest: PullRequest) => {\n if (pullRequest.head.user?.login && pullRequest.base.user?.login && pullRequest.head.user?.login !== pullRequest.base.user?.login) {\n try {\n // update fork default branch with upstream\n await octokit.repos.mergeUpstream({\n ...context.repo,\n branch: pullRequest.base.repo.default_branch\n });\n } catch (error) {\n if ((error as GithubError).status === 409) {\n core.setFailed('Attempt to update fork branch with upstream failed; conflict on default branch between fork and upstream.');\n } else core.setFailed((error as GithubError).message);\n }\n }\n try {\n await octokit.repos.merge({\n base: pullRequest.head.ref,\n head: 'HEAD',\n ...context.repo\n });\n } catch (error) {\n const noEvictUponConflict = core.getBooleanInput('no_evict_upon_conflict');\n if ((error as GithubError).status === 409) {\n if (!noEvictUponConflict) await removePrFromQueue(pullRequest);\n core.setFailed('The first PR in the queue has a merge conflict.');\n } else core.setFailed((error as GithubError).message);\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 { 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 { PipelineState } from '../types/github';\nimport { HelperInputs } from '../types/generated';\nimport { context as githubContext } from '@actions/github';\nimport { map } from 'bluebird';\nimport { octokit } from '../octokit';\n\nexport class SetCommitStatus extends HelperInputs {\n sha = '';\n context = '';\n state = '';\n description?: string;\n target_url?: string;\n skip_if_already_set?: string;\n}\n\nexport const setCommitStatus = async ({ sha, context, state, description, target_url, skip_if_already_set }: SetCommitStatus) => {\n await map(context.split('\\n').filter(Boolean), async context => {\n if (skip_if_already_set === 'true') {\n const check_runs = await octokit.checks.listForRef({\n ...githubContext.repo,\n ref: sha\n });\n const run = check_runs.data.check_runs.find(({ name }) => name === context);\n const runCompletedAndIsValid = run?.status === 'completed' && (run?.conclusion === 'failure' || run?.conclusion === 'success');\n if (runCompletedAndIsValid) {\n core.info(`${context} already completed with a ${run.conclusion} conclusion.`);\n return;\n }\n }\n\n octokit.repos.createCommitStatus({\n sha,\n context,\n state: state as PipelineState,\n description,\n target_url,\n ...githubContext.repo\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 * 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 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}\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 axios from 'axios';\nimport { context } from '@actions/github';\nimport { octokit } from '../octokit';\n\ninterface NotifyUser {\n login: string;\n pull_number: number;\n slack_webhook_url: string;\n}\n\nexport const notifyUser = async ({ login, pull_number, slack_webhook_url }: NotifyUser) => {\n core.info(`Notifying user ${login}...`);\n const {\n data: { email }\n } = await octokit.users.getByUsername({ username: login });\n if (!email) {\n core.info(`No github email found for user ${login}. Ensure you have set your email to be publicly visible on your Github profile.`);\n return;\n }\n const {\n data: { title, html_url }\n } = await octokit.pulls.get({ pull_number, ...context.repo });\n\n try {\n await axios.post(slack_webhook_url, {\n assignee: email,\n title,\n html_url,\n repo: context.repo.repo\n });\n } catch (error) {\n core.warning('User notification failed');\n core.warning(error as Error);\n }\n};\n","/*\nCopyright 2022 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 { PullRequestList } from '../types/github';\nimport { octokit } from '../octokit';\nimport { context } from '@actions/github';\n\nexport const paginateAllOpenPullRequests = async (page = 1): Promise => {\n const response = await octokit.pulls.list({\n state: 'open',\n sort: 'updated',\n direction: 'desc',\n per_page: 100,\n page,\n ...context.repo\n });\n if (!response.data.length) {\n return [];\n }\n return response.data.concat(await paginateAllOpenPullRequests(page + 1));\n};\n"],"names":[],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"676.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;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;ACvFA;;;;;;;;;;;AAWA;;;;;;;;;;AAEA;AACA;AAQA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;;;;AAIA;AACA;AAAA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;AC3HA;;;;;;;;;;;AAWA;;;;;;;;;;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AAEA;;AACA;AACA;AAEA;AAGA;;AACA;AACA;AACA;AACA;AAIA;AAAA;AACA;AACA;AACA;;AAAA;AACA;AACA;AACA;AACA;AAKA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;;AAAA;AACA;AACA;;;;;;;;;;;;;;;;;;;;AClEA;;;;;;;;;;;AAWA;;;;;;;;;;AAEA;AAEA;AACA;AACA;AAEA;AAAA;;AACA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;ACrCA;;;;;;;;;;;AAWA;;;;;;;;;;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAAA;;AACA;AACA;AACA;AAIA;AAAA;AAEA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAGA;AAGA;AACA;;;;;;;;;;;;;;;;;;ACrDA;;;;;;;;;;;AAWA;AAEA;AACA;AACA;AAEA;AACA;;;;;;;;;;;AClBA;;;;;;;;;;;AAWA;AAEA;AA2CA;;;;;;;;;;;;;;;;;ACxDA;;;;;;;;;;;AAWA;;;;;;;;;;AAEA;AACA;AACA;AACA;AAQA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;AChDA;;;;;;;;;;;AAWA;;;;;;;;;;AAGA;AACA;AAEA;AACA;AAQA;AACA;AACA;AACA;AACA","sources":[".././src/constants.ts",".././src/utils/update-merge-queue.ts",".././src/helpers/manage-merge-queue.ts",".././src/helpers/prepare-queued-pr-for-merge.ts",".././src/helpers/remove-label.ts",".././src/helpers/set-commit-status.ts",".././src/octokit.ts",".././src/types/generated.ts",".././src/utils/notify-user.ts",".././src/utils/paginate-open-pull-requests.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 { JUMP_THE_QUEUE_PR_LABEL, MERGE_QUEUE_STATUS, QUEUED_FOR_MERGE_PREFIX } from '../constants';\nimport { PullRequestList } from '../types/github';\nimport { context } from '@actions/github';\nimport { map } from 'bluebird';\nimport { octokit } from '../octokit';\nimport { removeLabelIfExists } from '../helpers/remove-label';\nimport { updatePrWithDefaultBranch } from '../helpers/prepare-queued-pr-for-merge';\nimport { setCommitStatus } from '../helpers/set-commit-status';\n\nexport const updateMergeQueue = (queuedPrs: PullRequestList) => {\n const sortedPrs = sortPrsByQueuePosition(queuedPrs);\n return map(sortedPrs, updateQueuePosition);\n};\n\nconst sortPrsByQueuePosition = (queuedPrs: PullRequestList) =>\n queuedPrs\n .map(pr => {\n const label = pr.labels.find(label => label.name?.startsWith(QUEUED_FOR_MERGE_PREFIX))?.name;\n const isJumpingTheQueue = Boolean(pr.labels.find(label => label.name === JUMP_THE_QUEUE_PR_LABEL));\n const queuePosition = isJumpingTheQueue ? 0 : Number(label?.split('#')?.[1]);\n return {\n number: pr.number,\n label,\n queuePosition,\n sha: pr.head.sha\n };\n })\n .sort((pr1, pr2) => pr1.queuePosition - pr2.queuePosition);\n\nconst updateQueuePosition = async (pr: ReturnType[number], index: number) => {\n const { number, label, queuePosition, sha } = pr;\n const newQueuePosition = index + 1;\n if (!label || isNaN(queuePosition) || queuePosition === newQueuePosition) {\n return;\n }\n const prIsNowFirstInQueue = newQueuePosition === 1;\n if (prIsNowFirstInQueue) {\n const { data: firstPrInQueue } = await octokit.pulls.get({ pull_number: number, ...context.repo });\n await Promise.all([removeLabelIfExists(JUMP_THE_QUEUE_PR_LABEL, number), updatePrWithDefaultBranch(firstPrInQueue)]);\n const {\n data: {\n head: { sha: updatedHeadSha }\n }\n } = await octokit.pulls.get({ pull_number: number, ...context.repo });\n return Promise.all([\n octokit.issues.addLabels({\n labels: [`${QUEUED_FOR_MERGE_PREFIX} #${newQueuePosition}`],\n issue_number: number,\n ...context.repo\n }),\n removeLabelIfExists(label, number),\n setCommitStatus({\n sha: updatedHeadSha,\n context: MERGE_QUEUE_STATUS,\n state: 'success',\n description: 'This PR is next to merge.'\n })\n ]);\n }\n\n return Promise.all([\n octokit.issues.addLabels({\n labels: [`${QUEUED_FOR_MERGE_PREFIX} #${newQueuePosition}`],\n issue_number: number,\n ...context.repo\n }),\n removeLabelIfExists(label, number),\n setCommitStatus({\n sha,\n context: MERGE_QUEUE_STATUS,\n state: 'pending',\n description: 'This PR is in line to merge.'\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 {\n CORE_APPROVED_PR_LABEL,\n FIRST_QUEUED_PR_LABEL,\n JUMP_THE_QUEUE_PR_LABEL,\n MERGE_QUEUE_STATUS,\n QUEUED_FOR_MERGE_PREFIX,\n READY_FOR_MERGE_PR_LABEL\n} from '../constants';\nimport { HelperInputs } from '../types/generated';\nimport { PullRequest, PullRequestList } from '../types/github';\nimport { context } from '@actions/github';\nimport { map } from 'bluebird';\nimport { notifyUser } from '../utils/notify-user';\nimport { octokit, octokitGraphql } from '../octokit';\nimport { removeLabelIfExists } from './remove-label';\nimport { setCommitStatus } from './set-commit-status';\nimport { updateMergeQueue } from '../utils/update-merge-queue';\nimport { paginateAllOpenPullRequests } from '../utils/paginate-open-pull-requests';\nimport { updatePrWithDefaultBranch } from './prepare-queued-pr-for-merge';\n\nexport class ManageMergeQueue extends HelperInputs {\n login?: string;\n slack_webhook_url?: string;\n}\n\nexport const manageMergeQueue = async ({ login, slack_webhook_url }: ManageMergeQueue = {}) => {\n const { data: pullRequest } = await octokit.pulls.get({ pull_number: context.issue.number, ...context.repo });\n if (pullRequest.merged || !pullRequest.labels.find(label => label.name === READY_FOR_MERGE_PR_LABEL)) {\n core.info('This PR is not in the merge queue.');\n return removePrFromQueue(pullRequest);\n }\n if (!pullRequest.labels.find(label => label.name === CORE_APPROVED_PR_LABEL)) {\n core.info('This PR is not core approved.');\n return removePrFromQueue(pullRequest);\n }\n const queuedPrs = await getQueuedPullRequests();\n const queuePosition = queuedPrs.length;\n if (pullRequest.labels.find(label => label.name === JUMP_THE_QUEUE_PR_LABEL)) {\n return updateMergeQueue(queuedPrs);\n }\n if (!pullRequest.labels.find(label => label.name?.startsWith(QUEUED_FOR_MERGE_PREFIX))) {\n await addPrToQueue(pullRequest, queuePosition);\n }\n\n const isFirstQueuePosition = queuePosition === 1 || pullRequest.labels.find(label => label.name === FIRST_QUEUED_PR_LABEL);\n\n if (isFirstQueuePosition) {\n await updatePrWithDefaultBranch(pullRequest);\n }\n\n await setCommitStatus({\n sha: pullRequest.head.sha,\n context: MERGE_QUEUE_STATUS,\n state: isFirstQueuePosition ? 'success' : 'pending',\n description: isFirstQueuePosition ? 'This PR is next to merge.' : 'This PR is in line to merge.'\n });\n\n if (isFirstQueuePosition && slack_webhook_url && login) {\n await notifyUser({\n login,\n pull_number: context.issue.number,\n slack_webhook_url\n });\n }\n};\n\nexport const removePrFromQueue = async (pullRequest: PullRequest) => {\n const queueLabel = pullRequest.labels.find(label => label.name?.startsWith(QUEUED_FOR_MERGE_PREFIX))?.name;\n if (queueLabel) {\n await map([READY_FOR_MERGE_PR_LABEL, queueLabel], async label => removeLabelIfExists(label, pullRequest.number));\n await setCommitStatus({\n sha: pullRequest.head.sha,\n context: MERGE_QUEUE_STATUS,\n state: 'pending',\n description: 'This PR is not in the merge queue.'\n });\n const queuedPrs = await getQueuedPullRequests();\n await updateMergeQueue(queuedPrs);\n }\n};\n\nconst addPrToQueue = async (pullRequest: PullRequest, queuePosition: number) =>\n Promise.all([\n octokit.issues.addLabels({\n labels: [`${QUEUED_FOR_MERGE_PREFIX} #${queuePosition}`],\n issue_number: context.issue.number,\n ...context.repo\n }),\n enableAutoMerge(pullRequest.node_id)\n ]);\n\nconst getQueuedPullRequests = async (): Promise => {\n const openPullRequests = await paginateAllOpenPullRequests();\n return openPullRequests.filter(pr => pr.labels.some(label => label.name === READY_FOR_MERGE_PR_LABEL));\n};\n\nexport const enableAutoMerge = async (pullRequestId: string, mergeMethod = 'SQUASH') => {\n try {\n return await octokitGraphql(`\n mutation {\n enablePullRequestAutoMerge(input: { pullRequestId: \"${pullRequestId}\", mergeMethod: ${mergeMethod} }) {\n clientMutationId\n }\n }\n `);\n } catch (error) {\n core.warning('Auto merge could not be enabled. Perhaps you need to enable auto-merge on your repo?');\n core.warning(error as Error);\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, JUMP_THE_QUEUE_PR_LABEL, READY_FOR_MERGE_PR_LABEL } from '../constants';\nimport { GithubError, PullRequest, PullRequestList, SinglePullRequest } from '../types/github';\nimport { context } from '@actions/github';\nimport { octokit } from '../octokit';\nimport { removePrFromQueue } from './manage-merge-queue';\n\nexport const prepareQueuedPrForMerge = async () => {\n const { data } = await octokit.pulls.list({\n state: 'open',\n per_page: 100,\n ...context.repo\n });\n const pullRequest = findNextPrToMerge(data);\n if (pullRequest) {\n return updatePrWithDefaultBranch(pullRequest as PullRequest);\n }\n};\n\nconst findNextPrToMerge = (pullRequests: PullRequestList) =>\n pullRequests.find(pr => hasRequiredLabels(pr, [READY_FOR_MERGE_PR_LABEL, JUMP_THE_QUEUE_PR_LABEL])) ??\n pullRequests.find(pr => hasRequiredLabels(pr, [READY_FOR_MERGE_PR_LABEL, FIRST_QUEUED_PR_LABEL]));\n\nconst hasRequiredLabels = (pr: SinglePullRequest, requiredLabels: string[]) =>\n requiredLabels.every(mergeQueueLabel => pr.labels.some(label => label.name === mergeQueueLabel));\n\nexport const updatePrWithDefaultBranch = async (pullRequest: PullRequest) => {\n if (pullRequest.head.user?.login && pullRequest.base.user?.login && pullRequest.head.user?.login !== pullRequest.base.user?.login) {\n try {\n // update fork default branch with upstream\n await octokit.repos.mergeUpstream({\n ...context.repo,\n branch: pullRequest.base.repo.default_branch\n });\n } catch (error) {\n if ((error as GithubError).status === 409) {\n core.setFailed('Attempt to update fork branch with upstream failed; conflict on default branch between fork and upstream.');\n } else core.setFailed((error as GithubError).message);\n }\n }\n try {\n await octokit.repos.merge({\n base: pullRequest.head.ref,\n head: 'HEAD',\n ...context.repo\n });\n } catch (error) {\n const noEvictUponConflict = core.getBooleanInput('no_evict_upon_conflict');\n if ((error as GithubError).status === 409) {\n if (!noEvictUponConflict) await removePrFromQueue(pullRequest);\n core.setFailed('The first PR in the queue has a merge conflict.');\n } else core.setFailed((error as GithubError).message);\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 { 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 { PipelineState } from '../types/github';\nimport { HelperInputs } from '../types/generated';\nimport { context as githubContext } from '@actions/github';\nimport { map } from 'bluebird';\nimport { octokit } from '../octokit';\n\nexport class SetCommitStatus extends HelperInputs {\n sha = '';\n context = '';\n state = '';\n description?: string;\n target_url?: string;\n skip_if_already_set?: string;\n}\n\nexport const setCommitStatus = async ({ sha, context, state, description, target_url, skip_if_already_set }: SetCommitStatus) => {\n await map(context.split('\\n').filter(Boolean), async context => {\n if (skip_if_already_set === 'true') {\n const check_runs = await octokit.checks.listForRef({\n ...githubContext.repo,\n ref: sha\n });\n const run = check_runs.data.check_runs.find(({ name }) => name === context);\n const runCompletedAndIsValid = run?.status === 'completed' && (run?.conclusion === 'failure' || run?.conclusion === 'success');\n if (runCompletedAndIsValid) {\n core.info(`${context} already completed with a ${run.conclusion} conclusion.`);\n return;\n }\n }\n\n octokit.repos.createCommitStatus({\n sha,\n context,\n state: state as PipelineState,\n description,\n target_url,\n ...githubContext.repo\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 * 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 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}\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 axios from 'axios';\nimport { context } from '@actions/github';\nimport { octokit } from '../octokit';\n\ninterface NotifyUser {\n login: string;\n pull_number: number;\n slack_webhook_url: string;\n}\n\nexport const notifyUser = async ({ login, pull_number, slack_webhook_url }: NotifyUser) => {\n core.info(`Notifying user ${login}...`);\n const {\n data: { email }\n } = await octokit.users.getByUsername({ username: login });\n if (!email) {\n core.info(`No github email found for user ${login}. Ensure you have set your email to be publicly visible on your Github profile.`);\n return;\n }\n const {\n data: { title, html_url }\n } = await octokit.pulls.get({ pull_number, ...context.repo });\n\n try {\n await axios.post(slack_webhook_url, {\n assignee: email,\n title,\n html_url,\n repo: context.repo.repo\n });\n } catch (error) {\n core.warning('User notification failed');\n core.warning(error as Error);\n }\n};\n","/*\nCopyright 2022 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 { PullRequestList } from '../types/github';\nimport { octokit } from '../octokit';\nimport { context } from '@actions/github';\n\nexport const paginateAllOpenPullRequests = async (page = 1): Promise => {\n const response = await octokit.pulls.list({\n state: 'open',\n sort: 'updated',\n direction: 'desc',\n per_page: 100,\n page,\n ...context.repo\n });\n if (!response.data.length) {\n return [];\n }\n return response.data.concat(await paginateAllOpenPullRequests(page + 1));\n};\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/src/helpers/manage-merge-queue.ts b/src/helpers/manage-merge-queue.ts index 15f135537..92fb983ae 100644 --- a/src/helpers/manage-merge-queue.ts +++ b/src/helpers/manage-merge-queue.ts @@ -13,6 +13,7 @@ limitations under the License. import * as core from '@actions/core'; import { + CORE_APPROVED_PR_LABEL, FIRST_QUEUED_PR_LABEL, JUMP_THE_QUEUE_PR_LABEL, MERGE_QUEUE_STATUS, @@ -42,6 +43,10 @@ export const manageMergeQueue = async ({ login, slack_webhook_url }: ManageMerge core.info('This PR is not in the merge queue.'); return removePrFromQueue(pullRequest); } + if (!pullRequest.labels.find(label => label.name === CORE_APPROVED_PR_LABEL)) { + core.info('This PR is not core approved.'); + return removePrFromQueue(pullRequest); + } const queuedPrs = await getQueuedPullRequests(); const queuePosition = queuedPrs.length; if (pullRequest.labels.find(label => label.name === JUMP_THE_QUEUE_PR_LABEL)) { diff --git a/test/helpers/manage-merge-queue.test.ts b/test/helpers/manage-merge-queue.test.ts index 3f051a572..bf4d28531 100644 --- a/test/helpers/manage-merge-queue.test.ts +++ b/test/helpers/manage-merge-queue.test.ts @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { JUMP_THE_QUEUE_PR_LABEL, MERGE_QUEUE_STATUS, READY_FOR_MERGE_PR_LABEL } from '../../src/constants'; +import { CORE_APPROVED_PR_LABEL, JUMP_THE_QUEUE_PR_LABEL, MERGE_QUEUE_STATUS, READY_FOR_MERGE_PR_LABEL } from '../../src/constants'; import { Mocktokit } from '../types'; import { context } from '@actions/github'; import { manageMergeQueue } from '../../src/helpers/manage-merge-queue'; @@ -53,6 +53,33 @@ describe('manageMergeQueue', () => { merged: true, head: { sha: 'sha' }, number: 123, + labels: [{ name: READY_FOR_MERGE_PR_LABEL }, { name: CORE_APPROVED_PR_LABEL }, { name: 'QUEUED FOR MERGE #1' }] + } + })); + await manageMergeQueue(); + }); + + it('should call remove label with correct params', () => { + expect(removeLabelIfExists).toHaveBeenCalledWith(READY_FOR_MERGE_PR_LABEL, 123); + expect(removeLabelIfExists).toHaveBeenCalledWith('QUEUED FOR MERGE #1', 123); + }); + + it('should call updateMergeQueue with correct params', () => { + expect(updateMergeQueue).toHaveBeenCalledWith(queuedPrs); + }); + }); + + describe('pr not core approved case', () => { + const queuedPrs = [{ labels: [{ name: READY_FOR_MERGE_PR_LABEL }] }, { labels: [{ name: READY_FOR_MERGE_PR_LABEL }] }]; + beforeEach(async () => { + (octokit.pulls.list as unknown as Mocktokit).mockImplementation(async ({ page }) => ({ + data: page === 1 ? queuedPrs : [] + })); + (octokit.pulls.get as unknown as Mocktokit).mockImplementation(async () => ({ + data: { + merged: false, + head: { sha: 'sha' }, + number: 123, labels: [{ name: READY_FOR_MERGE_PR_LABEL }, { name: 'QUEUED FOR MERGE #1' }] } })); @@ -80,7 +107,7 @@ describe('manageMergeQueue', () => { merged: false, head: { sha: 'sha' }, number: 123, - labels: [{ name: 'QUEUED FOR MERGE #2' }] + labels: [{ name: 'QUEUED FOR MERGE #2' }, { name: CORE_APPROVED_PR_LABEL }] } })); await manageMergeQueue(); @@ -115,7 +142,7 @@ describe('manageMergeQueue', () => { data: { merged: false, head: { sha: 'sha' }, - labels: [{ name: READY_FOR_MERGE_PR_LABEL }] + labels: [{ name: READY_FOR_MERGE_PR_LABEL }, { name: CORE_APPROVED_PR_LABEL }] } })); await manageMergeQueue(); @@ -152,7 +179,7 @@ describe('manageMergeQueue', () => { const pullRequest = { merged: false, head: { sha: 'sha' }, - labels: [{ name: READY_FOR_MERGE_PR_LABEL }] + labels: [{ name: READY_FOR_MERGE_PR_LABEL }, { name: CORE_APPROVED_PR_LABEL }] }; beforeEach(async () => { (octokit.pulls.list as unknown as Mocktokit).mockImplementation(async ({ page }) => ({ @@ -200,7 +227,7 @@ describe('manageMergeQueue', () => { data: { merged: false, head: { sha: 'sha' }, - labels: [{ name: READY_FOR_MERGE_PR_LABEL }] + labels: [{ name: READY_FOR_MERGE_PR_LABEL }, { name: CORE_APPROVED_PR_LABEL }] } })); (octokitGraphql as unknown as jest.Mock).mockRejectedValue(new Error('Auto merge is not allowed for this repo')); @@ -245,7 +272,7 @@ describe('manageMergeQueue', () => { data: { merged: false, head: { sha: 'sha' }, - labels: [{ name: READY_FOR_MERGE_PR_LABEL }, { name: 'QUEUED FOR MERGE #5' }] + labels: [{ name: READY_FOR_MERGE_PR_LABEL }, { name: 'QUEUED FOR MERGE #5' }, { name: CORE_APPROVED_PR_LABEL }] } })); await manageMergeQueue(); @@ -274,7 +301,12 @@ describe('manageMergeQueue', () => { data: { merged: false, head: { sha: 'sha' }, - labels: [{ name: READY_FOR_MERGE_PR_LABEL }, { name: JUMP_THE_QUEUE_PR_LABEL }, { name: 'QUEUED FOR MERGE #5' }] + labels: [ + { name: READY_FOR_MERGE_PR_LABEL }, + { name: JUMP_THE_QUEUE_PR_LABEL }, + { name: 'QUEUED FOR MERGE #5' }, + { name: CORE_APPROVED_PR_LABEL } + ] } })); await manageMergeQueue(); @@ -298,7 +330,7 @@ describe('manageMergeQueue', () => { data: { merged: false, head: { sha: 'sha' }, - labels: [{ name: READY_FOR_MERGE_PR_LABEL }] + labels: [{ name: READY_FOR_MERGE_PR_LABEL }, { name: CORE_APPROVED_PR_LABEL }] } })); @@ -322,7 +354,7 @@ describe('manageMergeQueue', () => { data: { merged: false, head: { sha: 'sha' }, - labels: [{ name: READY_FOR_MERGE_PR_LABEL }, { name: 'QUEUED FOR MERGE #5' }] + labels: [{ name: READY_FOR_MERGE_PR_LABEL }, { name: 'QUEUED FOR MERGE #5' }, { name: CORE_APPROVED_PR_LABEL }] } })); await manageMergeQueue({ login, slack_webhook_url }); @@ -339,7 +371,7 @@ describe('manageMergeQueue', () => { data: { merged: false, head: { sha: 'sha' }, - labels: [{ name: READY_FOR_MERGE_PR_LABEL }] + labels: [{ name: READY_FOR_MERGE_PR_LABEL }, { name: CORE_APPROVED_PR_LABEL }] } })); @@ -357,7 +389,7 @@ describe('manageMergeQueue', () => { data: { merged: false, head: { sha: 'sha' }, - labels: [{ name: READY_FOR_MERGE_PR_LABEL }] + labels: [{ name: READY_FOR_MERGE_PR_LABEL }, { name: CORE_APPROVED_PR_LABEL }] } })); @@ -380,7 +412,7 @@ describe('manageMergeQueue', () => { merged: true, head: { sha: 'sha' }, number: 123, - labels: [{ name: READY_FOR_MERGE_PR_LABEL }, { name: 'QUEUED FOR MERGE #1' }] + labels: [{ name: READY_FOR_MERGE_PR_LABEL }, { name: 'QUEUED FOR MERGE #1' }, { name: CORE_APPROVED_PR_LABEL }] } })); await manageMergeQueue();