Skip to content

Commit 6b487d2

Browse files
committed
implement commenting on deployed Mobile-Expensify PRs
1 parent ced60b0 commit 6b487d2

7 files changed

Lines changed: 102 additions & 10 deletions

File tree

.github/actions/javascript/getDeployPullRequestList/action.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ inputs:
1313
outputs:
1414
PR_LIST:
1515
description: Array of pull request numbers
16+
MOBILE_EXPENSIFY_PR_LIST:
17+
description: Array of Mobile-Expensify pull request numbers
1618
runs:
1719
using: 'node20'
1820
main: './index.js'

.github/actions/javascript/getDeployPullRequestList/getDeployPullRequestList.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,24 @@ async function run() {
6464
const prList = await GitUtils.getPullRequestsDeployedBetween(priorTag, inputTag, CONST.APP_REPO);
6565
console.log('Found the pull request list: ', prList);
6666
core.setOutput('PR_LIST', prList);
67+
68+
// Get Mobile-Expensify PRs deployed between the same tags
69+
let mobileExpensifyPRList: number[] = [];
70+
try {
71+
mobileExpensifyPRList = await GitUtils.getPullRequestsDeployedBetween(priorTag, inputTag, CONST.MOBILE_EXPENSIFY_REPO);
72+
console.log('Found Mobile-Expensify pull request list: ', mobileExpensifyPRList);
73+
} catch (error) {
74+
// Check if this is a forked repository
75+
if (process.env.GITHUB_REPOSITORY !== 'Expensify/App') {
76+
console.warn(
77+
"⚠️ Unable to fetch Mobile-Expensify PRs because this workflow is running on a forked repository and secrets aren't accessible. This is expected for development/testing on forks.",
78+
);
79+
} else {
80+
console.error('Failed to fetch Mobile-Expensify PRs from main repository:', error);
81+
// Don't fail the entire workflow, just skip Mobile-Expensify PRs
82+
}
83+
}
84+
core.setOutput('MOBILE_EXPENSIFY_PR_LIST', mobileExpensifyPRList);
6785
} catch (error) {
6886
console.error((error as Error).message);
6987
core.setFailed(error as Error);

.github/actions/javascript/markPullRequestsAsDeployed/action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ inputs:
44
PR_LIST:
55
description: "Array of pull request numbers"
66
required: true
7+
MOBILE_EXPENSIFY_PR_LIST:
8+
description: "Array of Mobile-Expensify pull request numbers"
9+
required: false
710
IS_PRODUCTION_DEPLOY:
811
description: "Check if deploying to production"
912
required: false

.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,12 @@ function getDeployTableMessage(platformResult: PlatformResult): string {
2626
}
2727
}
2828

