Skip to content

Commit

Permalink
Merge pull request #26301 from Expensify/Rory-RefactorCreateOrUpdateS…
Browse files Browse the repository at this point in the history
…tagingDeploy

[No QA] Refactor create or update staging deploy to use async/await
  • Loading branch information
luacmartins authored Aug 30, 2023
2 parents 4fee350 + f5fa30b commit c9a5f41
Show file tree
Hide file tree
Showing 4 changed files with 274 additions and 351 deletions.
8 changes: 8 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,5 +163,13 @@ module.exports = {
],
},
},
{
files: ['tests/**/*.{js,jsx,ts,tsx}', '.github/**/*.{js,jsx,ts,tsx}'],
rules: {
'@lwc/lwc/no-async-await': 'off',
'no-await-in-loop': 'off',
'no-restricted-syntax': ['error', 'ForInStatement', 'LabeledStatement', 'WithStatement'],
},
},
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,167 +5,127 @@ const CONST = require('../../../libs/CONST');
const GithubUtils = require('../../../libs/GithubUtils');
const GitUtils = require('../../../libs/GitUtils');

const run = function () {
async function run() {
const newVersion = core.getInput('NPM_VERSION');
console.log('New version found from action input:', newVersion);

let shouldCreateNewStagingDeployCash = false;
let newTag = newVersion;
let newDeployBlockers = [];
let previousStagingDeployCashData = {};
let currentStagingDeployCashData = null;

// Start by fetching the list of recent StagingDeployCash issues, along with the list of open deploy blockers
return Promise.all([
GithubUtils.octokit.issues.listForRepo({
try {
// Start by fetching the list of recent StagingDeployCash issues, along with the list of open deploy blockers
const {data: recentDeployChecklists} = await GithubUtils.octokit.issues.listForRepo({
log: console,
owner: CONST.GITHUB_OWNER,
repo: CONST.APP_REPO,
labels: CONST.LABELS.STAGING_DEPLOY,
state: 'all',
}),
GithubUtils.octokit.issues.listForRepo({
log: console,
owner: CONST.GITHUB_OWNER,
repo: CONST.APP_REPO,
labels: CONST.LABELS.DEPLOY_BLOCKER,
}),
])
.then(([stagingDeployResponse, deployBlockerResponse]) => {
if (!stagingDeployResponse || !stagingDeployResponse.data || _.isEmpty(stagingDeployResponse.data)) {
console.error('Failed fetching StagingDeployCash issues from Github!', stagingDeployResponse);
throw new Error('Failed fetching StagingDeployCash issues from Github');
}

if (!deployBlockerResponse || !deployBlockerResponse.data) {
console.log('Failed fetching DeployBlockerCash issues from Github, continuing...');
}

newDeployBlockers = _.map(deployBlockerResponse.data, ({html_url}) => ({
url: html_url,
number: GithubUtils.getIssueOrPullRequestNumberFromURL(html_url),
isResolved: false,
}));

// Look at the state of the most recent StagingDeployCash,
// if it is open then we'll update the existing one, otherwise, we'll create a new one.
shouldCreateNewStagingDeployCash = Boolean(stagingDeployResponse.data[0].state !== 'open');
if (shouldCreateNewStagingDeployCash) {
console.log('Latest StagingDeployCash is closed, creating a new one.', stagingDeployResponse.data[0]);
} else {
console.log(
'Latest StagingDeployCash is open, updating it instead of creating a new one.',
'Current:',
stagingDeployResponse.data[0],
'Previous:',
stagingDeployResponse.data[1],
);
}

// Parse the data from the previous StagingDeployCash
// (newest if there are none open, otherwise second-newest)
previousStagingDeployCashData = shouldCreateNewStagingDeployCash
? GithubUtils.getStagingDeployCashData(stagingDeployResponse.data[0])
: GithubUtils.getStagingDeployCashData(stagingDeployResponse.data[1]);

console.log('Found tag of previous StagingDeployCash:', previousStagingDeployCashData.tag);

// Find the list of PRs merged between the last StagingDeployCash and the new version
if (shouldCreateNewStagingDeployCash) {
return GitUtils.getPullRequestsMergedBetween(previousStagingDeployCashData.tag, newTag);
}

currentStagingDeployCashData = GithubUtils.getStagingDeployCashData(stagingDeployResponse.data[0]);
console.log('Parsed the following data from the current StagingDeployCash:', currentStagingDeployCashData);

// If we aren't sent a tag, then use the existing tag
newTag = newTag || currentStagingDeployCashData.tag;

return GitUtils.getPullRequestsMergedBetween(previousStagingDeployCashData.tag, newTag);
})
.then((mergedPRs) => {
console.log(`The following PRs have been merged between the previous StagingDeployCash (${previousStagingDeployCashData.tag}) and new version (${newVersion}):`, mergedPRs);

if (shouldCreateNewStagingDeployCash) {
return GithubUtils.generateStagingDeployCashBody(newTag, _.map(mergedPRs, GithubUtils.getPullRequestURLFromNumber));
}

const didVersionChange = newVersion ? newVersion !== currentStagingDeployCashData.tag : false;

// Generate the PR list, preserving the previous state of `isVerified` for existing PRs
const PRList = _.sortBy(
_.unique(
_.union(
currentStagingDeployCashData.PRList,
_.map(mergedPRs, (number) => ({
number,
url: GithubUtils.getPullRequestURLFromNumber(number),
});

// Since this is the second argument to _.union,
// it will appear later in the array than any duplicate.
// Since it is later in the array, it will be truncated by _.unique,
// and the original value of isVerified will be preserved.
isVerified: false,
})),
),
false,
(item) => item.number,
),
'number',
// Look at the state of the most recent StagingDeployCash,
// if it is open then we'll update the existing one, otherwise, we'll create a new one.
const mostRecentChecklist = recentDeployChecklists[0];
const shouldCreateNewDeployChecklist = mostRecentChecklist.state !== 'open';
const previousChecklist = shouldCreateNewDeployChecklist ? mostRecentChecklist : recentDeployChecklists[1];
if (shouldCreateNewDeployChecklist) {
console.log('Latest StagingDeployCash is closed, creating a new one.', mostRecentChecklist);
} else {
console.log('Latest StagingDeployCash is open, updating it instead of creating a new one.', 'Current:', mostRecentChecklist, 'Previous:', previousChecklist);
}

// Parse the data from the previous and current checklists into the format used to generate the checklist
const previousChecklistData = GithubUtils.getStagingDeployCashData(previousChecklist);
const currentChecklistData = shouldCreateNewDeployChecklist ? {} : GithubUtils.getStagingDeployCashData(mostRecentChecklist);

// Find the list of PRs merged between the current checklist and the previous checklist
// Note that any time we're creating a new checklist we MUST have `NPM_VERSION` passed in as an input
const newTag = newVersion || _.get(currentChecklistData, 'tag');
const mergedPRs = await GitUtils.getPullRequestsMergedBetween(previousChecklistData.tag, newTag);

// Next, we generate the checklist body
let checklistBody = '';
if (shouldCreateNewDeployChecklist) {
checklistBody = await GithubUtils.generateStagingDeployCashBody(newTag, _.map(mergedPRs, GithubUtils.getPullRequestURLFromNumber));
} else {
// Generate the updated PR list, preserving the previous state of `isVerified` for existing PRs
const PRList = _.reduce(
mergedPRs,
(memo, prNum) => {
const indexOfPRInCurrentChecklist = _.findIndex(currentChecklistData.PRList, (pr) => pr.number === prNum);
const isVerified = indexOfPRInCurrentChecklist >= 0 ? currentChecklistData.PRList[indexOfPRInCurrentChecklist].isVerified : false;
memo.push({
number: prNum,
url: GithubUtils.getPullRequestURLFromNumber(prNum),
isVerified,
});
return memo;
},
[],
);

// Generate the deploy blocker list, preserving the previous state of `isResolved`
const deployBlockers = _.sortBy(
_.unique(_.union(currentStagingDeployCashData.deployBlockers, newDeployBlockers), false, (item) => item.number),
'number',
const {data: openDeployBlockers} = await GithubUtils.octokit.issues.listForRepo({
log: console,
owner: CONST.GITHUB_OWNER,
repo: CONST.APP_REPO,
labels: CONST.LABELS.DEPLOY_BLOCKER,
});
const deployBlockers = _.reduce(
openDeployBlockers,
(memo, deployBlocker) => {
const {html_url, number} = deployBlocker;
const indexInCurrentChecklist = _.findIndex(currentChecklistData.deployBlockers, (item) => item.number === number);
const isResolved = indexInCurrentChecklist >= 0 ? currentChecklistData.deployBlockers[indexInCurrentChecklist].isResolved : false;
memo.push({
number,
url: html_url,
isResolved,
});
return memo;
},
[],
);

// Get the internalQA PR list, preserving the previous state of `isResolved`
const internalQAPRList = _.sortBy(currentStagingDeployCashData.internalQAPRList, 'number');

return GithubUtils.generateStagingDeployCashBody(
const didVersionChange = newVersion ? newVersion !== currentChecklistData.tag : false;
checklistBody = await GithubUtils.generateStagingDeployCashBody(
newTag,
_.pluck(PRList, 'url'),
_.pluck(_.where(PRList, {isVerified: true}), 'url'),
_.pluck(deployBlockers, 'url'),
_.pluck(_.where(deployBlockers, {isResolved: true}), 'url'),
_.pluck(_.where(internalQAPRList, {isResolved: true}), 'url'),
didVersionChange ? false : currentStagingDeployCashData.isTimingDashboardChecked,
didVersionChange ? false : currentStagingDeployCashData.isFirebaseChecked,
didVersionChange ? false : currentStagingDeployCashData.isGHStatusChecked,
_.pluck(_.where(currentChecklistData.internalQAPRList, {isResolved: true}), 'url'),
didVersionChange ? false : currentChecklistData.isTimingDashboardChecked,
didVersionChange ? false : currentChecklistData.isFirebaseChecked,
didVersionChange ? false : currentChecklistData.isGHStatusChecked,
);
})
.then((body) => {
const defaultPayload = {
owner: CONST.GITHUB_OWNER,
repo: CONST.APP_REPO,
body,
};
}

if (shouldCreateNewStagingDeployCash) {
return GithubUtils.octokit.issues.create({
...defaultPayload,
title: `Deploy Checklist: New Expensify ${moment().format('YYYY-MM-DD')}`,
labels: [CONST.LABELS.STAGING_DEPLOY],
assignees: [CONST.APPLAUSE_BOT],
});
}
// Finally, create or update the checklist
const defaultPayload = {
owner: CONST.GITHUB_OWNER,
repo: CONST.APP_REPO,
body: checklistBody,
};

return GithubUtils.octokit.issues.update({
if (shouldCreateNewDeployChecklist) {
const {data: newChecklist} = await GithubUtils.octokit.issues.create({
...defaultPayload,
issue_number: currentStagingDeployCashData.number,
title: `Deploy Checklist: New Expensify ${moment().format('YYYY-MM-DD')}`,
labels: [CONST.LABELS.STAGING_DEPLOY],
assignees: [CONST.APPLAUSE_BOT],
});
})
.then(({data}) => {
// eslint-disable-next-line max-len
console.log(`Successfully ${shouldCreateNewStagingDeployCash ? 'created new' : 'updated'} StagingDeployCash! 🎉 ${data.html_url}`);
return data;
})
.catch((err) => {
console.error('An unknown error occurred!', err);
core.setFailed(err);
console.log(`Successfully created new StagingDeployCash! 🎉 ${newChecklist.html_url}`);
return newChecklist;
}

const {data: updatedChecklist} = await GithubUtils.octokit.issues.update({
...defaultPayload,
issue_number: currentChecklistData.number,
});
};
console.log(`Successfully updated StagingDeployCash! 🎉 ${updatedChecklist.html_url}`);
return updatedChecklist;
} catch (err) {
console.error('An unknown error occurred!', err);
core.setFailed(err);
}
}

if (require.main === module) {
run();
Expand Down
Loading

0 comments on commit c9a5f41

Please sign in to comment.