diff --git a/.github/actions/checkDeployBlockers/index.js b/.github/actions/checkDeployBlockers/index.js index 6917bddd09fe..eb39d3a08daa 100644 --- a/.github/actions/checkDeployBlockers/index.js +++ b/.github/actions/checkDeployBlockers/index.js @@ -103,6 +103,7 @@ const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/ const APPLAUSE_BOT = 'applausebot'; const STAGING_DEPLOY_CASH_LABEL = 'StagingDeployCash'; +const DEPLOY_BLOCKER_CASH_LABEL = 'DeployBlockerCash'; class GithubUtils { /** @@ -185,6 +186,7 @@ class GithubUtils { return { title: issue.title, url: issue.url, + number: this.getIssueOrPullRequestNumberFromURL(issue.url), labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), @@ -269,87 +271,9 @@ class GithubUtils { ); } - /** - * Creates a new StagingDeployCash issue. - * - * @param {String} title - * @param {String} tag - * @param {Array} PRList - * @returns {Promise} - */ - static createNewStagingDeployCash(title, tag, PRList) { - return this.generateStagingDeployCashBody(tag, PRList) - .then(body => this.octokit.issues.create({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - labels: [STAGING_DEPLOY_CASH_LABEL], - assignees: [APPLAUSE_BOT], - title, - body, - })); - } - - /** - * Updates the existing open StagingDeployCash issue. - * - * @param {String} [newTag] - * @param {Array} newPRs - * @param {Array} newDeployBlockers - * @returns {Promise} - * @throws {Error} If the StagingDeployCash could not be found or updated. - */ - static updateStagingDeployCash(newTag = '', newPRs, newDeployBlockers) { - let issueNumber; - return this.getStagingDeployCash() - .then(({ - url, - tag: oldTag, - PRList: oldPRs, - deployBlockers: oldDeployBlockers, - }) => { - issueNumber = GithubUtils.getIssueNumberFromURL(url); - - // If we aren't sent a tag, then use the existing tag - const tag = _.isEmpty(newTag) ? oldTag : newTag; - - const PRList = _.sortBy( - _.union(oldPRs, _.map(newPRs, URL => ({ - url: URL, - number: GithubUtils.getPullRequestNumberFromURL(URL), - isVerified: false, - }))), - 'number', - ); - const deployBlockers = _.sortBy( - _.union(oldDeployBlockers, _.map(newDeployBlockers, URL => ({ - url: URL, - number: GithubUtils.getIssueOrPullRequestNumberFromURL(URL), - isResolved: false, - }))), - 'number', - ); - - return this.generateStagingDeployCashBody( - tag, - _.pluck(PRList, 'url'), - _.pluck(_.where(PRList, {isVerified: true}), 'url'), - _.pluck(deployBlockers, 'url'), - _.pluck(_.where(deployBlockers, {isResolved: true}), 'url'), - ); - }) - .then(updatedBody => this.octokit.issues.update({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - issue_number: issueNumber, - body: updatedBody, - })); - } - /** * Generate the issue body for a StagingDeployCash. * - * @private - * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash * @param {Array} [verifiedPRList] - The list of PR URLs which have passed QA. @@ -533,6 +457,8 @@ module.exports = GithubUtils; module.exports.GITHUB_OWNER = GITHUB_OWNER; module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO; module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL; +module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL; +module.exports.APPLAUSE_BOT = APPLAUSE_BOT; /***/ }), diff --git a/.github/actions/createOrUpdateStagingDeploy/action.yml b/.github/actions/createOrUpdateStagingDeploy/action.yml index 4f73ac487371..8d76b65f21c6 100644 --- a/.github/actions/createOrUpdateStagingDeploy/action.yml +++ b/.github/actions/createOrUpdateStagingDeploy/action.yml @@ -7,12 +7,6 @@ inputs: NPM_VERSION: description: The new NPM version of the StagingDeployCash issue. required: false - NEW_PULL_REQUESTS: - description: A comma-separated list of pull request URLs to add to the open StagingDeployCash issue. - required: false - NEW_DEPLOY_BLOCKERS: - description: A comma-separated list of deploy blockers to add to the open StagingDeployCash issue. - required: false runs: using: 'node12' main: './index.js' diff --git a/.github/actions/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js b/.github/actions/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js index a0bfd322ec3a..64ac1dffd26b 100644 --- a/.github/actions/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js +++ b/.github/actions/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js @@ -4,66 +4,145 @@ const moment = require('moment'); const GithubUtils = require('../../libs/GithubUtils'); const GitUtils = require('../../libs/GitUtils'); -const newVersion = core.getInput('NPM_VERSION'); - - -GithubUtils.getStagingDeployCash() - .then(() => GithubUtils.updateStagingDeployCash( - newVersion, - _.filter( - _.map(core.getInput('NEW_PULL_REQUESTS').split(','), PR => PR.trim()), - PR => !_.isEmpty(PR), - ), - _.filter( - _.map(core.getInput('NEW_DEPLOY_BLOCKERS').split(','), deployBlocker => deployBlocker.trim()), - PR => !_.isEmpty(PR), - ), - )) - .then(({data}) => { - console.log('Successfully updated StagingDeployCash! 🎉', data.html_url); - process.exit(0); - }) - .catch((err) => { - // Unable to find the open StagingDeployCash - if (err && err.code === 404) { - console.log('No open StagingDeployCash found, creating a new one.'); - - // Fetch all the StagingDeployCash issues - return GithubUtils.octokit.issues.listForRepo({ - log: console, +const run = function () { + const newVersion = core.getInput('NPM_VERSION'); + + let shouldCreateNewStagingDeployCash = false; + let currentStagingDeployCashIssueNumber = null; + + // Start by fetching the list of recent StagingDeployCash issues, along with the list of open deploy blockers + return Promise.all([ + GithubUtils.octokit.issues.listForRepo({ + log: console, + owner: GithubUtils.GITHUB_OWNER, + repo: GithubUtils.EXPENSIFY_CASH_REPO, + labels: GithubUtils.STAGING_DEPLOY_CASH_LABEL, + }), + GithubUtils.octokit.issues.listForRepo({ + log: console, + owner: GithubUtils.GITHUB_OWNER, + repo: GithubUtils.EXPENSIFY_CASH_REPO, + labels: GithubUtils.DEPLOY_BLOCKER_CASH_LABEL, + state: 'open', + }), + ]) + .then((results) => { + const [stagingDeployResponse, deployBlockerResponse] = results; + if (!stagingDeployResponse || !stagingDeployResponse.data || _.isEmpty(stagingDeployResponse.data)) { + console.error('Failed fetching StagingDeployCash issues from Github!', stagingDeployResponse); + throw new Error('Failed fetching StagingDeployCash issues from Github'); + } + + if (!deployBlockerResponse || !deployBlockerResponse.data) { + console.log('Failed fetching DeployBlockerCash issues from Github, continuing...'); + } + + // Look at the state of the most recent StagingDeployCash, + // if it is open then we'll update the existing one, otherwise, we'll create a new one. + shouldCreateNewStagingDeployCash = Boolean(stagingDeployResponse.data[0].state !== 'open'); + + // Parse the data from the previous StagingDeployCash + // (newest if there are none open, otherwise second-newest) + const previousStagingDeployCashData = shouldCreateNewStagingDeployCash + ? GithubUtils.getStagingDeployCashData(stagingDeployResponse.data[0]) + : GithubUtils.getStagingDeployCashData(stagingDeployResponse.data[1]); + + console.log('Found tag of previous StagingDeployCash:', previousStagingDeployCashData.tag); + + // Find the list of PRs merged between the last StagingDeployCash and the new version + const mergedPRs = GitUtils.getPullRequestsMergedBetween(previousStagingDeployCashData.tag, newVersion); + + if (shouldCreateNewStagingDeployCash) { + // We're in the create flow, not update + // TODO: if there are open DeployBlockers and we are opening a new checklist, + // then we should close / remove the DeployBlockerCash label from those + return GithubUtils.generateStagingDeployCashBody(newVersion, mergedPRs); + } + + // There is an open StagingDeployCash, so we'll be updating it, not creating a new one + const currentStagingDeployCashData = GithubUtils.getStagingDeployCashData(stagingDeployResponse.data[0]); + currentStagingDeployCashIssueNumber = currentStagingDeployCashData.number; + + const newDeployBlockers = _.map(deployBlockerResponse.data, ({url}) => ({ + url, + number: GithubUtils.getIssueOrPullRequestNumberFromURL(url), + isResolved: false, + })); + + // If we aren't sent a tag, then use the existing tag + const tag = newVersion || currentStagingDeployCashData.tag; + + // Generate the PR list, preserving the previous state of `isVerified` for existing PRs + const PRList = _.sortBy( + _.unique( + _.union(currentStagingDeployCashData.PRList, _.map(mergedPRs, url => ({ + url, + number: GithubUtils.getPullRequestNumberFromURL(url), + + // Since this is the second argument to _.union, + // it will appear later in the array than any duplicate. + // Since it is later in the array, it will be truncated by _.unique, + // and the original value of isVerified will be preserved. + isVerified: false, + }))), + false, + item => item.number, + ), + 'number', + ); + + // Generate the deploy blocker list, preserving the previous state of `isResolved` + const deployBlockers = _.sortBy( + _.unique( + _.union(currentStagingDeployCashData.deployBlockers, newDeployBlockers), + false, + item => item.number, + ), + 'number', + ); + + return GithubUtils.generateStagingDeployCashBody( + tag, + _.pluck(PRList, 'url'), + _.pluck(_.where(PRList, {isVerified: true}), 'url'), + _.pluck(deployBlockers, 'url'), + _.pluck(_.where(deployBlockers, {isResolved: true}), 'url'), + ); + }) + .then((body) => { + const defaultPayload = { owner: GithubUtils.GITHUB_OWNER, repo: GithubUtils.EXPENSIFY_CASH_REPO, - labels: GithubUtils.STAGING_DEPLOY_CASH_LABEL, - state: 'closed', + body, + }; + + if (shouldCreateNewStagingDeployCash) { + return GithubUtils.octokit.issues.create({ + ...defaultPayload, + title: `Deploy Checklist: Expensify.cash ${moment().format('YYYY-MM-DD')}`, + labels: [GithubUtils.STAGING_DEPLOY_CASH_LABEL], + assignees: [GithubUtils.APPLAUSE_BOT], + }); + } + + return GithubUtils.octokit.issues.update({ + ...defaultPayload, + issue_number: currentStagingDeployCashIssueNumber, }); - } - - // Unexpected error! - console.error('Unexpected error occurred finding the StagingDeployCash!' - + ' There may have been more than one open StagingDeployCash found,' - + ' or there was some other problem with the Github API request.', err); - core.setFailed(err); - }) - .then((githubResponse) => { - if (!githubResponse || !githubResponse.data || _.isEmpty(githubResponse.data)) { - console.error('Failed fetching data from Github!', githubResponse); - throw new Error('Failed fetching data from Github'); - } - - // Parse the tag from the most recent StagingDeployCash - const lastTag = GithubUtils.getStagingDeployCashData(githubResponse.data[0]).tag; - console.log('Found tag of previous StagingDeployCash:', lastTag); - - // Find the list of PRs merged between the last StagingDeployCash and the new version - return GitUtils.getPullRequestsMergedBetween(lastTag, newVersion); - }) - .then(PRNumbers => GithubUtils.createNewStagingDeployCash( - `Deploy Checklist: Expensify.cash ${moment().format('YYYY-MM-DD')}`, - newVersion, - _.map(PRNumbers, GithubUtils.getPullRequestURLFromNumber), - )) - .then(({data}) => console.log('Successfully created new StagingDeployCash! 🎉', data.html_url)) - .catch((err) => { - console.error('An error occurred!', err); - core.setFailed(err); - }); + }) + .then(({data}) => { + // eslint-disable-next-line max-len + console.log(`Successfully ${shouldCreateNewStagingDeployCash ? 'created new' : 'updated'} StagingDeployCash! 🎉 ${data.html_url}`); + return data; + }) + .catch((err) => { + console.error('An unknown error occurred!', err); + core.setFailed(err); + }); +}; + +if (require.main === module) { + run(); +} + +module.exports = run; diff --git a/.github/actions/createOrUpdateStagingDeploy/index.js b/.github/actions/createOrUpdateStagingDeploy/index.js index 23c24b21191a..dbe401cd1ab4 100644 --- a/.github/actions/createOrUpdateStagingDeploy/index.js +++ b/.github/actions/createOrUpdateStagingDeploy/index.js @@ -6,7 +6,7 @@ module.exports = /******/ var __webpack_modules__ = ({ /***/ 2730: -/***/ ((__unused_webpack_module, __unused_webpack_exports, __nccwpck_require__) => { +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { const _ = __nccwpck_require__(4987); const core = __nccwpck_require__(2186); @@ -14,69 +14,148 @@ const moment = __nccwpck_require__(9623); const GithubUtils = __nccwpck_require__(7999); const GitUtils = __nccwpck_require__(669); -const newVersion = core.getInput('NPM_VERSION'); +const run = function () { + const newVersion = core.getInput('NPM_VERSION'); + + let shouldCreateNewStagingDeployCash = false; + let currentStagingDeployCashIssueNumber = null; + + // Start by fetching the list of recent StagingDeployCash issues, along with the list of open deploy blockers + return Promise.all([ + GithubUtils.octokit.issues.listForRepo({ + log: console, + owner: GithubUtils.GITHUB_OWNER, + repo: GithubUtils.EXPENSIFY_CASH_REPO, + labels: GithubUtils.STAGING_DEPLOY_CASH_LABEL, + }), + GithubUtils.octokit.issues.listForRepo({ + log: console, + owner: GithubUtils.GITHUB_OWNER, + repo: GithubUtils.EXPENSIFY_CASH_REPO, + labels: GithubUtils.DEPLOY_BLOCKER_CASH_LABEL, + state: 'open', + }), + ]) + .then((results) => { + const [stagingDeployResponse, deployBlockerResponse] = results; + if (!stagingDeployResponse || !stagingDeployResponse.data || _.isEmpty(stagingDeployResponse.data)) { + console.error('Failed fetching StagingDeployCash issues from Github!', stagingDeployResponse); + throw new Error('Failed fetching StagingDeployCash issues from Github'); + } + if (!deployBlockerResponse || !deployBlockerResponse.data) { + console.log('Failed fetching DeployBlockerCash issues from Github, continuing...'); + } -GithubUtils.getStagingDeployCash() - .then(() => GithubUtils.updateStagingDeployCash( - newVersion, - _.filter( - _.map(core.getInput('NEW_PULL_REQUESTS').split(','), PR => PR.trim()), - PR => !_.isEmpty(PR), - ), - _.filter( - _.map(core.getInput('NEW_DEPLOY_BLOCKERS').split(','), deployBlocker => deployBlocker.trim()), - PR => !_.isEmpty(PR), - ), - )) - .then(({data}) => { - console.log('Successfully updated StagingDeployCash! 🎉', data.html_url); - process.exit(0); - }) - .catch((err) => { - // Unable to find the open StagingDeployCash - if (err && err.code === 404) { - console.log('No open StagingDeployCash found, creating a new one.'); - - // Fetch all the StagingDeployCash issues - return GithubUtils.octokit.issues.listForRepo({ - log: console, + // Look at the state of the most recent StagingDeployCash, + // if it is open then we'll update the existing one, otherwise, we'll create a new one. + shouldCreateNewStagingDeployCash = Boolean(stagingDeployResponse.data[0].state !== 'open'); + + // Parse the data from the previous StagingDeployCash + // (newest if there are none open, otherwise second-newest) + const previousStagingDeployCashData = shouldCreateNewStagingDeployCash + ? GithubUtils.getStagingDeployCashData(stagingDeployResponse.data[0]) + : GithubUtils.getStagingDeployCashData(stagingDeployResponse.data[1]); + + console.log('Found tag of previous StagingDeployCash:', previousStagingDeployCashData.tag); + + // Find the list of PRs merged between the last StagingDeployCash and the new version + const mergedPRs = GitUtils.getPullRequestsMergedBetween(previousStagingDeployCashData.tag, newVersion); + + if (shouldCreateNewStagingDeployCash) { + // We're in the create flow, not update + // TODO: if there are open DeployBlockers and we are opening a new checklist, + // then we should close / remove the DeployBlockerCash label from those + return GithubUtils.generateStagingDeployCashBody(newVersion, mergedPRs); + } + + // There is an open StagingDeployCash, so we'll be updating it, not creating a new one + const currentStagingDeployCashData = GithubUtils.getStagingDeployCashData(stagingDeployResponse.data[0]); + currentStagingDeployCashIssueNumber = currentStagingDeployCashData.number; + + const newDeployBlockers = _.map(deployBlockerResponse.data, ({url}) => ({ + url, + number: GithubUtils.getIssueOrPullRequestNumberFromURL(url), + isResolved: false, + })); + + // If we aren't sent a tag, then use the existing tag + const tag = newVersion || currentStagingDeployCashData.tag; + + // Generate the PR list, preserving the previous state of `isVerified` for existing PRs + const PRList = _.sortBy( + _.unique( + _.union(currentStagingDeployCashData.PRList, _.map(mergedPRs, url => ({ + url, + number: GithubUtils.getPullRequestNumberFromURL(url), + + // Since this is the second argument to _.union, + // it will appear later in the array than any duplicate. + // Since it is later in the array, it will be truncated by _.unique, + // and the original value of isVerified will be preserved. + isVerified: false, + }))), + false, + item => item.number, + ), + 'number', + ); + + // Generate the deploy blocker list, preserving the previous state of `isResolved` + const deployBlockers = _.sortBy( + _.unique( + _.union(currentStagingDeployCashData.deployBlockers, newDeployBlockers), + false, + item => item.number, + ), + 'number', + ); + + return GithubUtils.generateStagingDeployCashBody( + tag, + _.pluck(PRList, 'url'), + _.pluck(_.where(PRList, {isVerified: true}), 'url'), + _.pluck(deployBlockers, 'url'), + _.pluck(_.where(deployBlockers, {isResolved: true}), 'url'), + ); + }) + .then((body) => { + const defaultPayload = { owner: GithubUtils.GITHUB_OWNER, repo: GithubUtils.EXPENSIFY_CASH_REPO, - labels: GithubUtils.STAGING_DEPLOY_CASH_LABEL, - state: 'closed', - }); - } + body, + }; - // Unexpected error! - console.error('Unexpected error occurred finding the StagingDeployCash!' - + ' There may have been more than one open StagingDeployCash found,' - + ' or there was some other problem with the Github API request.', err); - core.setFailed(err); - }) - .then((githubResponse) => { - if (!githubResponse || !githubResponse.data || _.isEmpty(githubResponse.data)) { - console.error('Failed fetching data from Github!', githubResponse); - throw new Error('Failed fetching data from Github'); - } + if (shouldCreateNewStagingDeployCash) { + return GithubUtils.octokit.issues.create({ + ...defaultPayload, + title: `Deploy Checklist: Expensify.cash ${moment().format('YYYY-MM-DD')}`, + labels: [GithubUtils.STAGING_DEPLOY_CASH_LABEL], + assignees: [GithubUtils.APPLAUSE_BOT], + }); + } - // Parse the tag from the most recent StagingDeployCash - const lastTag = GithubUtils.getStagingDeployCashData(githubResponse.data[0]).tag; - console.log('Found tag of previous StagingDeployCash:', lastTag); + return GithubUtils.octokit.issues.update({ + ...defaultPayload, + issue_number: currentStagingDeployCashIssueNumber, + }); + }) + .then(({data}) => { + // eslint-disable-next-line max-len + console.log(`Successfully ${shouldCreateNewStagingDeployCash ? 'created new' : 'updated'} StagingDeployCash! 🎉 ${data.html_url}`); + return data; + }) + .catch((err) => { + console.error('An unknown error occurred!', err); + core.setFailed(err); + }); +}; - // Find the list of PRs merged between the last StagingDeployCash and the new version - return GitUtils.getPullRequestsMergedBetween(lastTag, newVersion); - }) - .then(PRNumbers => GithubUtils.createNewStagingDeployCash( - `Deploy Checklist: Expensify.cash ${moment().format('YYYY-MM-DD')}`, - newVersion, - _.map(PRNumbers, GithubUtils.getPullRequestURLFromNumber), - )) - .then(({data}) => console.log('Successfully created new StagingDeployCash! 🎉', data.html_url)) - .catch((err) => { - console.error('An error occurred!', err); - core.setFailed(err); - }); +if (require.main === require.cache[eval('__filename')]) { + run(); +} + +module.exports = run; /***/ }), @@ -84,22 +163,22 @@ GithubUtils.getStagingDeployCash() /***/ 669: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { -const {promisify} = __nccwpck_require__(1669); -const exec = promisify(__nccwpck_require__(3129).exec); +const _ = __nccwpck_require__(4987); +const {execSync} = __nccwpck_require__(3129); /** * Takes in two git refs and returns a list of PR numbers of all PRs merged between those two refs * * @param {String} fromRef * @param {String} toRef - * @returns {Promise} + * @returns {Array} */ function getPullRequestsMergedBetween(fromRef, toRef) { - return exec(`git log --format="%s" ${fromRef}...${toRef}`) - .then(({stdout}) => ( - [...stdout.matchAll(/Merge pull request #(\d{1,6}) from (?!Expensify\/(?:master|main|version-))/g)] - .map(match => match[1]) - )); + const localGitLogs = execSync(`git log --format="%s" ${fromRef}...${toRef}`).toString(); + return _.map( + [...localGitLogs.matchAll(/Merge pull request #(\d{1,6}) from (?!Expensify\/(?:master|main|version-))/g)], + match => match[1], + ); } module.exports = { @@ -129,6 +208,7 @@ const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/ const APPLAUSE_BOT = 'applausebot'; const STAGING_DEPLOY_CASH_LABEL = 'StagingDeployCash'; +const DEPLOY_BLOCKER_CASH_LABEL = 'DeployBlockerCash'; class GithubUtils { /** @@ -211,6 +291,7 @@ class GithubUtils { return { title: issue.title, url: issue.url, + number: this.getIssueOrPullRequestNumberFromURL(issue.url), labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), @@ -295,87 +376,9 @@ class GithubUtils { ); } - /** - * Creates a new StagingDeployCash issue. - * - * @param {String} title - * @param {String} tag - * @param {Array} PRList - * @returns {Promise} - */ - static createNewStagingDeployCash(title, tag, PRList) { - return this.generateStagingDeployCashBody(tag, PRList) - .then(body => this.octokit.issues.create({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - labels: [STAGING_DEPLOY_CASH_LABEL], - assignees: [APPLAUSE_BOT], - title, - body, - })); - } - - /** - * Updates the existing open StagingDeployCash issue. - * - * @param {String} [newTag] - * @param {Array} newPRs - * @param {Array} newDeployBlockers - * @returns {Promise} - * @throws {Error} If the StagingDeployCash could not be found or updated. - */ - static updateStagingDeployCash(newTag = '', newPRs, newDeployBlockers) { - let issueNumber; - return this.getStagingDeployCash() - .then(({ - url, - tag: oldTag, - PRList: oldPRs, - deployBlockers: oldDeployBlockers, - }) => { - issueNumber = GithubUtils.getIssueNumberFromURL(url); - - // If we aren't sent a tag, then use the existing tag - const tag = _.isEmpty(newTag) ? oldTag : newTag; - - const PRList = _.sortBy( - _.union(oldPRs, _.map(newPRs, URL => ({ - url: URL, - number: GithubUtils.getPullRequestNumberFromURL(URL), - isVerified: false, - }))), - 'number', - ); - const deployBlockers = _.sortBy( - _.union(oldDeployBlockers, _.map(newDeployBlockers, URL => ({ - url: URL, - number: GithubUtils.getIssueOrPullRequestNumberFromURL(URL), - isResolved: false, - }))), - 'number', - ); - - return this.generateStagingDeployCashBody( - tag, - _.pluck(PRList, 'url'), - _.pluck(_.where(PRList, {isVerified: true}), 'url'), - _.pluck(deployBlockers, 'url'), - _.pluck(_.where(deployBlockers, {isResolved: true}), 'url'), - ); - }) - .then(updatedBody => this.octokit.issues.update({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - issue_number: issueNumber, - body: updatedBody, - })); - } - /** * Generate the issue body for a StagingDeployCash. * - * @private - * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash * @param {Array} [verifiedPRList] - The list of PR URLs which have passed QA. @@ -559,6 +562,8 @@ module.exports = GithubUtils; module.exports.GITHUB_OWNER = GITHUB_OWNER; module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO; module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL; +module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL; +module.exports.APPLAUSE_BOT = APPLAUSE_BOT; /***/ }), diff --git a/.github/actions/getDeployPullRequestList/getDeployPullRequestList.js b/.github/actions/getDeployPullRequestList/getDeployPullRequestList.js index f42fc40be0a5..4a22245f3843 100644 --- a/.github/actions/getDeployPullRequestList/getDeployPullRequestList.js +++ b/.github/actions/getDeployPullRequestList/getDeployPullRequestList.js @@ -55,9 +55,7 @@ getTagsOrReleases(isProductionDeploy) console.log(`Given ${itemToFetch}: ${inputTag}`); console.log(`Prior ${itemToFetch}: ${priorTag}`); - return GitUtils.getPullRequestsMergedBetween(priorTag, inputTag); - }) - .then((pullRequestList) => { + const pullRequestList = GitUtils.getPullRequestsMergedBetween(priorTag, inputTag); console.log(`Found the pull request list: ${pullRequestList}`); return core.setOutput('PR_LIST', pullRequestList); }) diff --git a/.github/actions/getDeployPullRequestList/index.js b/.github/actions/getDeployPullRequestList/index.js index 968e69b41892..78806b702f45 100644 --- a/.github/actions/getDeployPullRequestList/index.js +++ b/.github/actions/getDeployPullRequestList/index.js @@ -65,9 +65,7 @@ getTagsOrReleases(isProductionDeploy) console.log(`Given ${itemToFetch}: ${inputTag}`); console.log(`Prior ${itemToFetch}: ${priorTag}`); - return GitUtils.getPullRequestsMergedBetween(priorTag, inputTag); - }) - .then((pullRequestList) => { + const pullRequestList = GitUtils.getPullRequestsMergedBetween(priorTag, inputTag); console.log(`Found the pull request list: ${pullRequestList}`); return core.setOutput('PR_LIST', pullRequestList); }) @@ -108,22 +106,22 @@ module.exports = { /***/ 669: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { -const {promisify} = __nccwpck_require__(1669); -const exec = promisify(__nccwpck_require__(3129).exec); +const _ = __nccwpck_require__(4987); +const {execSync} = __nccwpck_require__(3129); /** * Takes in two git refs and returns a list of PR numbers of all PRs merged between those two refs * * @param {String} fromRef * @param {String} toRef - * @returns {Promise} + * @returns {Array} */ function getPullRequestsMergedBetween(fromRef, toRef) { - return exec(`git log --format="%s" ${fromRef}...${toRef}`) - .then(({stdout}) => ( - [...stdout.matchAll(/Merge pull request #(\d{1,6}) from (?!Expensify\/(?:master|main|version-))/g)] - .map(match => match[1]) - )); + const localGitLogs = execSync(`git log --format="%s" ${fromRef}...${toRef}`).toString(); + return _.map( + [...localGitLogs.matchAll(/Merge pull request #(\d{1,6}) from (?!Expensify\/(?:master|main|version-))/g)], + match => match[1], + ); } module.exports = { diff --git a/.github/actions/getMergeCommitForPullRequest/index.js b/.github/actions/getMergeCommitForPullRequest/index.js index 5eaa9ed6f954..281144981729 100644 --- a/.github/actions/getMergeCommitForPullRequest/index.js +++ b/.github/actions/getMergeCommitForPullRequest/index.js @@ -131,6 +131,7 @@ const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/ const APPLAUSE_BOT = 'applausebot'; const STAGING_DEPLOY_CASH_LABEL = 'StagingDeployCash'; +const DEPLOY_BLOCKER_CASH_LABEL = 'DeployBlockerCash'; class GithubUtils { /** @@ -213,6 +214,7 @@ class GithubUtils { return { title: issue.title, url: issue.url, + number: this.getIssueOrPullRequestNumberFromURL(issue.url), labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), @@ -297,87 +299,9 @@ class GithubUtils { ); } - /** - * Creates a new StagingDeployCash issue. - * - * @param {String} title - * @param {String} tag - * @param {Array} PRList - * @returns {Promise} - */ - static createNewStagingDeployCash(title, tag, PRList) { - return this.generateStagingDeployCashBody(tag, PRList) - .then(body => this.octokit.issues.create({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - labels: [STAGING_DEPLOY_CASH_LABEL], - assignees: [APPLAUSE_BOT], - title, - body, - })); - } - - /** - * Updates the existing open StagingDeployCash issue. - * - * @param {String} [newTag] - * @param {Array} newPRs - * @param {Array} newDeployBlockers - * @returns {Promise} - * @throws {Error} If the StagingDeployCash could not be found or updated. - */ - static updateStagingDeployCash(newTag = '', newPRs, newDeployBlockers) { - let issueNumber; - return this.getStagingDeployCash() - .then(({ - url, - tag: oldTag, - PRList: oldPRs, - deployBlockers: oldDeployBlockers, - }) => { - issueNumber = GithubUtils.getIssueNumberFromURL(url); - - // If we aren't sent a tag, then use the existing tag - const tag = _.isEmpty(newTag) ? oldTag : newTag; - - const PRList = _.sortBy( - _.union(oldPRs, _.map(newPRs, URL => ({ - url: URL, - number: GithubUtils.getPullRequestNumberFromURL(URL), - isVerified: false, - }))), - 'number', - ); - const deployBlockers = _.sortBy( - _.union(oldDeployBlockers, _.map(newDeployBlockers, URL => ({ - url: URL, - number: GithubUtils.getIssueOrPullRequestNumberFromURL(URL), - isResolved: false, - }))), - 'number', - ); - - return this.generateStagingDeployCashBody( - tag, - _.pluck(PRList, 'url'), - _.pluck(_.where(PRList, {isVerified: true}), 'url'), - _.pluck(deployBlockers, 'url'), - _.pluck(_.where(deployBlockers, {isResolved: true}), 'url'), - ); - }) - .then(updatedBody => this.octokit.issues.update({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - issue_number: issueNumber, - body: updatedBody, - })); - } - /** * Generate the issue body for a StagingDeployCash. * - * @private - * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash * @param {Array} [verifiedPRList] - The list of PR URLs which have passed QA. @@ -561,6 +485,8 @@ module.exports = GithubUtils; module.exports.GITHUB_OWNER = GITHUB_OWNER; module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO; module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL; +module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL; +module.exports.APPLAUSE_BOT = APPLAUSE_BOT; /***/ }), diff --git a/.github/actions/getReleaseBody/index.js b/.github/actions/getReleaseBody/index.js index fc51be4111c5..bd52404c57cc 100644 --- a/.github/actions/getReleaseBody/index.js +++ b/.github/actions/getReleaseBody/index.js @@ -74,6 +74,7 @@ const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/ const APPLAUSE_BOT = 'applausebot'; const STAGING_DEPLOY_CASH_LABEL = 'StagingDeployCash'; +const DEPLOY_BLOCKER_CASH_LABEL = 'DeployBlockerCash'; class GithubUtils { /** @@ -156,6 +157,7 @@ class GithubUtils { return { title: issue.title, url: issue.url, + number: this.getIssueOrPullRequestNumberFromURL(issue.url), labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), @@ -240,87 +242,9 @@ class GithubUtils { ); } - /** - * Creates a new StagingDeployCash issue. - * - * @param {String} title - * @param {String} tag - * @param {Array} PRList - * @returns {Promise} - */ - static createNewStagingDeployCash(title, tag, PRList) { - return this.generateStagingDeployCashBody(tag, PRList) - .then(body => this.octokit.issues.create({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - labels: [STAGING_DEPLOY_CASH_LABEL], - assignees: [APPLAUSE_BOT], - title, - body, - })); - } - - /** - * Updates the existing open StagingDeployCash issue. - * - * @param {String} [newTag] - * @param {Array} newPRs - * @param {Array} newDeployBlockers - * @returns {Promise} - * @throws {Error} If the StagingDeployCash could not be found or updated. - */ - static updateStagingDeployCash(newTag = '', newPRs, newDeployBlockers) { - let issueNumber; - return this.getStagingDeployCash() - .then(({ - url, - tag: oldTag, - PRList: oldPRs, - deployBlockers: oldDeployBlockers, - }) => { - issueNumber = GithubUtils.getIssueNumberFromURL(url); - - // If we aren't sent a tag, then use the existing tag - const tag = _.isEmpty(newTag) ? oldTag : newTag; - - const PRList = _.sortBy( - _.union(oldPRs, _.map(newPRs, URL => ({ - url: URL, - number: GithubUtils.getPullRequestNumberFromURL(URL), - isVerified: false, - }))), - 'number', - ); - const deployBlockers = _.sortBy( - _.union(oldDeployBlockers, _.map(newDeployBlockers, URL => ({ - url: URL, - number: GithubUtils.getIssueOrPullRequestNumberFromURL(URL), - isResolved: false, - }))), - 'number', - ); - - return this.generateStagingDeployCashBody( - tag, - _.pluck(PRList, 'url'), - _.pluck(_.where(PRList, {isVerified: true}), 'url'), - _.pluck(deployBlockers, 'url'), - _.pluck(_.where(deployBlockers, {isResolved: true}), 'url'), - ); - }) - .then(updatedBody => this.octokit.issues.update({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - issue_number: issueNumber, - body: updatedBody, - })); - } - /** * Generate the issue body for a StagingDeployCash. * - * @private - * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash * @param {Array} [verifiedPRList] - The list of PR URLs which have passed QA. @@ -504,6 +428,8 @@ module.exports = GithubUtils; module.exports.GITHUB_OWNER = GITHUB_OWNER; module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO; module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL; +module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL; +module.exports.APPLAUSE_BOT = APPLAUSE_BOT; /***/ }), diff --git a/.github/actions/isPullRequestMergeable/index.js b/.github/actions/isPullRequestMergeable/index.js index 4690716d5f62..8abd0df83937 100644 --- a/.github/actions/isPullRequestMergeable/index.js +++ b/.github/actions/isPullRequestMergeable/index.js @@ -77,6 +77,7 @@ const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/ const APPLAUSE_BOT = 'applausebot'; const STAGING_DEPLOY_CASH_LABEL = 'StagingDeployCash'; +const DEPLOY_BLOCKER_CASH_LABEL = 'DeployBlockerCash'; class GithubUtils { /** @@ -159,6 +160,7 @@ class GithubUtils { return { title: issue.title, url: issue.url, + number: this.getIssueOrPullRequestNumberFromURL(issue.url), labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), @@ -243,87 +245,9 @@ class GithubUtils { ); } - /** - * Creates a new StagingDeployCash issue. - * - * @param {String} title - * @param {String} tag - * @param {Array} PRList - * @returns {Promise} - */ - static createNewStagingDeployCash(title, tag, PRList) { - return this.generateStagingDeployCashBody(tag, PRList) - .then(body => this.octokit.issues.create({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - labels: [STAGING_DEPLOY_CASH_LABEL], - assignees: [APPLAUSE_BOT], - title, - body, - })); - } - - /** - * Updates the existing open StagingDeployCash issue. - * - * @param {String} [newTag] - * @param {Array} newPRs - * @param {Array} newDeployBlockers - * @returns {Promise} - * @throws {Error} If the StagingDeployCash could not be found or updated. - */ - static updateStagingDeployCash(newTag = '', newPRs, newDeployBlockers) { - let issueNumber; - return this.getStagingDeployCash() - .then(({ - url, - tag: oldTag, - PRList: oldPRs, - deployBlockers: oldDeployBlockers, - }) => { - issueNumber = GithubUtils.getIssueNumberFromURL(url); - - // If we aren't sent a tag, then use the existing tag - const tag = _.isEmpty(newTag) ? oldTag : newTag; - - const PRList = _.sortBy( - _.union(oldPRs, _.map(newPRs, URL => ({ - url: URL, - number: GithubUtils.getPullRequestNumberFromURL(URL), - isVerified: false, - }))), - 'number', - ); - const deployBlockers = _.sortBy( - _.union(oldDeployBlockers, _.map(newDeployBlockers, URL => ({ - url: URL, - number: GithubUtils.getIssueOrPullRequestNumberFromURL(URL), - isResolved: false, - }))), - 'number', - ); - - return this.generateStagingDeployCashBody( - tag, - _.pluck(PRList, 'url'), - _.pluck(_.where(PRList, {isVerified: true}), 'url'), - _.pluck(deployBlockers, 'url'), - _.pluck(_.where(deployBlockers, {isResolved: true}), 'url'), - ); - }) - .then(updatedBody => this.octokit.issues.update({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - issue_number: issueNumber, - body: updatedBody, - })); - } - /** * Generate the issue body for a StagingDeployCash. * - * @private - * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash * @param {Array} [verifiedPRList] - The list of PR URLs which have passed QA. @@ -507,6 +431,8 @@ module.exports = GithubUtils; module.exports.GITHUB_OWNER = GITHUB_OWNER; module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO; module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL; +module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL; +module.exports.APPLAUSE_BOT = APPLAUSE_BOT; /***/ }), diff --git a/.github/actions/isStagingDeployLocked/index.js b/.github/actions/isStagingDeployLocked/index.js index 8c8653bf24ec..d85fc16db06f 100644 --- a/.github/actions/isStagingDeployLocked/index.js +++ b/.github/actions/isStagingDeployLocked/index.js @@ -53,6 +53,7 @@ const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/ const APPLAUSE_BOT = 'applausebot'; const STAGING_DEPLOY_CASH_LABEL = 'StagingDeployCash'; +const DEPLOY_BLOCKER_CASH_LABEL = 'DeployBlockerCash'; class GithubUtils { /** @@ -135,6 +136,7 @@ class GithubUtils { return { title: issue.title, url: issue.url, + number: this.getIssueOrPullRequestNumberFromURL(issue.url), labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), @@ -219,87 +221,9 @@ class GithubUtils { ); } - /** - * Creates a new StagingDeployCash issue. - * - * @param {String} title - * @param {String} tag - * @param {Array} PRList - * @returns {Promise} - */ - static createNewStagingDeployCash(title, tag, PRList) { - return this.generateStagingDeployCashBody(tag, PRList) - .then(body => this.octokit.issues.create({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - labels: [STAGING_DEPLOY_CASH_LABEL], - assignees: [APPLAUSE_BOT], - title, - body, - })); - } - - /** - * Updates the existing open StagingDeployCash issue. - * - * @param {String} [newTag] - * @param {Array} newPRs - * @param {Array} newDeployBlockers - * @returns {Promise} - * @throws {Error} If the StagingDeployCash could not be found or updated. - */ - static updateStagingDeployCash(newTag = '', newPRs, newDeployBlockers) { - let issueNumber; - return this.getStagingDeployCash() - .then(({ - url, - tag: oldTag, - PRList: oldPRs, - deployBlockers: oldDeployBlockers, - }) => { - issueNumber = GithubUtils.getIssueNumberFromURL(url); - - // If we aren't sent a tag, then use the existing tag - const tag = _.isEmpty(newTag) ? oldTag : newTag; - - const PRList = _.sortBy( - _.union(oldPRs, _.map(newPRs, URL => ({ - url: URL, - number: GithubUtils.getPullRequestNumberFromURL(URL), - isVerified: false, - }))), - 'number', - ); - const deployBlockers = _.sortBy( - _.union(oldDeployBlockers, _.map(newDeployBlockers, URL => ({ - url: URL, - number: GithubUtils.getIssueOrPullRequestNumberFromURL(URL), - isResolved: false, - }))), - 'number', - ); - - return this.generateStagingDeployCashBody( - tag, - _.pluck(PRList, 'url'), - _.pluck(_.where(PRList, {isVerified: true}), 'url'), - _.pluck(deployBlockers, 'url'), - _.pluck(_.where(deployBlockers, {isResolved: true}), 'url'), - ); - }) - .then(updatedBody => this.octokit.issues.update({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - issue_number: issueNumber, - body: updatedBody, - })); - } - /** * Generate the issue body for a StagingDeployCash. * - * @private - * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash * @param {Array} [verifiedPRList] - The list of PR URLs which have passed QA. @@ -483,6 +407,8 @@ module.exports = GithubUtils; module.exports.GITHUB_OWNER = GITHUB_OWNER; module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO; module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL; +module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL; +module.exports.APPLAUSE_BOT = APPLAUSE_BOT; /***/ }), diff --git a/.github/actions/markPullRequestsAsDeployed/index.js b/.github/actions/markPullRequestsAsDeployed/index.js index 670259f52cfc..f7206fc4fbcf 100644 --- a/.github/actions/markPullRequestsAsDeployed/index.js +++ b/.github/actions/markPullRequestsAsDeployed/index.js @@ -127,6 +127,7 @@ const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/ const APPLAUSE_BOT = 'applausebot'; const STAGING_DEPLOY_CASH_LABEL = 'StagingDeployCash'; +const DEPLOY_BLOCKER_CASH_LABEL = 'DeployBlockerCash'; class GithubUtils { /** @@ -209,6 +210,7 @@ class GithubUtils { return { title: issue.title, url: issue.url, + number: this.getIssueOrPullRequestNumberFromURL(issue.url), labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), @@ -293,87 +295,9 @@ class GithubUtils { ); } - /** - * Creates a new StagingDeployCash issue. - * - * @param {String} title - * @param {String} tag - * @param {Array} PRList - * @returns {Promise} - */ - static createNewStagingDeployCash(title, tag, PRList) { - return this.generateStagingDeployCashBody(tag, PRList) - .then(body => this.octokit.issues.create({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - labels: [STAGING_DEPLOY_CASH_LABEL], - assignees: [APPLAUSE_BOT], - title, - body, - })); - } - - /** - * Updates the existing open StagingDeployCash issue. - * - * @param {String} [newTag] - * @param {Array} newPRs - * @param {Array} newDeployBlockers - * @returns {Promise} - * @throws {Error} If the StagingDeployCash could not be found or updated. - */ - static updateStagingDeployCash(newTag = '', newPRs, newDeployBlockers) { - let issueNumber; - return this.getStagingDeployCash() - .then(({ - url, - tag: oldTag, - PRList: oldPRs, - deployBlockers: oldDeployBlockers, - }) => { - issueNumber = GithubUtils.getIssueNumberFromURL(url); - - // If we aren't sent a tag, then use the existing tag - const tag = _.isEmpty(newTag) ? oldTag : newTag; - - const PRList = _.sortBy( - _.union(oldPRs, _.map(newPRs, URL => ({ - url: URL, - number: GithubUtils.getPullRequestNumberFromURL(URL), - isVerified: false, - }))), - 'number', - ); - const deployBlockers = _.sortBy( - _.union(oldDeployBlockers, _.map(newDeployBlockers, URL => ({ - url: URL, - number: GithubUtils.getIssueOrPullRequestNumberFromURL(URL), - isResolved: false, - }))), - 'number', - ); - - return this.generateStagingDeployCashBody( - tag, - _.pluck(PRList, 'url'), - _.pluck(_.where(PRList, {isVerified: true}), 'url'), - _.pluck(deployBlockers, 'url'), - _.pluck(_.where(deployBlockers, {isResolved: true}), 'url'), - ); - }) - .then(updatedBody => this.octokit.issues.update({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - issue_number: issueNumber, - body: updatedBody, - })); - } - /** * Generate the issue body for a StagingDeployCash. * - * @private - * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash * @param {Array} [verifiedPRList] - The list of PR URLs which have passed QA. @@ -557,6 +481,8 @@ module.exports = GithubUtils; module.exports.GITHUB_OWNER = GITHUB_OWNER; module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO; module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL; +module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL; +module.exports.APPLAUSE_BOT = APPLAUSE_BOT; /***/ }), diff --git a/.github/actions/reopenIssueWithComment/index.js b/.github/actions/reopenIssueWithComment/index.js index 66f41eda7e97..c0e4bd2822ba 100644 --- a/.github/actions/reopenIssueWithComment/index.js +++ b/.github/actions/reopenIssueWithComment/index.js @@ -66,6 +66,7 @@ const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/ const APPLAUSE_BOT = 'applausebot'; const STAGING_DEPLOY_CASH_LABEL = 'StagingDeployCash'; +const DEPLOY_BLOCKER_CASH_LABEL = 'DeployBlockerCash'; class GithubUtils { /** @@ -148,6 +149,7 @@ class GithubUtils { return { title: issue.title, url: issue.url, + number: this.getIssueOrPullRequestNumberFromURL(issue.url), labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), @@ -232,87 +234,9 @@ class GithubUtils { ); } - /** - * Creates a new StagingDeployCash issue. - * - * @param {String} title - * @param {String} tag - * @param {Array} PRList - * @returns {Promise} - */ - static createNewStagingDeployCash(title, tag, PRList) { - return this.generateStagingDeployCashBody(tag, PRList) - .then(body => this.octokit.issues.create({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - labels: [STAGING_DEPLOY_CASH_LABEL], - assignees: [APPLAUSE_BOT], - title, - body, - })); - } - - /** - * Updates the existing open StagingDeployCash issue. - * - * @param {String} [newTag] - * @param {Array} newPRs - * @param {Array} newDeployBlockers - * @returns {Promise} - * @throws {Error} If the StagingDeployCash could not be found or updated. - */ - static updateStagingDeployCash(newTag = '', newPRs, newDeployBlockers) { - let issueNumber; - return this.getStagingDeployCash() - .then(({ - url, - tag: oldTag, - PRList: oldPRs, - deployBlockers: oldDeployBlockers, - }) => { - issueNumber = GithubUtils.getIssueNumberFromURL(url); - - // If we aren't sent a tag, then use the existing tag - const tag = _.isEmpty(newTag) ? oldTag : newTag; - - const PRList = _.sortBy( - _.union(oldPRs, _.map(newPRs, URL => ({ - url: URL, - number: GithubUtils.getPullRequestNumberFromURL(URL), - isVerified: false, - }))), - 'number', - ); - const deployBlockers = _.sortBy( - _.union(oldDeployBlockers, _.map(newDeployBlockers, URL => ({ - url: URL, - number: GithubUtils.getIssueOrPullRequestNumberFromURL(URL), - isResolved: false, - }))), - 'number', - ); - - return this.generateStagingDeployCashBody( - tag, - _.pluck(PRList, 'url'), - _.pluck(_.where(PRList, {isVerified: true}), 'url'), - _.pluck(deployBlockers, 'url'), - _.pluck(_.where(deployBlockers, {isResolved: true}), 'url'), - ); - }) - .then(updatedBody => this.octokit.issues.update({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - issue_number: issueNumber, - body: updatedBody, - })); - } - /** * Generate the issue body for a StagingDeployCash. * - * @private - * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash * @param {Array} [verifiedPRList] - The list of PR URLs which have passed QA. @@ -496,6 +420,8 @@ module.exports = GithubUtils; module.exports.GITHUB_OWNER = GITHUB_OWNER; module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO; module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL; +module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL; +module.exports.APPLAUSE_BOT = APPLAUSE_BOT; /***/ }), diff --git a/.github/actions/triggerWorkflowAndWait/index.js b/.github/actions/triggerWorkflowAndWait/index.js index 5709b89cc580..f592f53a454b 100644 --- a/.github/actions/triggerWorkflowAndWait/index.js +++ b/.github/actions/triggerWorkflowAndWait/index.js @@ -217,6 +217,7 @@ const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/ const APPLAUSE_BOT = 'applausebot'; const STAGING_DEPLOY_CASH_LABEL = 'StagingDeployCash'; +const DEPLOY_BLOCKER_CASH_LABEL = 'DeployBlockerCash'; class GithubUtils { /** @@ -299,6 +300,7 @@ class GithubUtils { return { title: issue.title, url: issue.url, + number: this.getIssueOrPullRequestNumberFromURL(issue.url), labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), @@ -383,87 +385,9 @@ class GithubUtils { ); } - /** - * Creates a new StagingDeployCash issue. - * - * @param {String} title - * @param {String} tag - * @param {Array} PRList - * @returns {Promise} - */ - static createNewStagingDeployCash(title, tag, PRList) { - return this.generateStagingDeployCashBody(tag, PRList) - .then(body => this.octokit.issues.create({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - labels: [STAGING_DEPLOY_CASH_LABEL], - assignees: [APPLAUSE_BOT], - title, - body, - })); - } - - /** - * Updates the existing open StagingDeployCash issue. - * - * @param {String} [newTag] - * @param {Array} newPRs - * @param {Array} newDeployBlockers - * @returns {Promise} - * @throws {Error} If the StagingDeployCash could not be found or updated. - */ - static updateStagingDeployCash(newTag = '', newPRs, newDeployBlockers) { - let issueNumber; - return this.getStagingDeployCash() - .then(({ - url, - tag: oldTag, - PRList: oldPRs, - deployBlockers: oldDeployBlockers, - }) => { - issueNumber = GithubUtils.getIssueNumberFromURL(url); - - // If we aren't sent a tag, then use the existing tag - const tag = _.isEmpty(newTag) ? oldTag : newTag; - - const PRList = _.sortBy( - _.union(oldPRs, _.map(newPRs, URL => ({ - url: URL, - number: GithubUtils.getPullRequestNumberFromURL(URL), - isVerified: false, - }))), - 'number', - ); - const deployBlockers = _.sortBy( - _.union(oldDeployBlockers, _.map(newDeployBlockers, URL => ({ - url: URL, - number: GithubUtils.getIssueOrPullRequestNumberFromURL(URL), - isResolved: false, - }))), - 'number', - ); - - return this.generateStagingDeployCashBody( - tag, - _.pluck(PRList, 'url'), - _.pluck(_.where(PRList, {isVerified: true}), 'url'), - _.pluck(deployBlockers, 'url'), - _.pluck(_.where(deployBlockers, {isResolved: true}), 'url'), - ); - }) - .then(updatedBody => this.octokit.issues.update({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - issue_number: issueNumber, - body: updatedBody, - })); - } - /** * Generate the issue body for a StagingDeployCash. * - * @private - * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash * @param {Array} [verifiedPRList] - The list of PR URLs which have passed QA. @@ -647,6 +571,8 @@ module.exports = GithubUtils; module.exports.GITHUB_OWNER = GITHUB_OWNER; module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO; module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL; +module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL; +module.exports.APPLAUSE_BOT = APPLAUSE_BOT; /***/ }), diff --git a/.github/libs/GitUtils.js b/.github/libs/GitUtils.js index 6c7110c07074..40c9000a54df 100644 --- a/.github/libs/GitUtils.js +++ b/.github/libs/GitUtils.js @@ -1,19 +1,19 @@ -const {promisify} = require('util'); -const exec = promisify(require('child_process').exec); +const _ = require('underscore'); +const {execSync} = require('child_process'); /** * Takes in two git refs and returns a list of PR numbers of all PRs merged between those two refs * * @param {String} fromRef * @param {String} toRef - * @returns {Promise} + * @returns {Array} */ function getPullRequestsMergedBetween(fromRef, toRef) { - return exec(`git log --format="%s" ${fromRef}...${toRef}`) - .then(({stdout}) => ( - [...stdout.matchAll(/Merge pull request #(\d{1,6}) from (?!Expensify\/(?:master|main|version-))/g)] - .map(match => match[1]) - )); + const localGitLogs = execSync(`git log --format="%s" ${fromRef}...${toRef}`).toString(); + return _.map( + [...localGitLogs.matchAll(/Merge pull request #(\d{1,6}) from (?!Expensify\/(?:master|main|version-))/g)], + match => match[1], + ); } module.exports = { diff --git a/.github/libs/GithubUtils.js b/.github/libs/GithubUtils.js index d476497e0c34..d8cda484ba70 100644 --- a/.github/libs/GithubUtils.js +++ b/.github/libs/GithubUtils.js @@ -15,6 +15,7 @@ const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/ const APPLAUSE_BOT = 'applausebot'; const STAGING_DEPLOY_CASH_LABEL = 'StagingDeployCash'; +const DEPLOY_BLOCKER_CASH_LABEL = 'DeployBlockerCash'; class GithubUtils { /** @@ -97,6 +98,7 @@ class GithubUtils { return { title: issue.title, url: issue.url, + number: this.getIssueOrPullRequestNumberFromURL(issue.url), labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), @@ -181,87 +183,9 @@ class GithubUtils { ); } - /** - * Creates a new StagingDeployCash issue. - * - * @param {String} title - * @param {String} tag - * @param {Array} PRList - * @returns {Promise} - */ - static createNewStagingDeployCash(title, tag, PRList) { - return this.generateStagingDeployCashBody(tag, PRList) - .then(body => this.octokit.issues.create({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - labels: [STAGING_DEPLOY_CASH_LABEL], - assignees: [APPLAUSE_BOT], - title, - body, - })); - } - - /** - * Updates the existing open StagingDeployCash issue. - * - * @param {String} [newTag] - * @param {Array} newPRs - * @param {Array} newDeployBlockers - * @returns {Promise} - * @throws {Error} If the StagingDeployCash could not be found or updated. - */ - static updateStagingDeployCash(newTag = '', newPRs, newDeployBlockers) { - let issueNumber; - return this.getStagingDeployCash() - .then(({ - url, - tag: oldTag, - PRList: oldPRs, - deployBlockers: oldDeployBlockers, - }) => { - issueNumber = GithubUtils.getIssueNumberFromURL(url); - - // If we aren't sent a tag, then use the existing tag - const tag = _.isEmpty(newTag) ? oldTag : newTag; - - const PRList = _.sortBy( - _.union(oldPRs, _.map(newPRs, URL => ({ - url: URL, - number: GithubUtils.getPullRequestNumberFromURL(URL), - isVerified: false, - }))), - 'number', - ); - const deployBlockers = _.sortBy( - _.union(oldDeployBlockers, _.map(newDeployBlockers, URL => ({ - url: URL, - number: GithubUtils.getIssueOrPullRequestNumberFromURL(URL), - isResolved: false, - }))), - 'number', - ); - - return this.generateStagingDeployCashBody( - tag, - _.pluck(PRList, 'url'), - _.pluck(_.where(PRList, {isVerified: true}), 'url'), - _.pluck(deployBlockers, 'url'), - _.pluck(_.where(deployBlockers, {isResolved: true}), 'url'), - ); - }) - .then(updatedBody => this.octokit.issues.update({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - issue_number: issueNumber, - body: updatedBody, - })); - } - /** * Generate the issue body for a StagingDeployCash. * - * @private - * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash * @param {Array} [verifiedPRList] - The list of PR URLs which have passed QA. @@ -445,3 +369,5 @@ module.exports = GithubUtils; module.exports.GITHUB_OWNER = GITHUB_OWNER; module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO; module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL; +module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL; +module.exports.APPLAUSE_BOT = APPLAUSE_BOT; diff --git a/.github/workflows/deployBlocker.yml b/.github/workflows/deployBlocker.yml index 8b91eb4506be..095e8d6d7301 100644 --- a/.github/workflows/deployBlocker.yml +++ b/.github/workflows/deployBlocker.yml @@ -32,7 +32,6 @@ jobs: uses: Expensify/Expensify.cash/.github/actions/createOrUpdateStagingDeploy@main with: GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} - NEW_DEPLOY_BLOCKERS: ${{ env.DEPLOY_BLOCKER_URL }} - name: Give the issue/PR the Hourly, Engineering labels uses: andymckay/labeler@978f846c4ca6299fd136f465b42c5e87aca28cac diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml index cf83ea9127c0..aa96abf9d3d9 100644 --- a/.github/workflows/preDeploy.yml +++ b/.github/workflows/preDeploy.yml @@ -110,7 +110,6 @@ jobs: with: GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} NPM_VERSION: ${{ env.NEW_VERSION }} - NEW_PULL_REQUESTS: https://github.com/Expensify/Expensify.cash/pull/${{ needs.chooseDeployActions.outputs.mergedPullRequest }} # This Slack step is duplicated in all workflows, if you make a change to this step, make sure to update all # the other workflows with the same change diff --git a/tests/unit/GitUtilsTest.js b/tests/unit/GitUtilsTest.js index 77845a4c31c3..623c6c25e24d 100644 --- a/tests/unit/GitUtilsTest.js +++ b/tests/unit/GitUtilsTest.js @@ -2,8 +2,7 @@ const childProcess = require('child_process'); const GitUtils = require('../../.github/libs/GitUtils'); jest.mock('child_process'); -jest.mock('util', () => ({promisify: fn => fn})); -const {exec} = childProcess; +const {execSync} = childProcess; const data = [ { @@ -51,9 +50,9 @@ const data = [ describe('GitUtils', () => { describe.each(data)('getPullRequestsMergedBetween', (exampleCase) => { test('getPullRequestsMergedBetween', () => { - exec.mockResolvedValue({stdout: exampleCase.gitLog, stderr: ''}); - return GitUtils.getPullRequestsMergedBetween('testRef1', 'testRef2') - .then(pullRequestNumbers => expect(pullRequestNumbers).toStrictEqual(exampleCase.result)); + execSync.mockReturnValueOnce(exampleCase.gitLog); + const result = GitUtils.getPullRequestsMergedBetween('testRef1', 'testRef2'); + expect(result).toStrictEqual(exampleCase.result); }); }); }); diff --git a/tests/unit/GithubUtilsTest.js b/tests/unit/GithubUtilsTest.js index d1764089845e..660653266413 100644 --- a/tests/unit/GithubUtilsTest.js +++ b/tests/unit/GithubUtilsTest.js @@ -63,6 +63,7 @@ describe('GithubUtils', () => { tag: '1.0.1-47', title: 'Andrew Test Issue', url: 'https://api.github.com/repos/Andrew-Test-Org/Public-Test-Repo/issues/29', + number: 29, deployBlockers: [], }; const expectedResponseWithDeployBlockers = {...baseExpectedResponse}; @@ -144,103 +145,6 @@ describe('GithubUtils', () => { }); }); - describe('updateStagingDeployCash', () => { - test('successfully updates issue', () => { - const issueBefore = { - url: 'https://api.github.com/repos/Expensify/Expensify.cash/issues/29', - title: 'Test StagingDeployCash', - labels: [ - { - id: 2783847782, - node_id: 'MDU6TGFiZWwyNzgzODQ3Nzgy', - url: 'https://api.github.com/repos/Expensify/Expensify.cash/labels/StagingDeployCash', - name: 'StagingDeployCash', - color: '6FC269', - default: false, - description: '', - }, - ], - // eslint-disable-next-line max-len - body: '**Release Version:** `1.0.1-47`\r\n**Compare Changes:** https://github.com/Expensify/Expensify.cash/compare/production...staging\r\n\r\n**This release contains changes from the following pull requests:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/21\r\n- [x] https://github.com/Expensify/Expensify.cash/pull/22\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/23\r\n\r\n**Deploy Blockers:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/issues/1\r\n- [x] https://github.com/Expensify/Expensify.cash/issues/2\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/1234\r\n', - }; - - GithubUtils.octokit.repos.listTags = jest.fn().mockResolvedValue({ - data: [ - {name: '1.0.1-0'}, - {name: '1.0.1-47'}, - {name: '1.0.1-48'}, - ], - }); - GithubUtils.octokit.issues.listForRepo = jest.fn().mockResolvedValue({data: [issueBefore]}); - GithubUtils.octokit.issues.update = jest.fn().mockImplementation(arg => Promise.resolve(arg)); - - return GithubUtils.updateStagingDeployCash( - '1.0.1-48', - [ - 'https://github.com/Expensify/Expensify.cash/pull/24', - 'https://github.com/Expensify/Expensify.cash/pull/25', - ], - [ - 'https://github.com/Expensify/Expensify.cash/issues/3', - 'https://github.com/Expensify/Expensify.cash/pull/4321', - ], - ).then((result) => { - expect(result).toStrictEqual({ - owner: GithubUtils.GITHUB_OWNER, - repo: GithubUtils.EXPENSIFY_CASH_REPO, - issue_number: 29, - // eslint-disable-next-line max-len - body: '**Release Version:** `1.0.1-48`\r\n**Compare Changes:** https://github.com/Expensify/Expensify.cash/compare/production...staging\r\n\r\n**This release contains changes from the following pull requests:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/21\r\n- [x] https://github.com/Expensify/Expensify.cash/pull/22\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/23\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/24\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/25\r\n\r\n**Deploy Blockers:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/issues/1\r\n- [x] https://github.com/Expensify/Expensify.cash/issues/2\r\n- [ ] https://github.com/Expensify/Expensify.cash/issues/3\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/1234\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/4321\r\n\r\ncc @Expensify/applauseleads\r\n', - }); - }); - }); - - test('updates issue with just deploy blockers', () => { - const issueBefore = { - url: 'https://api.github.com/repos/Expensify/Expensify.cash/issues/29', - title: 'Test StagingDeployCash', - labels: [ - { - id: 2783847782, - node_id: 'MDU6TGFiZWwyNzgzODQ3Nzgy', - url: 'https://api.github.com/repos/Expensify/Expensify.cash/labels/StagingDeployCash', - name: 'StagingDeployCash', - color: '6FC269', - default: false, - description: '', - }, - ], - // eslint-disable-next-line max-len - body: '**Release Version:** `1.0.1-47`\r\n**Compare Changes:** https://github.com/Expensify/Expensify.cash/compare/production...staging\r\n\r\n**This release contains changes from the following pull requests:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/21\r\n- [x] https://github.com/Expensify/Expensify.cash/pull/22\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/23\r\n\r\n', - }; - - GithubUtils.octokit.repos.listTags = jest.fn().mockResolvedValue({ - data: [ - {name: '1.0.1-0'}, - {name: '1.0.1-47'}, - {name: '1.0.1-48'}, - ], - }); - GithubUtils.octokit.issues.listForRepo = jest.fn().mockResolvedValue({data: [issueBefore]}); - GithubUtils.octokit.issues.update = jest.fn().mockImplementation(arg => Promise.resolve(arg)); - - return GithubUtils.updateStagingDeployCash( - undefined, - undefined, - ['https://github.com/Expensify/Expensify.cash/pull/24', - 'https://github.com/Expensify/Expensify.cash/issues/25'], - ).then((result) => { - expect(result).toStrictEqual({ - owner: GithubUtils.GITHUB_OWNER, - repo: GithubUtils.EXPENSIFY_CASH_REPO, - issue_number: 29, - // eslint-disable-next-line max-len - body: '**Release Version:** `1.0.1-47`\r\n**Compare Changes:** https://github.com/Expensify/Expensify.cash/compare/production...staging\r\n\r\n**This release contains changes from the following pull requests:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/21\r\n- [x] https://github.com/Expensify/Expensify.cash/pull/22\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/23\r\n\r\n**Deploy Blockers:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/24\r\n- [ ] https://github.com/Expensify/Expensify.cash/issues/25\r\n\r\ncc @Expensify/applauseleads\r\n', - }); - }); - }); - }); - describe('getPullRequestNumberFromURL', () => { describe('valid pull requests', () => { test.each([ @@ -324,34 +228,6 @@ describe('GithubUtils', () => { }); }); - describe('createNewStagingDeployCash', () => { - test('Issue is successfully created', () => { - GithubUtils.octokit.repos.listTags = jest.fn().mockResolvedValue({data: [{name: '1.0.2-0'}]}); - GithubUtils.octokit.issues.create = jest.fn().mockImplementation(arg => Promise.resolve(arg)); - - const title = 'Test StagingDeployCash title'; - const tag = '1.0.2-12'; - const PRList = [ - 'https://github.com/Expensify/Expensify.cash/pull/2', - 'https://github.com/Expensify/Expensify.cash/pull/3', - 'https://github.com/Expensify/Expensify.cash/pull/3', - 'https://github.com/Expensify/Expensify.cash/pull/1', - ]; - return GithubUtils.createNewStagingDeployCash(title, tag, PRList) - .then((newIssue) => { - expect(newIssue).toStrictEqual({ - owner: GithubUtils.GITHUB_OWNER, - repo: GithubUtils.EXPENSIFY_CASH_REPO, - labels: ['StagingDeployCash'], - assignees: ['applausebot'], - title, - // eslint-disable-next-line max-len - body: `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/Expensify.cash/compare/production...staging\r\n\r\n**This release contains changes from the following pull requests:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/1\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/2\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/3\r\n\r\ncc @Expensify/applauseleads\r\n`, - }); - }); - }); - }); - describe('generateStagingDeployCashBody', () => { const mockTags = [{name: '1.0.2-0'}, {name: '1.0.2-12'}]; const mockPRs = [ diff --git a/tests/unit/createOrUpdateStagingDeployTest.js b/tests/unit/createOrUpdateStagingDeployTest.js new file mode 100644 index 000000000000..d19cc6a1c570 --- /dev/null +++ b/tests/unit/createOrUpdateStagingDeployTest.js @@ -0,0 +1,287 @@ +/** + * @jest-environment node + */ +const core = require('@actions/core'); +const moment = require('moment'); +const GitUtils = require('../../.github/libs/GitUtils'); +const GithubUtils = require('../../.github/libs/GithubUtils'); +const run = require('../../.github/actions/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy'); + +const mockGetInput = jest.fn(); +const mockListIssues = jest.fn(); +const mockGetPullRequestsMergedBetween = jest.fn(); + +beforeAll(() => { + // Mock core module + core.getInput = mockGetInput; + + // Mock octokit module + const mocktokit = { + issues: { + create: jest.fn().mockImplementation(arg => Promise.resolve({ + data: { + ...arg, + html_url: 'https://github.com/Expensify/Expensify.cash/issues/29', + }, + })), + update: jest.fn().mockImplementation(arg => Promise.resolve({ + data: { + ...arg, + html_url: `https://github.com/Expensify/Expensify.cash/issues/${arg.issue_number}`, + }, + })), + listForRepo: mockListIssues, + }, + pulls: { + // Static mock for pulls.list (only used to filter out automated PRs, and that functionality is covered + // in the test for GithubUtils.generateStagingDeployCashBody + list: jest.fn().mockResolvedValue([]), + }, + }; + GithubUtils.octokitInternal = mocktokit; + + // Mock GitUtils + GitUtils.getPullRequestsMergedBetween = mockGetPullRequestsMergedBetween; +}); + +afterEach(() => { + mockGetInput.mockClear(); + mockListIssues.mockClear(); + mockGetPullRequestsMergedBetween.mockClear(); +}); + +afterAll(() => { + jest.clearAllMocks(); +}); + +const LABELS = { + STAGING_DEPLOY_CASH: { + id: 2783847782, + node_id: 'MDU6TGFiZWwyNzgzODQ3Nzgy', + url: 'https://api.github.com/repos/Expensify/Expensify.cash/labels/StagingDeployCash', + name: GithubUtils.STAGING_DEPLOY_CASH_LABEL, + color: '6FC269', + default: false, + description: '', + }, + DEPLOY_BLOCKER_CASH: { + id: 2810597462, + node_id: 'MDU6TGFiZWwyODEwNTk3NDYy', + url: 'https://api.github.com/repos/Expensify/Expensify.cash/labels/DeployBlockerCash', + name: GithubUtils.DEPLOY_BLOCKER_CASH_LABEL, + color: '000000', + default: false, + description: 'This issue or pull request should block deployment', + }, +}; + +describe('createOrUpdateStagingDeployCash', () => { + const closedStagingDeployCash = { + url: 'https://api.github.com/repos/Expensify/Expensify.cash/issues/28', + title: 'Test StagingDeployCash', + number: 28, + labels: [LABELS.STAGING_DEPLOY_CASH], + html_url: 'https://github.com/Expensify/Expensify.cash/issues/29', + // eslint-disable-next-line max-len + body: '**Release Version:** `1.0.1-0`\r\n**Compare Changes:** https://github.com/Expensify/Expensify.cash/compare/production...staging\r\n\r\n**This release contains changes from the following pull requests:**\r\n- [x] https://github.com/Expensify/Expensify.cash/pull/1\r\n- [x] https://github.com/Expensify/Expensify.cash/pull/2\r\n- [x] https://github.com/Expensify/Expensify.cash/pull/3\r\n\r\n**Deploy Blockers:**\r\n- [x] https://github.com/Expensify/Expensify.cash/pull/1\r\n- [x] https://github.com/Expensify/Expensify.cash/issues/4\r\n- [x] https://github.com/Expensify/Expensify.cash/issues/5\r\n\r\ncc @Expensify/applauseleads\r\n', + state: 'closed', + }; + + const baseNewPullRequests = [ + 'https://github.com/Expensify/Expensify.cash/pull/6', + 'https://github.com/Expensify/Expensify.cash/pull/7', + 'https://github.com/Expensify/Expensify.cash/pull/8', + ]; + + test('creates new issue when there is none open', () => { + mockGetInput.mockImplementation((arg) => { + if (arg === 'GITHUB_TOKEN') { + return 'fake_token'; + } + + if (arg === 'NPM_VERSION') { + return '1.0.2-1'; + } + }); + + mockGetPullRequestsMergedBetween.mockImplementation((fromRef, toRef) => { + if (fromRef === '1.0.1-0' && toRef === '1.0.2-1') { + return [ + ...baseNewPullRequests, + ]; + } + return []; + }); + + mockListIssues.mockImplementation((args) => { + if (args.labels === GithubUtils.STAGING_DEPLOY_CASH_LABEL) { + return {data: [closedStagingDeployCash]}; + } + + return {data: []}; + }); + + return run().then((result) => { + expect(result).toStrictEqual({ + owner: GithubUtils.GITHUB_OWNER, + repo: GithubUtils.EXPENSIFY_CASH_REPO, + title: `Deploy Checklist: Expensify.cash ${moment().format('YYYY-MM-DD')}`, + labels: [GithubUtils.STAGING_DEPLOY_CASH_LABEL], + html_url: 'https://github.com/Expensify/Expensify.cash/issues/29', + assignees: [GithubUtils.APPLAUSE_BOT], + // eslint-disable-next-line max-len + body: '**Release Version:** `1.0.2-1`\r\n**Compare Changes:** https://github.com/Expensify/Expensify.cash/compare/production...staging\r\n\r\n**This release contains changes from the following pull requests:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/6\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/7\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/8\r\n\r\ncc @Expensify/applauseleads\r\n', + }); + }); + }); + + describe('updates existing issue when there is one open', () => { + const openStagingDeployCashBefore = { + url: 'https://api.github.com/repos/Expensify/Expensify.cash/issues/29', + title: 'Test StagingDeployCash', + number: 29, + labels: [LABELS.STAGING_DEPLOY_CASH], + // eslint-disable-next-line max-len + body: '**Release Version:** `1.0.2-1`\r\n**Compare Changes:** https://github.com/Expensify/Expensify.cash/compare/production...staging\r\n\r\n**This release contains changes from the following pull requests:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/6\r\n- [x] https://github.com/Expensify/Expensify.cash/pull/7\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/8\r\n\r\n**Deploy Blockers:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/6\r\n- [ ] https://github.com/Expensify/Expensify.cash/issues/9\r\n- [x] https://github.com/Expensify/Expensify.cash/issues/10\r\n\r\ncc @Expensify/applauseleads\r\n', + state: 'open', + }; + + const currentOpenDeployBlockers = [ + { + url: 'https://github.com/Expensify/Expensify.cash/pull/6', + number: 6, + state: 'open', + labels: [LABELS.DEPLOY_BLOCKER_CASH], + }, + { + url: 'https://github.com/Expensify/Expensify.cash/issues/9', + number: 9, + state: 'open', + labels: [LABELS.DEPLOY_BLOCKER_CASH], + }, + { + url: 'https://github.com/Expensify/Expensify.cash/issues/10', + number: 10, + state: 'open', + labels: [LABELS.DEPLOY_BLOCKER_CASH], + }, + ]; + + test('with NPM_VERSION input, pull requests, and deploy blockers', () => { + mockGetInput.mockImplementation((arg) => { + if (arg === 'GITHUB_TOKEN') { + return 'fake_token'; + } + + if (arg === 'NPM_VERSION') { + return '1.0.2-2'; + } + }); + + // New pull requests to add to open StagingDeployCash + const newPullRequests = [ + 'https://github.com/Expensify/Expensify.cash/pull/9', + 'https://github.com/Expensify/Expensify.cash/pull/10', + ]; + mockGetPullRequestsMergedBetween.mockImplementation((fromRef, toRef) => { + if (fromRef === '1.0.1-0' && toRef === '1.0.2-2') { + return [ + ...baseNewPullRequests, + ...newPullRequests, + ]; + } + return []; + }); + + mockListIssues.mockImplementation((args) => { + if (args.labels === GithubUtils.STAGING_DEPLOY_CASH_LABEL) { + return {data: [openStagingDeployCashBefore, closedStagingDeployCash]}; + } + + if (args.labels === GithubUtils.DEPLOY_BLOCKER_CASH_LABEL) { + return { + data: [ + ...currentOpenDeployBlockers, + { + url: 'https://github.com/Expensify/Expensify.cash/issues/11', // New + number: 11, + state: 'open', + labels: [LABELS.DEPLOY_BLOCKER_CASH], + }, + { + url: 'https://github.com/Expensify/Expensify.cash/issues/12', // New + number: 12, + state: 'open', + labels: [LABELS.DEPLOY_BLOCKER_CASH], + }, + ], + }; + } + + return {data: []}; + }); + + return run().then((result) => { + expect(result).toStrictEqual({ + owner: GithubUtils.GITHUB_OWNER, + repo: GithubUtils.EXPENSIFY_CASH_REPO, + issue_number: openStagingDeployCashBefore.number, + // eslint-disable-next-line max-len + html_url: `https://github.com/Expensify/Expensify.cash/issues/${openStagingDeployCashBefore.number}`, + // eslint-disable-next-line max-len + body: `**Release Version:** \`1.0.2-2\`\r\n**Compare Changes:** https://github.com/Expensify/Expensify.cash/compare/production...staging\r\n\r\n**This release contains changes from the following pull requests:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/6\r\n- [x] https://github.com/Expensify/Expensify.cash/pull/7\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/8\r\n- [ ] ${newPullRequests[0]}\r\n- [ ] ${newPullRequests[1]}\r\n\r\n**Deploy Blockers:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/6\r\n- [ ] https://github.com/Expensify/Expensify.cash/issues/9\r\n- [x] https://github.com/Expensify/Expensify.cash/issues/10\r\n- [ ] https://github.com/Expensify/Expensify.cash/issues/11\r\n- [ ] https://github.com/Expensify/Expensify.cash/issues/12\r\n\r\ncc @Expensify/applauseleads\r\n`, + }); + }); + }); + + test('without NPM_VERSION input, just a new deploy blocker', () => { + mockGetPullRequestsMergedBetween.mockImplementation((fromRef, toRef) => { + if (fromRef === '1.0.1-0' && toRef === '1.0.2-2') { + return [ + ...baseNewPullRequests, + ]; + } + return []; + }); + mockListIssues.mockImplementation((args) => { + if (args.labels === GithubUtils.STAGING_DEPLOY_CASH_LABEL) { + return {data: [openStagingDeployCashBefore, closedStagingDeployCash]}; + } + + if (args.labels === GithubUtils.DEPLOY_BLOCKER_CASH_LABEL) { + return { + data: [ + ...currentOpenDeployBlockers, + { + url: 'https://github.com/Expensify/Expensify.cash/issues/11', // New + number: 11, + state: 'open', + labels: [LABELS.DEPLOY_BLOCKER_CASH], + }, + { + url: 'https://github.com/Expensify/Expensify.cash/issues/12', // New + number: 12, + state: 'open', + labels: [LABELS.DEPLOY_BLOCKER_CASH], + }, + ], + }; + } + + return {data: []}; + }); + + return run().then((result) => { + expect(result).toStrictEqual({ + owner: GithubUtils.GITHUB_OWNER, + repo: GithubUtils.EXPENSIFY_CASH_REPO, + issue_number: openStagingDeployCashBefore.number, + // eslint-disable-next-line max-len + html_url: `https://github.com/Expensify/Expensify.cash/issues/${openStagingDeployCashBefore.number}`, + // eslint-disable-next-line max-len + body: '**Release Version:** `1.0.2-2`\r\n**Compare Changes:** https://github.com/Expensify/Expensify.cash/compare/production...staging\r\n\r\n**This release contains changes from the following pull requests:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/6\r\n- [x] https://github.com/Expensify/Expensify.cash/pull/7\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/8\r\n\r\n**Deploy Blockers:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/6\r\n- [ ] https://github.com/Expensify/Expensify.cash/issues/9\r\n- [x] https://github.com/Expensify/Expensify.cash/issues/10\r\n- [ ] https://github.com/Expensify/Expensify.cash/issues/11\r\n- [ ] https://github.com/Expensify/Expensify.cash/issues/12\r\n\r\ncc @Expensify/applauseleads\r\n', + }); + }); + }); + }); +});