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/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/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..ab1a3ecc7d64 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.",
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/ReportUtils.ts b/src/libs/ReportUtils.ts
index 9ee97ff8a988..e7a6af8c6f3a 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -1286,7 +1286,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);
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/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/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/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/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/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/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,