diff --git a/.github/workflows/awaiting-review.yaml b/.github/workflows/awaiting-review.yaml new file mode 100644 index 0000000000..06a85921d8 --- /dev/null +++ b/.github/workflows/awaiting-review.yaml @@ -0,0 +1,33 @@ +# on a PR open on PR review or comment, assign the awaiting-review label if the actor is the author +name: Set Awaiting Review +on: + pull_request_review: + types: [submitted] + pull_request_review_comment: + types: [created] + pull_request: + types: [opened, reopened, synchronize] + +permissions: + issues: write + pull-requests: write + +jobs: + label: + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v5 + id: fix-labels + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + // if it's the author, always add awaiting-review label + const isAuthor = context.payload.pull_request.user.login === context.actor + if (isAuthor) { + await github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['awaiting-review'] + }); + } diff --git a/.github/workflows/goalie.yaml b/.github/workflows/goalie.yaml index baa230cdf0..6aaaafc429 100644 --- a/.github/workflows/goalie.yaml +++ b/.github/workflows/goalie.yaml @@ -1,16 +1,8 @@ # on a review from someone in the reviewers group, remove the awaiting-review label and add the awaiting-author label -name: Set Goalie Labels +name: Goalie Workflow on: - pull_request_review: - types: [submitted] - pull_request_review_comment: - types: [created] - pull_request: - types: [opened, reopened, synchronize] - -permissions: - issues: write - pull-requests: write + schedule: + - cron: '* * * * *' jobs: label: @@ -20,8 +12,8 @@ jobs: id: get_workflow_token uses: peter-murray/workflow-application-token-action@v1 with: - application_id: ${{ secrets.BACKSTAGE_WORKFLOW_MEMBER_READ_APP_ID }} - application_private_key: ${{ secrets.BACKSTAGE_WORKFLOW_MEMBER_READ_PRIVATE_KEY }} + application_id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} + application_private_key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} organization: backstage - uses: actions/checkout@v2 @@ -31,113 +23,9 @@ jobs: - run: npm install codeowners - uses: actions/github-script@v5 - id: get-all-group-members + id: fix-labels with: github-token: ${{ steps.get_workflow_token.outputs.token }} script: | - // Get all teams and their respective members - const {data: teams} = await github.request('GET /orgs/{org}/teams', { - org: context.payload.organization.login, - }) - - const groupMembers = await Promise.all( - teams.map( - async (team) => { - const { data } = await github.rest.teams.listMembersInOrg({ - org: context.payload.organization.login, - team_slug: team.slug, - }); - - return { team: `@backstage/${team.slug}`, data }; - } - ) - ) - - return groupMembers; - - - uses: actions/github-script@v5 - id: get-all-changed-files - with: - script: | - const { data: allFiles } = await github.rest.pulls.listFiles({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - }); - - return allFiles; - - - uses: actions/github-script@v5 - id: get-all-current-reviews - with: - script: | - const { data: allReviews } = await github.rest.pulls.listReviews({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - }); - - return allReviews; - - - uses: actions/github-script@v5 - id: fix-labels - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - // if it's the author, always add awaiting-review label - const isAuthor = context.payload.pull_request.user.login === context.actor - if (isAuthor) { - await github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: ['awaiting-review'] - }); - return; - } - - // Go through each file changed and go through each codeowner entry and use minimatch to see if the file matches - // strip the backstage group from the name? - // If it does match push the owner to a list of reviewers - // check to see the reviews and if there is at least one matching reviewer from those group - - const changedFiles = ${{ steps.get-all-changed-files.outputs.result }} - const allReviews = ${{ steps.get-all-current-reviews.outputs.result }} - const groupMembers = ${{ steps.get-all-group-members.outputs.result }} - const Codeowners = require('codeowners'); - const codeowners = new Codeowners(); - - const expectedReviewers = new Set(); - - for (const file of changedFiles) { - expectedReviewers.add(...codeowners.getOwner(file.filename)); - } - - const hasReviewed = new Set(); - - // For each reviewer in the group, check to see if they have a review set - for (const reviewer of expectedReviewers) { - const members = groupMembers.find(member => member.team === reviewer); - if (members) { - // then we are dealing with a group - const hasMemberReview = allReviews.some(review => members.data.some(member => member.login === review.user.login)); - if (hasMemberReview) { - hasReviewed.add(reviewer); - } - } else { - // reviewer is a person - const hasReview = allReviews.some(review => reviewer === `@${review.user.login}`); - if (hasReview) { - hasReviewed.add(reviewer); - } - } - } - - if (hasReviewed.size === expectedReviewers.size) { - await github.rest.issues.removeLabel({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - name: 'awaiting-review' - }).catch(() => {}); - } + const script = require('./scripts/goalie-labels.js') + await script({github, context, core}) diff --git a/scripts/goalie-labels.js b/scripts/goalie-labels.js new file mode 100644 index 0000000000..e9765aebb8 --- /dev/null +++ b/scripts/goalie-labels.js @@ -0,0 +1,102 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const Codeowners = require('codeowners'); + +module.exports = async ({ github, context, core }) => { + // first get all open pull requests + const allPullRequests = await github.paginate(github.rest.pulls.list, { + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + }); + + const { data: teams } = await github.request('GET /orgs/{org}/teams', { + org: context.repo.owner, + }); + + const groupMembers = await Promise.all( + teams.map(async team => { + const { data } = await github.rest.teams.listMembersInOrg({ + org: context.repo.owner, + team_slug: team.slug, + }); + + return { team: `@backstage/${team.slug}`, data }; + }), + ); + + const codeowners = new Codeowners(); + + for (const pullRequest of allPullRequests) { + // Go through each file changed and go through each codeowner entry and use minimatch to see if the file matches + // strip the backstage group from the name? + // If it does match push the owner to a list of reviewers + // check to see the reviews and if there is at least one matching reviewer from those groupx + const changedFiles = await github.paginate(github.rest.pulls.listFiles, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pullRequest.number, + }); + + const allReviews = await github.paginate(github.rest.pulls.listReviews, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pullRequest.number, + }); + + const expectedReviewers = new Set(); + + for (const file of changedFiles) { + expectedReviewers.add(...codeowners.getOwner(file.filename)); + } + + const hasReviewed = new Set(); + + // For each reviewer in the group, check to see if they have a review set + for (const reviewer of expectedReviewers) { + const members = groupMembers.find(member => member.team === reviewer); + if (members) { + // then we are dealing with a group + const hasMemberReview = allReviews.some(review => + members.data.some(member => member.login === review.user.login), + ); + if (hasMemberReview) { + hasReviewed.add(reviewer); + } + } else { + // reviewer is a person + const hasReview = allReviews.some( + review => reviewer === `@${review.user.login}`, + ); + if (hasReview) { + hasReviewed.add(reviewer); + } + } + } + + if (hasReviewed.size === expectedReviewers.size) { + await github.rest.issues + .removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'awaiting-review', + }) + .catch(() => {}); + } + } +};