diff --git a/.github/actions/javascript/postTestBuildComment/action.yml b/.github/actions/javascript/postOrReplaceComment/action.yml similarity index 74% rename from .github/actions/javascript/postTestBuildComment/action.yml rename to .github/actions/javascript/postOrReplaceComment/action.yml index ac66781bf22a..d3d47dfc3650 100644 --- a/.github/actions/javascript/postTestBuildComment/action.yml +++ b/.github/actions/javascript/postOrReplaceComment/action.yml @@ -1,5 +1,5 @@ -name: "Mark Pull Requests as Deployed" -description: "Mark pull requests as deployed on production or staging" +name: "postOrReplaceComment" +description: "Post a test build or custom pull request comment, hiding the previous matching comment" inputs: REPO: description: "Repository to place a comment. Can be App or Mobile-Expensify" @@ -31,6 +31,12 @@ inputs: WEB_LINK: description: "Link for the web build" required: false + COMMENT_BODY: + description: "Custom comment body. When provided, posts this comment instead of the test build message" + required: false + COMMENT_PREFIX: + description: "Prefix used to find and hide the previous matching comment" + required: true runs: using: "node24" main: "./index.js" diff --git a/.github/actions/javascript/postTestBuildComment/index.js b/.github/actions/javascript/postOrReplaceComment/index.js similarity index 99% rename from .github/actions/javascript/postTestBuildComment/index.js rename to .github/actions/javascript/postOrReplaceComment/index.js index b1f4f7693b06..63b29a027e4b 100644 --- a/.github/actions/javascript/postTestBuildComment/index.js +++ b/.github/actions/javascript/postOrReplaceComment/index.js @@ -11538,7 +11538,7 @@ function wrappy (fn, cb) { /***/ }), -/***/ 3580: +/***/ 7071: /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -11638,7 +11638,7 @@ Built from${appPr ? ` App PR Expensify/App#${appPr}` : ''}${mobileExpensifyPr ? } /** Comment on a single PR */ async function commentPR(REPO, PR, message) { - console.log(`Posting test build comment on #${PR}`); + console.log(`Posting comment on #${PR}`); try { await GithubUtils_1.default.createComment(REPO, PR, message); console.log(`Comment created on #${PR} (${REPO}) successfully 🎉`); @@ -11650,10 +11650,37 @@ async function commentPR(REPO, PR, message) { } } } +async function hidePreviousComment(repo, issueNumber, commentPrefix) { + const comments = await GithubUtils_1.default.paginate(GithubUtils_1.default.octokit.issues.listComments, { + owner: CONST_1.default.GITHUB_OWNER, + repo, + // eslint-disable-next-line @typescript-eslint/naming-convention + issue_number: issueNumber, + // eslint-disable-next-line @typescript-eslint/naming-convention + per_page: 100, + }, (response) => response.data); + const previousComment = comments.findLast((comment) => comment.body?.startsWith(commentPrefix)); + if (!previousComment) { + return; + } + await GithubUtils_1.default.graphql(` + mutation MinimizeComment($subjectId: ID!) { + minimizeComment(input: {classifier: OUTDATED, subjectId: $subjectId}) { + minimizedComment { + minimizedReason + } + } + } + `, { + subjectId: previousComment.node_id, + }); +} async function run() { const APP_PR_NUMBER = Number(core.getInput('APP_PR_NUMBER', { required: false })); const MOBILE_EXPENSIFY_PR_NUMBER = Number(core.getInput('MOBILE_EXPENSIFY_PR_NUMBER', { required: false })); const REPO = String(core.getInput('REPO', { required: true })); + const COMMENT_BODY = core.getInput('COMMENT_BODY', { required: false }); + const COMMENT_PREFIX = core.getInput('COMMENT_PREFIX', { required: true }); if (REPO !== CONST_1.default.APP_REPO && REPO !== CONST_1.default.MOBILE_EXPENSIFY_REPO) { core.setFailed(`Invalid repository used to place output comment: ${REPO}`); return; @@ -11663,28 +11690,8 @@ async function run() { return; } const destinationPRNumber = REPO === CONST_1.default.APP_REPO ? APP_PR_NUMBER : MOBILE_EXPENSIFY_PR_NUMBER; - const comments = await GithubUtils_1.default.paginate(GithubUtils_1.default.octokit.issues.listComments, { - owner: CONST_1.default.GITHUB_OWNER, - repo: REPO, - // eslint-disable-next-line @typescript-eslint/naming-convention - issue_number: destinationPRNumber, - // eslint-disable-next-line @typescript-eslint/naming-convention - per_page: 100, - }, (response) => response.data); - const testBuildComment = comments.find((comment) => comment.body?.startsWith(':test_tube::test_tube: Use the links below to test this adhoc build')); - if (testBuildComment) { - console.log('Found previous build comment, hiding it', testBuildComment); - await GithubUtils_1.default.graphql(` - mutation { - minimizeComment(input: {classifier: OUTDATED, subjectId: "${testBuildComment.node_id}"}) { - minimizedComment { - minimizedReason - } - } - } - `); - } - await commentPR(REPO, destinationPRNumber, getTestBuildMessage(APP_PR_NUMBER, MOBILE_EXPENSIFY_PR_NUMBER)); + await hidePreviousComment(REPO, destinationPRNumber, COMMENT_PREFIX); + await commentPR(REPO, destinationPRNumber, COMMENT_BODY || getTestBuildMessage(APP_PR_NUMBER, MOBILE_EXPENSIFY_PR_NUMBER)); } if (require.main === require.cache[eval('__filename')]) { run(); @@ -12402,7 +12409,7 @@ module.exports = JSON.parse('[[[0,44],"disallowed_STD3_valid"],[[45,46],"valid"] /******/ // startup /******/ // Load entry module and return exports /******/ // This entry module is referenced by other modules so it can't be inlined -/******/ var __webpack_exports__ = __nccwpck_require__(3580); +/******/ var __webpack_exports__ = __nccwpck_require__(7071); /******/ module.exports = __webpack_exports__; /******/ /******/ })() diff --git a/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts b/.github/actions/javascript/postOrReplaceComment/postOrReplaceComment.ts similarity index 82% rename from .github/actions/javascript/postTestBuildComment/postTestBuildComment.ts rename to .github/actions/javascript/postOrReplaceComment/postOrReplaceComment.ts index a2278e10fce5..1ff862df798b 100644 --- a/.github/actions/javascript/postTestBuildComment/postTestBuildComment.ts +++ b/.github/actions/javascript/postOrReplaceComment/postOrReplaceComment.ts @@ -68,7 +68,7 @@ Built from${appPr ? ` App PR Expensify/App#${appPr}` : ''}${mobileExpensifyPr ? /** Comment on a single PR */ async function commentPR(REPO: string, PR: number, message: string) { - console.log(`Posting test build comment on #${PR}`); + console.log(`Posting comment on #${PR}`); try { await GithubUtils.createComment(REPO, PR, message); console.log(`Comment created on #${PR} (${REPO}) successfully 🎉`); @@ -81,48 +81,61 @@ async function commentPR(REPO: string, PR: number, message: string) { } } -async function run() { - const APP_PR_NUMBER = Number(core.getInput('APP_PR_NUMBER', {required: false})); - const MOBILE_EXPENSIFY_PR_NUMBER = Number(core.getInput('MOBILE_EXPENSIFY_PR_NUMBER', {required: false})); - const REPO = String(core.getInput('REPO', {required: true})); - - if (REPO !== CONST.APP_REPO && REPO !== CONST.MOBILE_EXPENSIFY_REPO) { - core.setFailed(`Invalid repository used to place output comment: ${REPO}`); - return; - } - - if ((REPO === CONST.APP_REPO && !APP_PR_NUMBER) || (REPO === CONST.MOBILE_EXPENSIFY_REPO && !MOBILE_EXPENSIFY_PR_NUMBER)) { - core.setFailed(`Please provide ${REPO} pull request number`); - return; - } - - const destinationPRNumber = REPO === CONST.APP_REPO ? APP_PR_NUMBER : MOBILE_EXPENSIFY_PR_NUMBER; +async function hidePreviousComment(repo: string, issueNumber: number, commentPrefix: string): Promise { const comments = await GithubUtils.paginate( GithubUtils.octokit.issues.listComments, { owner: CONST.GITHUB_OWNER, - repo: REPO, + repo, // eslint-disable-next-line @typescript-eslint/naming-convention - issue_number: destinationPRNumber, + issue_number: issueNumber, // eslint-disable-next-line @typescript-eslint/naming-convention per_page: 100, }, (response) => response.data, ); - const testBuildComment = comments.find((comment) => comment.body?.startsWith(':test_tube::test_tube: Use the links below to test this adhoc build')); - if (testBuildComment) { - console.log('Found previous build comment, hiding it', testBuildComment); - await GithubUtils.graphql(` - mutation { - minimizeComment(input: {classifier: OUTDATED, subjectId: "${testBuildComment.node_id}"}) { + const previousComment = comments.findLast((comment) => comment.body?.startsWith(commentPrefix)); + + if (!previousComment) { + return; + } + + await GithubUtils.graphql( + ` + mutation MinimizeComment($subjectId: ID!) { + minimizeComment(input: {classifier: OUTDATED, subjectId: $subjectId}) { minimizedComment { minimizedReason } } } - `); + `, + { + subjectId: previousComment.node_id, + }, + ); +} + +async function run() { + const APP_PR_NUMBER = Number(core.getInput('APP_PR_NUMBER', {required: false})); + const MOBILE_EXPENSIFY_PR_NUMBER = Number(core.getInput('MOBILE_EXPENSIFY_PR_NUMBER', {required: false})); + const REPO = String(core.getInput('REPO', {required: true})); + const COMMENT_BODY = core.getInput('COMMENT_BODY', {required: false}); + const COMMENT_PREFIX = core.getInput('COMMENT_PREFIX', {required: true}); + + if (REPO !== CONST.APP_REPO && REPO !== CONST.MOBILE_EXPENSIFY_REPO) { + core.setFailed(`Invalid repository used to place output comment: ${REPO}`); + return; + } + + if ((REPO === CONST.APP_REPO && !APP_PR_NUMBER) || (REPO === CONST.MOBILE_EXPENSIFY_REPO && !MOBILE_EXPENSIFY_PR_NUMBER)) { + core.setFailed(`Please provide ${REPO} pull request number`); + return; } - await commentPR(REPO, destinationPRNumber, getTestBuildMessage(APP_PR_NUMBER, MOBILE_EXPENSIFY_PR_NUMBER)); + + const destinationPRNumber = REPO === CONST.APP_REPO ? APP_PR_NUMBER : MOBILE_EXPENSIFY_PR_NUMBER; + await hidePreviousComment(REPO, destinationPRNumber, COMMENT_PREFIX); + await commentPR(REPO, destinationPRNumber, COMMENT_BODY || getTestBuildMessage(APP_PR_NUMBER, MOBILE_EXPENSIFY_PR_NUMBER)); } if (require.main === module) { diff --git a/.github/scripts/buildActions.sh b/.github/scripts/buildActions.sh index 3d2de54c31f4..ef9fbe1e2e44 100755 --- a/.github/scripts/buildActions.sh +++ b/.github/scripts/buildActions.sh @@ -25,7 +25,7 @@ declare -r GITHUB_ACTIONS=( "$ACTIONS_DIR/getPullRequestIncrementalChanges/getPullRequestIncrementalChanges.ts" "$ACTIONS_DIR/isDeployChecklistLocked/isDeployChecklistLocked.ts" "$ACTIONS_DIR/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts" - "$ACTIONS_DIR/postTestBuildComment/postTestBuildComment.ts" + "$ACTIONS_DIR/postOrReplaceComment/postOrReplaceComment.ts" "$ACTIONS_DIR/proposalPoliceComment/proposalPoliceComment.ts" "$ACTIONS_DIR/reopenIssueWithComment/reopenIssueWithComment.ts" "$ACTIONS_DIR/reviewerChecklist/reviewerChecklist.ts" diff --git a/.github/workflows/buildAdHoc.yml b/.github/workflows/buildAdHoc.yml index 8a692a8daf3e..bfc85efef93e 100644 --- a/.github/workflows/buildAdHoc.yml +++ b/.github/workflows/buildAdHoc.yml @@ -147,12 +147,13 @@ jobs: - name: Publish links to apps for download on Expensify/App PR if: ${{ inputs.APP_PR_NUMBER != '' }} - uses: ./.github/actions/javascript/postTestBuildComment + uses: ./.github/actions/javascript/postOrReplaceComment with: REPO: App APP_PR_NUMBER: ${{ inputs.APP_PR_NUMBER }} MOBILE_EXPENSIFY_PR_NUMBER: ${{ inputs.MOBILE_EXPENSIFY_PR }} GITHUB_TOKEN: ${{ github.token }} + COMMENT_PREFIX: ':test_tube::test_tube: Use the links below to test this adhoc build' ANDROID: ${{ needs.buildAndroid.result }} IOS: ${{ needs.buildIOS.result }} WEB: ${{ needs.buildWeb.result == 'failure' && 'failure' || needs.deployWebAdHoc.result }} @@ -162,11 +163,12 @@ jobs: - name: Publish links to apps for download on Expensify/Mobile-Expensify PR if: ${{ inputs.MOBILE_EXPENSIFY_PR != '' }} - uses: ./.github/actions/javascript/postTestBuildComment + uses: ./.github/actions/javascript/postOrReplaceComment with: REPO: Mobile-Expensify MOBILE_EXPENSIFY_PR_NUMBER: ${{ inputs.MOBILE_EXPENSIFY_PR }} GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + COMMENT_PREFIX: ':test_tube::test_tube: Use the links below to test this adhoc build' ANDROID: ${{ needs.buildAndroid.result }} IOS: ${{ needs.buildIOS.result }} ANDROID_LINK: ${{ needs.buildAndroid.outputs.ROCK_ARTIFACT_URL }} diff --git a/.github/workflows/deployExpensifyHelp.yml b/.github/workflows/deployExpensifyHelp.yml index c88b524e9dc3..4c4e17528bec 100644 --- a/.github/workflows/deployExpensifyHelp.yml +++ b/.github/workflows/deployExpensifyHelp.yml @@ -125,8 +125,11 @@ jobs: PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} - name: Leave a comment on the PR - uses: actions-cool/maintain-one-comment@de04bd2a3750d86b324829a3ff34d47e48e16f4b + uses: ./.github/actions/javascript/postOrReplaceComment if: ${{ github.event_name == 'pull_request' && env.IS_PR_FROM_FORK != 'true' }} with: - token: ${{ secrets.OS_BOTIFY_TOKEN }} - body: ${{ steps.preview_comment.outputs.BODY }} + REPO: App + APP_PR_NUMBER: ${{ github.event.pull_request.number }} + GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + COMMENT_PREFIX: A preview of your ExpensifyHelp changes have been deployed to + COMMENT_BODY: ${{ steps.preview_comment.outputs.BODY }} diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml index 84eeb03f6858..80083a4a53d0 100644 --- a/.github/workflows/deployNewHelp.yml +++ b/.github/workflows/deployNewHelp.yml @@ -88,12 +88,14 @@ jobs: # After deploying Cloudflare preview build, share wherever it deployed to in the PR comment. - name: Leave a comment on the PR - # v3.2.0 - uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b + uses: ./.github/actions/javascript/postOrReplaceComment if: ${{ github.event_name == 'pull_request' && env.IS_PR_FROM_FORK != 'true' }} with: - token: ${{ github.token }} - body: ${{ format('Your New Help changes have been deployed to {0} :zap:️', steps.cloudflarePagesAction.outputs.alias) }} + REPO: App + APP_PR_NUMBER: ${{ github.event.pull_request.number }} + GITHUB_TOKEN: ${{ github.token }} + COMMENT_PREFIX: Your New Help changes have been deployed to + COMMENT_BODY: ${{ format('Your New Help changes have been deployed to {0} :zap:️', steps.cloudflarePagesAction.outputs.alias) }} - name: Get merged pull request if: ${{ github.event_name == 'push' }} diff --git a/tests/unit/postTestBuildComment.ts b/tests/unit/postOrReplaceComment.ts similarity index 86% rename from tests/unit/postTestBuildComment.ts rename to tests/unit/postOrReplaceComment.ts index f7caf2dc6f61..2d45dd516c8d 100644 --- a/tests/unit/postTestBuildComment.ts +++ b/tests/unit/postOrReplaceComment.ts @@ -4,7 +4,7 @@ import * as core from '@actions/core'; import type {RestEndpointMethods} from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/method-types'; import {when} from 'jest-when'; -import ghAction from '@github/actions/javascript/postTestBuildComment/postTestBuildComment'; +import ghAction from '@github/actions/javascript/postOrReplaceComment/postOrReplaceComment'; import CONST from '@github/libs/CONST'; import type {CreateCommentResponse} from '@github/libs/GithubUtils'; import GithubUtils from '@github/libs/GithubUtils'; @@ -46,6 +46,7 @@ jest.mock('@actions/github', () => ({ const androidLink = 'https://expensify.app/ANDROID_LINK'; const iOSLink = 'https://expensify.app/IOS_LINK'; const webLink = 'https://expensify.app/WEB_LINK'; +const testBuildCommentPrefix = ':test_tube::test_tube: Use the links below to test this adhoc build'; const androidQRCode = `![Android](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${androidLink})`; const iOSQRCode = `![iOS](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${iOSLink})`; @@ -102,7 +103,7 @@ Built from Mobile-Expensify PR Expensify/Mobile-Expensify#13. :eyes: [View the workflow run that generated this build](https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/1234) :eyes: `; -describe('Post test build comments action tests', () => { +describe('postOrReplaceComment action tests', () => { beforeAll(() => { // Mock core module asMutable(core).getInput = mockGetInput; @@ -110,10 +111,30 @@ describe('Post test build comments action tests', () => { beforeEach(() => jest.clearAllMocks()); + function expectPreviousCommentToBeHidden() { + expect(mockGraphql).toHaveBeenCalledTimes(1); + expect(mockGraphql).toHaveBeenCalledWith( + ` + mutation MinimizeComment($subjectId: ID!) { + minimizeComment(input: {classifier: OUTDATED, subjectId: $subjectId}) { + minimizedComment { + minimizedReason + } + } + } + `, + { + subjectId: 'IC_abcd', + }, + ); + } + test('Test GH action', async () => { when(core.getInput).calledWith('REPO', {required: true}).mockReturnValue(CONST.APP_REPO); when(core.getInput).calledWith('APP_PR_NUMBER', {required: false}).mockReturnValue('12'); when(core.getInput).calledWith('MOBILE_EXPENSIFY_PR_NUMBER', {required: false}).mockReturnValue('13'); + when(core.getInput).calledWith('COMMENT_PREFIX', {required: true}).mockReturnValue(testBuildCommentPrefix); + when(core.getInput).calledWith('COMMENT_BODY', {required: false}).mockReturnValue(''); when(core.getInput).calledWith('ANDROID', {required: false}).mockReturnValue('success'); when(core.getInput).calledWith('IOS', {required: false}).mockReturnValue('success'); when(core.getInput).calledWith('WEB', {required: false}).mockReturnValue('success'); @@ -131,16 +152,7 @@ describe('Post test build comments action tests', () => { ], }); await ghAction(); - expect(mockGraphql).toHaveBeenCalledTimes(1); - expect(mockGraphql).toHaveBeenCalledWith(` - mutation { - minimizeComment(input: {classifier: OUTDATED, subjectId: "IC_abcd"}) { - minimizedComment { - minimizedReason - } - } - } - `); + expectPreviousCommentToBeHidden(); expect(createCommentMock).toHaveBeenCalledTimes(1); expect(createCommentMock).toHaveBeenCalledWith(CONST.APP_REPO, 12, message); }); @@ -149,6 +161,8 @@ describe('Post test build comments action tests', () => { when(core.getInput).calledWith('REPO', {required: true}).mockReturnValue(CONST.APP_REPO); when(core.getInput).calledWith('APP_PR_NUMBER', {required: false}).mockReturnValue('12'); when(core.getInput).calledWith('MOBILE_EXPENSIFY_PR_NUMBER', {required: false}).mockReturnValue(''); + when(core.getInput).calledWith('COMMENT_PREFIX', {required: true}).mockReturnValue(testBuildCommentPrefix); + when(core.getInput).calledWith('COMMENT_BODY', {required: false}).mockReturnValue(''); when(core.getInput).calledWith('ANDROID', {required: false}).mockReturnValue('success'); when(core.getInput).calledWith('IOS', {required: false}).mockReturnValue('skipped'); when(core.getInput).calledWith('WEB', {required: false}).mockReturnValue('skipped'); @@ -164,16 +178,7 @@ describe('Post test build comments action tests', () => { ], }); await ghAction(); - expect(mockGraphql).toHaveBeenCalledTimes(1); - expect(mockGraphql).toHaveBeenCalledWith(` - mutation { - minimizeComment(input: {classifier: OUTDATED, subjectId: "IC_abcd"}) { - minimizedComment { - minimizedReason - } - } - } - `); + expectPreviousCommentToBeHidden(); expect(createCommentMock).toHaveBeenCalledTimes(1); expect(createCommentMock).toHaveBeenCalledWith(CONST.APP_REPO, 12, onlyAppMessage); }); @@ -182,6 +187,8 @@ describe('Post test build comments action tests', () => { when(core.getInput).calledWith('REPO', {required: true}).mockReturnValue(CONST.MOBILE_EXPENSIFY_REPO); when(core.getInput).calledWith('APP_PR_NUMBER', {required: false}).mockReturnValue(''); when(core.getInput).calledWith('MOBILE_EXPENSIFY_PR_NUMBER', {required: false}).mockReturnValue('13'); + when(core.getInput).calledWith('COMMENT_PREFIX', {required: true}).mockReturnValue(testBuildCommentPrefix); + when(core.getInput).calledWith('COMMENT_BODY', {required: false}).mockReturnValue(''); when(core.getInput).calledWith('ANDROID', {required: false}).mockReturnValue('success'); when(core.getInput).calledWith('IOS', {required: false}).mockReturnValue('success'); when(core.getInput).calledWith('ANDROID_LINK').mockReturnValue(androidLink); @@ -198,16 +205,7 @@ describe('Post test build comments action tests', () => { ], }); await ghAction(); - expect(mockGraphql).toHaveBeenCalledTimes(1); - expect(mockGraphql).toHaveBeenCalledWith(` - mutation { - minimizeComment(input: {classifier: OUTDATED, subjectId: "IC_abcd"}) { - minimizedComment { - minimizedReason - } - } - } - `); + expectPreviousCommentToBeHidden(); expect(createCommentMock).toHaveBeenCalledTimes(1); expect(createCommentMock).toHaveBeenCalledWith(CONST.MOBILE_EXPENSIFY_REPO, 13, onlyMobileExpensifyMessage); });