diff --git a/.github/actions/checkDeployBlockers/checkDeployBlockers.js b/.github/actions/checkDeployBlockers/checkDeployBlockers.js index 000ea410b94d..c0c50d2cce4e 100644 --- a/.github/actions/checkDeployBlockers/checkDeployBlockers.js +++ b/.github/actions/checkDeployBlockers/checkDeployBlockers.js @@ -16,9 +16,8 @@ const run = function () { console.log('Checking for unverified PRs or unresolved deploy blockers', data); // Check the issue description to see if there are any unfinished/un-QAed items in the checklist. - const uncheckedBoxRegex = /-\s\[\s]/g; - const matches = uncheckedBoxRegex.exec(data.body); - if (matches !== null) { + const uncheckedBoxRegex = new RegExp(`-\\s\\[\\s]\\s(?:QA|${GithubUtils.ISSUE_OR_PULL_REQUEST_REGEX.source})`); + if (uncheckedBoxRegex.test(data.body)) { console.log('An unverified PR or unresolved deploy blocker was found.'); core.setOutput('HAS_DEPLOY_BLOCKERS', true); return; diff --git a/.github/actions/checkDeployBlockers/index.js b/.github/actions/checkDeployBlockers/index.js index 2162f09f07f5..79cbf3100e5b 100644 --- a/.github/actions/checkDeployBlockers/index.js +++ b/.github/actions/checkDeployBlockers/index.js @@ -26,9 +26,8 @@ const run = function () { console.log('Checking for unverified PRs or unresolved deploy blockers', data); // Check the issue description to see if there are any unfinished/un-QAed items in the checklist. - const uncheckedBoxRegex = /-\s\[\s]/g; - const matches = uncheckedBoxRegex.exec(data.body); - if (matches !== null) { + const uncheckedBoxRegex = new RegExp(`-\\s\\[\\s]\\s(?:QA|${GithubUtils.ISSUE_OR_PULL_REQUEST_REGEX.source})`); + if (uncheckedBoxRegex.test(data.body)) { console.log('An unverified PR or unresolved deploy blocker was found.'); core.setOutput('HAS_DEPLOY_BLOCKERS', true); return; @@ -512,6 +511,7 @@ 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; +module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; /***/ }), diff --git a/.github/actions/createOrUpdateStagingDeploy/index.js b/.github/actions/createOrUpdateStagingDeploy/index.js index 613ccb0401cb..1fc4bf16c44f 100644 --- a/.github/actions/createOrUpdateStagingDeploy/index.js +++ b/.github/actions/createOrUpdateStagingDeploy/index.js @@ -701,6 +701,7 @@ 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; +module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; /***/ }), diff --git a/.github/actions/getPullRequestDetails/index.js b/.github/actions/getPullRequestDetails/index.js index 6e9e8b9fe4cf..342373eebb12 100644 --- a/.github/actions/getPullRequestDetails/index.js +++ b/.github/actions/getPullRequestDetails/index.js @@ -565,6 +565,7 @@ 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; +module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; /***/ }), diff --git a/.github/actions/getReleaseBody/index.js b/.github/actions/getReleaseBody/index.js index 794b52333290..e34ba2955069 100644 --- a/.github/actions/getReleaseBody/index.js +++ b/.github/actions/getReleaseBody/index.js @@ -483,6 +483,7 @@ 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; +module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; /***/ }), diff --git a/.github/actions/isPullRequestMergeable/index.js b/.github/actions/isPullRequestMergeable/index.js index d6e271850742..e8f8e84cacea 100644 --- a/.github/actions/isPullRequestMergeable/index.js +++ b/.github/actions/isPullRequestMergeable/index.js @@ -501,6 +501,7 @@ 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; +module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; /***/ }), diff --git a/.github/actions/isStagingDeployLocked/index.js b/.github/actions/isStagingDeployLocked/index.js index 5a0e38ed3de3..7ea200ca6ff0 100644 --- a/.github/actions/isStagingDeployLocked/index.js +++ b/.github/actions/isStagingDeployLocked/index.js @@ -464,6 +464,7 @@ 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; +module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; /***/ }), diff --git a/.github/actions/markPullRequestsAsDeployed/index.js b/.github/actions/markPullRequestsAsDeployed/index.js index 9fc180ac5ec9..cfb143b877a3 100644 --- a/.github/actions/markPullRequestsAsDeployed/index.js +++ b/.github/actions/markPullRequestsAsDeployed/index.js @@ -616,6 +616,7 @@ 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; +module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; /***/ }), diff --git a/.github/actions/reopenIssueWithComment/index.js b/.github/actions/reopenIssueWithComment/index.js index a7ce41077ef5..4b34fae9bfce 100644 --- a/.github/actions/reopenIssueWithComment/index.js +++ b/.github/actions/reopenIssueWithComment/index.js @@ -475,6 +475,7 @@ 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; +module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; /***/ }), diff --git a/.github/actions/triggerWorkflowAndWait/index.js b/.github/actions/triggerWorkflowAndWait/index.js index 9ba263668e8e..7f1a048fe333 100644 --- a/.github/actions/triggerWorkflowAndWait/index.js +++ b/.github/actions/triggerWorkflowAndWait/index.js @@ -634,6 +634,7 @@ 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; +module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; /***/ }), diff --git a/.github/libs/GithubUtils.js b/.github/libs/GithubUtils.js index d750c7340ccb..a17a2b875543 100644 --- a/.github/libs/GithubUtils.js +++ b/.github/libs/GithubUtils.js @@ -424,3 +424,4 @@ 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; +module.exports.ISSUE_OR_PULL_REQUEST_REGEX = ISSUE_OR_PULL_REQUEST_REGEX; diff --git a/tests/unit/checkDeployBlockersTest.js b/tests/unit/checkDeployBlockersTest.js index d439c5ec99b2..983c94f2aac7 100644 --- a/tests/unit/checkDeployBlockersTest.js +++ b/tests/unit/checkDeployBlockersTest.js @@ -1,6 +1,7 @@ /** * @jest-environment node */ +const _ = require('underscore'); const core = require('@actions/core'); const GithubUtils = require('../../.github/libs/GithubUtils'); const run = require('../../.github/actions/checkDeployBlockers/checkDeployBlockers'); @@ -35,6 +36,23 @@ beforeAll(() => { GithubUtils.octokitInternal = mocktokit; }); +let baseComments = []; +beforeEach(() => { + baseComments = { + data: [ + { + body: 'foo', + }, + { + body: 'bar', + }, + { + body: ':shipit:', + }, + ], + }; +}); + afterEach(() => { mockSetOutput.mockClear(); mockGetIssue.mockClear(); @@ -45,69 +63,99 @@ afterAll(() => { jest.clearAllMocks(); }); -describe('checkDeployBlockers', () => { - const baseIssue = { +function checkbox(isClosed) { + return isClosed ? '[x]' : '[ ]'; +} + +function mockIssue(prList, deployBlockerList) { + return { data: { number: 1, - title: 'Scott\'s Unfinished QA Checklist', - body: 'Checklist for Deploy #668\r\n' - + '- [x] @foo https://github.com/Expensify/App/issues/1', + title: 'Scott\'s QA Checklist', + body: ` +**Release Version:** \`1.1.31-2\` +**Compare Changes:** https://github.com/Expensify/App/compare/production...staging + +**This release contains changes from the following pull requests:** +${_.map(prList, ({url, isQASuccess, isAccessibilitySuccess}) => ` +- ${url} + - ${checkbox(isQASuccess)} QA + - ${checkbox(isAccessibilitySuccess)} Accessibility +`)} +${!_.isEmpty(deployBlockerList) ? ` + +**Deploy Blockers:**` : ''} +${_.map(deployBlockerList, ({url, isQASuccess}) => ` +- ${checkbox(isQASuccess)} ${url} +`)} +cc @Expensify/applauseleads +`, }, }; +} - const baseComments = { - data: [ - { - body: 'foo', - }, - { - body: 'bar', - }, - { - body: ':shipit:', - }, - ], - }; +describe('checkDeployBlockers', () => { + const allClearIssue = mockIssue([{url: 'https://github.com/Expensify/App/pull/6882', isQASuccess: true, isAccessibilitySuccess: true}]); describe('checkDeployBlockers', () => { - test('Test an issue with a checked item and :shipit:', () => { - mockGetIssue.mockResolvedValue(baseIssue); + test('Test an issue with all checked items and :shipit:', () => { + mockGetIssue.mockResolvedValue(allClearIssue); mockListComments.mockResolvedValue(baseComments); return run().then(() => { expect(mockSetOutput).toHaveBeenCalledWith('HAS_DEPLOY_BLOCKERS', false); }); }); - test('Test an issue with an unchecked item and :shipit:', () => { - const uncheckedItemIssue = baseIssue; - uncheckedItemIssue.data.body += '\r\n- [ ] @bar https://github.com/Expensify/App/issues/23'; - mockGetIssue.mockResolvedValue(uncheckedItemIssue); - mockListComments.mockResolvedValue(baseComments); + test('Test an issue with all boxes checked but no :shipit:', () => { + mockGetIssue.mockResolvedValue(allClearIssue); + const extraComments = { + data: [ + ...baseComments.data, + {body: 'This issue either has unchecked QA steps or has not yet been stamped with a :shipit: comment. Reopening!'}, + ], + }; + mockListComments.mockResolvedValue(extraComments); return run().then(() => { expect(mockSetOutput).toHaveBeenCalledWith('HAS_DEPLOY_BLOCKERS', true); }); }); - test('Test an issue with all boxes checked but no :shipit:', () => { - const checkedBoxesNoShipitIssue = baseIssue; - checkedBoxesNoShipitIssue.data.body = 'Checklist for Deploy #668:\r\n' - + '- [x] @bar https://github.com/Expensify/App/issues/23\r\n' - + '- [x] @baz https://github.com/Expensify/App/issues/42'; - mockGetIssue.mockResolvedValue(checkedBoxesNoShipitIssue); - // eslint-disable-next-line max-len - baseComments.data.push({body: 'This issue either has unchecked QA steps or has not yet been stamped with a :shipit: comment. Reopening!'}); - mockListComments.mockResolvedValue(baseComments); + test('Test an issue with all boxes checked but no comments', () => { + mockGetIssue.mockResolvedValue(allClearIssue); + mockListComments.mockResolvedValue({data: []}); return run().then(() => { expect(mockSetOutput).toHaveBeenCalledWith('HAS_DEPLOY_BLOCKERS', true); }); }); - test('Test an issue with all boxes checked but no comments', () => { - mockGetIssue.mockResolvedValue(baseIssue); - mockListComments.mockResolvedValue({data: []}); + test('Test an issue with all QA checked but no accessibility', () => { + mockGetIssue.mockResolvedValue(mockIssue([{url: 'https://github.com/Expensify/App/pull/6882', isQASuccess: true, isAccessibilitySuccess: false}])); + mockListComments.mockResolvedValue(baseComments); + return run().then(() => { + expect(mockSetOutput).toHaveBeenCalledWith('HAS_DEPLOY_BLOCKERS', false); + }); + }); + + test('Test an issue with all QA checked but not all deploy blockers', () => { + mockGetIssue.mockResolvedValue(mockIssue( + [{url: 'https://github.com/Expensify/App/pull/6882', isQASuccess: true, isAccessibilitySuccess: false}], + [{url: 'https://github.com/Expensify/App/pull/6883', isQASuccess: false}], + )); + mockListComments.mockResolvedValue(baseComments); return run().then(() => { expect(mockSetOutput).toHaveBeenCalledWith('HAS_DEPLOY_BLOCKERS', true); }); }); + + test('Test an issue with all QA checked and all deploy blockers resolved', () => { + mockGetIssue.mockResolvedValue(mockIssue( + [{url: 'https://github.com/Expensify/App/pull/6882', isQASuccess: true, isAccessibilitySuccess: false}], + [{url: 'https://github.com/Expensify/App/pull/6883', isQASuccess: true}], + )); + mockListComments.mockResolvedValue(baseComments); + return run().then(() => { + expect(mockSetOutput).toHaveBeenCalledWith('HAS_DEPLOY_BLOCKERS', false); + }); + }); }); });