29-
/**
30-
* Comment Single PR
31-
*/
32-
async function commentPR(PR: number, message: string) {
29+
async function commentPR(PR: number, message: string, repo: string = context.repo.repo) {
3330
try {
34-
await GithubUtils.createComment(context.repo.repo, PR, message);
35-
console.log(`Comment created on #${PR} successfully 🎉`);
31+
await GithubUtils.createComment(repo, PR, message);
32+
console.log(`Comment created on ${repo}#${PR} successfully 🎉`);
3633
} catch (err) {
37-
console.log(`Unable to write comment on #${PR} 😞`);
34+
console.log(`Unable to write comment on ${repo}#${PR} 😞`);
3835
if (err instanceof Error) {
3936
core.setFailed(err.message);
4037
}
@@ -47,6 +44,7 @@ const getCommit = memoize(GithubUtils.octokit.git.getCommit);
4744

4845
async function run() {
4946
const prList = (ActionUtils.getJSONInput('PR_LIST', {required: true}) as string[]).map((num) => Number.parseInt(num, 10));
47+
const mobileExpensifyPRList = (ActionUtils.getJSONInput('MOBILE_EXPENSIFY_PR_LIST', {required: false}) as string[] | undefined)?.map((num) => Number.parseInt(num, 10)) ?? [];
5048
const isProd = ActionUtils.getJSONInput('IS_PRODUCTION_DEPLOY', {required: true}) as boolean;
5149
const version = core.getInput('DEPLOY_VERSION', {required: true});
5250

@@ -103,6 +101,11 @@ async function run() {
103101
for (const pr of prList) {
104102
await commentPR(pr, deployMessage);
105103
}
104+
105+
// Comment on Mobile-Expensify PRs as well
106+
for (const pr of mobileExpensifyPRList) {
107+
await commentPR(pr, deployMessage, CONST.MOBILE_EXPENSIFY_REPO);
108+
}
106109
return;
107110
}
108111

@@ -160,6 +163,70 @@ async function run() {
160163
}
161164
}
162165
}
166+
167+
// Handle Mobile-Expensify PRs for staging deploys
168+
// Note: We'll need to fetch tags from Mobile-Expensify repo for cherry-pick detection
169+
let mobileExpensifyRecentTags: typeof recentTags = [];
170+
try {
171+
const response = await GithubUtils.octokit.repos.listTags({
172+
owner: CONST.GITHUB_OWNER,
173+
repo: CONST.MOBILE_EXPENSIFY_REPO,
174+
per_page: 100,
175+
});
176+
mobileExpensifyRecentTags = response.data;
177+
} catch (error) {
178+
if (process.env.GITHUB_REPOSITORY !== 'Expensify/App') {
179+
console.warn('Unable to fetch Mobile-Expensify tags from forked repository. This is expected.');
180+
} else {
181+
console.error('Failed to fetch Mobile-Expensify tags:', error);
182+
}
183+
}
184+
185+
for (const prNumber of mobileExpensifyPRList) {
186+
try {
187+
const {data: pr} = await GithubUtils.octokit.pulls.get({
188+
owner: CONST.GITHUB_OWNER,
189+
repo: CONST.MOBILE_EXPENSIFY_REPO,
190+
pull_number: prNumber,
191+
});
192+
193+
// Check for the CP Staging label on the issue to see if it was cherry-picked
194+
const isCP = pr.labels.some(({name: labelName}) => labelName === CONST.LABELS.CP_STAGING);
195+
196+
// Determine the deployer. For most PRs it will be whoever merged the PR.
197+
// For CPs it will be whoever created the tag for the PR (i.e: whoever triggered the CP)
198+
let deployer = pr.merged_by?.login;
199+
if (isCP) {
200+
for (const tag of mobileExpensifyRecentTags) {
201+
const {data: commit} = await getCommit({
202+
owner: CONST.GITHUB_OWNER,
203+
repo: CONST.MOBILE_EXPENSIFY_REPO,
204+
commit_sha: tag.commit.sha,
205+
});
206+
const prNumForCPMergeCommit = commit.message.match(/Merge pull request #(\d+)[\S\s]*\(cherry picked from commit .*\)/);
207+
if (prNumForCPMergeCommit?.at(1) === String(prNumber)) {
208+
const cpActor = commit.message.match(/.*\(cherry-picked to .* by (.*)\)/)?.at(1);
209+
if (cpActor) {
210+
deployer = cpActor;
211+
}
212+
break;
213+
}
214+
}
215+
}
216+
217+
const title = pr.title;
218+
const deployMessage = deployer ? getDeployMessage(deployer, isCP ? 'Cherry-picked' : 'Deployed', title) : '';
219+
await commentPR(prNumber, deployMessage, CONST.MOBILE_EXPENSIFY_REPO);
220+
} catch (error) {
221+
if ((error as RequestError).status === 404) {
222+
console.log(`Unable to comment on Mobile-Expensify PR #${prNumber}. GitHub responded with 404.`);
223+
} else if (process.env.GITHUB_REPOSITORY !== 'Expensify/App') {
224+
console.warn(`Unable to comment on Mobile-Expensify PR #${prNumber} from forked repository. This is expected.`);
225+
} else {
226+
throw error;
227+
}
228+
}
229+
}
163230
}
164231

165232
if (require.main === module) {

.github/libs/GithubUtils.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ type CreateCommentResponse = RestEndpointMethodTypes['issues']['createComment'][
5757
type ListCommentsResponse = RestEndpointMethodTypes['issues']['listComments']['response'];
5858

5959
type StagingDeployCashData = {
60-
// TODO: when is this used?
6160
title: string;
6261
url: string;
6362
number: number;

.github/workflows/postDeployComments.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ jobs:
109109
uses: ./.github/actions/javascript/markPullRequestsAsDeployed
110110
with:
111111
PR_LIST: ${{ steps.getPullRequestList.outputs.PR_LIST }}
112+
MOBILE_EXPENSIFY_PR_LIST: ${{ steps.getPullRequestList.outputs.MOBILE_EXPENSIFY_PR_LIST }}
112113
IS_PRODUCTION_DEPLOY: ${{ inputs.env == 'production' }}
113114
DEPLOY_VERSION: ${{ inputs.version }}
114115
GITHUB_TOKEN: ${{ github.token }}

tests/unit/GithubUtilsTest.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,8 +451,9 @@ describe('GithubUtils', () => {
451451
if (typeof issue !== 'object') {
452452
return;
453453
}
454+
const expectedOutputWithMobileExpensify = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/${process.env.GITHUB_REPOSITORY}/compare/production...staging\r\n**Mobile-Expensify Changes:** https://github.com/Expensify/Mobile-Expensify/compare/production...staging\r\n\r\n${deployerFYIMessage}\r\n**This release contains changes from the following pull requests:**\r\n`;
454455
expect(issue.issueBody).toBe(
455-
`${baseExpectedOutput}` +
456+
`${expectedOutputWithMobileExpensify}` +
456457
`${openCheckbox}${basePRList.at(2)}` +
457458
`${lineBreak}${openCheckbox}${basePRList.at(0)}` +
458459
`${lineBreak}${openCheckbox}${basePRList.at(1)}` +
@@ -608,8 +609,9 @@ describe('GithubUtils', () => {
608609
return;
609610
}
610611

612+
const expectedOutputWithMobileExpensify = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/${process.env.GITHUB_REPOSITORY}/compare/production...staging\r\n**Mobile-Expensify Changes:** https://github.com/Expensify/Mobile-Expensify/compare/production...staging\r\n\r\n${deployerFYIMessage}\r\n**This release contains changes from the following pull requests:**\r\n`;
611613
expect(issue.issueBody).toBe(
612-
`${baseExpectedOutput}` +
614+
`${expectedOutputWithMobileExpensify}` +
613615
`${openCheckbox}${basePRList.at(2)}` +
614616
`${lineBreak}${openCheckbox}${basePRList.at(0)}` +
615617
`${lineBreak}${openCheckbox}${basePRList.at(1)}` +

0 commit comments

Comments
 (0)