diff --git a/.github/actions/checkDeployBlockers/index.js b/.github/actions/checkDeployBlockers/index.js index 94ad93b1bd6a..7ee8d9c5f25a 100644 --- a/.github/actions/checkDeployBlockers/index.js +++ b/.github/actions/checkDeployBlockers/index.js @@ -358,7 +358,8 @@ class GithubUtils { } return data; }) - .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))); + .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))) + .catch(err => console.error('Failed to get PR list', err)); } /** diff --git a/.github/actions/createOrUpdateStagingDeploy/index.js b/.github/actions/createOrUpdateStagingDeploy/index.js index 8a00f3099b86..93174ace2360 100644 --- a/.github/actions/createOrUpdateStagingDeploy/index.js +++ b/.github/actions/createOrUpdateStagingDeploy/index.js @@ -483,7 +483,8 @@ class GithubUtils { } return data; }) - .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))); + .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))) + .catch(err => console.error('Failed to get PR list', err)); } /** diff --git a/.github/actions/getMergeCommitForPullRequest/index.js b/.github/actions/getMergeCommitForPullRequest/index.js index 5f4d6bc8038a..b5ee068d1cb0 100644 --- a/.github/actions/getMergeCommitForPullRequest/index.js +++ b/.github/actions/getMergeCommitForPullRequest/index.js @@ -386,7 +386,8 @@ class GithubUtils { } return data; }) - .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))); + .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))) + .catch(err => console.error('Failed to get PR list', err)); } /** diff --git a/.github/actions/getReleaseBody/index.js b/.github/actions/getReleaseBody/index.js index 1db39d187031..4c126f7be915 100644 --- a/.github/actions/getReleaseBody/index.js +++ b/.github/actions/getReleaseBody/index.js @@ -329,7 +329,8 @@ class GithubUtils { } return data; }) - .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))); + .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))) + .catch(err => console.error('Failed to get PR list', err)); } /** diff --git a/.github/actions/isPullRequestMergeable/index.js b/.github/actions/isPullRequestMergeable/index.js index eb5430b21526..f2392939d31b 100644 --- a/.github/actions/isPullRequestMergeable/index.js +++ b/.github/actions/isPullRequestMergeable/index.js @@ -332,7 +332,8 @@ class GithubUtils { } return data; }) - .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))); + .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))) + .catch(err => console.error('Failed to get PR list', err)); } /** diff --git a/.github/actions/isStagingDeployLocked/index.js b/.github/actions/isStagingDeployLocked/index.js index 0451d13d7fb5..3439b57f813f 100644 --- a/.github/actions/isStagingDeployLocked/index.js +++ b/.github/actions/isStagingDeployLocked/index.js @@ -310,7 +310,8 @@ class GithubUtils { } return data; }) - .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))); + .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))) + .catch(err => console.error('Failed to get PR list', err)); } /** diff --git a/.github/actions/markPullRequestsAsDeployed/action.yml b/.github/actions/markPullRequestsAsDeployed/action.yml index c79e3abbdd6b..020298326dd9 100644 --- a/.github/actions/markPullRequestsAsDeployed/action.yml +++ b/.github/actions/markPullRequestsAsDeployed/action.yml @@ -8,9 +8,6 @@ inputs: description: "Check if deploying to production" required: false default: "false" - STAGING_DEPLOY_NUMBER: - description: "StagingDeployCash issue number" - required: true DEPLOY_VERSION: description: "The app version in which the pull requests were deployed" required: true diff --git a/.github/actions/markPullRequestsAsDeployed/index.js b/.github/actions/markPullRequestsAsDeployed/index.js index 73f966a01395..0cdd013beb67 100644 --- a/.github/actions/markPullRequestsAsDeployed/index.js +++ b/.github/actions/markPullRequestsAsDeployed/index.js @@ -18,10 +18,10 @@ const GithubUtils = __nccwpck_require__(7999); const prList = ActionUtils.getJSONInput('PR_LIST', {required: true}); const isProd = ActionUtils.getJSONInput('IS_PRODUCTION_DEPLOY', {required: true}); -const stagingDeployIssueNumber = ActionUtils.getJSONInput('STAGING_DEPLOY_NUMBER', {required: true}); const version = core.getInput('DEPLOY_VERSION', {required: true}); -let lockCashDeployLabelTimeline = []; const PRMap = {}; +const stagingDeployIssueMap = {}; +let stagingDeployIssuesList = []; /** @@ -44,15 +44,48 @@ function getDeployTableMessage(platformResult) { } } +const androidResult = getDeployTableMessage(core.getInput('ANDROID', {required: true})); +const desktopResult = getDeployTableMessage(core.getInput('DESKTOP', {required: true})); +const iOSResult = getDeployTableMessage(core.getInput('IOS', {required: true})); +const webResult = getDeployTableMessage(core.getInput('WEB', {required: true})); + +const workflowURL = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}` + + `/actions/runs/${process.env.GITHUB_RUN_ID}`; + /** - * Get the [added, removed] pairs for the `šŸ” LockCashDeploys šŸ”` label on StagingDeployCash + * Fetch all the StagingDeploy issues that were created after the passed fromTimestamp and + * including one before the fromTimestamp. * + * @param {String} fromTimestamp + * @returns {Promise} + */ +function fetchAllStagingDeployCash(fromTimestamp) { + return GithubUtils.octokit.paginate(GithubUtils.octokit.issues.listForRepo, { + owner: GithubUtils.GITHUB_OWNER, + repo: GithubUtils.EXPENSIFY_CASH_REPO, + state: 'all', + sort: 'created', + direction: 'desc', + labels: GithubUtils.STAGING_DEPLOY_CASH_LABEL, + }, ({data}, done) => { + const lastIssueIndex = _.findIndex(data, issue => moment(issue.created_at).isBefore(moment(fromTimestamp))); + if (lastIssueIndex !== -1) { + done(); + } + return data; + }) + .catch(err => console.error(`Failed to get ${GithubUtils.STAGING_DEPLOY_CASH_LABEL} issues list`, err)); +} + +/** + * Get the [added, removed] pairs for the `šŸ” LockCashDeploys šŸ”` label on StagingDeployCash + * @param {Number|String} stagingDeployIssueNumber * @return {Promise>} */ -function getLockCashDeploysTimeline() { +function fetchLockCashDeploysTimeline(stagingDeployIssueNumber) { return GithubUtils.octokit.paginate(GithubUtils.octokit.issues.listEvents, { owner: GithubUtils.GITHUB_OWNER, - repo: GithubUtils.GITHUB_REPOSITORY, + repo: GithubUtils.EXPENSIFY_CASH_REPO, issue_number: stagingDeployIssueNumber, per_page: 100, }).then((events) => { @@ -74,16 +107,29 @@ function getLockCashDeploysTimeline() { return pair.length > 1 ? pair : undefined; })); return startEndPairs; - }); + }).catch(err => console.error('Failed to get the šŸ” LockCashDeploys šŸ” label\'s timeline', err)); } -const androidResult = getDeployTableMessage(core.getInput('ANDROID', {required: true})); -const desktopResult = getDeployTableMessage(core.getInput('DESKTOP', {required: true})); -const iOSResult = getDeployTableMessage(core.getInput('IOS', {required: true})); -const webResult = getDeployTableMessage(core.getInput('WEB', {required: true})); - -const workflowURL = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}` - + `/actions/runs/${process.env.GITHUB_RUN_ID}`; +/** + * Get StagingDeployIssue timeline for the PR + * + * @param {Number} pr + * @return {Promise<[string, string][]>} + */ +function getPRLockCashDeploysTimeline(pr) { + const prData = PRMap[pr]; + const stagingDeployIssue = _.find( + stagingDeployIssuesList, issue => moment(issue.created_at).isBefore(moment(prData.mergedAt)), + ); + const stagingDeployIssueMapRef = stagingDeployIssueMap[stagingDeployIssue.number]; + if (stagingDeployIssueMapRef.timeline) { + return Promise.resolve(stagingDeployIssueMapRef.timeline); + } + return fetchLockCashDeploysTimeline(stagingDeployIssue.number).then((lockCashDeployLabelTimeSet) => { + stagingDeployIssueMap[stagingDeployIssue.number].timeline = lockCashDeployLabelTimeSet; + return lockCashDeployLabelTimeSet; + }); +} /** * Get Deploy Verb for the PR @@ -96,22 +142,25 @@ function getPRDeployVerb(pr) { const hasCPStagingLabel = _.contains(_.pluck(PR.labels, 'name'), 'CP Staging'); if (!hasCPStagingLabel) { - return 'Deployed'; + return Promise.resolve('Deployed'); } - const liesBetweenTimeline = _.some( - lockCashDeployLabelTimeline, - ([startAt, endAt]) => moment(PR.mergedAt).isBetween(startAt, endAt, undefined, '[]'), - ); - return liesBetweenTimeline ? 'Cherry-picked' : 'Deployed'; + return getPRLockCashDeploysTimeline(pr).then((lockCashDeployLabelTimeline) => { + const liesBetweenTimeline = _.some( + lockCashDeployLabelTimeline, + ([startAt, endAt]) => moment(PR.mergedAt).isBetween(startAt, endAt, undefined, '[]'), + ); + return liesBetweenTimeline ? 'Cherry-picked' : 'Deployed'; + }); } function getPRMessage(PR) { - const deployVerb = getPRDeployVerb(PR); - let message = `šŸš€ [${deployVerb}](${workflowURL}) to ${isProd ? 'production' : 'staging'}\ + return getPRDeployVerb(PR).then((deployVerb) => { + let message = `šŸš€ [${deployVerb}](${workflowURL}) to ${isProd ? 'production' : 'staging'}\ in version: ${version}šŸš€`; - message += `\n\n platform | result \n ---|--- \nšŸ¤– android šŸ¤–|${androidResult} \nšŸ–„ desktop šŸ–„|${desktopResult}`; - message += `\nšŸŽ iOS šŸŽ|${iOSResult} \nšŸ•ø web šŸ•ø|${webResult}`; - return message; + message += `\n\n platform | result \n ---|--- \nšŸ¤– android šŸ¤–|${androidResult} \nšŸ–„ desktop šŸ–„|${desktopResult}`; + message += `\nšŸŽ iOS šŸŽ|${iOSResult} \nšŸ•ø web šŸ•ø|${webResult}`; + return message; + }); } /** @@ -121,7 +170,7 @@ function getPRMessage(PR) { * @returns {Promise} */ function commentPR(pr) { - return GithubUtils.createComment(context.repo.repo, pr, getPRMessage(pr)) + return getPRMessage(pr).then(message => GithubUtils.createComment(context.repo.repo, pr, message)) .then(() => { console.log(`Comment created on #${pr} successfully šŸŽ‰`); }) @@ -132,21 +181,28 @@ function commentPR(pr) { } const run = function () { - return Promise.all([ - getLockCashDeploysTimeline(), - GithubUtils.fetchAllPullRequests(prList), - ]) - .then(([lockCashDeployLabelTimeSet, PRListWithDetails]) => { - lockCashDeployLabelTimeline = lockCashDeployLabelTimeSet; + return GithubUtils.fetchAllPullRequests(_.compact(_.map(prList, pr => parseInt(pr, 10)))) + .then((PRListWithDetails) => { _.each(PRListWithDetails, (PR) => { PRMap[PR.number] = PR; }); + const oldestPR = _.first(_.sortBy(prList)); + return fetchAllStagingDeployCash(PRMap[oldestPR].mergedAt); + }) + .then((issueList) => { + _.each(issueList, (issueData) => { + stagingDeployIssueMap[issueData.number] = { + data: issueData, + }; + }); + stagingDeployIssuesList = issueList; /** * Create comment on each pull request */ return prList.reduce((promise, pr) => promise.then(() => commentPR(pr)), Promise.resolve()); - }); + }) + .catch(err => console.error('Failed to get neccesary data to comment deployed PRs', err)); }; if (require.main === require.cache[eval('__filename')]) { @@ -462,7 +518,8 @@ class GithubUtils { } return data; }) - .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))); + .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))) + .catch(err => console.error('Failed to get PR list', err)); } /** diff --git a/.github/actions/markPullRequestsAsDeployed/markPullRequestsAsDeployed.js b/.github/actions/markPullRequestsAsDeployed/markPullRequestsAsDeployed.js index d21ee2837337..4c5a3090ee7a 100644 --- a/.github/actions/markPullRequestsAsDeployed/markPullRequestsAsDeployed.js +++ b/.github/actions/markPullRequestsAsDeployed/markPullRequestsAsDeployed.js @@ -8,10 +8,10 @@ const GithubUtils = require('../../libs/GithubUtils'); const prList = ActionUtils.getJSONInput('PR_LIST', {required: true}); const isProd = ActionUtils.getJSONInput('IS_PRODUCTION_DEPLOY', {required: true}); -const stagingDeployIssueNumber = ActionUtils.getJSONInput('STAGING_DEPLOY_NUMBER', {required: true}); const version = core.getInput('DEPLOY_VERSION', {required: true}); -let lockCashDeployLabelTimeline = []; const PRMap = {}; +const stagingDeployIssueMap = {}; +let stagingDeployIssuesList = []; /** @@ -34,15 +34,48 @@ function getDeployTableMessage(platformResult) { } } +const androidResult = getDeployTableMessage(core.getInput('ANDROID', {required: true})); +const desktopResult = getDeployTableMessage(core.getInput('DESKTOP', {required: true})); +const iOSResult = getDeployTableMessage(core.getInput('IOS', {required: true})); +const webResult = getDeployTableMessage(core.getInput('WEB', {required: true})); + +const workflowURL = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}` + + `/actions/runs/${process.env.GITHUB_RUN_ID}`; + /** - * Get the [added, removed] pairs for the `šŸ” LockCashDeploys šŸ”` label on StagingDeployCash + * Fetch all the StagingDeploy issues that were created after the passed fromTimestamp and + * including one before the fromTimestamp. * + * @param {String} fromTimestamp + * @returns {Promise} + */ +function fetchAllStagingDeployCash(fromTimestamp) { + return GithubUtils.octokit.paginate(GithubUtils.octokit.issues.listForRepo, { + owner: GithubUtils.GITHUB_OWNER, + repo: GithubUtils.EXPENSIFY_CASH_REPO, + state: 'all', + sort: 'created', + direction: 'desc', + labels: GithubUtils.STAGING_DEPLOY_CASH_LABEL, + }, ({data}, done) => { + const lastIssueIndex = _.findIndex(data, issue => moment(issue.created_at).isBefore(moment(fromTimestamp))); + if (lastIssueIndex !== -1) { + done(); + } + return data; + }) + .catch(err => console.error(`Failed to get ${GithubUtils.STAGING_DEPLOY_CASH_LABEL} issues list`, err)); +} + +/** + * Get the [added, removed] pairs for the `šŸ” LockCashDeploys šŸ”` label on StagingDeployCash + * @param {Number|String} stagingDeployIssueNumber * @return {Promise>} */ -function getLockCashDeploysTimeline() { +function fetchLockCashDeploysTimeline(stagingDeployIssueNumber) { return GithubUtils.octokit.paginate(GithubUtils.octokit.issues.listEvents, { owner: GithubUtils.GITHUB_OWNER, - repo: GithubUtils.GITHUB_REPOSITORY, + repo: GithubUtils.EXPENSIFY_CASH_REPO, issue_number: stagingDeployIssueNumber, per_page: 100, }).then((events) => { @@ -64,16 +97,29 @@ function getLockCashDeploysTimeline() { return pair.length > 1 ? pair : undefined; })); return startEndPairs; - }); + }).catch(err => console.error('Failed to get the šŸ” LockCashDeploys šŸ” label\'s timeline', err)); } -const androidResult = getDeployTableMessage(core.getInput('ANDROID', {required: true})); -const desktopResult = getDeployTableMessage(core.getInput('DESKTOP', {required: true})); -const iOSResult = getDeployTableMessage(core.getInput('IOS', {required: true})); -const webResult = getDeployTableMessage(core.getInput('WEB', {required: true})); - -const workflowURL = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}` - + `/actions/runs/${process.env.GITHUB_RUN_ID}`; +/** + * Get StagingDeployIssue timeline for the PR + * + * @param {Number} pr + * @return {Promise<[string, string][]>} + */ +function getPRLockCashDeploysTimeline(pr) { + const prData = PRMap[pr]; + const stagingDeployIssue = _.find( + stagingDeployIssuesList, issue => moment(issue.created_at).isBefore(moment(prData.mergedAt)), + ); + const stagingDeployIssueMapRef = stagingDeployIssueMap[stagingDeployIssue.number]; + if (stagingDeployIssueMapRef.timeline) { + return Promise.resolve(stagingDeployIssueMapRef.timeline); + } + return fetchLockCashDeploysTimeline(stagingDeployIssue.number).then((lockCashDeployLabelTimeSet) => { + stagingDeployIssueMap[stagingDeployIssue.number].timeline = lockCashDeployLabelTimeSet; + return lockCashDeployLabelTimeSet; + }); +} /** * Get Deploy Verb for the PR @@ -86,22 +132,25 @@ function getPRDeployVerb(pr) { const hasCPStagingLabel = _.contains(_.pluck(PR.labels, 'name'), 'CP Staging'); if (!hasCPStagingLabel) { - return 'Deployed'; + return Promise.resolve('Deployed'); } - const liesBetweenTimeline = _.some( - lockCashDeployLabelTimeline, - ([startAt, endAt]) => moment(PR.mergedAt).isBetween(startAt, endAt, undefined, '[]'), - ); - return liesBetweenTimeline ? 'Cherry-picked' : 'Deployed'; + return getPRLockCashDeploysTimeline(pr).then((lockCashDeployLabelTimeline) => { + const liesBetweenTimeline = _.some( + lockCashDeployLabelTimeline, + ([startAt, endAt]) => moment(PR.mergedAt).isBetween(startAt, endAt, undefined, '[]'), + ); + return liesBetweenTimeline ? 'Cherry-picked' : 'Deployed'; + }); } function getPRMessage(PR) { - const deployVerb = getPRDeployVerb(PR); - let message = `šŸš€ [${deployVerb}](${workflowURL}) to ${isProd ? 'production' : 'staging'}\ + return getPRDeployVerb(PR).then((deployVerb) => { + let message = `šŸš€ [${deployVerb}](${workflowURL}) to ${isProd ? 'production' : 'staging'}\ in version: ${version}šŸš€`; - message += `\n\n platform | result \n ---|--- \nšŸ¤– android šŸ¤–|${androidResult} \nšŸ–„ desktop šŸ–„|${desktopResult}`; - message += `\nšŸŽ iOS šŸŽ|${iOSResult} \nšŸ•ø web šŸ•ø|${webResult}`; - return message; + message += `\n\n platform | result \n ---|--- \nšŸ¤– android šŸ¤–|${androidResult} \nšŸ–„ desktop šŸ–„|${desktopResult}`; + message += `\nšŸŽ iOS šŸŽ|${iOSResult} \nšŸ•ø web šŸ•ø|${webResult}`; + return message; + }); } /** @@ -111,7 +160,7 @@ function getPRMessage(PR) { * @returns {Promise} */ function commentPR(pr) { - return GithubUtils.createComment(context.repo.repo, pr, getPRMessage(pr)) + return getPRMessage(pr).then(message => GithubUtils.createComment(context.repo.repo, pr, message)) .then(() => { console.log(`Comment created on #${pr} successfully šŸŽ‰`); }) @@ -122,21 +171,28 @@ function commentPR(pr) { } const run = function () { - return Promise.all([ - getLockCashDeploysTimeline(), - GithubUtils.fetchAllPullRequests(prList), - ]) - .then(([lockCashDeployLabelTimeSet, PRListWithDetails]) => { - lockCashDeployLabelTimeline = lockCashDeployLabelTimeSet; + return GithubUtils.fetchAllPullRequests(_.compact(_.map(prList, pr => parseInt(pr, 10)))) + .then((PRListWithDetails) => { _.each(PRListWithDetails, (PR) => { PRMap[PR.number] = PR; }); + const oldestPR = _.first(_.sortBy(prList)); + return fetchAllStagingDeployCash(PRMap[oldestPR].mergedAt); + }) + .then((issueList) => { + _.each(issueList, (issueData) => { + stagingDeployIssueMap[issueData.number] = { + data: issueData, + }; + }); + stagingDeployIssuesList = issueList; /** * Create comment on each pull request */ return prList.reduce((promise, pr) => promise.then(() => commentPR(pr)), Promise.resolve()); - }); + }) + .catch(err => console.error('Failed to get neccesary data to comment deployed PRs', err)); }; if (require.main === module) { diff --git a/.github/actions/reopenIssueWithComment/index.js b/.github/actions/reopenIssueWithComment/index.js index f6fb5e345902..0bb157477140 100644 --- a/.github/actions/reopenIssueWithComment/index.js +++ b/.github/actions/reopenIssueWithComment/index.js @@ -321,7 +321,8 @@ class GithubUtils { } return data; }) - .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))); + .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))) + .catch(err => console.error('Failed to get PR list', err)); } /** diff --git a/.github/actions/triggerWorkflowAndWait/index.js b/.github/actions/triggerWorkflowAndWait/index.js index ceb5b74309a9..2e7363b3b697 100644 --- a/.github/actions/triggerWorkflowAndWait/index.js +++ b/.github/actions/triggerWorkflowAndWait/index.js @@ -472,7 +472,8 @@ class GithubUtils { } return data; }) - .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))); + .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))) + .catch(err => console.error('Failed to get PR list', err)); } /** diff --git a/.github/libs/GithubUtils.js b/.github/libs/GithubUtils.js index 59263891fc6c..cbfa56600b2f 100644 --- a/.github/libs/GithubUtils.js +++ b/.github/libs/GithubUtils.js @@ -270,7 +270,8 @@ class GithubUtils { } return data; }) - .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))); + .then(prList => _.filter(prList, pr => _.contains(pullRequestNumbers, pr.number))) + .catch(err => console.error('Failed to get PR list', err)); } /**