-
Notifications
You must be signed in to change notification settings - Fork 2.9k
/
markPullRequestsAsDeployed.js
154 lines (140 loc) · 6.42 KB
/
markPullRequestsAsDeployed.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
const _ = require('underscore');
const lodashGet = require('lodash/get');
const core = require('@actions/core');
const {context} = require('@actions/github');
const CONST = require('../../../libs/CONST');
const ActionUtils = require('../../../libs/ActionUtils');
const GithubUtils = require('../../../libs/GithubUtils');
const prList = ActionUtils.getJSONInput('PR_LIST', {required: true});
const isProd = ActionUtils.getJSONInput('IS_PRODUCTION_DEPLOY', {required: true});
const version = core.getInput('DEPLOY_VERSION', {required: true});
/**
* Return a nicely formatted message for the table based on the result of the GitHub action job
*
* @param {String} platformResult
* @returns {String}
*/
function getDeployTableMessage(platformResult) {
switch (platformResult) {
case 'success':
return `${platformResult} ✅`;
case 'cancelled':
return `${platformResult} 🔪`;
case 'skipped':
return `${platformResult} 🚫`;
case 'failure':
default:
return `${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}`;
/**
* @param {String} deployer
* @param {String} deployVerb
* @param {String} prTitle
* @returns {String}
*/
function getDeployMessage(deployer, deployVerb, prTitle) {
let message = `🚀 [${deployVerb}](${workflowURL}) to ${isProd ? 'production' : 'staging'}`;
message += ` by https://github.com/${deployer} in version: ${version} 🚀`;
message += `\n\n platform | result \n ---|--- \n🤖 android 🤖|${androidResult} \n🖥 desktop 🖥|${desktopResult}`;
message += `\n🍎 iOS 🍎|${iOSResult} \n🕸 web 🕸|${webResult}`;
if (deployVerb === 'Cherry-picked' && !/no qa/gi.test(prTitle)) {
// eslint-disable-next-line max-len
message +=
'\n\n@Expensify/applauseleads please QA this PR and check it off on the [deploy checklist](https://github.com/Expensify/App/issues?q=is%3Aopen+is%3Aissue+label%3AStagingDeployCash) if it passes.';
}
return message;
}
/**
* Comment Single PR
*
* @param {Number} PR
* @param {String} message
* @returns {Promise<void>}
*/
function commentPR(PR, message) {
return GithubUtils.createComment(context.repo.repo, PR, message)
.then(() => console.log(`Comment created on #${PR} successfully 🎉`))
.catch((err) => {
console.log(`Unable to write comment on #${PR} 😞`);
core.setFailed(err.message);
});
}
const run = function () {
if (isProd) {
// First find the deployer (who closed the last deploy checklist)?
return GithubUtils.octokit.issues
.listForRepo({
owner: CONST.GITHUB_OWNER,
repo: CONST.APP_REPO,
labels: CONST.LABELS.STAGING_DEPLOY,
state: 'closed',
})
.then(({data}) => _.first(data).number)
.then((lastDeployChecklistNumber) => GithubUtils.getActorWhoClosedIssue(lastDeployChecklistNumber))
.then((actor) => {
// Create comment on each pull request (one after another to avoid throttling issues)
const deployMessage = getDeployMessage(actor, 'Deployed');
_.reduce(prList, (promise, pr) => promise.then(() => commentPR(pr, deployMessage)), Promise.resolve());
});
}
// First find out if this is a normal staging deploy or a CP by looking at the commit message on the tag
return GithubUtils.octokit.repos
.listTags({
owner: CONST.GITHUB_OWNER,
repo: CONST.APP_REPO,
per_page: 100,
})
.then(({data}) => {
const tagSHA = _.find(data, (tag) => tag.name === version).commit.sha;
return GithubUtils.octokit.git.getCommit({
owner: CONST.GITHUB_OWNER,
repo: CONST.APP_REPO,
commit_sha: tagSHA,
});
})
.then(({data}) => {
const isCP = /Merge pull request #\d+ from Expensify\/.*-?cherry-pick-staging-\d+/.test(data.message);
_.reduce(
prList,
(promise, PR) =>
promise
// Then, for each PR, find out who merged it and determine the deployer
.then(() =>
GithubUtils.octokit.pulls.get({
owner: CONST.GITHUB_OWNER,
repo: CONST.APP_REPO,
pull_number: PR,
}),
)
.then((response) => {
/*
* The deployer for staging deploys is:
* 1. For regular staging deploys, the person who merged the PR.
* 2. For automatic CPs (using the label), the person who merged the PR.
* 3. For manual CPs (using the GH UI), the person who triggered the workflow
* (reflected in the branch name).
*/
let deployer = lodashGet(response, 'data.merged_by.login', '');
const issueTitle = lodashGet(response, 'data.title', '');
const CPActorMatches = data.message.match(/Merge pull request #\d+ from Expensify\/(.+)-cherry-pick-staging-\d+/);
if (_.isArray(CPActorMatches) && CPActorMatches.length === 2 && CPActorMatches[1] !== CONST.OS_BOTIFY) {
deployer = CPActorMatches[1];
}
// Finally, comment on the PR
const deployMessage = getDeployMessage(deployer, isCP ? 'Cherry-picked' : 'Deployed', issueTitle);
return commentPR(PR, deployMessage);
}),
Promise.resolve(),
);
});
};
if (require.main === module) {
run();
}
module.exports = run;