From b56e69dc482673dab63cb4bfd6c1cef91cdade9b Mon Sep 17 00:00:00 2001 From: Siddarth Kumar Date: Wed, 25 Sep 2024 16:19:51 +0530 Subject: [PATCH 1/2] feat!: implement PR review workflow This commit Implements a PR review workflow like this : - This action will check if `breaking change` label exists on PR. - if it does then `@status-im/desktop-qa` and `@status-im/mobile-qa` are asked for review on this PR. - Unless 1 person from `@status-im/desktop-qa` and `@status-im/mobile-qa` approve that PR the Github action will block the PR. - Only after these conditions match the Github action will allow merging of this PR. --- .github/workflows/breaking-change-review.yml | 145 +++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 .github/workflows/breaking-change-review.yml diff --git a/.github/workflows/breaking-change-review.yml b/.github/workflows/breaking-change-review.yml new file mode 100644 index 00000000000..0d12d418d18 --- /dev/null +++ b/.github/workflows/breaking-change-review.yml @@ -0,0 +1,145 @@ +name: PR Review Workflow for Breaking Changes + +on: + pull_request: + types: + - opened + - reopened + - labeled + - unlabeled + - synchronize + +jobs: + check_breaking_change: + runs-on: ubuntu-latest + steps: + - name: Check for breaking change label + id: check_label + uses: actions/github-script@v6 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + const { data: labels } = await github.rest.issues.listLabelsOnIssue({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + const hasBreakingChange = labels.some(label => label.name === 'breaking change'); + console.log(`Has breaking change label: ${hasBreakingChange}`); + return hasBreakingChange; + + - name: Set QA reviewers + if: steps.check_label.outputs.result == 'true' + id: set_reviewers + run: | + echo "mobile_qa=churik,yevh-berdnyk,VolodLytvynenko,pavloburykh,mariia-skrypnyk,Horupa-Olena" >> $GITHUB_OUTPUT + echo "desktop_qa=anastasiyaig,virginiabalducci,glitchminer,antdanchenko" >> $GITHUB_OUTPUT + + - name: Request QA reviews + if: steps.check_label.outputs.result == 'true' + uses: actions/github-script@v6 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + const mobileQA = '${{ steps.set_reviewers.outputs.mobile_qa }}'.split(','); + const desktopQA = '${{ steps.set_reviewers.outputs.desktop_qa }}'.split(','); + const reviewers = [...mobileQA, ...desktopQA]; + + await github.rest.pulls.requestReviewers({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + reviewers: reviewers + }); + + - name: Check QA approvals + if: steps.check_label.outputs.result == 'true' + id: check_approvals + uses: actions/github-script@v6 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + const { data: reviews } = await github.rest.pulls.listReviews({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + }); + + const mobileQA = '${{ steps.set_reviewers.outputs.mobile_qa }}'.split(','); + const desktopQA = '${{ steps.set_reviewers.outputs.desktop_qa }}'.split(','); + + const mobileQAApproved = reviews.some(review => + review.state === 'APPROVED' && mobileQA.includes(review.user.login) + ); + const desktopQAApproved = reviews.some(review => + review.state === 'APPROVED' && desktopQA.includes(review.user.login) + ); + + console.log(`Mobile QA approved: ${mobileQAApproved}`); + console.log(`Desktop QA approved: ${desktopQAApproved}`); + + core.setOutput('mobile-qa-approved', mobileQAApproved); + core.setOutput('desktop-qa-approved', desktopQAApproved); + + return mobileQAApproved && desktopQAApproved; + + - name: Block PR if conditions not met + if: steps.check_label.outputs.result == 'true' && steps.check_approvals.outputs.result != 'true' + uses: actions/github-script@v6 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + const mobileQAApproved = ${{ steps.check_approvals.outputs.mobile-qa-approved }}; + const desktopQAApproved = ${{ steps.check_approvals.outputs.desktop-qa-approved }}; + + let message = 'This PR has the breaking change label and requires approval from both mobile-qa and desktop-qa teams before it can be merged.\n\n'; + + if (!mobileQAApproved && !desktopQAApproved) { + message += 'Both mobile-qa and desktop-qa teams have not approved this PR yet.'; + } else if (!mobileQAApproved) { + message += 'The mobile-qa team has not approved this PR yet.'; + } else if (!desktopQAApproved) { + message += 'The desktop-qa team has not approved this PR yet.'; + } + + await github.rest.checks.create({ + owner: context.repo.owner, + repo: context.repo.repo, + name: 'QA Approval Check', + head_sha: context.payload.pull_request.head.sha, + status: 'completed', + conclusion: 'failure', + output: { + title: 'QA Approval Required', + summary: message + } + }); + + - name: Allow PR merge if conditions are met + if: steps.check_label.outputs.result == 'true' && steps.check_approvals.outputs.result == 'true' + uses: actions/github-script@v6 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + + const { data: reviews } = await github.rest.pulls.listReviews({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + }); + + const botApprovalExists = reviews.some(review => + review.user.login === 'github-actions[bot]' && review.state === 'APPROVED' + ); + + if (!botApprovalExists) { + await github.rest.pulls.createReview({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + event: 'APPROVE', + body: 'Breaking changes have been approved by Mobile and Desktop QA. This PR can now be merged.' + }); + } else { + console.log('Bot approval already exists. No action taken.'); + } \ No newline at end of file From fc73ab2b7927fb73bf4ce27c77b12028897183a9 Mon Sep 17 00:00:00 2001 From: Siddarth Kumar Date: Thu, 3 Oct 2024 17:00:08 +0530 Subject: [PATCH 2/2] chore!: get QA reviewers from a file --- .github/qa-teams.yml | 18 +++++++++++ .github/workflows/breaking-change-review.yml | 34 +++++++++++++------- 2 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 .github/qa-teams.yml diff --git a/.github/qa-teams.yml b/.github/qa-teams.yml new file mode 100644 index 00000000000..a465d7c4a80 --- /dev/null +++ b/.github/qa-teams.yml @@ -0,0 +1,18 @@ +# QA Teams Configuration +mobile_qa: + reviewers_required: 2 + members: + - churik + - yevh-berdnyk + - VolodLytvynenko + - pavloburykh + - mariia-skrypnyk + - Horupa-Olena + +desktop_qa: + reviewers_required: 2 + members: + - anastasiyaig + - virginiabalducci + - glitchminer + - antdanchenko \ No newline at end of file diff --git a/.github/workflows/breaking-change-review.yml b/.github/workflows/breaking-change-review.yml index 0d12d418d18..62433b8e38e 100644 --- a/.github/workflows/breaking-change-review.yml +++ b/.github/workflows/breaking-change-review.yml @@ -1,4 +1,4 @@ -name: PR Review Workflow for Breaking Changes +name: PR Review Workflow on: pull_request: @@ -11,8 +11,12 @@ on: jobs: check_breaking_change: + name: Require QA review runs-on: ubuntu-latest steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Check for breaking change label id: check_label uses: actions/github-script@v6 @@ -28,12 +32,21 @@ jobs: console.log(`Has breaking change label: ${hasBreakingChange}`); return hasBreakingChange; - - name: Set QA reviewers + - name: Set random QA reviewers if: steps.check_label.outputs.result == 'true' id: set_reviewers run: | - echo "mobile_qa=churik,yevh-berdnyk,VolodLytvynenko,pavloburykh,mariia-skrypnyk,Horupa-Olena" >> $GITHUB_OUTPUT - echo "desktop_qa=anastasiyaig,virginiabalducci,glitchminer,antdanchenko" >> $GITHUB_OUTPUT + mobile_qa_required=$(yq eval '.mobile_qa.reviewers_required' .github/qa-teams.yml) + desktop_qa_required=$(yq eval '.desktop_qa.reviewers_required' .github/qa-teams.yml) + + mobile_qa_members=$(yq eval '.mobile_qa.members[]' .github/qa-teams.yml | tr '\n' ' ') + desktop_qa_members=$(yq eval '.desktop_qa.members[]' .github/qa-teams.yml | tr '\n' ' ') + + mobile_qa=$(echo $mobile_qa_members | tr ' ' '\n' | shuf -n $mobile_qa_required | tr '\n' ',' | sed 's/,$//') + desktop_qa=$(echo $desktop_qa_members | tr ' ' '\n' | shuf -n $desktop_qa_required | tr '\n' ',' | sed 's/,$//') + + echo "mobile_qa=$mobile_qa" >> $GITHUB_OUTPUT + echo "desktop_qa=$desktop_qa" >> $GITHUB_OUTPUT - name: Request QA reviews if: steps.check_label.outputs.result == 'true' @@ -45,6 +58,8 @@ jobs: const desktopQA = '${{ steps.set_reviewers.outputs.desktop_qa }}'.split(','); const reviewers = [...mobileQA, ...desktopQA]; + console.log('Requesting reviews from:', reviewers); + await github.rest.pulls.requestReviewers({ owner: context.repo.owner, repo: context.repo.repo, @@ -94,13 +109,9 @@ jobs: let message = 'This PR has the breaking change label and requires approval from both mobile-qa and desktop-qa teams before it can be merged.\n\n'; - if (!mobileQAApproved && !desktopQAApproved) { - message += 'Both mobile-qa and desktop-qa teams have not approved this PR yet.'; - } else if (!mobileQAApproved) { - message += 'The mobile-qa team has not approved this PR yet.'; - } else if (!desktopQAApproved) { - message += 'The desktop-qa team has not approved this PR yet.'; - } + message += `- ${mobileQAApproved ? '[ ]' : '[x]'} Mobile QA team review\n` + message += `- ${desktopQAApproved ? '[ ]' : '[x]'} Desktop QA team review` + await github.rest.checks.create({ owner: context.repo.owner, @@ -121,7 +132,6 @@ jobs: with: github-token: ${{secrets.GITHUB_TOKEN}} script: | - const { data: reviews } = await github.rest.pulls.listReviews({ owner: context.repo.owner, repo: context.repo.repo,