Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[No QA] Use full Git history to populate StagingDeployCash #3173

Merged
merged 9 commits into from
Jun 17, 2021
82 changes: 4 additions & 78 deletions .github/actions/checkDeployBlockers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const ISSUE_OR_PULL_REQUEST_REGEX = new RegExp(`${GITHUB_BASE_URL_REGEX.source}/

const APPLAUSE_BOT = 'applausebot';
const STAGING_DEPLOY_CASH_LABEL = 'StagingDeployCash';
const DEPLOY_BLOCKER_CASH_LABEL = 'DeployBlockerCash';

class GithubUtils {
/**
Expand Down Expand Up @@ -185,6 +186,7 @@ class GithubUtils {
return {
title: issue.title,
url: issue.url,
number: this.getIssueOrPullRequestNumberFromURL(issue.url),
labels: issue.labels,
PRList: this.getStagingDeployCashPRList(issue),
deployBlockers: this.getStagingDeployCashDeployBlockers(issue),
Expand Down Expand Up @@ -269,87 +271,9 @@ class GithubUtils {
);
}

/**
* Creates a new StagingDeployCash issue.
*
* @param {String} title
* @param {String} tag
* @param {Array} PRList
* @returns {Promise}
*/
static createNewStagingDeployCash(title, tag, PRList) {
return this.generateStagingDeployCashBody(tag, PRList)
.then(body => this.octokit.issues.create({
owner: GITHUB_OWNER,
repo: EXPENSIFY_CASH_REPO,
labels: [STAGING_DEPLOY_CASH_LABEL],
assignees: [APPLAUSE_BOT],
title,
body,
}));
}

/**
* Updates the existing open StagingDeployCash issue.
*
* @param {String} [newTag]
* @param {Array} newPRs
* @param {Array} newDeployBlockers
* @returns {Promise}
* @throws {Error} If the StagingDeployCash could not be found or updated.
*/
static updateStagingDeployCash(newTag = '', newPRs, newDeployBlockers) {
let issueNumber;
return this.getStagingDeployCash()
.then(({
url,
tag: oldTag,
PRList: oldPRs,
deployBlockers: oldDeployBlockers,
}) => {
issueNumber = GithubUtils.getIssueNumberFromURL(url);

// If we aren't sent a tag, then use the existing tag
const tag = _.isEmpty(newTag) ? oldTag : newTag;

const PRList = _.sortBy(
_.union(oldPRs, _.map(newPRs, URL => ({
url: URL,
number: GithubUtils.getPullRequestNumberFromURL(URL),
isVerified: false,
}))),
'number',
);
const deployBlockers = _.sortBy(
_.union(oldDeployBlockers, _.map(newDeployBlockers, URL => ({
url: URL,
number: GithubUtils.getIssueOrPullRequestNumberFromURL(URL),
isResolved: false,
}))),
'number',
);

return this.generateStagingDeployCashBody(
tag,
_.pluck(PRList, 'url'),
_.pluck(_.where(PRList, {isVerified: true}), 'url'),
_.pluck(deployBlockers, 'url'),
_.pluck(_.where(deployBlockers, {isResolved: true}), 'url'),
);
})
.then(updatedBody => this.octokit.issues.update({
owner: GITHUB_OWNER,
repo: EXPENSIFY_CASH_REPO,
issue_number: issueNumber,
body: updatedBody,
}));
}

/**
* Generate the issue body for a StagingDeployCash.
*
* @private
*
* @param {String} tag
* @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash
* @param {Array} [verifiedPRList] - The list of PR URLs which have passed QA.
Expand Down Expand Up @@ -533,6 +457,8 @@ module.exports = GithubUtils;
module.exports.GITHUB_OWNER = GITHUB_OWNER;
module.exports.EXPENSIFY_CASH_REPO = EXPENSIFY_CASH_REPO;
module.exports.STAGING_DEPLOY_CASH_LABEL = STAGING_DEPLOY_CASH_LABEL;
module.exports.DEPLOY_BLOCKER_CASH_LABEL = DEPLOY_BLOCKER_CASH_LABEL;
module.exports.APPLAUSE_BOT = APPLAUSE_BOT;


/***/ }),
Expand Down
6 changes: 0 additions & 6 deletions .github/actions/createOrUpdateStagingDeploy/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ inputs:
NPM_VERSION:
description: The new NPM version of the StagingDeployCash issue.
required: false
NEW_PULL_REQUESTS:
description: A comma-separated list of pull request URLs to add to the open StagingDeployCash issue.
required: false
NEW_DEPLOY_BLOCKERS:
description: A comma-separated list of deploy blockers to add to the open StagingDeployCash issue.
required: false
runs:
using: 'node12'
main: './index.js'
Original file line number Diff line number Diff line change
Expand Up @@ -4,66 +4,145 @@ const moment = require('moment');
const GithubUtils = require('../../libs/GithubUtils');
const GitUtils = require('../../libs/GitUtils');

const newVersion = core.getInput('NPM_VERSION');


GithubUtils.getStagingDeployCash()
.then(() => GithubUtils.updateStagingDeployCash(
newVersion,
_.filter(
_.map(core.getInput('NEW_PULL_REQUESTS').split(','), PR => PR.trim()),
PR => !_.isEmpty(PR),
),
_.filter(
_.map(core.getInput('NEW_DEPLOY_BLOCKERS').split(','), deployBlocker => deployBlocker.trim()),
PR => !_.isEmpty(PR),
),
))
.then(({data}) => {
console.log('Successfully updated StagingDeployCash! 🎉', data.html_url);
process.exit(0);
})
.catch((err) => {
// Unable to find the open StagingDeployCash
if (err && err.code === 404) {
console.log('No open StagingDeployCash found, creating a new one.');

// Fetch all the StagingDeployCash issues
return GithubUtils.octokit.issues.listForRepo({
log: console,
const run = function () {
const newVersion = core.getInput('NPM_VERSION');

let shouldCreateNewStagingDeployCash = false;
let currentStagingDeployCashIssueNumber = null;

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

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

// Look at the state of the most recent StagingDeployCash,
// if it is open then we'll update the existing one, otherwise, we'll create a new one.
shouldCreateNewStagingDeployCash = Boolean(stagingDeployResponse.data[0].state !== 'open');

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

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

// Find the list of PRs merged between the last StagingDeployCash and the new version
const mergedPRs = GitUtils.getPullRequestsMergedBetween(previousStagingDeployCashData.tag, newVersion);

if (shouldCreateNewStagingDeployCash) {
// We're in the create flow, not update
// TODO: if there are open DeployBlockers and we are opening a new checklist,
// then we should close / remove the DeployBlockerCash label from those
return GithubUtils.generateStagingDeployCashBody(newVersion, mergedPRs);
}

// There is an open StagingDeployCash, so we'll be updating it, not creating a new one
const currentStagingDeployCashData = GithubUtils.getStagingDeployCashData(stagingDeployResponse.data[0]);
currentStagingDeployCashIssueNumber = currentStagingDeployCashData.number;

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

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

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

// Since this is the second argument to _.union,
// it will appear later in the array than any duplicate.
// Since it is later in the array, it will be truncated by _.unique,
// and the original value of isVerified will be preserved.
isVerified: false,
}))),
false,
item => item.number,
),
'number',
);

// Generate the deploy blocker list, preserving the previous state of `isResolved`
const deployBlockers = _.sortBy(
_.unique(
_.union(currentStagingDeployCashData.deployBlockers, newDeployBlockers),
false,
item => item.number,
),
'number',
);

return GithubUtils.generateStagingDeployCashBody(
tag,
_.pluck(PRList, 'url'),
_.pluck(_.where(PRList, {isVerified: true}), 'url'),
_.pluck(deployBlockers, 'url'),
_.pluck(_.where(deployBlockers, {isResolved: true}), 'url'),
);
})
.then((body) => {
const defaultPayload = {
owner: GithubUtils.GITHUB_OWNER,
repo: GithubUtils.EXPENSIFY_CASH_REPO,
labels: GithubUtils.STAGING_DEPLOY_CASH_LABEL,
state: 'closed',
body,
};

if (shouldCreateNewStagingDeployCash) {
return GithubUtils.octokit.issues.create({
...defaultPayload,
title: `Deploy Checklist: Expensify.cash ${moment().format('YYYY-MM-DD')}`,
labels: [GithubUtils.STAGING_DEPLOY_CASH_LABEL],
assignees: [GithubUtils.APPLAUSE_BOT],
});
}

return GithubUtils.octokit.issues.update({
...defaultPayload,
issue_number: currentStagingDeployCashIssueNumber,
});
}

// Unexpected error!
console.error('Unexpected error occurred finding the StagingDeployCash!'
+ ' There may have been more than one open StagingDeployCash found,'
+ ' or there was some other problem with the Github API request.', err);
core.setFailed(err);
})
.then((githubResponse) => {
if (!githubResponse || !githubResponse.data || _.isEmpty(githubResponse.data)) {
console.error('Failed fetching data from Github!', githubResponse);
throw new Error('Failed fetching data from Github');
}

// Parse the tag from the most recent StagingDeployCash
const lastTag = GithubUtils.getStagingDeployCashData(githubResponse.data[0]).tag;
console.log('Found tag of previous StagingDeployCash:', lastTag);

// Find the list of PRs merged between the last StagingDeployCash and the new version
return GitUtils.getPullRequestsMergedBetween(lastTag, newVersion);
})
.then(PRNumbers => GithubUtils.createNewStagingDeployCash(
`Deploy Checklist: Expensify.cash ${moment().format('YYYY-MM-DD')}`,
newVersion,
_.map(PRNumbers, GithubUtils.getPullRequestURLFromNumber),
))
.then(({data}) => console.log('Successfully created new StagingDeployCash! 🎉', data.html_url))
.catch((err) => {
console.error('An error occurred!', err);
core.setFailed(err);
});
})
.then(({data}) => {
// eslint-disable-next-line max-len
console.log(`Successfully ${shouldCreateNewStagingDeployCash ? 'created new' : 'updated'} StagingDeployCash! 🎉 ${data.html_url}`);
return data;
})
.catch((err) => {
console.error('An unknown error occurred!', err);
core.setFailed(err);
});
};

if (require.main === module) {
run();
}

module.exports = run;
Loading