diff --git a/.github/actions/createOrUpdateStagingDeploy/action.yml b/.github/actions/createOrUpdateStagingDeploy/action.yml index 4277a29587c6..4f73ac487371 100644 --- a/.github/actions/createOrUpdateStagingDeploy/action.yml +++ b/.github/actions/createOrUpdateStagingDeploy/action.yml @@ -1,12 +1,12 @@ name: 'Create or Update StagingDeployCash' description: 'Creates a new StagingDeployCash issue if there is not one open, or updates the existing one.' inputs: - NPM_VERSION: - description: The new NPM version of the StagingDeployCash issue. - required: true GITHUB_TOKEN: description: Auth token for Expensify.cash Github required: true + 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 diff --git a/.github/actions/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js b/.github/actions/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js index 259eff10e07c..e8703fb7885a 100644 --- a/.github/actions/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js +++ b/.github/actions/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js @@ -5,7 +5,7 @@ const moment = require('moment'); const GithubUtils = require('../../libs/GithubUtils'); const GitUtils = require('../../libs/GitUtils'); -const newVersion = core.getInput('NPM_VERSION', {required: true}); +const newVersion = core.getInput('NPM_VERSION'); const octokit = github.getOctokit(core.getInput('GITHUB_TOKEN', {required: true})); const githubUtils = new GithubUtils(octokit); @@ -42,8 +42,8 @@ githubUtils.getStagingDeployCash() // 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); + + ' 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) => { diff --git a/.github/actions/createOrUpdateStagingDeploy/index.js b/.github/actions/createOrUpdateStagingDeploy/index.js index a222120ea722..eaa231dcc952 100644 --- a/.github/actions/createOrUpdateStagingDeploy/index.js +++ b/.github/actions/createOrUpdateStagingDeploy/index.js @@ -15,7 +15,7 @@ const moment = __nccwpck_require__(9623); const GithubUtils = __nccwpck_require__(7999); const GitUtils = __nccwpck_require__(669); -const newVersion = core.getInput('NPM_VERSION', {required: true}); +const newVersion = core.getInput('NPM_VERSION'); const octokit = github.getOctokit(core.getInput('GITHUB_TOKEN', {required: true})); const githubUtils = new GithubUtils(octokit); @@ -52,8 +52,8 @@ githubUtils.getStagingDeployCash() // 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); + + ' 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) => { @@ -352,22 +352,26 @@ class GithubUtils { /** * Updates the existing open StagingDeployCash issue. * - * @param {String} newTag + * @param {String} [newTag] * @param {Array} newPRs * @param {Array} newDeployBlockers * @returns {Promise} * @throws {Error} If the StagingDeployCash could not be found or updated. */ - updateStagingDeployCash(newTag, newPRs, newDeployBlockers) { + 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, @@ -386,7 +390,7 @@ class GithubUtils { ); return this.generateStagingDeployCashBody( - newTag, + tag, _.pluck(PRList, 'url'), _.pluck(_.where(PRList, {isVerified: true}), 'url'), _.pluck(deployBlockers, 'url'), diff --git a/.github/actions/isStagingDeployLocked/index.js b/.github/actions/isStagingDeployLocked/index.js index 61127cabc765..f8dc18151c01 100644 --- a/.github/actions/isStagingDeployLocked/index.js +++ b/.github/actions/isStagingDeployLocked/index.js @@ -267,22 +267,26 @@ class GithubUtils { /** * Updates the existing open StagingDeployCash issue. * - * @param {String} newTag + * @param {String} [newTag] * @param {Array} newPRs * @param {Array} newDeployBlockers * @returns {Promise} * @throws {Error} If the StagingDeployCash could not be found or updated. */ - updateStagingDeployCash(newTag, newPRs, newDeployBlockers) { + 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, @@ -301,7 +305,7 @@ class GithubUtils { ); return this.generateStagingDeployCashBody( - newTag, + tag, _.pluck(PRList, 'url'), _.pluck(_.where(PRList, {isVerified: true}), 'url'), _.pluck(deployBlockers, 'url'), diff --git a/.github/actions/markPullRequestsAsDeployed/index.js b/.github/actions/markPullRequestsAsDeployed/index.js index 2076df296212..9647afca013c 100644 --- a/.github/actions/markPullRequestsAsDeployed/index.js +++ b/.github/actions/markPullRequestsAsDeployed/index.js @@ -283,22 +283,26 @@ class GithubUtils { /** * Updates the existing open StagingDeployCash issue. * - * @param {String} newTag + * @param {String} [newTag] * @param {Array} newPRs * @param {Array} newDeployBlockers * @returns {Promise} * @throws {Error} If the StagingDeployCash could not be found or updated. */ - updateStagingDeployCash(newTag, newPRs, newDeployBlockers) { + 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, @@ -317,7 +321,7 @@ class GithubUtils { ); return this.generateStagingDeployCashBody( - newTag, + tag, _.pluck(PRList, 'url'), _.pluck(_.where(PRList, {isVerified: true}), 'url'), _.pluck(deployBlockers, 'url'), diff --git a/.github/libs/GithubUtils.js b/.github/libs/GithubUtils.js index 8ddcbc0bae5b..d28b221fd858 100644 --- a/.github/libs/GithubUtils.js +++ b/.github/libs/GithubUtils.js @@ -236,22 +236,26 @@ class GithubUtils { /** * Updates the existing open StagingDeployCash issue. * - * @param {String} newTag + * @param {String} [newTag] * @param {Array} newPRs * @param {Array} newDeployBlockers * @returns {Promise} * @throws {Error} If the StagingDeployCash could not be found or updated. */ - updateStagingDeployCash(newTag, newPRs, newDeployBlockers) { + 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, @@ -270,7 +274,7 @@ class GithubUtils { ); return this.generateStagingDeployCashBody( - newTag, + tag, _.pluck(PRList, 'url'), _.pluck(_.where(PRList, {isVerified: true}), 'url'), _.pluck(deployBlockers, 'url'), diff --git a/.github/workflows/deployBlocker.yml b/.github/workflows/deployBlocker.yml new file mode 100644 index 000000000000..a7cd57f453df --- /dev/null +++ b/.github/workflows/deployBlocker.yml @@ -0,0 +1,50 @@ +name: Update Deploy Blockers + +on: + pull_request: + types: + - labeled + issues: + types: + - labeled + +jobs: + deployBlocker: + runs-on: ubuntu-latest + if: ${{ github.event.label.name == 'DeployBlockerCash' }} + + steps: + - name: Update StagingDeployCash with issue + uses: Expensify/Expensify.cash/.github/actions/createOrUpdateStagingDeploy@master + if: ${{ github.event_name == 'issues' }} + with: + GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + NEW_PULL_REQUESTS: ${{ github.event.issue.html_url }} + + - name: Update StagingDeployCash with issue + uses: Expensify/Expensify.cash/.github/actions/createOrUpdateStagingDeploy@master + if: ${{ github.event_name == 'pull_request' }} + with: + GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + NEW_PULL_REQUESTS: ${{ github.event.pull_request.html_url }} + + # This Slack step is duplicated in all workflows, if you make a change to this step, make sure to update all + # the other workflows with the same change + - uses: 8398a7/action-slack@v3 + name: Job failed Slack notification + if: ${{ failure() }} + with: + status: custom + fields: workflow, repo + custom_payload: | + { + channel: '#announce', + attachments: [{ + color: "#DB4545", + pretext: ``, + text: `💥 ${process.env.AS_REPO} failed on ${process.env.AS_WORKFLOW} workflow 💥`, + }] + } + env: + GITHUB_TOKEN: ${{ github.token }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} diff --git a/tests/unit/GithubUtilsTest.js b/tests/unit/GithubUtilsTest.js index e21175d5427f..3a5a3bd409fe 100644 --- a/tests/unit/GithubUtilsTest.js +++ b/tests/unit/GithubUtilsTest.js @@ -173,6 +173,53 @@ describe('GithubUtils', () => { }); }); }); + + test('updates issue with just deploy blockers', () => { + const issueBefore = { + url: 'https://api.github.com/repos/Expensify/Expensify.cash/issues/29', + title: 'Test StagingDeployCash', + labels: [ + { + id: 2783847782, + node_id: 'MDU6TGFiZWwyNzgzODQ3Nzgy', + url: 'https://api.github.com/repos/Expensify/Expensify.cash/labels/StagingDeployCash', + name: 'StagingDeployCash', + color: '6FC269', + default: false, + description: '', + }, + ], + // eslint-disable-next-line max-len + body: '**Release Version:** `1.0.1-47`\r\n**Compare Changes:** https://github.com/Expensify/Expensify.cash/compare/1.0.1-0...1.0.1-47\r\n\r\n**This release contains changes from the following pull requests:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/21\r\n- [x] https://github.com/Expensify/Expensify.cash/pull/22\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/23\r\n\r\n', + }; + + const octokit = new Octokit(); + octokit.repos.listTags = jest.fn().mockResolvedValue({ + data: [ + {name: '1.0.1-0'}, + {name: '1.0.1-47'}, + {name: '1.0.1-48'}, + ], + }); + octokit.issues.listForRepo = jest.fn().mockResolvedValue({data: [issueBefore]}); + octokit.issues.update = jest.fn().mockImplementation(arg => Promise.resolve(arg)); + const githubUtils = new GithubUtils(octokit); + + return githubUtils.updateStagingDeployCash( + undefined, + undefined, + ['https://github.com/Expensify/Expensify.cash/pull/24', + 'https://github.com/Expensify/Expensify.cash/issues/25'], + ).then((result) => { + expect(result).toStrictEqual({ + owner: GithubUtils.GITHUB_OWNER, + repo: GithubUtils.EXPENSIFY_CASH_REPO, + issue_number: 29, + // eslint-disable-next-line max-len + body: '**Release Version:** `1.0.1-47`\r\n**Compare Changes:** https://github.com/Expensify/Expensify.cash/compare/1.0.1-0...1.0.1-47\r\n\r\n**This release contains changes from the following pull requests:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/21\r\n- [x] https://github.com/Expensify/Expensify.cash/pull/22\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/23\r\n\r\n**Deploy Blockers:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/24\r\n- [ ] https://github.com/Expensify/Expensify.cash/issues/25\r\n\r\ncc @Expensify/applauseleads\r\n', + }); + }); + }); }); describe('getPullRequestNumberFromURL', () => {