diff --git a/.github/actions/javascript/authorChecklist/index.js b/.github/actions/javascript/authorChecklist/index.js index 528a0a11498a..b20cc83498ba 100644 --- a/.github/actions/javascript/authorChecklist/index.js +++ b/.github/actions/javascript/authorChecklist/index.js @@ -255,7 +255,7 @@ class GithubUtils { } /** - * Generate the issue body for a StagingDeployCash. + * Generate the issue body and assignees for a StagingDeployCash. * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash @@ -268,7 +268,7 @@ class GithubUtils { * @param {Boolean} [isGHStatusChecked] * @returns {Promise} */ - static generateStagingDeployCashBody( + static generateStagingDeployCashBodyAndAssignees( tag, PRList, verifiedPRList = [], @@ -281,85 +281,89 @@ class GithubUtils { ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { - // The format of this map is following: - // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] - // } - const internalQAPRMap = _.reduce( - _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), - (map, pr) => { - // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); - return map; - }, - {}, - ); - console.log('Found the following Internal QA PRs:', internalQAPRMap); - - const noQAPRs = _.pluck( - _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), - 'html_url', - ); - console.log('Found the following NO QA PRs:', noQAPRs); - const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); - - const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); - const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); - - // Tag version and comparison URL - // eslint-disable-next-line max-len - let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; - - // PR list - if (!_.isEmpty(sortedPRList)) { - issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; - _.each(sortedPRList, (URL) => { - issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; - issueBody += ` ${URL}\r\n`; - }); - issueBody += '\r\n\r\n'; - } + const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))); + return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => { + // The format of this map is following: + // { + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' + // } + const internalQAPRMap = _.reduce( + results, + (acc, {url, mergerLogin}) => { + acc[url] = mergerLogin; + return acc; + }, + {}, + ); + console.log('Found the following Internal QA PRs:', internalQAPRMap); + + const noQAPRs = _.pluck( + _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), + 'html_url', + ); + console.log('Found the following NO QA PRs:', noQAPRs); + const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); + + const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); + const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); + + // Tag version and comparison URL + // eslint-disable-next-line max-len + let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; + + // PR list + if (!_.isEmpty(sortedPRList)) { + issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; + _.each(sortedPRList, (URL) => { + issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; + issueBody += ` ${URL}\r\n`; + }); + issueBody += '\r\n\r\n'; + } - // Internal QA PR list - if (!_.isEmpty(internalQAPRMap)) { - console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); - issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); - issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; - issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Internal QA PR list + if (!_.isEmpty(internalQAPRMap)) { + console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); + issueBody += '**Internal QA:**\r\n'; + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; + issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; + issueBody += `${URL}`; + issueBody += ` - ${mergerMention}`; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - // Deploy blockers - if (!_.isEmpty(deployBlockers)) { - issueBody += '**Deploy Blockers:**\r\n'; - _.each(sortedDeployBlockers, (URL) => { - issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; - issueBody += URL; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Deploy blockers + if (!_.isEmpty(deployBlockers)) { + issueBody += '**Deploy Blockers:**\r\n'; + _.each(sortedDeployBlockers, (URL) => { + issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; + issueBody += URL; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - issueBody += '**Deployer verifications:**'; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isTimingDashboardChecked ? 'x' : ' ' - }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isFirebaseChecked ? 'x' : ' ' - }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; - - issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + issueBody += '**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isTimingDashboardChecked ? 'x' : ' ' + }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isFirebaseChecked ? 'x' : ' ' + }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; + + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; + const issueAssignees = _.uniq(_.values(internalQAPRMap)); + const issue = {issueBody, issueAssignees}; + return issue; + }); }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } @@ -393,6 +397,20 @@ class GithubUtils { .catch((err) => console.error('Failed to get PR list', err)); } + /** + * @param {Number} pullRequestNumber + * @returns {Promise} + */ + static getPullRequestMergerLogin(pullRequestNumber) { + return this.octokit.pulls + .get({ + owner: CONST.GITHUB_OWNER, + repo: CONST.APP_REPO, + pull_number: pullRequestNumber, + }) + .then(({data: pullRequest}) => pullRequest.merged_by.login); + } + /** * @param {Number} pullRequestNumber * @returns {Promise} diff --git a/.github/actions/javascript/awaitStagingDeploys/index.js b/.github/actions/javascript/awaitStagingDeploys/index.js index f042dbb38a91..6b8401a08d6d 100644 --- a/.github/actions/javascript/awaitStagingDeploys/index.js +++ b/.github/actions/javascript/awaitStagingDeploys/index.js @@ -367,7 +367,7 @@ class GithubUtils { } /** - * Generate the issue body for a StagingDeployCash. + * Generate the issue body and assignees for a StagingDeployCash. * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash @@ -380,7 +380,7 @@ class GithubUtils { * @param {Boolean} [isGHStatusChecked] * @returns {Promise} */ - static generateStagingDeployCashBody( + static generateStagingDeployCashBodyAndAssignees( tag, PRList, verifiedPRList = [], @@ -393,85 +393,89 @@ class GithubUtils { ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { - // The format of this map is following: - // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] - // } - const internalQAPRMap = _.reduce( - _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), - (map, pr) => { - // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); - return map; - }, - {}, - ); - console.log('Found the following Internal QA PRs:', internalQAPRMap); - - const noQAPRs = _.pluck( - _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), - 'html_url', - ); - console.log('Found the following NO QA PRs:', noQAPRs); - const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); - - const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); - const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); - - // Tag version and comparison URL - // eslint-disable-next-line max-len - let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; - - // PR list - if (!_.isEmpty(sortedPRList)) { - issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; - _.each(sortedPRList, (URL) => { - issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; - issueBody += ` ${URL}\r\n`; - }); - issueBody += '\r\n\r\n'; - } + const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))); + return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => { + // The format of this map is following: + // { + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' + // } + const internalQAPRMap = _.reduce( + results, + (acc, {url, mergerLogin}) => { + acc[url] = mergerLogin; + return acc; + }, + {}, + ); + console.log('Found the following Internal QA PRs:', internalQAPRMap); + + const noQAPRs = _.pluck( + _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), + 'html_url', + ); + console.log('Found the following NO QA PRs:', noQAPRs); + const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); + + const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); + const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); + + // Tag version and comparison URL + // eslint-disable-next-line max-len + let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; + + // PR list + if (!_.isEmpty(sortedPRList)) { + issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; + _.each(sortedPRList, (URL) => { + issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; + issueBody += ` ${URL}\r\n`; + }); + issueBody += '\r\n\r\n'; + } - // Internal QA PR list - if (!_.isEmpty(internalQAPRMap)) { - console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); - issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); - issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; - issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Internal QA PR list + if (!_.isEmpty(internalQAPRMap)) { + console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); + issueBody += '**Internal QA:**\r\n'; + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; + issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; + issueBody += `${URL}`; + issueBody += ` - ${mergerMention}`; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - // Deploy blockers - if (!_.isEmpty(deployBlockers)) { - issueBody += '**Deploy Blockers:**\r\n'; - _.each(sortedDeployBlockers, (URL) => { - issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; - issueBody += URL; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Deploy blockers + if (!_.isEmpty(deployBlockers)) { + issueBody += '**Deploy Blockers:**\r\n'; + _.each(sortedDeployBlockers, (URL) => { + issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; + issueBody += URL; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - issueBody += '**Deployer verifications:**'; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isTimingDashboardChecked ? 'x' : ' ' - }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isFirebaseChecked ? 'x' : ' ' - }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; - - issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + issueBody += '**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isTimingDashboardChecked ? 'x' : ' ' + }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isFirebaseChecked ? 'x' : ' ' + }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; + + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; + const issueAssignees = _.uniq(_.values(internalQAPRMap)); + const issue = {issueBody, issueAssignees}; + return issue; + }); }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } @@ -505,6 +509,20 @@ class GithubUtils { .catch((err) => console.error('Failed to get PR list', err)); } + /** + * @param {Number} pullRequestNumber + * @returns {Promise} + */ + static getPullRequestMergerLogin(pullRequestNumber) { + return this.octokit.pulls + .get({ + owner: CONST.GITHUB_OWNER, + repo: CONST.APP_REPO, + pull_number: pullRequestNumber, + }) + .then(({data: pullRequest}) => pullRequest.merged_by.login); + } + /** * @param {Number} pullRequestNumber * @returns {Promise} diff --git a/.github/actions/javascript/checkDeployBlockers/index.js b/.github/actions/javascript/checkDeployBlockers/index.js index 8e10f8b1d8b6..dffc089ea5c5 100644 --- a/.github/actions/javascript/checkDeployBlockers/index.js +++ b/.github/actions/javascript/checkDeployBlockers/index.js @@ -334,7 +334,7 @@ class GithubUtils { } /** - * Generate the issue body for a StagingDeployCash. + * Generate the issue body and assignees for a StagingDeployCash. * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash @@ -347,7 +347,7 @@ class GithubUtils { * @param {Boolean} [isGHStatusChecked] * @returns {Promise} */ - static generateStagingDeployCashBody( + static generateStagingDeployCashBodyAndAssignees( tag, PRList, verifiedPRList = [], @@ -360,85 +360,89 @@ class GithubUtils { ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { - // The format of this map is following: - // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] - // } - const internalQAPRMap = _.reduce( - _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), - (map, pr) => { - // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); - return map; - }, - {}, - ); - console.log('Found the following Internal QA PRs:', internalQAPRMap); - - const noQAPRs = _.pluck( - _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), - 'html_url', - ); - console.log('Found the following NO QA PRs:', noQAPRs); - const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); - - const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); - const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); - - // Tag version and comparison URL - // eslint-disable-next-line max-len - let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; - - // PR list - if (!_.isEmpty(sortedPRList)) { - issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; - _.each(sortedPRList, (URL) => { - issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; - issueBody += ` ${URL}\r\n`; - }); - issueBody += '\r\n\r\n'; - } + const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))); + return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => { + // The format of this map is following: + // { + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' + // } + const internalQAPRMap = _.reduce( + results, + (acc, {url, mergerLogin}) => { + acc[url] = mergerLogin; + return acc; + }, + {}, + ); + console.log('Found the following Internal QA PRs:', internalQAPRMap); + + const noQAPRs = _.pluck( + _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), + 'html_url', + ); + console.log('Found the following NO QA PRs:', noQAPRs); + const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); + + const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); + const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); + + // Tag version and comparison URL + // eslint-disable-next-line max-len + let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; + + // PR list + if (!_.isEmpty(sortedPRList)) { + issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; + _.each(sortedPRList, (URL) => { + issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; + issueBody += ` ${URL}\r\n`; + }); + issueBody += '\r\n\r\n'; + } - // Internal QA PR list - if (!_.isEmpty(internalQAPRMap)) { - console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); - issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); - issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; - issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Internal QA PR list + if (!_.isEmpty(internalQAPRMap)) { + console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); + issueBody += '**Internal QA:**\r\n'; + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; + issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; + issueBody += `${URL}`; + issueBody += ` - ${mergerMention}`; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - // Deploy blockers - if (!_.isEmpty(deployBlockers)) { - issueBody += '**Deploy Blockers:**\r\n'; - _.each(sortedDeployBlockers, (URL) => { - issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; - issueBody += URL; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Deploy blockers + if (!_.isEmpty(deployBlockers)) { + issueBody += '**Deploy Blockers:**\r\n'; + _.each(sortedDeployBlockers, (URL) => { + issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; + issueBody += URL; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - issueBody += '**Deployer verifications:**'; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isTimingDashboardChecked ? 'x' : ' ' - }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isFirebaseChecked ? 'x' : ' ' - }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; - - issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + issueBody += '**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isTimingDashboardChecked ? 'x' : ' ' + }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isFirebaseChecked ? 'x' : ' ' + }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; + + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; + const issueAssignees = _.uniq(_.values(internalQAPRMap)); + const issue = {issueBody, issueAssignees}; + return issue; + }); }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } @@ -472,6 +476,20 @@ class GithubUtils { .catch((err) => console.error('Failed to get PR list', err)); } + /** + * @param {Number} pullRequestNumber + * @returns {Promise} + */ + static getPullRequestMergerLogin(pullRequestNumber) { + return this.octokit.pulls + .get({ + owner: CONST.GITHUB_OWNER, + repo: CONST.APP_REPO, + pull_number: pullRequestNumber, + }) + .then(({data: pullRequest}) => pullRequest.merged_by.login); + } + /** * @param {Number} pullRequestNumber * @returns {Promise} diff --git a/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js b/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js index 4441348a3c36..63398614fd11 100644 --- a/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js +++ b/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js @@ -40,8 +40,11 @@ async function run() { // Next, we generate the checklist body let checklistBody = ''; + let checklistAssignees = []; if (shouldCreateNewDeployChecklist) { - checklistBody = await GithubUtils.generateStagingDeployCashBody(newVersionTag, _.map(mergedPRs, GithubUtils.getPullRequestURLFromNumber)); + const {issueBody, issueAssignees} = await GithubUtils.generateStagingDeployCashBodyAndAssignees(newVersionTag, _.map(mergedPRs, GithubUtils.getPullRequestURLFromNumber)); + checklistBody = issueBody; + checklistAssignees = issueAssignees; } else { // Generate the updated PR list, preserving the previous state of `isVerified` for existing PRs const PRList = _.reduce( @@ -94,7 +97,7 @@ async function run() { } const didVersionChange = newVersionTag !== currentChecklistData.tag; - checklistBody = await GithubUtils.generateStagingDeployCashBody( + const {issueBody, issueAssignees} = await GithubUtils.generateStagingDeployCashBodyAndAssignees( newVersionTag, _.pluck(PRList, 'url'), _.pluck(_.where(PRList, {isVerified: true}), 'url'), @@ -105,6 +108,8 @@ async function run() { didVersionChange ? false : currentChecklistData.isFirebaseChecked, didVersionChange ? false : currentChecklistData.isGHStatusChecked, ); + checklistBody = issueBody; + checklistAssignees = issueAssignees; } // Finally, create or update the checklist @@ -119,7 +124,7 @@ async function run() { ...defaultPayload, title: `Deploy Checklist: New Expensify ${format(new Date(), CONST.DATE_FORMAT_STRING)}`, labels: [CONST.LABELS.STAGING_DEPLOY], - assignees: [CONST.APPLAUSE_BOT], + assignees: [CONST.APPLAUSE_BOT].concat(checklistAssignees), }); console.log(`Successfully created new StagingDeployCash! 🎉 ${newChecklist.html_url}`); return newChecklist; diff --git a/.github/actions/javascript/createOrUpdateStagingDeploy/index.js b/.github/actions/javascript/createOrUpdateStagingDeploy/index.js index 154dacbdc3c3..60ec0b9f0ae3 100644 --- a/.github/actions/javascript/createOrUpdateStagingDeploy/index.js +++ b/.github/actions/javascript/createOrUpdateStagingDeploy/index.js @@ -49,8 +49,11 @@ async function run() { // Next, we generate the checklist body let checklistBody = ''; + let checklistAssignees = []; if (shouldCreateNewDeployChecklist) { - checklistBody = await GithubUtils.generateStagingDeployCashBody(newVersionTag, _.map(mergedPRs, GithubUtils.getPullRequestURLFromNumber)); + const {issueBody, issueAssignees} = await GithubUtils.generateStagingDeployCashBodyAndAssignees(newVersionTag, _.map(mergedPRs, GithubUtils.getPullRequestURLFromNumber)); + checklistBody = issueBody; + checklistAssignees = issueAssignees; } else { // Generate the updated PR list, preserving the previous state of `isVerified` for existing PRs const PRList = _.reduce( @@ -103,7 +106,7 @@ async function run() { } const didVersionChange = newVersionTag !== currentChecklistData.tag; - checklistBody = await GithubUtils.generateStagingDeployCashBody( + const {issueBody, issueAssignees} = await GithubUtils.generateStagingDeployCashBodyAndAssignees( newVersionTag, _.pluck(PRList, 'url'), _.pluck(_.where(PRList, {isVerified: true}), 'url'), @@ -114,6 +117,8 @@ async function run() { didVersionChange ? false : currentChecklistData.isFirebaseChecked, didVersionChange ? false : currentChecklistData.isGHStatusChecked, ); + checklistBody = issueBody; + checklistAssignees = issueAssignees; } // Finally, create or update the checklist @@ -128,7 +133,7 @@ async function run() { ...defaultPayload, title: `Deploy Checklist: New Expensify ${format(new Date(), CONST.DATE_FORMAT_STRING)}`, labels: [CONST.LABELS.STAGING_DEPLOY], - assignees: [CONST.APPLAUSE_BOT], + assignees: [CONST.APPLAUSE_BOT].concat(checklistAssignees), }); console.log(`Successfully created new StagingDeployCash! 🎉 ${newChecklist.html_url}`); return newChecklist; @@ -406,7 +411,7 @@ class GithubUtils { } /** - * Generate the issue body for a StagingDeployCash. + * Generate the issue body and assignees for a StagingDeployCash. * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash @@ -419,7 +424,7 @@ class GithubUtils { * @param {Boolean} [isGHStatusChecked] * @returns {Promise} */ - static generateStagingDeployCashBody( + static generateStagingDeployCashBodyAndAssignees( tag, PRList, verifiedPRList = [], @@ -432,85 +437,89 @@ class GithubUtils { ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { - // The format of this map is following: - // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] - // } - const internalQAPRMap = _.reduce( - _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), - (map, pr) => { - // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); - return map; - }, - {}, - ); - console.log('Found the following Internal QA PRs:', internalQAPRMap); - - const noQAPRs = _.pluck( - _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), - 'html_url', - ); - console.log('Found the following NO QA PRs:', noQAPRs); - const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); - - const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); - const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); - - // Tag version and comparison URL - // eslint-disable-next-line max-len - let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; - - // PR list - if (!_.isEmpty(sortedPRList)) { - issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; - _.each(sortedPRList, (URL) => { - issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; - issueBody += ` ${URL}\r\n`; - }); - issueBody += '\r\n\r\n'; - } + const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))); + return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => { + // The format of this map is following: + // { + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' + // } + const internalQAPRMap = _.reduce( + results, + (acc, {url, mergerLogin}) => { + acc[url] = mergerLogin; + return acc; + }, + {}, + ); + console.log('Found the following Internal QA PRs:', internalQAPRMap); + + const noQAPRs = _.pluck( + _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), + 'html_url', + ); + console.log('Found the following NO QA PRs:', noQAPRs); + const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); + + const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); + const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); + + // Tag version and comparison URL + // eslint-disable-next-line max-len + let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; + + // PR list + if (!_.isEmpty(sortedPRList)) { + issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; + _.each(sortedPRList, (URL) => { + issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; + issueBody += ` ${URL}\r\n`; + }); + issueBody += '\r\n\r\n'; + } - // Internal QA PR list - if (!_.isEmpty(internalQAPRMap)) { - console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); - issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); - issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; - issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Internal QA PR list + if (!_.isEmpty(internalQAPRMap)) { + console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); + issueBody += '**Internal QA:**\r\n'; + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; + issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; + issueBody += `${URL}`; + issueBody += ` - ${mergerMention}`; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - // Deploy blockers - if (!_.isEmpty(deployBlockers)) { - issueBody += '**Deploy Blockers:**\r\n'; - _.each(sortedDeployBlockers, (URL) => { - issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; - issueBody += URL; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Deploy blockers + if (!_.isEmpty(deployBlockers)) { + issueBody += '**Deploy Blockers:**\r\n'; + _.each(sortedDeployBlockers, (URL) => { + issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; + issueBody += URL; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - issueBody += '**Deployer verifications:**'; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isTimingDashboardChecked ? 'x' : ' ' - }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isFirebaseChecked ? 'x' : ' ' - }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; - - issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + issueBody += '**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isTimingDashboardChecked ? 'x' : ' ' + }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isFirebaseChecked ? 'x' : ' ' + }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; + + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; + const issueAssignees = _.uniq(_.values(internalQAPRMap)); + const issue = {issueBody, issueAssignees}; + return issue; + }); }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } @@ -544,6 +553,20 @@ class GithubUtils { .catch((err) => console.error('Failed to get PR list', err)); } + /** + * @param {Number} pullRequestNumber + * @returns {Promise} + */ + static getPullRequestMergerLogin(pullRequestNumber) { + return this.octokit.pulls + .get({ + owner: CONST.GITHUB_OWNER, + repo: CONST.APP_REPO, + pull_number: pullRequestNumber, + }) + .then(({data: pullRequest}) => pullRequest.merged_by.login); + } + /** * @param {Number} pullRequestNumber * @returns {Promise} diff --git a/.github/actions/javascript/getArtifactInfo/index.js b/.github/actions/javascript/getArtifactInfo/index.js index ea56ff5f4ebd..b8cb062feba5 100644 --- a/.github/actions/javascript/getArtifactInfo/index.js +++ b/.github/actions/javascript/getArtifactInfo/index.js @@ -293,7 +293,7 @@ class GithubUtils { } /** - * Generate the issue body for a StagingDeployCash. + * Generate the issue body and assignees for a StagingDeployCash. * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash @@ -306,7 +306,7 @@ class GithubUtils { * @param {Boolean} [isGHStatusChecked] * @returns {Promise} */ - static generateStagingDeployCashBody( + static generateStagingDeployCashBodyAndAssignees( tag, PRList, verifiedPRList = [], @@ -319,85 +319,89 @@ class GithubUtils { ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { - // The format of this map is following: - // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] - // } - const internalQAPRMap = _.reduce( - _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), - (map, pr) => { - // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); - return map; - }, - {}, - ); - console.log('Found the following Internal QA PRs:', internalQAPRMap); - - const noQAPRs = _.pluck( - _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), - 'html_url', - ); - console.log('Found the following NO QA PRs:', noQAPRs); - const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); - - const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); - const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); - - // Tag version and comparison URL - // eslint-disable-next-line max-len - let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; - - // PR list - if (!_.isEmpty(sortedPRList)) { - issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; - _.each(sortedPRList, (URL) => { - issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; - issueBody += ` ${URL}\r\n`; - }); - issueBody += '\r\n\r\n'; - } + const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))); + return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => { + // The format of this map is following: + // { + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' + // } + const internalQAPRMap = _.reduce( + results, + (acc, {url, mergerLogin}) => { + acc[url] = mergerLogin; + return acc; + }, + {}, + ); + console.log('Found the following Internal QA PRs:', internalQAPRMap); + + const noQAPRs = _.pluck( + _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), + 'html_url', + ); + console.log('Found the following NO QA PRs:', noQAPRs); + const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); + + const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); + const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); + + // Tag version and comparison URL + // eslint-disable-next-line max-len + let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; + + // PR list + if (!_.isEmpty(sortedPRList)) { + issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; + _.each(sortedPRList, (URL) => { + issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; + issueBody += ` ${URL}\r\n`; + }); + issueBody += '\r\n\r\n'; + } - // Internal QA PR list - if (!_.isEmpty(internalQAPRMap)) { - console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); - issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); - issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; - issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Internal QA PR list + if (!_.isEmpty(internalQAPRMap)) { + console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); + issueBody += '**Internal QA:**\r\n'; + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; + issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; + issueBody += `${URL}`; + issueBody += ` - ${mergerMention}`; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - // Deploy blockers - if (!_.isEmpty(deployBlockers)) { - issueBody += '**Deploy Blockers:**\r\n'; - _.each(sortedDeployBlockers, (URL) => { - issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; - issueBody += URL; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Deploy blockers + if (!_.isEmpty(deployBlockers)) { + issueBody += '**Deploy Blockers:**\r\n'; + _.each(sortedDeployBlockers, (URL) => { + issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; + issueBody += URL; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - issueBody += '**Deployer verifications:**'; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isTimingDashboardChecked ? 'x' : ' ' - }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isFirebaseChecked ? 'x' : ' ' - }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; - - issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + issueBody += '**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isTimingDashboardChecked ? 'x' : ' ' + }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isFirebaseChecked ? 'x' : ' ' + }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; + + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; + const issueAssignees = _.uniq(_.values(internalQAPRMap)); + const issue = {issueBody, issueAssignees}; + return issue; + }); }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } @@ -431,6 +435,20 @@ class GithubUtils { .catch((err) => console.error('Failed to get PR list', err)); } + /** + * @param {Number} pullRequestNumber + * @returns {Promise} + */ + static getPullRequestMergerLogin(pullRequestNumber) { + return this.octokit.pulls + .get({ + owner: CONST.GITHUB_OWNER, + repo: CONST.APP_REPO, + pull_number: pullRequestNumber, + }) + .then(({data: pullRequest}) => pullRequest.merged_by.login); + } + /** * @param {Number} pullRequestNumber * @returns {Promise} diff --git a/.github/actions/javascript/getDeployPullRequestList/index.js b/.github/actions/javascript/getDeployPullRequestList/index.js index f272929d536a..c57ebf0fefe4 100644 --- a/.github/actions/javascript/getDeployPullRequestList/index.js +++ b/.github/actions/javascript/getDeployPullRequestList/index.js @@ -349,7 +349,7 @@ class GithubUtils { } /** - * Generate the issue body for a StagingDeployCash. + * Generate the issue body and assignees for a StagingDeployCash. * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash @@ -362,7 +362,7 @@ class GithubUtils { * @param {Boolean} [isGHStatusChecked] * @returns {Promise} */ - static generateStagingDeployCashBody( + static generateStagingDeployCashBodyAndAssignees( tag, PRList, verifiedPRList = [], @@ -375,85 +375,89 @@ class GithubUtils { ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { - // The format of this map is following: - // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] - // } - const internalQAPRMap = _.reduce( - _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), - (map, pr) => { - // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); - return map; - }, - {}, - ); - console.log('Found the following Internal QA PRs:', internalQAPRMap); - - const noQAPRs = _.pluck( - _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), - 'html_url', - ); - console.log('Found the following NO QA PRs:', noQAPRs); - const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); - - const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); - const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); - - // Tag version and comparison URL - // eslint-disable-next-line max-len - let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; - - // PR list - if (!_.isEmpty(sortedPRList)) { - issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; - _.each(sortedPRList, (URL) => { - issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; - issueBody += ` ${URL}\r\n`; - }); - issueBody += '\r\n\r\n'; - } + const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))); + return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => { + // The format of this map is following: + // { + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' + // } + const internalQAPRMap = _.reduce( + results, + (acc, {url, mergerLogin}) => { + acc[url] = mergerLogin; + return acc; + }, + {}, + ); + console.log('Found the following Internal QA PRs:', internalQAPRMap); + + const noQAPRs = _.pluck( + _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), + 'html_url', + ); + console.log('Found the following NO QA PRs:', noQAPRs); + const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); + + const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); + const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); + + // Tag version and comparison URL + // eslint-disable-next-line max-len + let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; + + // PR list + if (!_.isEmpty(sortedPRList)) { + issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; + _.each(sortedPRList, (URL) => { + issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; + issueBody += ` ${URL}\r\n`; + }); + issueBody += '\r\n\r\n'; + } - // Internal QA PR list - if (!_.isEmpty(internalQAPRMap)) { - console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); - issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); - issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; - issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Internal QA PR list + if (!_.isEmpty(internalQAPRMap)) { + console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); + issueBody += '**Internal QA:**\r\n'; + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; + issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; + issueBody += `${URL}`; + issueBody += ` - ${mergerMention}`; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - // Deploy blockers - if (!_.isEmpty(deployBlockers)) { - issueBody += '**Deploy Blockers:**\r\n'; - _.each(sortedDeployBlockers, (URL) => { - issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; - issueBody += URL; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Deploy blockers + if (!_.isEmpty(deployBlockers)) { + issueBody += '**Deploy Blockers:**\r\n'; + _.each(sortedDeployBlockers, (URL) => { + issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; + issueBody += URL; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - issueBody += '**Deployer verifications:**'; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isTimingDashboardChecked ? 'x' : ' ' - }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isFirebaseChecked ? 'x' : ' ' - }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; - - issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + issueBody += '**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isTimingDashboardChecked ? 'x' : ' ' + }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isFirebaseChecked ? 'x' : ' ' + }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; + + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; + const issueAssignees = _.uniq(_.values(internalQAPRMap)); + const issue = {issueBody, issueAssignees}; + return issue; + }); }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } @@ -487,6 +491,20 @@ class GithubUtils { .catch((err) => console.error('Failed to get PR list', err)); } + /** + * @param {Number} pullRequestNumber + * @returns {Promise} + */ + static getPullRequestMergerLogin(pullRequestNumber) { + return this.octokit.pulls + .get({ + owner: CONST.GITHUB_OWNER, + repo: CONST.APP_REPO, + pull_number: pullRequestNumber, + }) + .then(({data: pullRequest}) => pullRequest.merged_by.login); + } + /** * @param {Number} pullRequestNumber * @returns {Promise} diff --git a/.github/actions/javascript/getPullRequestDetails/index.js b/.github/actions/javascript/getPullRequestDetails/index.js index b8d7d821d64e..9eb608a8cc05 100644 --- a/.github/actions/javascript/getPullRequestDetails/index.js +++ b/.github/actions/javascript/getPullRequestDetails/index.js @@ -301,7 +301,7 @@ class GithubUtils { } /** - * Generate the issue body for a StagingDeployCash. + * Generate the issue body and assignees for a StagingDeployCash. * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash @@ -314,7 +314,7 @@ class GithubUtils { * @param {Boolean} [isGHStatusChecked] * @returns {Promise} */ - static generateStagingDeployCashBody( + static generateStagingDeployCashBodyAndAssignees( tag, PRList, verifiedPRList = [], @@ -327,85 +327,89 @@ class GithubUtils { ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { - // The format of this map is following: - // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] - // } - const internalQAPRMap = _.reduce( - _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), - (map, pr) => { - // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); - return map; - }, - {}, - ); - console.log('Found the following Internal QA PRs:', internalQAPRMap); - - const noQAPRs = _.pluck( - _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), - 'html_url', - ); - console.log('Found the following NO QA PRs:', noQAPRs); - const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); - - const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); - const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); - - // Tag version and comparison URL - // eslint-disable-next-line max-len - let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; - - // PR list - if (!_.isEmpty(sortedPRList)) { - issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; - _.each(sortedPRList, (URL) => { - issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; - issueBody += ` ${URL}\r\n`; - }); - issueBody += '\r\n\r\n'; - } + const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))); + return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => { + // The format of this map is following: + // { + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' + // } + const internalQAPRMap = _.reduce( + results, + (acc, {url, mergerLogin}) => { + acc[url] = mergerLogin; + return acc; + }, + {}, + ); + console.log('Found the following Internal QA PRs:', internalQAPRMap); + + const noQAPRs = _.pluck( + _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), + 'html_url', + ); + console.log('Found the following NO QA PRs:', noQAPRs); + const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); + + const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); + const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); + + // Tag version and comparison URL + // eslint-disable-next-line max-len + let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; + + // PR list + if (!_.isEmpty(sortedPRList)) { + issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; + _.each(sortedPRList, (URL) => { + issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; + issueBody += ` ${URL}\r\n`; + }); + issueBody += '\r\n\r\n'; + } - // Internal QA PR list - if (!_.isEmpty(internalQAPRMap)) { - console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); - issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); - issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; - issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Internal QA PR list + if (!_.isEmpty(internalQAPRMap)) { + console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); + issueBody += '**Internal QA:**\r\n'; + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; + issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; + issueBody += `${URL}`; + issueBody += ` - ${mergerMention}`; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - // Deploy blockers - if (!_.isEmpty(deployBlockers)) { - issueBody += '**Deploy Blockers:**\r\n'; - _.each(sortedDeployBlockers, (URL) => { - issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; - issueBody += URL; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Deploy blockers + if (!_.isEmpty(deployBlockers)) { + issueBody += '**Deploy Blockers:**\r\n'; + _.each(sortedDeployBlockers, (URL) => { + issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; + issueBody += URL; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - issueBody += '**Deployer verifications:**'; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isTimingDashboardChecked ? 'x' : ' ' - }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isFirebaseChecked ? 'x' : ' ' - }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; - - issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + issueBody += '**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isTimingDashboardChecked ? 'x' : ' ' + }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isFirebaseChecked ? 'x' : ' ' + }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; + + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; + const issueAssignees = _.uniq(_.values(internalQAPRMap)); + const issue = {issueBody, issueAssignees}; + return issue; + }); }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } @@ -439,6 +443,20 @@ class GithubUtils { .catch((err) => console.error('Failed to get PR list', err)); } + /** + * @param {Number} pullRequestNumber + * @returns {Promise} + */ + static getPullRequestMergerLogin(pullRequestNumber) { + return this.octokit.pulls + .get({ + owner: CONST.GITHUB_OWNER, + repo: CONST.APP_REPO, + pull_number: pullRequestNumber, + }) + .then(({data: pullRequest}) => pullRequest.merged_by.login); + } + /** * @param {Number} pullRequestNumber * @returns {Promise} diff --git a/.github/actions/javascript/getReleaseBody/index.js b/.github/actions/javascript/getReleaseBody/index.js index cc1321ce5cd5..ea12ef5f0df0 100644 --- a/.github/actions/javascript/getReleaseBody/index.js +++ b/.github/actions/javascript/getReleaseBody/index.js @@ -301,7 +301,7 @@ class GithubUtils { } /** - * Generate the issue body for a StagingDeployCash. + * Generate the issue body and assignees for a StagingDeployCash. * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash @@ -314,7 +314,7 @@ class GithubUtils { * @param {Boolean} [isGHStatusChecked] * @returns {Promise} */ - static generateStagingDeployCashBody( + static generateStagingDeployCashBodyAndAssignees( tag, PRList, verifiedPRList = [], @@ -327,85 +327,89 @@ class GithubUtils { ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { - // The format of this map is following: - // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] - // } - const internalQAPRMap = _.reduce( - _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), - (map, pr) => { - // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); - return map; - }, - {}, - ); - console.log('Found the following Internal QA PRs:', internalQAPRMap); - - const noQAPRs = _.pluck( - _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), - 'html_url', - ); - console.log('Found the following NO QA PRs:', noQAPRs); - const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); - - const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); - const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); - - // Tag version and comparison URL - // eslint-disable-next-line max-len - let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; - - // PR list - if (!_.isEmpty(sortedPRList)) { - issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; - _.each(sortedPRList, (URL) => { - issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; - issueBody += ` ${URL}\r\n`; - }); - issueBody += '\r\n\r\n'; - } + const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))); + return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => { + // The format of this map is following: + // { + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' + // } + const internalQAPRMap = _.reduce( + results, + (acc, {url, mergerLogin}) => { + acc[url] = mergerLogin; + return acc; + }, + {}, + ); + console.log('Found the following Internal QA PRs:', internalQAPRMap); + + const noQAPRs = _.pluck( + _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), + 'html_url', + ); + console.log('Found the following NO QA PRs:', noQAPRs); + const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); + + const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); + const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); + + // Tag version and comparison URL + // eslint-disable-next-line max-len + let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; + + // PR list + if (!_.isEmpty(sortedPRList)) { + issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; + _.each(sortedPRList, (URL) => { + issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; + issueBody += ` ${URL}\r\n`; + }); + issueBody += '\r\n\r\n'; + } - // Internal QA PR list - if (!_.isEmpty(internalQAPRMap)) { - console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); - issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); - issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; - issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Internal QA PR list + if (!_.isEmpty(internalQAPRMap)) { + console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); + issueBody += '**Internal QA:**\r\n'; + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; + issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; + issueBody += `${URL}`; + issueBody += ` - ${mergerMention}`; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - // Deploy blockers - if (!_.isEmpty(deployBlockers)) { - issueBody += '**Deploy Blockers:**\r\n'; - _.each(sortedDeployBlockers, (URL) => { - issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; - issueBody += URL; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Deploy blockers + if (!_.isEmpty(deployBlockers)) { + issueBody += '**Deploy Blockers:**\r\n'; + _.each(sortedDeployBlockers, (URL) => { + issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; + issueBody += URL; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - issueBody += '**Deployer verifications:**'; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isTimingDashboardChecked ? 'x' : ' ' - }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isFirebaseChecked ? 'x' : ' ' - }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; - - issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + issueBody += '**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isTimingDashboardChecked ? 'x' : ' ' + }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isFirebaseChecked ? 'x' : ' ' + }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; + + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; + const issueAssignees = _.uniq(_.values(internalQAPRMap)); + const issue = {issueBody, issueAssignees}; + return issue; + }); }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } @@ -439,6 +443,20 @@ class GithubUtils { .catch((err) => console.error('Failed to get PR list', err)); } + /** + * @param {Number} pullRequestNumber + * @returns {Promise} + */ + static getPullRequestMergerLogin(pullRequestNumber) { + return this.octokit.pulls + .get({ + owner: CONST.GITHUB_OWNER, + repo: CONST.APP_REPO, + pull_number: pullRequestNumber, + }) + .then(({data: pullRequest}) => pullRequest.merged_by.login); + } + /** * @param {Number} pullRequestNumber * @returns {Promise} diff --git a/.github/actions/javascript/isStagingDeployLocked/index.js b/.github/actions/javascript/isStagingDeployLocked/index.js index 8124c5795a5a..32b8b64d7b82 100644 --- a/.github/actions/javascript/isStagingDeployLocked/index.js +++ b/.github/actions/javascript/isStagingDeployLocked/index.js @@ -285,7 +285,7 @@ class GithubUtils { } /** - * Generate the issue body for a StagingDeployCash. + * Generate the issue body and assignees for a StagingDeployCash. * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash @@ -298,7 +298,7 @@ class GithubUtils { * @param {Boolean} [isGHStatusChecked] * @returns {Promise} */ - static generateStagingDeployCashBody( + static generateStagingDeployCashBodyAndAssignees( tag, PRList, verifiedPRList = [], @@ -311,85 +311,89 @@ class GithubUtils { ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { - // The format of this map is following: - // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] - // } - const internalQAPRMap = _.reduce( - _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), - (map, pr) => { - // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); - return map; - }, - {}, - ); - console.log('Found the following Internal QA PRs:', internalQAPRMap); - - const noQAPRs = _.pluck( - _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), - 'html_url', - ); - console.log('Found the following NO QA PRs:', noQAPRs); - const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); - - const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); - const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); - - // Tag version and comparison URL - // eslint-disable-next-line max-len - let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; - - // PR list - if (!_.isEmpty(sortedPRList)) { - issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; - _.each(sortedPRList, (URL) => { - issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; - issueBody += ` ${URL}\r\n`; - }); - issueBody += '\r\n\r\n'; - } + const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))); + return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => { + // The format of this map is following: + // { + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' + // } + const internalQAPRMap = _.reduce( + results, + (acc, {url, mergerLogin}) => { + acc[url] = mergerLogin; + return acc; + }, + {}, + ); + console.log('Found the following Internal QA PRs:', internalQAPRMap); + + const noQAPRs = _.pluck( + _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), + 'html_url', + ); + console.log('Found the following NO QA PRs:', noQAPRs); + const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); + + const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); + const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); + + // Tag version and comparison URL + // eslint-disable-next-line max-len + let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; + + // PR list + if (!_.isEmpty(sortedPRList)) { + issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; + _.each(sortedPRList, (URL) => { + issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; + issueBody += ` ${URL}\r\n`; + }); + issueBody += '\r\n\r\n'; + } - // Internal QA PR list - if (!_.isEmpty(internalQAPRMap)) { - console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); - issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); - issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; - issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Internal QA PR list + if (!_.isEmpty(internalQAPRMap)) { + console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); + issueBody += '**Internal QA:**\r\n'; + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; + issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; + issueBody += `${URL}`; + issueBody += ` - ${mergerMention}`; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - // Deploy blockers - if (!_.isEmpty(deployBlockers)) { - issueBody += '**Deploy Blockers:**\r\n'; - _.each(sortedDeployBlockers, (URL) => { - issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; - issueBody += URL; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Deploy blockers + if (!_.isEmpty(deployBlockers)) { + issueBody += '**Deploy Blockers:**\r\n'; + _.each(sortedDeployBlockers, (URL) => { + issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; + issueBody += URL; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - issueBody += '**Deployer verifications:**'; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isTimingDashboardChecked ? 'x' : ' ' - }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isFirebaseChecked ? 'x' : ' ' - }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; - - issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + issueBody += '**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isTimingDashboardChecked ? 'x' : ' ' + }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isFirebaseChecked ? 'x' : ' ' + }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; + + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; + const issueAssignees = _.uniq(_.values(internalQAPRMap)); + const issue = {issueBody, issueAssignees}; + return issue; + }); }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } @@ -423,6 +427,20 @@ class GithubUtils { .catch((err) => console.error('Failed to get PR list', err)); } + /** + * @param {Number} pullRequestNumber + * @returns {Promise} + */ + static getPullRequestMergerLogin(pullRequestNumber) { + return this.octokit.pulls + .get({ + owner: CONST.GITHUB_OWNER, + repo: CONST.APP_REPO, + pull_number: pullRequestNumber, + }) + .then(({data: pullRequest}) => pullRequest.merged_by.login); + } + /** * @param {Number} pullRequestNumber * @returns {Promise} diff --git a/.github/actions/javascript/markPullRequestsAsDeployed/index.js b/.github/actions/javascript/markPullRequestsAsDeployed/index.js index 36cd0aaefe4a..d275edf9d789 100644 --- a/.github/actions/javascript/markPullRequestsAsDeployed/index.js +++ b/.github/actions/javascript/markPullRequestsAsDeployed/index.js @@ -450,7 +450,7 @@ class GithubUtils { } /** - * Generate the issue body for a StagingDeployCash. + * Generate the issue body and assignees for a StagingDeployCash. * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash @@ -463,7 +463,7 @@ class GithubUtils { * @param {Boolean} [isGHStatusChecked] * @returns {Promise} */ - static generateStagingDeployCashBody( + static generateStagingDeployCashBodyAndAssignees( tag, PRList, verifiedPRList = [], @@ -476,85 +476,89 @@ class GithubUtils { ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { - // The format of this map is following: - // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] - // } - const internalQAPRMap = _.reduce( - _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), - (map, pr) => { - // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); - return map; - }, - {}, - ); - console.log('Found the following Internal QA PRs:', internalQAPRMap); - - const noQAPRs = _.pluck( - _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), - 'html_url', - ); - console.log('Found the following NO QA PRs:', noQAPRs); - const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); - - const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); - const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); - - // Tag version and comparison URL - // eslint-disable-next-line max-len - let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; - - // PR list - if (!_.isEmpty(sortedPRList)) { - issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; - _.each(sortedPRList, (URL) => { - issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; - issueBody += ` ${URL}\r\n`; - }); - issueBody += '\r\n\r\n'; - } + const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))); + return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => { + // The format of this map is following: + // { + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' + // } + const internalQAPRMap = _.reduce( + results, + (acc, {url, mergerLogin}) => { + acc[url] = mergerLogin; + return acc; + }, + {}, + ); + console.log('Found the following Internal QA PRs:', internalQAPRMap); + + const noQAPRs = _.pluck( + _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), + 'html_url', + ); + console.log('Found the following NO QA PRs:', noQAPRs); + const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); + + const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); + const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); + + // Tag version and comparison URL + // eslint-disable-next-line max-len + let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; + + // PR list + if (!_.isEmpty(sortedPRList)) { + issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; + _.each(sortedPRList, (URL) => { + issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; + issueBody += ` ${URL}\r\n`; + }); + issueBody += '\r\n\r\n'; + } - // Internal QA PR list - if (!_.isEmpty(internalQAPRMap)) { - console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); - issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); - issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; - issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Internal QA PR list + if (!_.isEmpty(internalQAPRMap)) { + console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); + issueBody += '**Internal QA:**\r\n'; + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; + issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; + issueBody += `${URL}`; + issueBody += ` - ${mergerMention}`; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - // Deploy blockers - if (!_.isEmpty(deployBlockers)) { - issueBody += '**Deploy Blockers:**\r\n'; - _.each(sortedDeployBlockers, (URL) => { - issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; - issueBody += URL; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Deploy blockers + if (!_.isEmpty(deployBlockers)) { + issueBody += '**Deploy Blockers:**\r\n'; + _.each(sortedDeployBlockers, (URL) => { + issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; + issueBody += URL; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - issueBody += '**Deployer verifications:**'; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isTimingDashboardChecked ? 'x' : ' ' - }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isFirebaseChecked ? 'x' : ' ' - }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; - - issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + issueBody += '**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isTimingDashboardChecked ? 'x' : ' ' + }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isFirebaseChecked ? 'x' : ' ' + }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; + + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; + const issueAssignees = _.uniq(_.values(internalQAPRMap)); + const issue = {issueBody, issueAssignees}; + return issue; + }); }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } @@ -588,6 +592,20 @@ class GithubUtils { .catch((err) => console.error('Failed to get PR list', err)); } + /** + * @param {Number} pullRequestNumber + * @returns {Promise} + */ + static getPullRequestMergerLogin(pullRequestNumber) { + return this.octokit.pulls + .get({ + owner: CONST.GITHUB_OWNER, + repo: CONST.APP_REPO, + pull_number: pullRequestNumber, + }) + .then(({data: pullRequest}) => pullRequest.merged_by.login); + } + /** * @param {Number} pullRequestNumber * @returns {Promise} diff --git a/.github/actions/javascript/postTestBuildComment/index.js b/.github/actions/javascript/postTestBuildComment/index.js index 329e0d3aad5d..3bd3e6121be8 100644 --- a/.github/actions/javascript/postTestBuildComment/index.js +++ b/.github/actions/javascript/postTestBuildComment/index.js @@ -360,7 +360,7 @@ class GithubUtils { } /** - * Generate the issue body for a StagingDeployCash. + * Generate the issue body and assignees for a StagingDeployCash. * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash @@ -373,7 +373,7 @@ class GithubUtils { * @param {Boolean} [isGHStatusChecked] * @returns {Promise} */ - static generateStagingDeployCashBody( + static generateStagingDeployCashBodyAndAssignees( tag, PRList, verifiedPRList = [], @@ -386,85 +386,89 @@ class GithubUtils { ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { - // The format of this map is following: - // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] - // } - const internalQAPRMap = _.reduce( - _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), - (map, pr) => { - // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); - return map; - }, - {}, - ); - console.log('Found the following Internal QA PRs:', internalQAPRMap); - - const noQAPRs = _.pluck( - _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), - 'html_url', - ); - console.log('Found the following NO QA PRs:', noQAPRs); - const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); - - const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); - const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); - - // Tag version and comparison URL - // eslint-disable-next-line max-len - let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; - - // PR list - if (!_.isEmpty(sortedPRList)) { - issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; - _.each(sortedPRList, (URL) => { - issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; - issueBody += ` ${URL}\r\n`; - }); - issueBody += '\r\n\r\n'; - } + const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))); + return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => { + // The format of this map is following: + // { + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' + // } + const internalQAPRMap = _.reduce( + results, + (acc, {url, mergerLogin}) => { + acc[url] = mergerLogin; + return acc; + }, + {}, + ); + console.log('Found the following Internal QA PRs:', internalQAPRMap); + + const noQAPRs = _.pluck( + _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), + 'html_url', + ); + console.log('Found the following NO QA PRs:', noQAPRs); + const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); + + const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); + const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); + + // Tag version and comparison URL + // eslint-disable-next-line max-len + let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; + + // PR list + if (!_.isEmpty(sortedPRList)) { + issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; + _.each(sortedPRList, (URL) => { + issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; + issueBody += ` ${URL}\r\n`; + }); + issueBody += '\r\n\r\n'; + } - // Internal QA PR list - if (!_.isEmpty(internalQAPRMap)) { - console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); - issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); - issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; - issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Internal QA PR list + if (!_.isEmpty(internalQAPRMap)) { + console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); + issueBody += '**Internal QA:**\r\n'; + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; + issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; + issueBody += `${URL}`; + issueBody += ` - ${mergerMention}`; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - // Deploy blockers - if (!_.isEmpty(deployBlockers)) { - issueBody += '**Deploy Blockers:**\r\n'; - _.each(sortedDeployBlockers, (URL) => { - issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; - issueBody += URL; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Deploy blockers + if (!_.isEmpty(deployBlockers)) { + issueBody += '**Deploy Blockers:**\r\n'; + _.each(sortedDeployBlockers, (URL) => { + issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; + issueBody += URL; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - issueBody += '**Deployer verifications:**'; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isTimingDashboardChecked ? 'x' : ' ' - }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isFirebaseChecked ? 'x' : ' ' - }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; - - issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + issueBody += '**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isTimingDashboardChecked ? 'x' : ' ' + }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isFirebaseChecked ? 'x' : ' ' + }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; + + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; + const issueAssignees = _.uniq(_.values(internalQAPRMap)); + const issue = {issueBody, issueAssignees}; + return issue; + }); }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } @@ -498,6 +502,20 @@ class GithubUtils { .catch((err) => console.error('Failed to get PR list', err)); } + /** + * @param {Number} pullRequestNumber + * @returns {Promise} + */ + static getPullRequestMergerLogin(pullRequestNumber) { + return this.octokit.pulls + .get({ + owner: CONST.GITHUB_OWNER, + repo: CONST.APP_REPO, + pull_number: pullRequestNumber, + }) + .then(({data: pullRequest}) => pullRequest.merged_by.login); + } + /** * @param {Number} pullRequestNumber * @returns {Promise} diff --git a/.github/actions/javascript/reopenIssueWithComment/index.js b/.github/actions/javascript/reopenIssueWithComment/index.js index 6a5f89badb5e..9c740914dc1b 100644 --- a/.github/actions/javascript/reopenIssueWithComment/index.js +++ b/.github/actions/javascript/reopenIssueWithComment/index.js @@ -255,7 +255,7 @@ class GithubUtils { } /** - * Generate the issue body for a StagingDeployCash. + * Generate the issue body and assignees for a StagingDeployCash. * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash @@ -268,7 +268,7 @@ class GithubUtils { * @param {Boolean} [isGHStatusChecked] * @returns {Promise} */ - static generateStagingDeployCashBody( + static generateStagingDeployCashBodyAndAssignees( tag, PRList, verifiedPRList = [], @@ -281,85 +281,89 @@ class GithubUtils { ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { - // The format of this map is following: - // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] - // } - const internalQAPRMap = _.reduce( - _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), - (map, pr) => { - // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); - return map; - }, - {}, - ); - console.log('Found the following Internal QA PRs:', internalQAPRMap); - - const noQAPRs = _.pluck( - _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), - 'html_url', - ); - console.log('Found the following NO QA PRs:', noQAPRs); - const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); - - const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); - const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); - - // Tag version and comparison URL - // eslint-disable-next-line max-len - let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; - - // PR list - if (!_.isEmpty(sortedPRList)) { - issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; - _.each(sortedPRList, (URL) => { - issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; - issueBody += ` ${URL}\r\n`; - }); - issueBody += '\r\n\r\n'; - } + const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))); + return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => { + // The format of this map is following: + // { + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' + // } + const internalQAPRMap = _.reduce( + results, + (acc, {url, mergerLogin}) => { + acc[url] = mergerLogin; + return acc; + }, + {}, + ); + console.log('Found the following Internal QA PRs:', internalQAPRMap); + + const noQAPRs = _.pluck( + _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), + 'html_url', + ); + console.log('Found the following NO QA PRs:', noQAPRs); + const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); + + const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); + const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); + + // Tag version and comparison URL + // eslint-disable-next-line max-len + let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; + + // PR list + if (!_.isEmpty(sortedPRList)) { + issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; + _.each(sortedPRList, (URL) => { + issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; + issueBody += ` ${URL}\r\n`; + }); + issueBody += '\r\n\r\n'; + } - // Internal QA PR list - if (!_.isEmpty(internalQAPRMap)) { - console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); - issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); - issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; - issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Internal QA PR list + if (!_.isEmpty(internalQAPRMap)) { + console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); + issueBody += '**Internal QA:**\r\n'; + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; + issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; + issueBody += `${URL}`; + issueBody += ` - ${mergerMention}`; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - // Deploy blockers - if (!_.isEmpty(deployBlockers)) { - issueBody += '**Deploy Blockers:**\r\n'; - _.each(sortedDeployBlockers, (URL) => { - issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; - issueBody += URL; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Deploy blockers + if (!_.isEmpty(deployBlockers)) { + issueBody += '**Deploy Blockers:**\r\n'; + _.each(sortedDeployBlockers, (URL) => { + issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; + issueBody += URL; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - issueBody += '**Deployer verifications:**'; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isTimingDashboardChecked ? 'x' : ' ' - }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isFirebaseChecked ? 'x' : ' ' - }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; - - issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + issueBody += '**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isTimingDashboardChecked ? 'x' : ' ' + }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isFirebaseChecked ? 'x' : ' ' + }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; + + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; + const issueAssignees = _.uniq(_.values(internalQAPRMap)); + const issue = {issueBody, issueAssignees}; + return issue; + }); }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } @@ -393,6 +397,20 @@ class GithubUtils { .catch((err) => console.error('Failed to get PR list', err)); } + /** + * @param {Number} pullRequestNumber + * @returns {Promise} + */ + static getPullRequestMergerLogin(pullRequestNumber) { + return this.octokit.pulls + .get({ + owner: CONST.GITHUB_OWNER, + repo: CONST.APP_REPO, + pull_number: pullRequestNumber, + }) + .then(({data: pullRequest}) => pullRequest.merged_by.login); + } + /** * @param {Number} pullRequestNumber * @returns {Promise} diff --git a/.github/actions/javascript/reviewerChecklist/index.js b/.github/actions/javascript/reviewerChecklist/index.js index 322b529b89bf..7b162f06840d 100644 --- a/.github/actions/javascript/reviewerChecklist/index.js +++ b/.github/actions/javascript/reviewerChecklist/index.js @@ -255,7 +255,7 @@ class GithubUtils { } /** - * Generate the issue body for a StagingDeployCash. + * Generate the issue body and assignees for a StagingDeployCash. * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash @@ -268,7 +268,7 @@ class GithubUtils { * @param {Boolean} [isGHStatusChecked] * @returns {Promise} */ - static generateStagingDeployCashBody( + static generateStagingDeployCashBodyAndAssignees( tag, PRList, verifiedPRList = [], @@ -281,85 +281,89 @@ class GithubUtils { ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { - // The format of this map is following: - // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] - // } - const internalQAPRMap = _.reduce( - _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), - (map, pr) => { - // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); - return map; - }, - {}, - ); - console.log('Found the following Internal QA PRs:', internalQAPRMap); - - const noQAPRs = _.pluck( - _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), - 'html_url', - ); - console.log('Found the following NO QA PRs:', noQAPRs); - const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); - - const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); - const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); - - // Tag version and comparison URL - // eslint-disable-next-line max-len - let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; - - // PR list - if (!_.isEmpty(sortedPRList)) { - issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; - _.each(sortedPRList, (URL) => { - issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; - issueBody += ` ${URL}\r\n`; - }); - issueBody += '\r\n\r\n'; - } + const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))); + return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => { + // The format of this map is following: + // { + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' + // } + const internalQAPRMap = _.reduce( + results, + (acc, {url, mergerLogin}) => { + acc[url] = mergerLogin; + return acc; + }, + {}, + ); + console.log('Found the following Internal QA PRs:', internalQAPRMap); + + const noQAPRs = _.pluck( + _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), + 'html_url', + ); + console.log('Found the following NO QA PRs:', noQAPRs); + const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); + + const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); + const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); + + // Tag version and comparison URL + // eslint-disable-next-line max-len + let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; + + // PR list + if (!_.isEmpty(sortedPRList)) { + issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; + _.each(sortedPRList, (URL) => { + issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; + issueBody += ` ${URL}\r\n`; + }); + issueBody += '\r\n\r\n'; + } - // Internal QA PR list - if (!_.isEmpty(internalQAPRMap)) { - console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); - issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); - issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; - issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Internal QA PR list + if (!_.isEmpty(internalQAPRMap)) { + console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); + issueBody += '**Internal QA:**\r\n'; + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; + issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; + issueBody += `${URL}`; + issueBody += ` - ${mergerMention}`; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - // Deploy blockers - if (!_.isEmpty(deployBlockers)) { - issueBody += '**Deploy Blockers:**\r\n'; - _.each(sortedDeployBlockers, (URL) => { - issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; - issueBody += URL; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Deploy blockers + if (!_.isEmpty(deployBlockers)) { + issueBody += '**Deploy Blockers:**\r\n'; + _.each(sortedDeployBlockers, (URL) => { + issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; + issueBody += URL; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - issueBody += '**Deployer verifications:**'; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isTimingDashboardChecked ? 'x' : ' ' - }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isFirebaseChecked ? 'x' : ' ' - }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; - - issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + issueBody += '**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isTimingDashboardChecked ? 'x' : ' ' + }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isFirebaseChecked ? 'x' : ' ' + }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; + + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; + const issueAssignees = _.uniq(_.values(internalQAPRMap)); + const issue = {issueBody, issueAssignees}; + return issue; + }); }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } @@ -393,6 +397,20 @@ class GithubUtils { .catch((err) => console.error('Failed to get PR list', err)); } + /** + * @param {Number} pullRequestNumber + * @returns {Promise} + */ + static getPullRequestMergerLogin(pullRequestNumber) { + return this.octokit.pulls + .get({ + owner: CONST.GITHUB_OWNER, + repo: CONST.APP_REPO, + pull_number: pullRequestNumber, + }) + .then(({data: pullRequest}) => pullRequest.merged_by.login); + } + /** * @param {Number} pullRequestNumber * @returns {Promise} diff --git a/.github/actions/javascript/verifySignedCommits/index.js b/.github/actions/javascript/verifySignedCommits/index.js index ba188d3a2b86..07173cb19bc5 100644 --- a/.github/actions/javascript/verifySignedCommits/index.js +++ b/.github/actions/javascript/verifySignedCommits/index.js @@ -255,7 +255,7 @@ class GithubUtils { } /** - * Generate the issue body for a StagingDeployCash. + * Generate the issue body and assignees for a StagingDeployCash. * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash @@ -268,7 +268,7 @@ class GithubUtils { * @param {Boolean} [isGHStatusChecked] * @returns {Promise} */ - static generateStagingDeployCashBody( + static generateStagingDeployCashBodyAndAssignees( tag, PRList, verifiedPRList = [], @@ -281,85 +281,89 @@ class GithubUtils { ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { - // The format of this map is following: - // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] - // } - const internalQAPRMap = _.reduce( - _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), - (map, pr) => { - // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); - return map; - }, - {}, - ); - console.log('Found the following Internal QA PRs:', internalQAPRMap); - - const noQAPRs = _.pluck( - _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), - 'html_url', - ); - console.log('Found the following NO QA PRs:', noQAPRs); - const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); - - const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); - const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); - - // Tag version and comparison URL - // eslint-disable-next-line max-len - let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; - - // PR list - if (!_.isEmpty(sortedPRList)) { - issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; - _.each(sortedPRList, (URL) => { - issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; - issueBody += ` ${URL}\r\n`; - }); - issueBody += '\r\n\r\n'; - } + const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))); + return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => { + // The format of this map is following: + // { + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' + // } + const internalQAPRMap = _.reduce( + results, + (acc, {url, mergerLogin}) => { + acc[url] = mergerLogin; + return acc; + }, + {}, + ); + console.log('Found the following Internal QA PRs:', internalQAPRMap); + + const noQAPRs = _.pluck( + _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), + 'html_url', + ); + console.log('Found the following NO QA PRs:', noQAPRs); + const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); + + const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); + const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); + + // Tag version and comparison URL + // eslint-disable-next-line max-len + let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; + + // PR list + if (!_.isEmpty(sortedPRList)) { + issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; + _.each(sortedPRList, (URL) => { + issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; + issueBody += ` ${URL}\r\n`; + }); + issueBody += '\r\n\r\n'; + } - // Internal QA PR list - if (!_.isEmpty(internalQAPRMap)) { - console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); - issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); - issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; - issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Internal QA PR list + if (!_.isEmpty(internalQAPRMap)) { + console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); + issueBody += '**Internal QA:**\r\n'; + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; + issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; + issueBody += `${URL}`; + issueBody += ` - ${mergerMention}`; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - // Deploy blockers - if (!_.isEmpty(deployBlockers)) { - issueBody += '**Deploy Blockers:**\r\n'; - _.each(sortedDeployBlockers, (URL) => { - issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; - issueBody += URL; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } + // Deploy blockers + if (!_.isEmpty(deployBlockers)) { + issueBody += '**Deploy Blockers:**\r\n'; + _.each(sortedDeployBlockers, (URL) => { + issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; + issueBody += URL; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } - issueBody += '**Deployer verifications:**'; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isTimingDashboardChecked ? 'x' : ' ' - }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isFirebaseChecked ? 'x' : ' ' - }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; - - issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + issueBody += '**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isTimingDashboardChecked ? 'x' : ' ' + }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isFirebaseChecked ? 'x' : ' ' + }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; + + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; + const issueAssignees = _.uniq(_.values(internalQAPRMap)); + const issue = {issueBody, issueAssignees}; + return issue; + }); }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } @@ -393,6 +397,20 @@ class GithubUtils { .catch((err) => console.error('Failed to get PR list', err)); } + /** + * @param {Number} pullRequestNumber + * @returns {Promise} + */ + static getPullRequestMergerLogin(pullRequestNumber) { + return this.octokit.pulls + .get({ + owner: CONST.GITHUB_OWNER, + repo: CONST.APP_REPO, + pull_number: pullRequestNumber, + }) + .then(({data: pullRequest}) => pullRequest.merged_by.login); + } + /** * @param {Number} pullRequestNumber * @returns {Promise} diff --git a/.github/libs/GithubUtils.js b/.github/libs/GithubUtils.js index 0cd407c78153..47ad2440c165 100644 --- a/.github/libs/GithubUtils.js +++ b/.github/libs/GithubUtils.js @@ -222,7 +222,7 @@ class GithubUtils { } /** - * Generate the issue body for a StagingDeployCash. + * Generate the issue body and assignees for a StagingDeployCash. * * @param {String} tag * @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash @@ -235,7 +235,7 @@ class GithubUtils { * @param {Boolean} [isGHStatusChecked] * @returns {Promise} */ - static generateStagingDeployCashBody( + static generateStagingDeployCashBodyAndAssignees( tag, PRList, verifiedPRList = [], @@ -248,85 +248,89 @@ class GithubUtils { ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { - // The format of this map is following: - // { - // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ], - // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ] - // } - const internalQAPRMap = _.reduce( - _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))), - (map, pr) => { - // eslint-disable-next-line no-param-reassign - map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login')); - return map; - }, - {}, - ); - console.log('Found the following Internal QA PRs:', internalQAPRMap); - - const noQAPRs = _.pluck( - _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), - 'html_url', - ); - console.log('Found the following NO QA PRs:', noQAPRs); - const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); - - const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); - const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); - - // Tag version and comparison URL - // eslint-disable-next-line max-len - let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; - - // PR list - if (!_.isEmpty(sortedPRList)) { - issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; - _.each(sortedPRList, (URL) => { - issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; - issueBody += ` ${URL}\r\n`; - }); - issueBody += '\r\n\r\n'; - } - - // Internal QA PR list - if (!_.isEmpty(internalQAPRMap)) { - console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); - issueBody += '**Internal QA:**\r\n'; - _.each(internalQAPRMap, (assignees, URL) => { - const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, ''); - issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; - issueBody += `${URL}`; - issueBody += ` -${assigneeMentions}`; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } - - // Deploy blockers - if (!_.isEmpty(deployBlockers)) { - issueBody += '**Deploy Blockers:**\r\n'; - _.each(sortedDeployBlockers, (URL) => { - issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; - issueBody += URL; - issueBody += '\r\n'; - }); - issueBody += '\r\n\r\n'; - } - - issueBody += '**Deployer verifications:**'; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isTimingDashboardChecked ? 'x' : ' ' - }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${ - isFirebaseChecked ? 'x' : ' ' - }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; - // eslint-disable-next-line max-len - issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; - - issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; + const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))); + return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => { + // The format of this map is following: + // { + // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv', + // 'https://github.com/Expensify/App/pull/9642': 'mountiny' + // } + const internalQAPRMap = _.reduce( + results, + (acc, {url, mergerLogin}) => { + acc[url] = mergerLogin; + return acc; + }, + {}, + ); + console.log('Found the following Internal QA PRs:', internalQAPRMap); + + const noQAPRs = _.pluck( + _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)), + 'html_url', + ); + console.log('Found the following NO QA PRs:', noQAPRs); + const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs); + + const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value(); + const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL); + + // Tag version and comparison URL + // eslint-disable-next-line max-len + let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`; + + // PR list + if (!_.isEmpty(sortedPRList)) { + issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n'; + _.each(sortedPRList, (URL) => { + issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]'; + issueBody += ` ${URL}\r\n`; + }); + issueBody += '\r\n\r\n'; + } + + // Internal QA PR list + if (!_.isEmpty(internalQAPRMap)) { + console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs); + issueBody += '**Internal QA:**\r\n'; + _.each(internalQAPRMap, (merger, URL) => { + const mergerMention = `@${merger}`; + issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `; + issueBody += `${URL}`; + issueBody += ` - ${mergerMention}`; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } + + // Deploy blockers + if (!_.isEmpty(deployBlockers)) { + issueBody += '**Deploy Blockers:**\r\n'; + _.each(sortedDeployBlockers, (URL) => { + issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] '; + issueBody += URL; + issueBody += '\r\n'; + }); + issueBody += '\r\n\r\n'; + } + + issueBody += '**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isTimingDashboardChecked ? 'x' : ' ' + }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${ + isFirebaseChecked ? 'x' : ' ' + }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`; + + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; + const issueAssignees = _.uniq(_.values(internalQAPRMap)); + const issue = {issueBody, issueAssignees}; + return issue; + }); }) .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err)); } @@ -360,6 +364,20 @@ class GithubUtils { .catch((err) => console.error('Failed to get PR list', err)); } + /** + * @param {Number} pullRequestNumber + * @returns {Promise} + */ + static getPullRequestMergerLogin(pullRequestNumber) { + return this.octokit.pulls + .get({ + owner: CONST.GITHUB_OWNER, + repo: CONST.APP_REPO, + pull_number: pullRequestNumber, + }) + .then(({data: pullRequest}) => pullRequest.merged_by.login); + } + /** * @param {Number} pullRequestNumber * @returns {Promise} diff --git a/__mocks__/@react-native-clipboard/clipboard.js b/__mocks__/@react-native-clipboard/clipboard.js deleted file mode 100644 index e56e290c3cc9..000000000000 --- a/__mocks__/@react-native-clipboard/clipboard.js +++ /dev/null @@ -1,3 +0,0 @@ -import MockClipboard from '@react-native-clipboard/clipboard/jest/clipboard-mock'; - -export default MockClipboard; diff --git a/__mocks__/@react-native-clipboard/clipboard.ts b/__mocks__/@react-native-clipboard/clipboard.ts new file mode 100644 index 000000000000..75b6f09f5345 --- /dev/null +++ b/__mocks__/@react-native-clipboard/clipboard.ts @@ -0,0 +1,3 @@ +import clipboardMock from '@react-native-clipboard/clipboard/jest/clipboard-mock'; + +export default clipboardMock; diff --git a/__mocks__/react-native-document-picker.js b/__mocks__/react-native-document-picker.ts similarity index 64% rename from __mocks__/react-native-document-picker.js rename to __mocks__/react-native-document-picker.ts index 8cba2bc1eba4..6d26a0227fc3 100644 --- a/__mocks__/react-native-document-picker.js +++ b/__mocks__/react-native-document-picker.ts @@ -1,9 +1,16 @@ -export default { - getConstants: jest.fn(), +import type {pick, pickDirectory, releaseSecureAccess, types} from 'react-native-document-picker'; + +type ReactNativeDocumentPickerMock = { + pick: typeof pick; + releaseSecureAccess: typeof releaseSecureAccess; + pickDirectory: typeof pickDirectory; + types: typeof types; +}; + +const reactNativeDocumentPickerMock: ReactNativeDocumentPickerMock = { pick: jest.fn(), releaseSecureAccess: jest.fn(), pickDirectory: jest.fn(), - types: Object.freeze({ allFiles: 'public.item', audio: 'public.audio', @@ -21,3 +28,5 @@ export default { zip: 'public.zip-archive', }), }; + +export default reactNativeDocumentPickerMock; diff --git a/android/app/build.gradle b/android/app/build.gradle index 51f7ffc466b6..62e30858e73c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,8 +98,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001045601 - versionName "1.4.56-1" + versionCode 1001045602 + versionName "1.4.56-2" } flavorDimensions "default" diff --git a/desktop/package-lock.json b/desktop/package-lock.json index b8ae9d0a2be5..efa8a25a0614 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -10,7 +10,7 @@ "electron-context-menu": "^2.3.0", "electron-log": "^4.4.8", "electron-serve": "^1.3.0", - "electron-updater": "^6.1.9", + "electron-updater": "^6.2.1", "node-machine-id": "^1.1.12" } }, @@ -156,9 +156,9 @@ } }, "node_modules/electron-updater": { - "version": "6.1.9", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.9.tgz", - "integrity": "sha512-omoTwGSG+/H8G62cEZS/dc5Lmc4HohAd4198AP+JNv8H7bfxXUCKekaR6WpsN1n2DiWzvcqOusfGSogZv/uj9w==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.2.1.tgz", + "integrity": "sha512-83eKIPW14qwZqUUM6wdsIRwVKZyjmHxQ4/8G+1C6iS5PdDt7b1umYQyj1/qPpH510GmHEQe4q0kCPe3qmb3a0Q==", "dependencies": { "builder-util-runtime": "9.2.4", "fs-extra": "^10.1.0", @@ -541,9 +541,9 @@ "integrity": "sha512-OEC/48ZBJxR6XNSZtCl4cKPyQ1lvsu8yp8GdCplMWwGS1eEyMcEmzML5BRs/io/RLDnpgyf+7rSL+X6ICifRIg==" }, "electron-updater": { - "version": "6.1.9", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.9.tgz", - "integrity": "sha512-omoTwGSG+/H8G62cEZS/dc5Lmc4HohAd4198AP+JNv8H7bfxXUCKekaR6WpsN1n2DiWzvcqOusfGSogZv/uj9w==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.2.1.tgz", + "integrity": "sha512-83eKIPW14qwZqUUM6wdsIRwVKZyjmHxQ4/8G+1C6iS5PdDt7b1umYQyj1/qPpH510GmHEQe4q0kCPe3qmb3a0Q==", "requires": { "builder-util-runtime": "9.2.4", "fs-extra": "^10.1.0", diff --git a/desktop/package.json b/desktop/package.json index 606fcac92500..4249f3fcfba9 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -7,7 +7,7 @@ "electron-context-menu": "^2.3.0", "electron-log": "^4.4.8", "electron-serve": "^1.3.0", - "electron-updater": "^6.1.9", + "electron-updater": "^6.2.1", "node-machine-id": "^1.1.12" }, "author": "Expensify, Inc.", diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index e8f3bc7c38e2..a962c69f0bc6 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.56.1 + 1.4.56.2 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index fec078bdb26c..9f20eb574abc 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.56.1 + 1.4.56.2 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 3fce69f13cdc..2319ff879a03 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.56 CFBundleVersion - 1.4.56.1 + 1.4.56.2 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 9f0916546172..fcebc3cd46dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.56-1", + "version": "1.4.56-2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.56-1", + "version": "1.4.56-2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index e6531ae5e7e7..c41afac9d570 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.56-1", + "version": "1.4.56-2", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -50,8 +50,8 @@ "analyze-packages": "ANALYZE_BUNDLE=true webpack --config config/webpack/webpack.common.js --env envFile=.env.production", "symbolicate:android": "npx metro-symbolicate android/app/build/generated/sourcemaps/react/release/index.android.bundle.map", "symbolicate:ios": "npx metro-symbolicate main.jsbundle.map", - "symbolicate-release:ios": "scripts/release-profile.js --platform=ios", - "symbolicate-release:android": "scripts/release-profile.js --platform=android", + "symbolicate-release:ios": "scripts/release-profile.ts --platform=ios", + "symbolicate-release:android": "scripts/release-profile.ts --platform=android", "test:e2e": "ts-node tests/e2e/testRunner.ts --config ./config.local.ts", "test:e2e:dev": "ts-node tests/e2e/testRunner.ts --config ./config.dev.ts", "gh-actions-unused-styles": "./.github/scripts/findUnusedKeys.sh", diff --git a/scripts/release-profile.js b/scripts/release-profile.ts similarity index 83% rename from scripts/release-profile.js rename to scripts/release-profile.ts index 0f96232bcdca..8ec0979f9f9e 100755 --- a/scripts/release-profile.js +++ b/scripts/release-profile.ts @@ -1,13 +1,15 @@ -#!/usr/bin/env node +#!/usr/bin/env ts-node + /* eslint-disable no-console */ +import {execSync} from 'child_process'; +import fs from 'fs'; -const fs = require('fs'); -const {execSync} = require('child_process'); +type ArgsMap = Record; // Function to parse command-line arguments into a key-value object -function parseCommandLineArguments() { +function parseCommandLineArguments(): ArgsMap { const args = process.argv.slice(2); // Skip node and script paths - const argsMap = {}; + const argsMap: ArgsMap = {}; args.forEach((arg) => { const [key, value] = arg.split('='); if (key.startsWith('--')) { @@ -20,14 +22,13 @@ function parseCommandLineArguments() { // Function to find .cpuprofile files in the current directory function findCpuProfileFiles() { const files = fs.readdirSync(process.cwd()); - // eslint-disable-next-line rulesdir/prefer-underscore-method return files.filter((file) => file.endsWith('.cpuprofile')); } const argsMap = parseCommandLineArguments(); // Determine sourcemapPath based on the platform flag passed -let sourcemapPath; +let sourcemapPath: string | undefined; if (argsMap.platform === 'ios') { sourcemapPath = 'main.jsbundle.map'; } else if (argsMap.platform === 'android') { @@ -57,7 +58,10 @@ if (cpuProfiles.length === 0) { const output = execSync(command, {stdio: 'inherit'}); console.log(output.toString()); } catch (error) { - console.error(`Error executing command: ${error}`); + if (error instanceof Error) { + console.error(`Error executing command: ${error.toString()}`); + } + process.exit(1); } } diff --git a/src/CONST.ts b/src/CONST.ts index 532939c10f1a..d13c3b374f5a 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1679,7 +1679,7 @@ const CONST = { POLICY_ID_FROM_PATH: /\/w\/([a-zA-Z0-9]+)(\/|$)/, - SHORT_MENTION: new RegExp(`@[\\w\\-\\+\\'#]+(?:\\.[\\w\\-\\'\\+]+)*`, 'gim'), + SHORT_MENTION: new RegExp(`@[\\w\\-\\+\\'#@]+(?:\\.[\\w\\-\\'\\+]+)*`, 'gim'), }, PRONOUNS: { diff --git a/src/components/HeaderWithBackButton/index.tsx b/src/components/HeaderWithBackButton/index.tsx index 21f3e9a3b605..f3293596aa46 100755 --- a/src/components/HeaderWithBackButton/index.tsx +++ b/src/components/HeaderWithBackButton/index.tsx @@ -15,7 +15,6 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useThrottledButtonState from '@hooks/useThrottledButtonState'; -import useWaitForNavigation from '@hooks/useWaitForNavigation'; import getButtonState from '@libs/getButtonState'; import Navigation from '@libs/Navigation/Navigation'; import variables from '@styles/variables'; @@ -58,7 +57,6 @@ function HeaderWithBackButton({ children = null, shouldOverlayDots = false, shouldOverlay = false, - singleExecution = (func) => func, shouldNavigateToTopMostReport = false, style, }: HeaderWithBackButtonProps) { @@ -68,7 +66,6 @@ function HeaderWithBackButton({ const [isDownloadButtonActive, temporarilyDisableDownloadButton] = useThrottledButtonState(); const {translate} = useLocalize(); const {isKeyboardShown} = useKeyboardState(); - const waitForNavigate = useWaitForNavigation(); // If the icon is present, the header bar should be taller and use different font. const isCentralPaneSettings = !!icon; @@ -175,7 +172,7 @@ function HeaderWithBackButton({ Navigation.navigate(ROUTES.GET_ASSISTANCE.getRoute(guidesCallTaskID, Navigation.getActiveRoute()))))} + onPress={() => Navigation.navigate(ROUTES.GET_ASSISTANCE.getRoute(guidesCallTaskID, Navigation.getActiveRoute()))} style={[styles.touchableButtonImage]} role="button" accessibilityLabel={translate('getAssistancePage.questionMarkButtonTooltip')} diff --git a/src/components/ScreenWrapper.tsx b/src/components/ScreenWrapper.tsx index 827eec8088a6..b78e274371ca 100644 --- a/src/components/ScreenWrapper.tsx +++ b/src/components/ScreenWrapper.tsx @@ -23,6 +23,7 @@ import KeyboardAvoidingView from './KeyboardAvoidingView'; import OfflineIndicator from './OfflineIndicator'; import SafeAreaConsumer from './SafeAreaConsumer'; import TestToolsModal from './TestToolsModal'; +import withNavigationFallback from './withNavigationFallback'; type ChildrenProps = { insets: EdgeInsets; @@ -279,4 +280,4 @@ function ScreenWrapper( ScreenWrapper.displayName = 'ScreenWrapper'; -export default forwardRef(ScreenWrapper); +export default withNavigationFallback(forwardRef(ScreenWrapper)); diff --git a/src/components/SettlementButton.tsx b/src/components/SettlementButton.tsx index 0ea8ea308d6a..690a9485f099 100644 --- a/src/components/SettlementButton.tsx +++ b/src/components/SettlementButton.tsx @@ -143,9 +143,8 @@ function SettlementButton({ const session = useSession(); const chatReport = ReportUtils.getReport(chatReportID); const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(chatReport as OnyxEntry); - const shouldShowPaywithExpensifyOption = - !isPaidGroupPolicy || - (!shouldHidePaymentOptions && policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES && policy?.reimburserEmail === session?.email); + const shouldShowPaywithExpensifyOption = !isPaidGroupPolicy || (!shouldHidePaymentOptions && ReportUtils.isPayer(session, iouReport as OnyxEntry)); + const shouldShowPayElsewhereOption = !isPaidGroupPolicy || policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL; const paymentButtonOptions = useMemo(() => { const buttonOptions = []; const isExpenseReport = ReportUtils.isExpenseReport(iouReport); @@ -189,7 +188,9 @@ function SettlementButton({ if (isExpenseReport && shouldShowPaywithExpensifyOption) { buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.VBBA]); } - buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.ELSEWHERE]); + if (shouldShowPayElsewhereOption) { + buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.ELSEWHERE]); + } if (shouldShowApproveButton) { buttonOptions.push(approveButtonOption); diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index f501244a725d..3b3e24867a4f 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -5,9 +5,9 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {PolicyTagList, ReportAction} from '@src/types/onyx'; import * as CurrencyUtils from './CurrencyUtils'; import DateUtils from './DateUtils'; +import getReportPolicyID from './getReportPolicyID'; import * as Localize from './Localize'; import * as PolicyUtils from './PolicyUtils'; -import * as ReportUtils from './ReportUtils'; import type {ExpenseOriginalMessage} from './ReportUtils'; import * as TransactionUtils from './TransactionUtils'; @@ -97,12 +97,12 @@ function getForDistanceRequest(newDistance: string, oldDistance: string, newAmou * ModifiedExpense::getNewDotComment in Web-Expensify should match this. * If we change this function be sure to update the backend as well. */ -function getForReportAction(reportID: string | undefined, reportAction: OnyxEntry): string { +function getForReportAction(reportID: string | undefined, reportAction: OnyxEntry | ReportAction | Record): string { if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE) { return ''; } const reportActionOriginalMessage = reportAction?.originalMessage as ExpenseOriginalMessage | undefined; - const policyID = ReportUtils.getReportPolicyID(reportID) ?? ''; + const policyID = getReportPolicyID(reportID) ?? ''; const removalFragments: string[] = []; const setFragments: string[] = []; diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 1df59508257a..64bac4f46b5a 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -135,7 +135,7 @@ function isReportPreviewAction(reportAction: OnyxEntry): boolean { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW; } -function isModifiedExpenseAction(reportAction: OnyxEntry): boolean { +function isModifiedExpenseAction(reportAction: OnyxEntry | ReportAction | Record): boolean { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9ee97ff8a988..aca25f4926a8 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -55,11 +55,13 @@ import * as store from './actions/ReimbursementAccount/store'; import * as CollectionUtils from './CollectionUtils'; import * as CurrencyUtils from './CurrencyUtils'; import DateUtils from './DateUtils'; +import originalGetReportPolicyID from './getReportPolicyID'; import isReportMessageAttachment from './isReportMessageAttachment'; import localeCompare from './LocaleCompare'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; import {isEmailPublicDomain} from './LoginUtils'; +import ModifiedExpenseMessage from './ModifiedExpenseMessage'; import linkingConfig from './Navigation/linkingConfig'; import Navigation from './Navigation/Navigation'; import * as NumberUtils from './NumberUtils'; @@ -1286,7 +1288,7 @@ function isPayer(session: OnyxEntry, iouReport: OnyxEntry) { if (isPaidGroupPolicy(iouReport)) { if (policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES) { const isReimburser = session?.email === policy?.reimburserEmail; - return isReimburser && (isApproved || isManager); + return (!policy?.reimburserEmail || isReimburser) && (isApproved || isManager); } if (policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL) { return isAdmin && (isApproved || isManager); @@ -2772,6 +2774,9 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu if (parentReportActionMessage && isArchivedRoom(report)) { return `${parentReportActionMessage} (${Localize.translateLocal('common.archived')})`; } + if (ReportActionsUtils.isModifiedExpenseAction(parentReportAction)) { + return ModifiedExpenseMessage.getForReportAction(report?.reportID, parentReportAction); + } return parentReportActionMessage; } @@ -4619,7 +4624,7 @@ function getReportIDFromLink(url: string | null): string { * Get the report policyID given a reportID */ function getReportPolicyID(reportID?: string): string | undefined { - return getReport(reportID)?.policyID; + return originalGetReportPolicyID(reportID); } /** diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 857f6f39173a..5632268ef6ca 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4484,30 +4484,6 @@ function sendMoneyWithWallet(report: OnyxTypes.Report, amount: number, currency: Report.notifyNewAction(params.chatReportID, managerID); } -function canIOUBePaid(iouReport: OnyxEntry | EmptyObject, chatReport: OnyxEntry | EmptyObject, policy: OnyxEntry | EmptyObject) { - const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport); - const iouCanceled = ReportUtils.isArchivedRoom(chatReport); - - if (isEmptyObject(iouReport)) { - return false; - } - - const isPayer = ReportUtils.isPayer( - { - email: currentUserEmail, - accountID: userAccountID, - }, - iouReport, - ); - - const isOpenExpenseReport = isPolicyExpenseChat && ReportUtils.isOpenExpenseReport(iouReport); - const iouSettled = ReportUtils.isSettled(iouReport?.reportID); - - const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport); - const isAutoReimbursable = ReportUtils.canBeAutoReimbursed(iouReport, policy); - return isPayer && !isOpenExpenseReport && !iouSettled && !iouReport?.isWaitingOnBankAccount && reimbursableSpend !== 0 && !iouCanceled && !isAutoReimbursable; -} - function canApproveIOU(iouReport: OnyxEntry | EmptyObject, chatReport: OnyxEntry | EmptyObject, policy: OnyxEntry | EmptyObject) { if (isEmptyObject(chatReport)) { return false; @@ -4534,6 +4510,36 @@ function canApproveIOU(iouReport: OnyxEntry | EmptyObject, cha return isCurrentUserManager && !isOpenExpenseReport && !isApproved && !iouSettled; } +function canIOUBePaid(iouReport: OnyxEntry | EmptyObject, chatReport: OnyxEntry | EmptyObject, policy: OnyxEntry | EmptyObject) { + const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport); + const iouCanceled = ReportUtils.isArchivedRoom(chatReport); + + if (isEmptyObject(iouReport)) { + return false; + } + + if (policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO) { + return false; + } + + const isPayer = ReportUtils.isPayer( + { + email: currentUserEmail, + accountID: userAccountID, + }, + iouReport, + ); + + const isOpenExpenseReport = isPolicyExpenseChat && ReportUtils.isOpenExpenseReport(iouReport); + const iouSettled = ReportUtils.isSettled(iouReport?.reportID); + + const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport); + const isAutoReimbursable = policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES ? false : ReportUtils.canBeAutoReimbursed(iouReport, policy); + const shouldBeApproved = canApproveIOU(iouReport, chatReport, policy); + + return isPayer && !isOpenExpenseReport && !iouSettled && !iouReport?.isWaitingOnBankAccount && reimbursableSpend !== 0 && !iouCanceled && !isAutoReimbursable && !shouldBeApproved; +} + function hasIOUToApproveOrPay(chatReport: OnyxEntry | EmptyObject, excludedIOUReportID: string): boolean { const chatReportActions = ReportActionsUtils.getAllReportActions(chatReport?.reportID ?? ''); diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index ae95424f5776..c83c946aed46 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -10,6 +10,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; +import * as Session from './Session'; let isNetworkOffline = false; Onyx.connect({ @@ -102,6 +103,10 @@ function openLink(href: string, environmentURL: string, isAttachment = false) { // If we are handling a New Expensify link then we will assume this should be opened by the app internally. This ensures that the links are opened internally via react-navigation // instead of in a new tab or with a page refresh (which is the default behavior of an anchor tag) if (internalNewExpensifyPath && hasSameOrigin) { + if (Session.isAnonymousUser() && !Session.canAnonymousUserAccessRoute(internalNewExpensifyPath)) { + Session.signOutAndRedirectToSignIn(); + return; + } Navigation.navigate(internalNewExpensifyPath as Route); return; } diff --git a/src/libs/getReportPolicyID.ts b/src/libs/getReportPolicyID.ts new file mode 100644 index 000000000000..12124f24fbe7 --- /dev/null +++ b/src/libs/getReportPolicyID.ts @@ -0,0 +1,33 @@ +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Report} from '@src/types/onyx'; +import type {EmptyObject} from '@src/types/utils/EmptyObject'; + +let allReports: OnyxCollection; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (value) => (allReports = value), +}); + +/** + * Get the report given a reportID + */ +function getReport(reportID: string | undefined): OnyxEntry | EmptyObject { + if (!allReports) { + return {}; + } + + return allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? {}; +} + +/** + * Get the report policyID given a reportID. + * We need to define this method in a separate file to avoid cyclic dependency. + */ +function getReportPolicyID(reportID?: string): string | undefined { + return getReport(reportID)?.policyID; +} + +export default getReportPolicyID; diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index aa03714394b1..9bc0c4c3a4a8 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -269,7 +269,6 @@ function ReportScreen({ const hasHelpfulErrors = Object.keys(report?.errorFields ?? {}).some((key) => key !== 'notFound'); const shouldHideReport = !hasHelpfulErrors && !ReportUtils.canAccessReport(report, policies, betas); - const isLoading = !reportIDFromRoute || !isSidebarLoaded || PersonalDetailsUtils.isPersonalDetailsEmpty(); const lastReportAction: OnyxEntry = useMemo( () => reportActions.length @@ -331,14 +330,28 @@ function ReportScreen({ return reportIDFromRoute !== '' && !!report.reportID && !isTransitioning; }, [report, reportIDFromRoute]); + const isLoading = !ReportUtils.isValidReportIDFromPath(reportIDFromRoute) || !isSidebarLoaded || PersonalDetailsUtils.isPersonalDetailsEmpty(); const shouldShowSkeleton = isLinkingToMessage || !isCurrentReportLoadedFromOnyx || (reportActions.length === 0 && !!reportMetadata?.isLoadingInitialReportActions) || isLoading || (!!reportActionIDFromRoute && reportMetadata?.isLoadingInitialReportActions); - const shouldShowReportActionList = isCurrentReportLoadedFromOnyx && !isLoading; + // eslint-disable-next-line rulesdir/no-negated-variables + const shouldShowNotFoundPage = useMemo( + (): boolean => + !shouldShowSkeleton && + ((!wasReportAccessibleRef.current && + !firstRenderRef.current && + !report.reportID && + !isOptimisticDelete && + !reportMetadata?.isLoadingInitialReportActions && + !userLeavingStatus) || + shouldHideReport || + (!!reportIDFromRoute && !ReportUtils.isValidReportIDFromPath(reportIDFromRoute))), + [shouldShowSkeleton, report.reportID, isOptimisticDelete, reportMetadata?.isLoadingInitialReportActions, userLeavingStatus, shouldHideReport, reportIDFromRoute], + ); const fetchReport = useCallback(() => { Report.openReport(reportIDFromRoute, reportActionIDFromRoute); @@ -528,21 +541,6 @@ function ReportScreen({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - // eslint-disable-next-line rulesdir/no-negated-variables - const shouldShowNotFoundPage = useMemo( - (): boolean => - (!wasReportAccessibleRef.current && - !firstRenderRef.current && - !report.reportID && - !isOptimisticDelete && - !reportMetadata?.isLoadingInitialReportActions && - !isLoading && - !userLeavingStatus) || - shouldHideReport || - (!!reportIDFromRoute && !ReportUtils.isValidReportIDFromPath(reportIDFromRoute)), - [report, reportMetadata, isLoading, shouldHideReport, isOptimisticDelete, userLeavingStatus, reportIDFromRoute], - ); - const actionListValue = useMemo((): ActionListContextType => ({flatListRef, scrollPosition, setScrollPosition}), [flatListRef, scrollPosition, setScrollPosition]); // This helps in tracking from the moment 'route' triggers useMemo until isLoadingInitialReportActions becomes true. It prevents blinking when loading reportActions from cache. diff --git a/src/stories/HeaderWithBackButton.stories.tsx b/src/stories/HeaderWithBackButton.stories.tsx index 8306d8e19225..ca723715d5f0 100644 --- a/src/stories/HeaderWithBackButton.stories.tsx +++ b/src/stories/HeaderWithBackButton.stories.tsx @@ -2,25 +2,22 @@ import type {ComponentMeta, ComponentStory} from '@storybook/react'; import React from 'react'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import type HeaderWithBackButtonProps from '@components/HeaderWithBackButton/types'; -import withNavigationFallback from '@components/withNavigationFallback'; -const HeaderWithBackButtonWithNavigation = withNavigationFallback(HeaderWithBackButton); - -type HeaderWithBackButtonStory = ComponentStory; +type HeaderWithBackButtonStory = ComponentStory; /** * We use the Component Story Format for writing stories. Follow the docs here: * * https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format */ -const story: ComponentMeta = { +const story: ComponentMeta = { title: 'Components/HeaderWithBackButton', - component: HeaderWithBackButtonWithNavigation, + component: HeaderWithBackButton, }; function Template(props: HeaderWithBackButtonProps) { // eslint-disable-next-line react/jsx-props-no-spreading - return ; + return ; } // Arguments can be passed to the component by binding diff --git a/src/styles/index.ts b/src/styles/index.ts index 120b848bd5a4..a56a858d1707 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1552,7 +1552,6 @@ const styles = (theme: ThemeColors) => breadcrumbLogo: { top: 1.66, // Pixel-perfect alignment due to a small difference between logo height and breadcrumb text height - height: variables.lineHeightSizeh1, }, LHPNavigatorContainer: (isSmallScreenWidth: boolean) => diff --git a/src/types/modules/act.d.ts b/src/types/modules/act.d.ts deleted file mode 100644 index 5fe00ec479cf..000000000000 --- a/src/types/modules/act.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type {StepIdentifier as ActStepIdentifier} from '@kie/act-js'; - -declare module '@kie/act-js' { - // eslint-disable-next-line rulesdir/no-inline-named-export - export declare type StepIdentifier = { - id?: string; - name: string; - run?: string; - mockWith?: string; - with?: string; - envs?: string[]; - inputs?: string[]; - } & Omit; -} diff --git a/src/types/modules/react-native-clipboard.d.ts b/src/types/modules/react-native-clipboard.d.ts index 14f418a3f8b9..c38b3b2ff7cc 100644 --- a/src/types/modules/react-native-clipboard.d.ts +++ b/src/types/modules/react-native-clipboard.d.ts @@ -1,16 +1,7 @@ declare module '@react-native-clipboard/clipboard/jest/clipboard-mock' { - const mockClipboard: { - getString: jest.MockedFunction<() => Promise>; - getImagePNG: jest.MockedFunction<() => void>; - getImageJPG: jest.MockedFunction<() => void>; - setImage: jest.MockedFunction<() => void>; - setString: jest.MockedFunction<() => void>; - hasString: jest.MockedFunction<() => Promise>; - hasImage: jest.MockedFunction<() => Promise>; - hasURL: jest.MockedFunction<() => Promise>; - addListener: jest.MockedFunction<() => void>; - removeAllListeners: jest.MockedFunction<() => void>; - useClipboard: jest.MockedFunction<() => [string, jest.MockedFunction<() => void>]>; - }; - export default mockClipboard; + import type Clipboard from '@react-native-clipboard/clipboard'; + + const clipboardMock: typeof Clipboard; + + export default clipboardMock; } diff --git a/tests/unit/GithubUtilsTest.ts b/tests/unit/GithubUtilsTest.ts index 794139286527..8267b26d3d0f 100644 --- a/tests/unit/GithubUtilsTest.ts +++ b/tests/unit/GithubUtilsTest.ts @@ -355,7 +355,7 @@ describe('GithubUtils', () => { }, { number: 6, - title: '[Internal QA] Test Internal QA PR', + title: '[Internal QA] Another Test Internal QA PR', html_url: 'https://github.com/Expensify/App/pull/6', user: {login: 'testUser'}, labels: [ @@ -368,14 +368,7 @@ describe('GithubUtils', () => { color: 'f29513', }, ], - assignees: [ - { - login: 'octocat', - }, - { - login: 'hubot', - }, - ], + assignees: [], }, { number: 7, @@ -392,16 +385,12 @@ describe('GithubUtils', () => { color: 'f29513', }, ], - assignees: [ - { - login: 'octocat', - }, - { - login: 'hubot', - }, - ], + assignees: [], }, ]; + const mockInternalQaPR = { + merged_by: {login: 'octocat'}, + }; const mockGithub = jest.fn(() => ({ getOctokit: () => ({ rest: { @@ -410,6 +399,7 @@ describe('GithubUtils', () => { }, pulls: { list: jest.fn().mockResolvedValue({data: mockPRs}), + get: jest.fn().mockResolvedValue({data: mockInternalQaPR}), }, }, paginate: jest.fn().mockImplementation((objectMethod: () => Promise>) => objectMethod().then(({data}) => data)), @@ -446,7 +436,7 @@ describe('GithubUtils', () => { const internalQAHeader = '\r\n\r\n**Internal QA:**'; const lineBreak = '\r\n'; const lineBreakDouble = '\r\n\r\n'; - const assignOctocatHubot = ' - @octocat @hubot'; + const assignOctocat = ' - @octocat'; const deployerVerificationsHeader = '\r\n**Deployer verifications:**'; // eslint-disable-next-line max-len const timingDashboardVerification = @@ -468,8 +458,8 @@ describe('GithubUtils', () => { `${lineBreak}`; test('Test no verified PRs', () => { - githubUtils.generateStagingDeployCashBody(tag, basePRList).then((issueBody: string) => { - expect(issueBody).toBe( + githubUtils.generateStagingDeployCashBodyAndAssignees(tag, basePRList).then((issue) => { + expect(issue.issueBody).toBe( `${baseExpectedOutput}` + `${openCheckbox}${basePRList[2]}` + `${lineBreak}${openCheckbox}${basePRList[0]}` + @@ -482,12 +472,13 @@ describe('GithubUtils', () => { `${lineBreak}${openCheckbox}${ghVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, ); + expect(issue.issueAssignees).toEqual([]); }); }); test('Test some verified PRs', () => { - githubUtils.generateStagingDeployCashBody(tag, basePRList, [basePRList[0]]).then((issueBody: string) => { - expect(issueBody).toBe( + githubUtils.generateStagingDeployCashBodyAndAssignees(tag, basePRList, [basePRList[0]]).then((issue) => { + expect(issue.issueBody).toBe( `${baseExpectedOutput}` + `${openCheckbox}${basePRList[2]}` + `${lineBreak}${closedCheckbox}${basePRList[0]}` + @@ -500,12 +491,13 @@ describe('GithubUtils', () => { `${lineBreak}${openCheckbox}${ghVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, ); + expect(issue.issueAssignees).toEqual([]); }); }); test('Test all verified PRs', () => { - githubUtils.generateStagingDeployCashBody(tag, basePRList, basePRList).then((issueBody: string) => { - expect(issueBody).toBe( + githubUtils.generateStagingDeployCashBodyAndAssignees(tag, basePRList, basePRList).then((issue) => { + expect(issue.issueBody).toBe( `${allVerifiedExpectedOutput}` + `${lineBreak}${deployerVerificationsHeader}` + `${lineBreak}${openCheckbox}${timingDashboardVerification}` + @@ -513,12 +505,13 @@ describe('GithubUtils', () => { `${lineBreak}${openCheckbox}${ghVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, ); + expect(issue.issueAssignees).toEqual([]); }); }); test('Test no resolved deploy blockers', () => { - githubUtils.generateStagingDeployCashBody(tag, basePRList, basePRList, baseDeployBlockerList).then((issueBody: string) => { - expect(issueBody).toBe( + githubUtils.generateStagingDeployCashBodyAndAssignees(tag, basePRList, basePRList, baseDeployBlockerList).then((issue) => { + expect(issue.issueBody).toBe( `${allVerifiedExpectedOutput}` + `${lineBreak}${deployBlockerHeader}` + `${lineBreak}${openCheckbox}${baseDeployBlockerList[0]}` + @@ -529,12 +522,13 @@ describe('GithubUtils', () => { `${lineBreak}${openCheckbox}${ghVerification}${lineBreak}` + `${lineBreak}${ccApplauseLeads}`, ); + expect(issue.issueAssignees).toEqual([]); }); }); test('Test some resolved deploy blockers', () => { - githubUtils.generateStagingDeployCashBody(tag, basePRList, basePRList, baseDeployBlockerList, [baseDeployBlockerList[0]]).then((issueBody: string) => { - expect(issueBody).toBe( + githubUtils.generateStagingDeployCashBodyAndAssignees(tag, basePRList, basePRList, baseDeployBlockerList, [baseDeployBlockerList[0]]).then((issue) => { + expect(issue.issueBody).toBe( `${allVerifiedExpectedOutput}` + `${lineBreak}${deployBlockerHeader}` + `${lineBreak}${closedCheckbox}${baseDeployBlockerList[0]}` + @@ -545,12 +539,13 @@ describe('GithubUtils', () => { `${lineBreak}${openCheckbox}${ghVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, ); + expect(issue.issueAssignees).toEqual([]); }); }); test('Test all resolved deploy blockers', () => { - githubUtils.generateStagingDeployCashBody(tag, basePRList, basePRList, baseDeployBlockerList, baseDeployBlockerList).then((issueBody: string) => { - expect(issueBody).toBe( + githubUtils.generateStagingDeployCashBodyAndAssignees(tag, basePRList, basePRList, baseDeployBlockerList, baseDeployBlockerList).then((issue) => { + expect(issue.issueBody).toBe( `${baseExpectedOutput}` + `${closedCheckbox}${basePRList[2]}` + `${lineBreak}${closedCheckbox}${basePRList[0]}` + @@ -566,12 +561,13 @@ describe('GithubUtils', () => { `${lineBreak}${openCheckbox}${ghVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, ); + expect(issue.issueAssignees).toEqual([]); }); }); test('Test internalQA PRs', () => { - githubUtils.generateStagingDeployCashBody(tag, [...basePRList, ...internalQAPRList]).then((issueBody: string) => { - expect(issueBody).toBe( + githubUtils.generateStagingDeployCashBodyAndAssignees(tag, [...basePRList, ...internalQAPRList]).then((issue) => { + expect(issue.issueBody).toBe( `${baseExpectedOutput}` + `${openCheckbox}${basePRList[2]}` + `${lineBreak}${openCheckbox}${basePRList[0]}` + @@ -579,20 +575,21 @@ describe('GithubUtils', () => { `${lineBreak}${closedCheckbox}${basePRList[4]}` + `${lineBreak}${closedCheckbox}${basePRList[5]}` + `${lineBreak}${internalQAHeader}` + - `${lineBreak}${openCheckbox}${internalQAPRList[0]}${assignOctocatHubot}` + - `${lineBreak}${openCheckbox}${internalQAPRList[1]}${assignOctocatHubot}` + + `${lineBreak}${openCheckbox}${internalQAPRList[0]}${assignOctocat}` + + `${lineBreak}${openCheckbox}${internalQAPRList[1]}${assignOctocat}` + `${lineBreakDouble}${deployerVerificationsHeader}` + `${lineBreak}${openCheckbox}${timingDashboardVerification}` + `${lineBreak}${openCheckbox}${firebaseVerification}` + `${lineBreak}${openCheckbox}${ghVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, ); + expect(issue.issueAssignees).toEqual(['octocat']); }); }); test('Test some verified internalQA PRs', () => { - githubUtils.generateStagingDeployCashBody(tag, [...basePRList, ...internalQAPRList], [], [], [], [internalQAPRList[0]]).then((issueBody: string) => { - expect(issueBody).toBe( + githubUtils.generateStagingDeployCashBodyAndAssignees(tag, [...basePRList, ...internalQAPRList], [], [], [], [internalQAPRList[0]]).then((issue) => { + expect(issue.issueBody).toBe( `${baseExpectedOutput}` + `${openCheckbox}${basePRList[2]}` + `${lineBreak}${openCheckbox}${basePRList[0]}` + @@ -600,14 +597,15 @@ describe('GithubUtils', () => { `${lineBreak}${closedCheckbox}${basePRList[4]}` + `${lineBreak}${closedCheckbox}${basePRList[5]}` + `${lineBreak}${internalQAHeader}` + - `${lineBreak}${closedCheckbox}${internalQAPRList[0]}${assignOctocatHubot}` + - `${lineBreak}${openCheckbox}${internalQAPRList[1]}${assignOctocatHubot}` + + `${lineBreak}${closedCheckbox}${internalQAPRList[0]}${assignOctocat}` + + `${lineBreak}${openCheckbox}${internalQAPRList[1]}${assignOctocat}` + `${lineBreakDouble}${deployerVerificationsHeader}` + `${lineBreak}${openCheckbox}${timingDashboardVerification}` + `${lineBreak}${openCheckbox}${firebaseVerification}` + `${lineBreak}${openCheckbox}${ghVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, ); + expect(issue.issueAssignees).toEqual(['octocat']); }); }); }); diff --git a/tsconfig.json b/tsconfig.json index 79413fdd2ca7..d50f97a48aa2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -48,6 +48,6 @@ } }, "exclude": ["**/node_modules/*", "**/dist/*", ".github/actions/**/index.js", "**/docs/*"], - "include": ["src", "desktop", "web", "website", "docs", "assets", "config", "tests", "jest", "__mocks__", ".github/**/*", ".storybook/**/*", "workflow_tests"], + "include": ["src", "desktop", "web", "website", "docs", "assets", "config", "tests", "jest", "__mocks__", ".github/**/*", ".storybook/**/*", "workflow_tests", "scripts"], "extends": "expo/tsconfig.base" } diff --git a/workflow_tests/createNewVersion.test.ts b/workflow_tests/createNewVersion.test.js similarity index 93% rename from workflow_tests/createNewVersion.test.ts rename to workflow_tests/createNewVersion.test.js index 05dcc2b10073..08ac63ba9948 100644 --- a/workflow_tests/createNewVersion.test.ts +++ b/workflow_tests/createNewVersion.test.js @@ -1,13 +1,12 @@ -import {MockGithub} from '@kie/mock-github'; -import path from 'path'; -import assertions from './assertions/createNewVersionAssertions'; -import mocks from './mocks/createNewVersionMocks'; -import ExtendedAct from './utils/ExtendedAct'; -import * as utils from './utils/utils'; +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/createNewVersionAssertions'); +const mocks = require('./mocks/createNewVersionMocks'); +const ExtendedAct = require('./utils/ExtendedAct').default; jest.setTimeout(90 * 1000); // 90 sec -let mockGithub: MockGithub; - +let mockGithub; const FILES_TO_COPY_INTO_TEST_REPO = [ ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), { @@ -17,7 +16,7 @@ const FILES_TO_COPY_INTO_TEST_REPO = [ ]; describe('test workflow createNewVersion', () => { - beforeAll(() => { + beforeAll(async () => { // in case of the tests being interrupted without cleanup the mock repo directory may be left behind // which breaks the next test run, this removes any possible leftovers utils.removeMockRepoDir(); @@ -25,7 +24,7 @@ describe('test workflow createNewVersion', () => { beforeEach(async () => { // create a local repository and copy required files - mockGithub = new MockGithub({ + mockGithub = new kieMockGithub.MockGithub({ repo: { testCreateNewVersionWorkflowRepo: { files: FILES_TO_COPY_INTO_TEST_REPO, @@ -57,7 +56,7 @@ describe('test workflow createNewVersion', () => { describe('actor is admin', () => { const validateActorMockSteps = mocks.CREATENEWVERSION__VALIDATEACTOR__ADMIN__STEP_MOCKS; it('executes full workflow', async () => { - const repoPath = mockGithub.repo.getPath('testCreateNewVersionWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testCreateNewVersionWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'createNewVersion.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams(act, event, {}, secrets, githubToken, {}, inputs); @@ -80,7 +79,7 @@ describe('test workflow createNewVersion', () => { describe('actor is writer', () => { const validateActorMockSteps = mocks.CREATENEWVERSION__VALIDATEACTOR__WRITER__STEP_MOCKS; it('executes full workflow', async () => { - const repoPath = mockGithub.repo.getPath('testCreateNewVersionWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testCreateNewVersionWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'createNewVersion.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams(act, event, {}, secrets, githubToken, {}, inputs); @@ -103,7 +102,7 @@ describe('test workflow createNewVersion', () => { describe('actor is reader', () => { const validateActorMockSteps = mocks.CREATENEWVERSION__VALIDATEACTOR__NO_PERMISSION__STEP_MOCKS; it('stops after validation', async () => { - const repoPath = mockGithub.repo.getPath('testCreateNewVersionWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testCreateNewVersionWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'createNewVersion.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams(act, event, {}, secrets, githubToken, {}, inputs); @@ -125,7 +124,7 @@ describe('test workflow createNewVersion', () => { describe('one step fails', () => { it('announces failure on Slack', async () => { - const repoPath = mockGithub.repo.getPath('testCreateNewVersionWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testCreateNewVersionWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'createNewVersion.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams(act, event, {}, secrets, githubToken, {}, inputs); @@ -134,7 +133,7 @@ describe('test workflow createNewVersion', () => { validateActor: mocks.CREATENEWVERSION__VALIDATEACTOR__ADMIN__STEP_MOCKS, createNewVersion: utils.deepCopy(mocks.CREATENEWVERSION__CREATENEWVERSION__STEP_MOCKS), }; - testMockSteps.createNewVersion[5] = utils.createMockStep('Commit new version', 'Commit new version', 'CREATENEWVERSION', [], [], {}, {}, false); + testMockSteps.createNewVersion[5] = utils.createMockStep('Commit new version', 'Commit new version', 'CREATENEWVERSION', [], [], [], [], false); const result = await act.runEvent(event, { workflowFile: path.join(repoPath, '.github', 'workflows', 'createNewVersion.yml'), mockSteps: testMockSteps, @@ -147,7 +146,7 @@ describe('test workflow createNewVersion', () => { }); it('chooses source branch depending on the SEMVER_LEVEL', async () => { - const repoPath = mockGithub.repo.getPath('testCreateNewVersionWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testCreateNewVersionWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'createNewVersion.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams(act, event, {}, secrets, githubToken, {}, {SEMVER_LEVEL: 'MAJOR'}); diff --git a/workflow_tests/deploy.test.ts b/workflow_tests/deploy.test.js similarity index 93% rename from workflow_tests/deploy.test.ts rename to workflow_tests/deploy.test.js index 4edb3b252d38..cf5b658d3ffb 100644 --- a/workflow_tests/deploy.test.ts +++ b/workflow_tests/deploy.test.js @@ -1,13 +1,12 @@ -import {MockGithub} from '@kie/mock-github'; -import path from 'path'; -import assertions from './assertions/deployAssertions'; -import mocks from './mocks/deployMocks'; -import ExtendedAct from './utils/ExtendedAct'; -import * as utils from './utils/utils'; +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/deployAssertions'); +const mocks = require('./mocks/deployMocks'); +const ExtendedAct = require('./utils/ExtendedAct').default; jest.setTimeout(90 * 1000); -let mockGithub: MockGithub; - +let mockGithub; const FILES_TO_COPY_INTO_TEST_REPO = [ ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), { @@ -17,7 +16,7 @@ const FILES_TO_COPY_INTO_TEST_REPO = [ ]; describe('test workflow deploy', () => { - beforeAll(() => { + beforeAll(async () => { // in case of the tests being interrupted without cleanup the mock repo directory may be left behind // which breaks the next test run, this removes any possible leftovers utils.removeMockRepoDir(); @@ -25,7 +24,7 @@ describe('test workflow deploy', () => { beforeEach(async () => { // create a local repository and copy required files - mockGithub = new MockGithub({ + mockGithub = new kieMockGithub.MockGithub({ repo: { testDeployWorkflowRepo: { files: FILES_TO_COPY_INTO_TEST_REPO, @@ -49,7 +48,7 @@ describe('test workflow deploy', () => { }; describe('push', () => { it('to main - nothing triggered', async () => { - const repoPath = mockGithub.repo.getPath('testDeployWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testDeployWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'deploy.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams( @@ -76,7 +75,7 @@ describe('test workflow deploy', () => { }); it('to staging - deployStaging triggered', async () => { - const repoPath = mockGithub.repo.getPath('testDeployWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testDeployWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'deploy.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams( @@ -103,7 +102,7 @@ describe('test workflow deploy', () => { }); it('to production - deployProduction triggered', async () => { - const repoPath = mockGithub.repo.getPath('testDeployWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testDeployWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'deploy.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams( @@ -131,7 +130,7 @@ describe('test workflow deploy', () => { }); it('different event than push - workflow does not execute', async () => { - const repoPath = mockGithub.repo.getPath('testDeployWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testDeployWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'deploy.yml'); let act = new ExtendedAct(repoPath, workflowPath); const testMockSteps = { diff --git a/workflow_tests/deployBlocker.test.ts b/workflow_tests/deployBlocker.test.js similarity index 92% rename from workflow_tests/deployBlocker.test.ts rename to workflow_tests/deployBlocker.test.js index d6ee67f97f2c..42c4f9e741f7 100644 --- a/workflow_tests/deployBlocker.test.ts +++ b/workflow_tests/deployBlocker.test.js @@ -1,13 +1,12 @@ -import {MockGithub} from '@kie/mock-github'; -import path from 'path'; -import assertions from './assertions/deployBlockerAssertions'; -import mocks from './mocks/deployBlockerMocks'; -import ExtendedAct from './utils/ExtendedAct'; -import * as utils from './utils/utils'; +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/deployBlockerAssertions'); +const mocks = require('./mocks/deployBlockerMocks'); +const ExtendedAct = require('./utils/ExtendedAct').default; jest.setTimeout(90 * 1000); -let mockGithub: MockGithub; - +let mockGithub; const FILES_TO_COPY_INTO_TEST_REPO = [ ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), { @@ -24,7 +23,7 @@ describe('test workflow deployBlocker', () => { SLACK_WEBHOOK: 'dummy_slack_webhook', }; - beforeAll(() => { + beforeAll(async () => { // in case of the tests being interrupted without cleanup the mock repo directory may be left behind // which breaks the next test run, this removes any possible leftovers utils.removeMockRepoDir(); @@ -32,7 +31,7 @@ describe('test workflow deployBlocker', () => { beforeEach(async () => { // create a local repository and copy required files - mockGithub = new MockGithub({ + mockGithub = new kieMockGithub.MockGithub({ repo: { testDeployBlockerWorkflowRepo: { files: FILES_TO_COPY_INTO_TEST_REPO, @@ -58,7 +57,6 @@ describe('test workflow deployBlocker', () => { issue: { title: 'Labeled issue title', number: '1234', - // eslint-disable-next-line @typescript-eslint/naming-convention html_url: 'http://issue.html.url', }, }; @@ -66,7 +64,7 @@ describe('test workflow deployBlocker', () => { const testEventOptions = utils.deepCopy(eventOptions); testEventOptions.label = {name: 'DeployBlockerCash'}; it('runs the workflow and announces success on Slack', async () => { - const repoPath = mockGithub.repo.getPath('testDeployBlockerWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testDeployBlockerWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'deployBlocker.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams(act, event, testEventOptions, secrets, githubToken, {}, {}); @@ -92,7 +90,7 @@ describe('test workflow deployBlocker', () => { }); describe('one step fails', () => { it('announces failure on Slack', async () => { - const repoPath = mockGithub.repo.getPath('testDeployBlockerWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testDeployBlockerWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'deployBlocker.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams(act, event, testEventOptions, secrets, githubToken, {}, {}); @@ -132,7 +130,7 @@ describe('test workflow deployBlocker', () => { const testEventOptions = utils.deepCopy(eventOptions); testEventOptions.label = {name: 'Different Label'}; it('does not run workflow', async () => { - const repoPath = mockGithub.repo.getPath('testDeployBlockerWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testDeployBlockerWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'deployBlocker.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams(act, event, testEventOptions, secrets, githubToken, {}, {}); diff --git a/workflow_tests/failureNotifier.test.js b/workflow_tests/failureNotifier.test.ts similarity index 68% rename from workflow_tests/failureNotifier.test.js rename to workflow_tests/failureNotifier.test.ts index d521c6cde00e..8dfc092c7e61 100644 --- a/workflow_tests/failureNotifier.test.js +++ b/workflow_tests/failureNotifier.test.ts @@ -1,28 +1,31 @@ -const path = require('path'); -const kieMockGithub = require('@kie/mock-github'); -const assertions = require('./assertions/failureNotifierAssertions'); -const mocks = require('./mocks/failureNotifierMocks'); -const ExtendedAct = require('./utils/ExtendedAct').default; +import type {MockStep} from '@kie/act-js/build/src/step-mocker/step-mocker.types'; +import type {CreateRepositoryFile} from '@kie/mock-github'; +import {MockGithub} from '@kie/mock-github'; +import path from 'path'; +import assertions from './assertions/failureNotifierAssertions'; +import mocks from './mocks/failureNotifierMocks'; +import ExtendedAct from './utils/ExtendedAct'; jest.setTimeout(90 * 1000); -let mockGithub; +let mockGithub: MockGithub; + const FILES_TO_COPY_INTO_TEST_REPO = [ { src: path.resolve(__dirname, '..', '.github', 'workflows', 'failureNotifier.yml'), dest: '.github/workflows/failureNotifier.yml', }, -]; +] as const satisfies CreateRepositoryFile[]; describe('test workflow failureNotifier', () => { const actor = 'Dummy Actor'; beforeEach(async () => { // create a local repository and copy required files - mockGithub = new kieMockGithub.MockGithub({ + mockGithub = new MockGithub({ repo: { testFailureNotifierWorkflowRepo: { files: FILES_TO_COPY_INTO_TEST_REPO, - // if any branches besides main are need add: pushedBranches: ['staging', 'production'], + // if any branches besides main are needed add: pushedBranches: ['staging', 'production'], }, }, }); @@ -34,11 +37,12 @@ describe('test workflow failureNotifier', () => { await mockGithub.teardown(); }); it('runs the notify failure when main fails', async () => { - const repoPath = mockGithub.repo.getPath('testFailureNotifierWorkflowRepo') || ''; + const repoPath = mockGithub.repo.getPath('testFailureNotifierWorkflowRepo') ?? ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'failureNotifier.yml'); let act = new ExtendedAct(repoPath, workflowPath); const event = 'workflow_run'; act = act.setEvent({ + // eslint-disable-next-line @typescript-eslint/naming-convention workflow_run: { name: 'Process new code merged to main', conclusion: 'failure', @@ -46,7 +50,8 @@ describe('test workflow failureNotifier', () => { }); const testMockSteps = { notifyFailure: mocks.FAILURENOTIFIER__NOTIFYFAILURE__STEP_MOCKS, - }; + } as const satisfies MockStep; + const result = await act.runEvent(event, { workflowFile: path.join(repoPath, '.github', 'workflows', 'failureNotifier.yml'), mockSteps: testMockSteps, diff --git a/workflow_tests/finishReleaseCycle.test.ts b/workflow_tests/finishReleaseCycle.test.js similarity index 94% rename from workflow_tests/finishReleaseCycle.test.ts rename to workflow_tests/finishReleaseCycle.test.js index 44f9c57ab9da..20ab66471d91 100644 --- a/workflow_tests/finishReleaseCycle.test.ts +++ b/workflow_tests/finishReleaseCycle.test.js @@ -1,13 +1,12 @@ -import {MockGithub} from '@kie/mock-github'; -import path from 'path'; -import assertions from './assertions/finishReleaseCycleAssertions'; -import mocks from './mocks/finishReleaseCycleMocks'; -import ExtendedAct from './utils/ExtendedAct'; -import type {MockJobs} from './utils/JobMocker'; -import * as utils from './utils/utils'; +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/finishReleaseCycleAssertions'); +const mocks = require('./mocks/finishReleaseCycleMocks'); +const ExtendedAct = require('./utils/ExtendedAct').default; jest.setTimeout(90 * 1000); -let mockGithub: MockGithub; +let mockGithub; const FILES_TO_COPY_INTO_TEST_REPO = [ ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), { @@ -17,7 +16,7 @@ const FILES_TO_COPY_INTO_TEST_REPO = [ ]; describe('test workflow finishReleaseCycle', () => { - beforeAll(() => { + beforeAll(async () => { // in case of the tests being interrupted without cleanup the mock repo directory may be left behind // which breaks the next test run, this removes any possible leftovers utils.removeMockRepoDir(); @@ -25,7 +24,7 @@ describe('test workflow finishReleaseCycle', () => { beforeEach(async () => { // create a local repository and copy required files - mockGithub = new MockGithub({ + mockGithub = new kieMockGithub.MockGithub({ repo: { testFinishReleaseCycleWorkflowRepo: { files: FILES_TO_COPY_INTO_TEST_REPO, @@ -51,7 +50,7 @@ describe('test workflow finishReleaseCycle', () => { describe('actor is a team member', () => { describe('no deploy blockers', () => { it('production updated, new version created', async () => { - const repoPath = mockGithub.repo.getPath('testFinishReleaseCycleWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testFinishReleaseCycleWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'finishReleaseCycle.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams( @@ -72,7 +71,7 @@ describe('test workflow finishReleaseCycle', () => { updateProduction: mocks.FINISHRELEASECYCLE__UPDATEPRODUCTION__STEP_MOCKS, updateStaging: mocks.FINISHRELEASECYCLE__UPDATESTAGING__STEP_MOCKS, }; - const testMockJobs: MockJobs = { + const testMockJobs = { createNewPatchVersion: { steps: mocks.FINISHRELEASECYCLE__CREATENEWPATCHVERSION__STEP_MOCKS, outputs: { @@ -97,7 +96,7 @@ describe('test workflow finishReleaseCycle', () => { }); describe('deploy blockers', () => { it('production not updated, new version not created, issue reopened', async () => { - const repoPath = mockGithub.repo.getPath('testFinishReleaseCycleWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testFinishReleaseCycleWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'finishReleaseCycle.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams( @@ -144,7 +143,7 @@ describe('test workflow finishReleaseCycle', () => { }); describe('actor is not a team member', () => { it('production not updated, new version not created, issue reopened', async () => { - const repoPath = mockGithub.repo.getPath('testFinishReleaseCycleWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testFinishReleaseCycleWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'finishReleaseCycle.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams( @@ -191,7 +190,7 @@ describe('test workflow finishReleaseCycle', () => { }); describe('issue does not have StagingDeployCash', () => { it('validate job not run', async () => { - const repoPath = mockGithub.repo.getPath('testFinishReleaseCycleWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testFinishReleaseCycleWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'finishReleaseCycle.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams( diff --git a/workflow_tests/lint.test.ts b/workflow_tests/lint.test.js similarity index 91% rename from workflow_tests/lint.test.ts rename to workflow_tests/lint.test.js index f6ede86c5fd3..b13b4c1c3564 100644 --- a/workflow_tests/lint.test.ts +++ b/workflow_tests/lint.test.js @@ -1,14 +1,12 @@ -import type {MockStep} from '@kie/act-js/build/src/step-mocker/step-mocker.types'; -import {MockGithub} from '@kie/mock-github'; -import path from 'path'; -import assertions from './assertions/lintAssertions'; -import mocks from './mocks/lintMocks'; -import ExtendedAct from './utils/ExtendedAct'; -import * as utils from './utils/utils'; +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/lintAssertions'); +const mocks = require('./mocks/lintMocks'); +const ExtendedAct = require('./utils/ExtendedAct').default; jest.setTimeout(90 * 1000); -let mockGithub: MockGithub; - +let mockGithub; const FILES_TO_COPY_INTO_TEST_REPO = [ ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), { @@ -21,7 +19,7 @@ describe('test workflow lint', () => { const githubToken = 'dummy_github_token'; const actor = 'Dummy Actor'; - beforeAll(() => { + beforeAll(async () => { // in case of the tests being interrupted without cleanup the mock repo directory may be left behind // which breaks the next test run, this removes any possible leftovers utils.removeMockRepoDir(); @@ -29,7 +27,7 @@ describe('test workflow lint', () => { beforeEach(async () => { // create a local repository and copy required files - mockGithub = new MockGithub({ + mockGithub = new kieMockGithub.MockGithub({ repo: { testLintWorkflowRepo: { files: FILES_TO_COPY_INTO_TEST_REPO, @@ -49,14 +47,13 @@ describe('test workflow lint', () => { const event = 'workflow_call'; const eventOptions = {}; it('runs the lint', async () => { - const repoPath = mockGithub.repo.getPath('testLintWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testLintWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'lint.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); - const testMockSteps: MockStep = { + const testMockSteps = { lint: mocks.LINT__LINT__STEP_MOCKS, }; - const result = await act.runEvent(event, { workflowFile: path.join(repoPath, '.github', 'workflows', 'lint.yml'), mockSteps: testMockSteps, @@ -69,14 +66,13 @@ describe('test workflow lint', () => { describe('actor is OSBotify', () => { const testActor = 'OSBotify'; it('runs the lint', async () => { - const repoPath = mockGithub.repo.getPath('testLintWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testLintWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'lint.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); const testMockSteps = { lint: mocks.LINT__LINT__STEP_MOCKS, }; - const result = await act.runEvent(event, { workflowFile: path.join(repoPath, '.github', 'workflows', 'lint.yml'), mockSteps: testMockSteps, @@ -95,14 +91,13 @@ describe('test workflow lint', () => { action: 'opened', }; it('runs the lint', async () => { - const repoPath = mockGithub.repo.getPath('testLintWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testLintWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'lint.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); const testMockSteps = { lint: mocks.LINT__LINT__STEP_MOCKS, }; - const result = await act.runEvent(event, { workflowFile: path.join(repoPath, '.github', 'workflows', 'lint.yml'), mockSteps: testMockSteps, @@ -115,14 +110,13 @@ describe('test workflow lint', () => { describe('actor is OSBotify', () => { const testActor = 'OSBotify'; it('does not run the lint', async () => { - const repoPath = mockGithub.repo.getPath('testLintWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testLintWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'lint.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); const testMockSteps = { lint: mocks.LINT__LINT__STEP_MOCKS, }; - const result = await act.runEvent(event, { workflowFile: path.join(repoPath, '.github', 'workflows', 'lint.yml'), mockSteps: testMockSteps, @@ -139,14 +133,13 @@ describe('test workflow lint', () => { action: 'synchronize', }; it('runs the lint', async () => { - const repoPath = mockGithub.repo.getPath('testLintWorkflowRepo') ?? ''; + const repoPath = mockGithub.repo.getPath('testLintWorkflowRepo') || ''; const workflowPath = path.join(repoPath, '.github', 'workflows', 'lint.yml'); let act = new ExtendedAct(repoPath, workflowPath); act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); const testMockSteps = { lint: mocks.LINT__LINT__STEP_MOCKS, }; - const result = await act.runEvent(event, { workflowFile: path.join(repoPath, '.github', 'workflows', 'lint.yml'), mockSteps: testMockSteps, diff --git a/workflow_tests/mocks/authorChecklistMocks.ts b/workflow_tests/mocks/authorChecklistMocks.ts index b4d520eb7a1f..3a7f49783111 100644 --- a/workflow_tests/mocks/authorChecklistMocks.ts +++ b/workflow_tests/mocks/authorChecklistMocks.ts @@ -4,9 +4,8 @@ import {createMockStep} from '../utils/utils'; // checklist const AUTHORCHECKLIST__CHECKLIST__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'CHECKLIST'); const AUTHORCHECKLIST__CHECKLIST__AUTHORCHECKLIST_JS__STEP_MOCK = createMockStep('authorChecklist.js', 'Running authorChecklist.js', 'CHECKLIST', ['GITHUB_TOKEN'], []); -const AUTHORCHECKLIST__CHECKLIST__STEP_MOCKS = [AUTHORCHECKLIST__CHECKLIST__CHECKOUT__STEP_MOCK, AUTHORCHECKLIST__CHECKLIST__AUTHORCHECKLIST_JS__STEP_MOCK] as const; +const AUTHORCHECKLIST__CHECKLIST__STEP_MOCKS = [AUTHORCHECKLIST__CHECKLIST__CHECKOUT__STEP_MOCK, AUTHORCHECKLIST__CHECKLIST__AUTHORCHECKLIST_JS__STEP_MOCK]; -export { - // eslint-disable-next-line import/prefer-default-export +export default { AUTHORCHECKLIST__CHECKLIST__STEP_MOCKS, }; diff --git a/workflow_tests/mocks/cherryPickMocks.ts b/workflow_tests/mocks/cherryPickMocks.ts index f1508a3dc39d..e7762731c066 100644 --- a/workflow_tests/mocks/cherryPickMocks.ts +++ b/workflow_tests/mocks/cherryPickMocks.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import type {StepIdentifier} from '@kie/act-js/build/src/step-mocker/step-mocker.types'; import {createMockStep} from '../utils/utils'; // validateactor @@ -18,8 +19,8 @@ const CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_FALSE__STEP_MOCK = cr ['GITHUB_TOKEN'], {IS_DEPLOYER: false}, ); -const CHERRYPICK__VALIDATEACTOR__TRUE__STEP_MOCKS = [CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_TRUE__STEP_MOCK] as const; -const CHERRYPICK__VALIDATEACTOR__FALSE__STEP_MOCKS = [CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_FALSE__STEP_MOCK] as const; +const CHERRYPICK__VALIDATEACTOR__TRUE__STEP_MOCKS = [CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_TRUE__STEP_MOCK]; +const CHERRYPICK__VALIDATEACTOR__FALSE__STEP_MOCKS = [CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_FALSE__STEP_MOCK]; // createnewversion const CHERRYPICK__CREATENEWVERSION__CREATE_NEW_VERSION__STEP_MOCK = createMockStep( @@ -33,7 +34,7 @@ const CHERRYPICK__CREATENEWVERSION__CREATE_NEW_VERSION__STEP_MOCK = createMockSt true, 'createNewVersion', ); -const CHERRYPICK__CREATENEWVERSION__STEP_MOCKS = [CHERRYPICK__CREATENEWVERSION__CREATE_NEW_VERSION__STEP_MOCK] as const; +const CHERRYPICK__CREATENEWVERSION__STEP_MOCKS = [CHERRYPICK__CREATENEWVERSION__CREATE_NEW_VERSION__STEP_MOCK]; // cherrypick const CHERRYPICK__CHERRYPICK__CHECKOUT_STAGING_BRANCH__STEP_MOCK = createMockStep('Checkout staging branch', 'Checking out staging branch', 'CHERRYPICK', ['ref', 'token'], []); @@ -93,21 +94,20 @@ const CHERRYPICK__CHERRYPICK__ANNOUNCES_A_CP_FAILURE_IN_THE_ANNOUNCE_SLACK_ROOM_ ['GITHUB_TOKEN', 'SLACK_WEBHOOK_URL'], ); -const getCherryPickMockSteps = (upToDate: boolean, hasConflicts: boolean) => - [ - CHERRYPICK__CHERRYPICK__CHECKOUT_STAGING_BRANCH__STEP_MOCK, - CHERRYPICK__CHERRYPICK__SET_UP_GIT_FOR_OSBOTIFY__STEP_MOCK, - CHERRYPICK__CHERRYPICK__GET_PREVIOUS_APP_VERSION__STEP_MOCK, - CHERRYPICK__CHERRYPICK__FETCH_HISTORY_OF_RELEVANT_REFS__STEP_MOCK, - CHERRYPICK__CHERRYPICK__GET_VERSION_BUMP_COMMIT__STEP_MOCK, - CHERRYPICK__CHERRYPICK__GET_MERGE_COMMIT_FOR_PULL_REQUEST_TO_CP__STEP_MOCK, - CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_VERSION_BUMP_TO_STAGING__STEP_MOCK, - hasConflicts - ? CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_MERGE_COMMIT_OF_TARGET_PR_TO_NEW_BRANCH__HAS_CONFLICTS__STEP_MOCK - : CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_MERGE_COMMIT_OF_TARGET_PR_TO_NEW_BRANCH__HAS_NO_CONFLICTS__STEP_MOCK, - CHERRYPICK__CHERRYPICK__PUSH_CHANGES__STEP_MOCK, - CHERRYPICK__CHERRYPICK__CREATE_PULL_REQUEST_TO_MANUALLY_FINISH_CP__STEP_MOCK, - CHERRYPICK__CHERRYPICK__ANNOUNCES_A_CP_FAILURE_IN_THE_ANNOUNCE_SLACK_ROOM__STEP_MOCK, - ] as const; +const getCherryPickMockSteps = (upToDate: boolean, hasConflicts: boolean): StepIdentifier[] => [ + CHERRYPICK__CHERRYPICK__CHECKOUT_STAGING_BRANCH__STEP_MOCK, + CHERRYPICK__CHERRYPICK__SET_UP_GIT_FOR_OSBOTIFY__STEP_MOCK, + CHERRYPICK__CHERRYPICK__GET_PREVIOUS_APP_VERSION__STEP_MOCK, + CHERRYPICK__CHERRYPICK__FETCH_HISTORY_OF_RELEVANT_REFS__STEP_MOCK, + CHERRYPICK__CHERRYPICK__GET_VERSION_BUMP_COMMIT__STEP_MOCK, + CHERRYPICK__CHERRYPICK__GET_MERGE_COMMIT_FOR_PULL_REQUEST_TO_CP__STEP_MOCK, + CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_VERSION_BUMP_TO_STAGING__STEP_MOCK, + hasConflicts + ? CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_MERGE_COMMIT_OF_TARGET_PR_TO_NEW_BRANCH__HAS_CONFLICTS__STEP_MOCK + : CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_MERGE_COMMIT_OF_TARGET_PR_TO_NEW_BRANCH__HAS_NO_CONFLICTS__STEP_MOCK, + CHERRYPICK__CHERRYPICK__PUSH_CHANGES__STEP_MOCK, + CHERRYPICK__CHERRYPICK__CREATE_PULL_REQUEST_TO_MANUALLY_FINISH_CP__STEP_MOCK, + CHERRYPICK__CHERRYPICK__ANNOUNCES_A_CP_FAILURE_IN_THE_ANNOUNCE_SLACK_ROOM__STEP_MOCK, +]; -export {CHERRYPICK__VALIDATEACTOR__TRUE__STEP_MOCKS, CHERRYPICK__VALIDATEACTOR__FALSE__STEP_MOCKS, CHERRYPICK__CREATENEWVERSION__STEP_MOCKS, getCherryPickMockSteps}; +export {CHERRYPICK__CREATENEWVERSION__STEP_MOCKS, CHERRYPICK__VALIDATEACTOR__FALSE__STEP_MOCKS, CHERRYPICK__VALIDATEACTOR__TRUE__STEP_MOCKS, getCherryPickMockSteps}; diff --git a/workflow_tests/mocks/claMocks.ts b/workflow_tests/mocks/claMocks.ts index ab2d5f15a756..ca065510cd4a 100644 --- a/workflow_tests/mocks/claMocks.ts +++ b/workflow_tests/mocks/claMocks.ts @@ -15,8 +15,8 @@ const CLA__CLA__CLA_ASSISTANT__STEP_MOCK = createMockStep( ['path-to-signatures', 'path-to-document', 'branch', 'remote-organization-name', 'remote-repository-name', 'lock-pullrequest-aftermerge', 'allowlist'], ['GITHUB_TOKEN', 'PERSONAL_ACCESS_TOKEN'], ); -const CLA__CLA__NO_MATCHES__STEP_MOCKS = [CLA__CLA__CLA_COMMENT_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_COMMENT_RE_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_ASSISTANT__STEP_MOCK] as const; -const CLA__CLA__CHECK_MATCH__STEP_MOCKS = [CLA__CLA__CLA_COMMENT_CHECK__MATCH__STEP_MOCK, CLA__CLA__CLA_COMMENT_RE_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_ASSISTANT__STEP_MOCK] as const; -const CLA__CLA__RECHECK_MATCH__STEP_MOCKS = [CLA__CLA__CLA_COMMENT_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_COMMENT_RE_CHECK__MATCH__STEP_MOCK, CLA__CLA__CLA_ASSISTANT__STEP_MOCK] as const; +const CLA__CLA__NO_MATCHES__STEP_MOCKS = [CLA__CLA__CLA_COMMENT_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_COMMENT_RE_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_ASSISTANT__STEP_MOCK]; +const CLA__CLA__CHECK_MATCH__STEP_MOCKS = [CLA__CLA__CLA_COMMENT_CHECK__MATCH__STEP_MOCK, CLA__CLA__CLA_COMMENT_RE_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_ASSISTANT__STEP_MOCK]; +const CLA__CLA__RECHECK_MATCH__STEP_MOCKS = [CLA__CLA__CLA_COMMENT_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_COMMENT_RE_CHECK__MATCH__STEP_MOCK, CLA__CLA__CLA_ASSISTANT__STEP_MOCK]; export {CLA__CLA__NO_MATCHES__STEP_MOCKS, CLA__CLA__CHECK_MATCH__STEP_MOCKS, CLA__CLA__RECHECK_MATCH__STEP_MOCKS}; diff --git a/workflow_tests/mocks/createNewVersionMocks.ts b/workflow_tests/mocks/createNewVersionMocks.ts index 59aa6bc910a2..d3ebdfa57734 100644 --- a/workflow_tests/mocks/createNewVersionMocks.ts +++ b/workflow_tests/mocks/createNewVersionMocks.ts @@ -11,9 +11,9 @@ const CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__WRITE__STEP_MOCK = const CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__NONE__STEP_MOCK = createMockStep('Get user permissions', 'Get user permissions', 'VALIDATEACTOR', [], ['GITHUB_TOKEN'], { PERMISSION: 'read', }); -const CREATENEWVERSION__VALIDATEACTOR__ADMIN__STEP_MOCKS = [CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__ADMIN__STEP_MOCK] as const; -const CREATENEWVERSION__VALIDATEACTOR__WRITER__STEP_MOCKS = [CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__WRITE__STEP_MOCK] as const; -const CREATENEWVERSION__VALIDATEACTOR__NO_PERMISSION__STEP_MOCKS = [CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__NONE__STEP_MOCK] as const; +const CREATENEWVERSION__VALIDATEACTOR__ADMIN__STEP_MOCKS = [CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__ADMIN__STEP_MOCK]; +const CREATENEWVERSION__VALIDATEACTOR__WRITER__STEP_MOCKS = [CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__WRITE__STEP_MOCK]; +const CREATENEWVERSION__VALIDATEACTOR__NO_PERMISSION__STEP_MOCKS = [CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__NONE__STEP_MOCK]; // createnewversion const CREATENEWVERSION__CREATENEWVERSION__RUN_TURNSTYLE__STEP_MOCK = createMockStep('Run turnstyle', 'Run turnstyle', 'CREATENEWVERSION', ['poll-interval-seconds'], ['GITHUB_TOKEN']); @@ -37,7 +37,7 @@ const CREATENEWVERSION__CREATENEWVERSION__STEP_MOCKS = [ CREATENEWVERSION__CREATENEWVERSION__COMMIT_NEW_VERSION__STEP_MOCK, CREATENEWVERSION__CREATENEWVERSION__UPDATE_MAIN_BRANCH__STEP_MOCK, CREATENEWVERSION__CREATENEWVERSION__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK, -] as const; +]; export { CREATENEWVERSION__VALIDATEACTOR__ADMIN__STEP_MOCKS, diff --git a/workflow_tests/mocks/deployBlockerMocks.ts b/workflow_tests/mocks/deployBlockerMocks.ts index 932d4626b67b..2280950890a0 100644 --- a/workflow_tests/mocks/deployBlockerMocks.ts +++ b/workflow_tests/mocks/deployBlockerMocks.ts @@ -3,7 +3,7 @@ import {createMockStep} from '../utils/utils'; // updateChecklist const DEPLOYBLOCKER__UPDATECHECKLIST__STEP_MOCK = createMockStep('updateChecklist', 'Run updateChecklist', 'UPDATECHECKLIST'); -const DEPLOYBLOCKER__UPDATECHECKLIST__STEP_MOCKS = [DEPLOYBLOCKER__UPDATECHECKLIST__STEP_MOCK] as const; +const DEPLOYBLOCKER__UPDATECHECKLIST__STEP_MOCKS = [DEPLOYBLOCKER__UPDATECHECKLIST__STEP_MOCK]; // deployblocker const DEPLOYBLOCKER__DEPLOYBLOCKER__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'DEPLOYBLOCKER'); @@ -35,6 +35,6 @@ const DEPLOYBLOCKER__DEPLOYBLOCKER__STEP_MOCKS = [ DEPLOYBLOCKER__DEPLOYBLOCKER__POST_THE_ISSUE_IN_THE_EXPENSIFY_OPEN_SOURCE_SLACK_ROOM__STEP_MOCK, DEPLOYBLOCKER__DEPLOYBLOCKER__COMMENT_ON_DEPLOY_BLOCKER__STEP_MOCK, DEPLOYBLOCKER__DEPLOYBLOCKER__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK, -] as const; +]; export {DEPLOYBLOCKER__UPDATECHECKLIST__STEP_MOCKS, DEPLOYBLOCKER__DEPLOYBLOCKER__STEP_MOCKS}; diff --git a/workflow_tests/mocks/deployMocks.ts b/workflow_tests/mocks/deployMocks.ts index d0795477cfca..5888320a87b2 100644 --- a/workflow_tests/mocks/deployMocks.ts +++ b/workflow_tests/mocks/deployMocks.ts @@ -9,12 +9,7 @@ const DEPLOY_STAGING__SETUP_GIT__STEP_MOCK = createMockStep('Setup git for OSBot ]); const DEPLOY_STAGING__TAG_VERSION__STEP_MOCK = createMockStep('Tag version', 'Tagging new version', 'DEPLOY_STAGING'); const DEPLOY_STAGING__PUSH_TAG__STEP_MOCK = createMockStep('🚀 Push tags to trigger staging deploy 🚀', 'Pushing tag to trigger staging deploy', 'DEPLOY_STAGING'); -const DEPLOY_STAGING_STEP_MOCKS = [ - DEPLOY_STAGING__CHECKOUT__STEP_MOCK, - DEPLOY_STAGING__SETUP_GIT__STEP_MOCK, - DEPLOY_STAGING__TAG_VERSION__STEP_MOCK, - DEPLOY_STAGING__PUSH_TAG__STEP_MOCK, -] as const; +const DEPLOY_STAGING_STEP_MOCKS = [DEPLOY_STAGING__CHECKOUT__STEP_MOCK, DEPLOY_STAGING__SETUP_GIT__STEP_MOCK, DEPLOY_STAGING__TAG_VERSION__STEP_MOCK, DEPLOY_STAGING__PUSH_TAG__STEP_MOCK]; const DEPLOY_PRODUCTION__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checking out', 'DEPLOY_PRODUCTION', ['ref', 'token']); const DEPLOY_PRODUCTION__SETUP_GIT__STEP_MOCK = createMockStep( @@ -53,6 +48,6 @@ const DEPLOY_PRODUCTION_STEP_MOCKS = [ DEPLOY_PRODUCTION__RELEASE_PR_LIST__STEP_MOCK, DEPLOY_PRODUCTION__GENERATE_RELEASE_BODY__STEP_MOCK, DEPLOY_PRODUCTION__CREATE_RELEASE__STEP_MOCK, -] as const; +]; export {DEPLOY_STAGING_STEP_MOCKS, DEPLOY_PRODUCTION_STEP_MOCKS}; diff --git a/workflow_tests/mocks/failureNotifierMocks.ts b/workflow_tests/mocks/failureNotifierMocks.ts index 5c869adae21b..cbea6fce95ae 100644 --- a/workflow_tests/mocks/failureNotifierMocks.ts +++ b/workflow_tests/mocks/failureNotifierMocks.ts @@ -6,12 +6,8 @@ import {createMockStep} from '../utils/utils'; // notifyfailure const FAILURENOTIFIER__NOTIFYFAILURE__FETCH_WORKFLOW_RUN_JOBS__STEP_MOCK = createMockStep('Fetch Workflow Run Jobs', 'Fetch Workflow Run Jobs', 'NOTIFYFAILURE', [], []); const FAILURENOTIFIER__NOTIFYFAILURE__PROCESS_EACH_FAILED_JOB__STEP_MOCK = createMockStep('Process Each Failed Job', 'Process Each Failed Job', 'NOTIFYFAILURE', [], []); -const FAILURENOTIFIER__NOTIFYFAILURE__STEP_MOCKS = [ - FAILURENOTIFIER__NOTIFYFAILURE__FETCH_WORKFLOW_RUN_JOBS__STEP_MOCK, - FAILURENOTIFIER__NOTIFYFAILURE__PROCESS_EACH_FAILED_JOB__STEP_MOCK, -] as const; +const FAILURENOTIFIER__NOTIFYFAILURE__STEP_MOCKS = [FAILURENOTIFIER__NOTIFYFAILURE__FETCH_WORKFLOW_RUN_JOBS__STEP_MOCK, FAILURENOTIFIER__NOTIFYFAILURE__PROCESS_EACH_FAILED_JOB__STEP_MOCK]; -export { - // eslint-disable-next-line import/prefer-default-export +export default { FAILURENOTIFIER__NOTIFYFAILURE__STEP_MOCKS, }; diff --git a/workflow_tests/mocks/finishReleaseCycleMocks.ts b/workflow_tests/mocks/finishReleaseCycleMocks.ts index 360bb017da88..86493fb077c9 100644 --- a/workflow_tests/mocks/finishReleaseCycleMocks.ts +++ b/workflow_tests/mocks/finishReleaseCycleMocks.ts @@ -73,7 +73,7 @@ const FINISHRELEASECYCLE__VALIDATE__TEAM_MEMBER_NO_BLOCKERS__STEP_MOCKS = [ FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_FALSE__STEP_MOCK, FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_HAS_BLOCKERS__STEP_MOCK, FINISHRELEASECYCLE__VALIDATE__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK, -] as const; +]; const FINISHRELEASECYCLE__VALIDATE__TEAM_MEMBER_BLOCKERS__STEP_MOCKS = [ FINISHRELEASECYCLE__VALIDATE__CHECKOUT__STEP_MOCK, FINISHRELEASECYCLE__VALIDATE__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK, @@ -82,7 +82,7 @@ const FINISHRELEASECYCLE__VALIDATE__TEAM_MEMBER_BLOCKERS__STEP_MOCKS = [ FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_TRUE__STEP_MOCK, FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_HAS_BLOCKERS__STEP_MOCK, FINISHRELEASECYCLE__VALIDATE__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK, -] as const; +]; // eslint-disable-next-line rulesdir/no-negated-variables const FINISHRELEASECYCLE__VALIDATE__NOT_TEAM_MEMBER_NO_BLOCKERS__STEP_MOCKS = [ FINISHRELEASECYCLE__VALIDATE__CHECKOUT__STEP_MOCK, @@ -92,7 +92,7 @@ const FINISHRELEASECYCLE__VALIDATE__NOT_TEAM_MEMBER_NO_BLOCKERS__STEP_MOCKS = [ FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_FALSE__STEP_MOCK, FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_HAS_BLOCKERS__STEP_MOCK, FINISHRELEASECYCLE__VALIDATE__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK, -] as const; +]; // eslint-disable-next-line rulesdir/no-negated-variables const FINISHRELEASECYCLE__VALIDATE__NOT_TEAM_MEMBER_BLOCKERS__STEP_MOCKS = [ FINISHRELEASECYCLE__VALIDATE__CHECKOUT__STEP_MOCK, @@ -102,7 +102,7 @@ const FINISHRELEASECYCLE__VALIDATE__NOT_TEAM_MEMBER_BLOCKERS__STEP_MOCKS = [ FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_TRUE__STEP_MOCK, FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_HAS_BLOCKERS__STEP_MOCK, FINISHRELEASECYCLE__VALIDATE__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK, -] as const; +]; // updateproduction const FINISHRELEASECYCLE__UPDATEPRODUCTION__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'UPDATEPRODUCTION', ['ref', 'token'], []); @@ -126,7 +126,7 @@ const FINISHRELEASECYCLE__UPDATEPRODUCTION__STEP_MOCKS = [ FINISHRELEASECYCLE__UPDATEPRODUCTION__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK, FINISHRELEASECYCLE__UPDATEPRODUCTION__UPDATE_PRODUCTION_BRANCH__STEP_MOCK, FINISHRELEASECYCLE__UPDATEPRODUCTION__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK, -] as const; +]; // createnewpatchversion const FINISHRELEASECYCLE__CREATENEWPATCHVERSION__CREATE_NEW_VERSION__STEP_MOCK = createMockStep( @@ -140,7 +140,7 @@ const FINISHRELEASECYCLE__CREATENEWPATCHVERSION__CREATE_NEW_VERSION__STEP_MOCK = true, 'createNewVersion', ); -const FINISHRELEASECYCLE__CREATENEWPATCHVERSION__STEP_MOCKS = [FINISHRELEASECYCLE__CREATENEWPATCHVERSION__CREATE_NEW_VERSION__STEP_MOCK] as const; +const FINISHRELEASECYCLE__CREATENEWPATCHVERSION__STEP_MOCKS = [FINISHRELEASECYCLE__CREATENEWPATCHVERSION__CREATE_NEW_VERSION__STEP_MOCK]; // updatestaging const FINISHRELEASECYCLE__UPDATESTAGING__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'UPDATESTAGING', ['ref', 'token'], []); @@ -164,7 +164,7 @@ const FINISHRELEASECYCLE__UPDATESTAGING__STEP_MOCKS = [ FINISHRELEASECYCLE__UPDATESTAGING__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK, FINISHRELEASECYCLE__UPDATESTAGING__UPDATE_STAGING_BRANCH_TO_TRIGGER_STAGING_DEPLOY__STEP_MOCK, FINISHRELEASECYCLE__UPDATESTAGING__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK, -] as const; +]; export { FINISHRELEASECYCLE__VALIDATE__TEAM_MEMBER_NO_BLOCKERS__STEP_MOCKS, diff --git a/workflow_tests/mocks/lintMocks.ts b/workflow_tests/mocks/lintMocks.ts index 93a3fd190a41..851555313cff 100644 --- a/workflow_tests/mocks/lintMocks.ts +++ b/workflow_tests/mocks/lintMocks.ts @@ -13,9 +13,8 @@ const LINT__LINT__STEP_MOCKS = [ LINT__LINT__LINT_JAVASCRIPT_WITH_ESLINT__STEP_MOCK, LINT__LINT__VERIFY_NO_PRETTIER__STEP_MOCK, LINT__LINT__RUN_UNUSED_SEARCHER__STEP_MOCK, -] as const; +]; -export { - // eslint-disable-next-line import/prefer-default-export +export default { LINT__LINT__STEP_MOCKS, }; diff --git a/workflow_tests/mocks/lockDeploysMocks.ts b/workflow_tests/mocks/lockDeploysMocks.ts index 2c9aa1fd6d11..138b30fbb306 100644 --- a/workflow_tests/mocks/lockDeploysMocks.ts +++ b/workflow_tests/mocks/lockDeploysMocks.ts @@ -29,9 +29,8 @@ const LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__STEP_MOCKS = [ LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__WAIT_FOR_STAGING_DEPLOYS_TO_FINISH__STEP_MOCK, LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__COMMENT_IN_STAGINGDEPLOYCASH_TO_GIVE_APPLAUSE_THE_GREEN_LIGHT_TO_BEGIN_QA__STEP_MOCK, LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__ANNOUNCE_FAILED_WORKFLOW__STEP_MOCK, -] as const; +]; -export { - // eslint-disable-next-line import/prefer-default-export +export default { LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__STEP_MOCKS, }; diff --git a/workflow_tests/mocks/platformDeployMocks.ts b/workflow_tests/mocks/platformDeployMocks.ts index 72176e34d1fa..1a14f488a81d 100644 --- a/workflow_tests/mocks/platformDeployMocks.ts +++ b/workflow_tests/mocks/platformDeployMocks.ts @@ -18,12 +18,12 @@ const PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__OUTSIDER__STEP_MOCK ['GITHUB_TOKEN'], {IS_DEPLOYER: false}, ); -const PLATFORM_DEPLOY__VALIDATE_ACTOR__TEAM_MEMBER__STEP_MOCKS = [PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__TEAM_MEMBER__STEP_MOCK] as const; -const PLATFORM_DEPLOY__VALIDATE_ACTOR__OUTSIDER__STEP_MOCKS = [PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__OUTSIDER__STEP_MOCK] as const; +const PLATFORM_DEPLOY__VALIDATE_ACTOR__TEAM_MEMBER__STEP_MOCKS = [PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__TEAM_MEMBER__STEP_MOCK]; +const PLATFORM_DEPLOY__VALIDATE_ACTOR__OUTSIDER__STEP_MOCKS = [PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__OUTSIDER__STEP_MOCK]; // deployChecklist const PLATFORM_DEPLOY__DEPLOY_CHECKLIST__STEP_MOCK = createMockStep('deployChecklist', 'Run deployChecklist', 'DEPLOY_CHECKLIST'); -const PLATFORM_DEPLOY__DEPLOY_CHECKLIST__STEP_MOCKS = [PLATFORM_DEPLOY__DEPLOY_CHECKLIST__STEP_MOCK] as const; +const PLATFORM_DEPLOY__DEPLOY_CHECKLIST__STEP_MOCKS = [PLATFORM_DEPLOY__DEPLOY_CHECKLIST__STEP_MOCK]; // android const PLATFORM_DEPLOY__ANDROID__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checking out', 'ANDROID'); @@ -75,7 +75,7 @@ const PLATFORM_DEPLOY__ANDROID__STEP_MOCKS = [ PLATFORM_DEPLOY__ANDROID__UPLOAD_ANDROID_VERSION_TO_GITHUB_ARTIFACTS__STEP_MOCK, PLATFORM_DEPLOY__ANDROID__UPLOAD_TO_BROWSER_STACK__STEP_MOCK, PLATFORM_DEPLOY__ANDROID__WARN_DEPLOYERS__STEP_MOCK, -] as const; +]; // desktop const PLATFORM_DEPLOY__DESKTOP__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checking out', 'DESKTOP'); @@ -105,7 +105,7 @@ const PLATFORM_DEPLOY__DESKTOP__STEP_MOCKS = [ PLATFORM_DEPLOY__DESKTOP__DECRYPT_ID__STEP_MOCK, PLATFORM_DEPLOY__DESKTOP__BUILD_PRODUCTION__STEP_MOCK, PLATFORM_DEPLOY__DESKTOP__BUILD_STAGING__STEP_MOCK, -] as const; +]; // ios const PLATFORM_DEPLOY__IOS__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checking out', 'IOS'); @@ -167,7 +167,7 @@ const PLATFORM_DEPLOY__IOS__STEP_MOCKS = [ PLATFORM_DEPLOY__IOS__SET_VERSION__STEP_MOCK, PLATFORM_DEPLOY__IOS__RELEASE_FASTLANE__STEP_MOCK, PLATFORM_DEPLOY__IOS__WARN_FAIL__STEP_MOCK, -] as const; +]; // web const PLATFORM_DEPLOY__WEB__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checking out', 'WEB'); @@ -199,13 +199,13 @@ const PLATFORM_DEPLOY__WEB__STEP_MOCKS = [ PLATFORM_DEPLOY__WEB__DEPLOY_STAGING_S3__STEP_MOCK, PLATFORM_DEPLOY__WEB__PURGE_PRODUCTION_CACHE__STEP_MOCK, PLATFORM_DEPLOY__WEB__PURGE_STAGING_CACHE__STEP_MOCK, -] as const; +]; // post slack message on failure const PLATFORM_DEPLOY__POST_SLACK_FAIL__POST_SLACK__STEP_MOCK = createMockStep('Post Slack message on failure', 'Posting Slack message on platform deploy failure', 'POST_SLACK_FAIL', [ 'SLACK_WEBHOOK', ]); -const PLATFORM_DEPLOY__POST_SLACK_FAIL__STEP_MOCKS = [PLATFORM_DEPLOY__POST_SLACK_FAIL__POST_SLACK__STEP_MOCK] as const; +const PLATFORM_DEPLOY__POST_SLACK_FAIL__STEP_MOCKS = [PLATFORM_DEPLOY__POST_SLACK_FAIL__POST_SLACK__STEP_MOCK]; // post slack message on success const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checking out', 'POST_SLACK_SUCCESS'); @@ -237,7 +237,7 @@ const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__STEP_MOCKS = [ PLATFORM_DEPLOY__POST_SLACK_SUCCESS__ANNOUNCE_CHANNEL__STEP_MOCK, PLATFORM_DEPLOY__POST_SLACK_SUCCESS__DEPLOYER_CHANNEL__STEP_MOCK, PLATFORM_DEPLOY__POST_SLACK_SUCCESS__EXPENSIFY_CHANNEL__STEP_MOCK, -] as const; +]; // post github comment const PLATFORM_DEPLOY__POST_GIHUB_COMMENT__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checking out', 'POST_GITHUB_COMMENT'); @@ -267,7 +267,7 @@ const PLATFORM_DEPLOY__POST_GITHUB_COMMENT__STEP_MOCKS = [ PLATFORM_DEPLOY__POST_GIHUB_COMMENT__SET_VERSION__STEP_MOCK, PLATFORM_DEPLOY__POST_GIHUB_COMMENT__GET_PR_LIST__STEP_MOCK, PLATFORM_DEPLOY__POST_GIHUB_COMMENT__COMMENT__STEP_MOCK, -] as const; +]; export { PLATFORM_DEPLOY__VALIDATE_ACTOR__TEAM_MEMBER__STEP_MOCKS, diff --git a/workflow_tests/mocks/preDeployMocks.ts b/workflow_tests/mocks/preDeployMocks.ts index df6bc63e7dd9..d9da9d51bd85 100644 --- a/workflow_tests/mocks/preDeployMocks.ts +++ b/workflow_tests/mocks/preDeployMocks.ts @@ -3,15 +3,15 @@ import {createMockStep} from '../utils/utils'; // typecheck const TYPECHECK_WORKFLOW_MOCK_STEP = createMockStep('Run typecheck workflow', 'Running typecheck workflow', 'TYPECHECK'); -const TYPECHECK_JOB_MOCK_STEPS = [TYPECHECK_WORKFLOW_MOCK_STEP] as const; +const TYPECHECK_JOB_MOCK_STEPS = [TYPECHECK_WORKFLOW_MOCK_STEP]; // lint const LINT_WORKFLOW_MOCK_STEP = createMockStep('Run lint workflow', 'Running lint workflow', 'LINT'); -const LINT_JOB_MOCK_STEPS = [LINT_WORKFLOW_MOCK_STEP] as const; +const LINT_JOB_MOCK_STEPS = [LINT_WORKFLOW_MOCK_STEP]; // test const TEST_WORKFLOW_MOCK_STEP = createMockStep('Run test workflow', 'Running test workflow', 'TEST'); -const TEST_JOB_MOCK_STEPS = [TEST_WORKFLOW_MOCK_STEP] as const; +const TEST_JOB_MOCK_STEPS = [TEST_WORKFLOW_MOCK_STEP]; // confirm_passing_build const ANNOUNCE_IN_SLACK_MOCK_STEP = createMockStep('Announce failed workflow in Slack', 'Announcing failed workflow in slack', 'CONFIRM_PASSING_BUILD', ['SLACK_WEBHOOK']); @@ -19,7 +19,7 @@ const CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS = [ ANNOUNCE_IN_SLACK_MOCK_STEP, // 2nd step runs normally -] as const; +]; // choose_deploy_actions const GET_MERGED_PULL_REQUEST_MOCK_STEP__CHOOSE_DEPLOY = createMockStep('Get merged pull request', 'Getting merged pull request', 'CHOOSE_DEPLOY_ACTIONS', ['github_token'], null, { @@ -47,21 +47,21 @@ const CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_LOCKED = [ CHECK_IF_STAGINGDEPLOYCASH_IS_LOCKED_MOCK_STEP__LOCKED, // step 3 runs normally -] as const; +]; const CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED = [ GET_MERGED_PULL_REQUEST_MOCK_STEP__CHOOSE_DEPLOY, CHECK_IF_STAGINGDEPLOYCASH_IS_LOCKED_MOCK_STEP__UNLOCKED, // step 3 runs normally -] as const; +]; // skip_deploy const COMMENT_ON_DEFERRED_PR_MOCK_STEP = createMockStep('Comment on deferred PR', 'Skipping deploy', 'SKIP_DEPLOY', ['github_token', 'number', 'body']); -const SKIP_DEPLOY_JOB_MOCK_STEPS = [COMMENT_ON_DEFERRED_PR_MOCK_STEP] as const; +const SKIP_DEPLOY_JOB_MOCK_STEPS = [COMMENT_ON_DEFERRED_PR_MOCK_STEP]; // create_new_version const CREATE_NEW_VERSION_MOCK_STEP = createMockStep('Create new version', 'Creating new version', 'CREATE_NEW_VERSION', null, null, {NEW_VERSION: '1.2.3'}, null, true, 'createNewVersion'); -const CREATE_NEW_VERSION_JOB_MOCK_STEPS = [CREATE_NEW_VERSION_MOCK_STEP] as const; +const CREATE_NEW_VERSION_JOB_MOCK_STEPS = [CREATE_NEW_VERSION_MOCK_STEP]; // update_staging const RUN_TURNSTYLE_MOCK_STEP = createMockStep('Run turnstyle', 'Running turnstyle', 'UPDATE_STAGING', ['poll-interval-seconds'], ['GITHUB_TOKEN']); @@ -75,10 +75,10 @@ const UPDATE_STAGING_JOB_MOCK_STEPS = [ SETUP_GIT_FOR_OSBOTIFY_MOCK_STEP, UPDATE_STAGING_BRANCH_FROM_MAIN_MOCK_STEP, ANNOUNCE_FAILED_WORKFLOW_IN_SLACK_MOCK_STEP, -] as const; +]; const PREDEPLOY__E2EPERFORMANCETESTS__PERFORM_E2E_TESTS__MOCK_STEP = createMockStep('Perform E2E tests', 'Perform E2E tests', 'E2EPERFORMANCETESTS'); -const PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS = [PREDEPLOY__E2EPERFORMANCETESTS__PERFORM_E2E_TESTS__MOCK_STEP] as const; +const PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS = [PREDEPLOY__E2EPERFORMANCETESTS__PERFORM_E2E_TESTS__MOCK_STEP]; export { TYPECHECK_JOB_MOCK_STEPS, diff --git a/workflow_tests/mocks/reviewerChecklistMocks.ts b/workflow_tests/mocks/reviewerChecklistMocks.ts index 20c87994f957..8a70c0c71597 100644 --- a/workflow_tests/mocks/reviewerChecklistMocks.ts +++ b/workflow_tests/mocks/reviewerChecklistMocks.ts @@ -3,9 +3,8 @@ import {createMockStep} from '../utils/utils'; // checklist const REVIEWERCHECKLIST__CHECKLIST__REVIEWERCHECKLIST_JS__STEP_MOCK = createMockStep('reviewerChecklist.js', 'reviewerChecklist.js', 'CHECKLIST', ['GITHUB_TOKEN'], []); -const REVIEWERCHECKLIST__CHECKLIST__STEP_MOCKS = [REVIEWERCHECKLIST__CHECKLIST__REVIEWERCHECKLIST_JS__STEP_MOCK] as const; +const REVIEWERCHECKLIST__CHECKLIST__STEP_MOCKS = [REVIEWERCHECKLIST__CHECKLIST__REVIEWERCHECKLIST_JS__STEP_MOCK]; -export { - // eslint-disable-next-line import/prefer-default-export +export default { REVIEWERCHECKLIST__CHECKLIST__STEP_MOCKS, }; diff --git a/workflow_tests/mocks/testBuildMocks.ts b/workflow_tests/mocks/testBuildMocks.ts index 12109d0a875b..f502bfb248ba 100644 --- a/workflow_tests/mocks/testBuildMocks.ts +++ b/workflow_tests/mocks/testBuildMocks.ts @@ -27,19 +27,19 @@ const TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__FALSE__STEP_M const TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_HAS_FLAG__STEP_MOCKS = [ TESTBUILD__VALIDATEACTOR__IS_TEAM_MEMBER__TRUE__STEP_MOCK, TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__TRUE__STEP_MOCK, -] as const; +]; const TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_NO_FLAG__STEP_MOCKS = [ TESTBUILD__VALIDATEACTOR__IS_TEAM_MEMBER__TRUE__STEP_MOCK, TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__FALSE__STEP_MOCK, -] as const; +]; const TESTBUILD__VALIDATEACTOR__NO_TEAM_MEMBER_HAS_FLAG__STEP_MOCKS = [ TESTBUILD__VALIDATEACTOR__IS_TEAM_MEMBER__FALSE__STEP_MOCK, TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__TRUE__STEP_MOCK, -] as const; +]; const TESTBUILD__VALIDATEACTOR__NO_TEAM_MEMBER_NO_FLAG__STEP_MOCKS = [ TESTBUILD__VALIDATEACTOR__IS_TEAM_MEMBER__FALSE__STEP_MOCK, TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__FALSE__STEP_MOCK, -] as const; +]; // getbranchref const TESTBUILD__GETBRANCHREF__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'GETBRANCHREF', [], []); @@ -51,7 +51,7 @@ const TESTBUILD__GETBRANCHREF__CHECK_IF_PULL_REQUEST_NUMBER_IS_CORRECT__STEP_MOC ['GITHUB_TOKEN'], {REF: 'test-ref'}, ); -const TESTBUILD__GETBRANCHREF__STEP_MOCKS = [TESTBUILD__GETBRANCHREF__CHECKOUT__STEP_MOCK, TESTBUILD__GETBRANCHREF__CHECK_IF_PULL_REQUEST_NUMBER_IS_CORRECT__STEP_MOCK] as const; +const TESTBUILD__GETBRANCHREF__STEP_MOCKS = [TESTBUILD__GETBRANCHREF__CHECKOUT__STEP_MOCK, TESTBUILD__GETBRANCHREF__CHECK_IF_PULL_REQUEST_NUMBER_IS_CORRECT__STEP_MOCK]; // android const TESTBUILD__ANDROID__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'ANDROID', ['ref'], []); @@ -95,7 +95,7 @@ const TESTBUILD__ANDROID__STEP_MOCKS = [ TESTBUILD__ANDROID__CONFIGURE_MAPBOX_SDK__STEP_MOCK, TESTBUILD__ANDROID__RUN_FASTLANE_BETA_TEST__STEP_MOCK, TESTBUILD__ANDROID__UPLOAD_ARTIFACT__STEP_MOCK, -] as const; +]; // ios const TESTBUILD__IOS__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'IOS', ['ref'], []); @@ -151,7 +151,7 @@ const TESTBUILD__IOS__STEP_MOCKS = [ TESTBUILD__IOS__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK, TESTBUILD__IOS__RUN_FASTLANE__STEP_MOCK, TESTBUILD__IOS__UPLOAD_ARTIFACT__STEP_MOCK, -] as const; +]; // desktop const TESTBUILD__DESKTOP__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'DESKTOP', ['ref'], []); @@ -191,7 +191,7 @@ const TESTBUILD__DESKTOP__STEP_MOCKS = [ TESTBUILD__DESKTOP__DECRYPT_DEVELOPER_ID_CERTIFICATE__STEP_MOCK, TESTBUILD__DESKTOP__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK, TESTBUILD__DESKTOP__BUILD_DESKTOP_APP_FOR_TESTING__STEP_MOCK, -] as const; +]; // web const TESTBUILD__WEB__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'WEB', ['ref'], []); @@ -221,7 +221,7 @@ const TESTBUILD__WEB__STEP_MOCKS = [ TESTBUILD__WEB__BUILD_WEB_FOR_TESTING__STEP_MOCK, TESTBUILD__WEB__BUILD_DOCS__STEP_MOCK, TESTBUILD__WEB__DEPLOY_TO_S3_FOR_INTERNAL_TESTING__STEP_MOCK, -] as const; +]; // postgithubcomment const TESTBUILD__POSTGITHUBCOMMENT__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'POSTGITHUBCOMMENT', ['ref'], []); @@ -245,7 +245,7 @@ const TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS = [ TESTBUILD__POSTGITHUBCOMMENT__READ_JSONS_WITH_ANDROID_PATHS__STEP_MOCK, TESTBUILD__POSTGITHUBCOMMENT__READ_JSONS_WITH_IOS_PATHS__STEP_MOCK, TESTBUILD__POSTGITHUBCOMMENT__PUBLISH_LINKS_TO_APPS_FOR_DOWNLOAD__STEP_MOCK, -] as const; +]; export { TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_HAS_FLAG__STEP_MOCKS, diff --git a/workflow_tests/mocks/testMocks.ts b/workflow_tests/mocks/testMocks.ts index 96f47d4204fb..cb8240169a8e 100644 --- a/workflow_tests/mocks/testMocks.ts +++ b/workflow_tests/mocks/testMocks.ts @@ -13,12 +13,12 @@ const TEST__JEST__STEP_MOCKS = [ TEST__JEST__GET_NUMBER_OF_CPU_CORES__STEP_MOCK, TEST__JEST__CACHE_JEST_CACHE__STEP_MOCK, TEST__JEST__JEST_TESTS__STEP_MOCK, -] as const; +]; // shelltests const TEST__SHELLTESTS__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'SHELLTESTS', [], []); const TEST__SHELLTESTS__SETUP_NODE__STEP_MOCK = createMockStep('Setup Node', 'Setup Node', 'SHELLTESTS', [], []); const TEST__SHELLTESTS__TEST_CI_GIT_LOGIC__STEP_MOCK = createMockStep('Test CI git logic', 'Test CI git logic', 'SHELLTESTS', [], []); -const TEST__SHELLTESTS__STEP_MOCKS = [TEST__SHELLTESTS__CHECKOUT__STEP_MOCK, TEST__SHELLTESTS__SETUP_NODE__STEP_MOCK, TEST__SHELLTESTS__TEST_CI_GIT_LOGIC__STEP_MOCK] as const; +const TEST__SHELLTESTS__STEP_MOCKS = [TEST__SHELLTESTS__CHECKOUT__STEP_MOCK, TEST__SHELLTESTS__SETUP_NODE__STEP_MOCK, TEST__SHELLTESTS__TEST_CI_GIT_LOGIC__STEP_MOCK]; export {TEST__JEST__STEP_MOCKS, TEST__SHELLTESTS__STEP_MOCKS}; diff --git a/workflow_tests/mocks/validateGithubActionsMocks.ts b/workflow_tests/mocks/validateGithubActionsMocks.ts index 8249945dbfc7..2c1afba74387 100644 --- a/workflow_tests/mocks/validateGithubActionsMocks.ts +++ b/workflow_tests/mocks/validateGithubActionsMocks.ts @@ -11,9 +11,8 @@ const VALIDATEGITHUBACTIONS__VERIFY__STEP_MOCKS = [ VALIDATEGITHUBACTIONS__VERIFY__SETUP_NODE__STEP_MOCK, VALIDATEGITHUBACTIONS__VERIFY__VERIFY_JAVASCRIPT_ACTION_BUILDS__STEP_MOCK, VALIDATEGITHUBACTIONS__VERIFY__VALIDATE_ACTIONS_AND_WORKFLOWS__STEP_MOCK, -] as const; +]; -export { - // eslint-disable-next-line import/prefer-default-export +export default { VALIDATEGITHUBACTIONS__VERIFY__STEP_MOCKS, }; diff --git a/workflow_tests/mocks/verifyPodfileMocks.ts b/workflow_tests/mocks/verifyPodfileMocks.ts index b1f0669b8b00..0cd7ea396dad 100644 --- a/workflow_tests/mocks/verifyPodfileMocks.ts +++ b/workflow_tests/mocks/verifyPodfileMocks.ts @@ -5,13 +5,8 @@ import {createMockStep} from '../utils/utils'; const VERIFYPODFILE__VERIFY__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'VERIFY'); const VERIFYPODFILE__VERIFY__SETUP_NODE__STEP_MOCK = createMockStep('Setup Node', 'Setup Node', 'VERIFY', [], []); const VERIFYPODFILE__VERIFY__VERIFY_PODFILE__STEP_MOCK = createMockStep('Verify podfile', 'Verify podfile', 'VERIFY', [], []); -const VERIFYPODFILE__VERIFY__STEP_MOCKS = [ - VERIFYPODFILE__VERIFY__CHECKOUT__STEP_MOCK, - VERIFYPODFILE__VERIFY__SETUP_NODE__STEP_MOCK, - VERIFYPODFILE__VERIFY__VERIFY_PODFILE__STEP_MOCK, -] as const; +const VERIFYPODFILE__VERIFY__STEP_MOCKS = [VERIFYPODFILE__VERIFY__CHECKOUT__STEP_MOCK, VERIFYPODFILE__VERIFY__SETUP_NODE__STEP_MOCK, VERIFYPODFILE__VERIFY__VERIFY_PODFILE__STEP_MOCK]; -export { - // eslint-disable-next-line import/prefer-default-export +export default { VERIFYPODFILE__VERIFY__STEP_MOCKS, }; diff --git a/workflow_tests/mocks/verifySignedCommitsMocks.ts b/workflow_tests/mocks/verifySignedCommitsMocks.ts index 953d09d1255f..6109af183fd7 100644 --- a/workflow_tests/mocks/verifySignedCommitsMocks.ts +++ b/workflow_tests/mocks/verifySignedCommitsMocks.ts @@ -9,9 +9,8 @@ const VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__VERIFY_SIGNED_COMMITS__STEP_MOCK ['GITHUB_TOKEN'], [], ); -const VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__STEP_MOCKS = [VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__VERIFY_SIGNED_COMMITS__STEP_MOCK] as const; +const VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__STEP_MOCKS = [VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__VERIFY_SIGNED_COMMITS__STEP_MOCK]; -export { - // eslint-disable-next-line import/prefer-default-export +export default { VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__STEP_MOCKS, }; diff --git a/workflow_tests/utils/JobMocker.ts b/workflow_tests/utils/JobMocker.ts index 171d625b2fe1..b6dc99771dd2 100644 --- a/workflow_tests/utils/JobMocker.ts +++ b/workflow_tests/utils/JobMocker.ts @@ -1,4 +1,3 @@ -import type {StepIdentifier} from '@kie/act-js'; import type {PathOrFileDescriptor} from 'fs'; import fs from 'fs'; import path from 'path'; @@ -12,12 +11,12 @@ type YamlWorkflow = { }; type MockJob = { - steps: StepIdentifier[]; + steps: MockJobStep[]; uses?: string; secrets?: string[]; with?: string; - outputs?: Record; - runsOn: string; + outputs?: string[]; + runsOn?: string; }; type MockJobs = Record; @@ -60,8 +59,8 @@ class JobMocker { jobWith = job.with; delete job.with; } - job.steps = mockJob.steps.map((step): StepIdentifier => { - const mockStep: StepIdentifier = { + job.steps = mockJob.steps.map((step) => { + const mockStep: MockJobStep = { name: step.name, run: step.mockWith, }; diff --git a/workflow_tests/utils/preGenerateTest.ts b/workflow_tests/utils/preGenerateTest.ts index c5e54d9a11c0..7698e618432d 100644 --- a/workflow_tests/utils/preGenerateTest.ts +++ b/workflow_tests/utils/preGenerateTest.ts @@ -1,11 +1,10 @@ /* eslint no-console: ["error", { allow: ["warn", "log"] }] */ -import type {StepIdentifier} from '@kie/act-js'; import type {PathLike} from 'fs'; import fs from 'fs'; import path from 'path'; import {exit} from 'process'; import yaml from 'yaml'; -import type {YamlMockJob, YamlWorkflow} from './JobMocker'; +import type {MockJobStep, YamlMockJob, YamlWorkflow} from './JobMocker'; const workflowsDirectory = path.resolve(__dirname, '..', '..', '.github', 'workflows'); const workflowTestsDirectory = path.resolve(__dirname, '..'); @@ -98,7 +97,7 @@ describe('test workflow ${workflowName}', () => { }); `; -const mockStepTemplate = (stepMockName: string, step: StepIdentifier, jobId: string | undefined) => ` +const mockStepTemplate = (stepMockName: string, step: MockJobStep, jobId: string | undefined) => ` const ${stepMockName} = utils.createMockStep( '${step.name ?? ''}', '${step.name ?? ''}', diff --git a/workflow_tests/utils/utils.ts b/workflow_tests/utils/utils.ts index 2b4036c8b826..df4cc0468963 100644 --- a/workflow_tests/utils/utils.ts +++ b/workflow_tests/utils/utils.ts @@ -1,10 +1,13 @@ -import type {StepIdentifier} from '@kie/act-js'; -import type {EventJSON} from '@kie/act-js/build/src/action-event/action-event.types'; +import type {StepIdentifier} from '@kie/act-js/build/src/step-mocker/step-mocker.types'; import fs from 'fs'; import path from 'path'; import yaml from 'yaml'; import type ExtendedAct from './ExtendedAct'; +type EventOptions = { + action?: string; +}; + type StepAssertionInputEntry = {key: string; value: string}; type StepAssertion = { @@ -16,7 +19,7 @@ type StepAssertion = { function setUpActParams( act: ExtendedAct, event: string | null = null, - eventOptions: EventJSON | null = null, + eventOptions: EventOptions | null = null, secrets: Record | null = null, githubToken: string | null = null, envVars: Record | null = null,