Update .github/workflows/track-review-project.yaml #1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ------------------------------------------------------------------------------ | ||
| # (c) Crown copyright Met Office. All rights reserved. | ||
| # The file LICENCE, distributed with this code, contains details of the terms | ||
| # under which the code may be used. | ||
| # ------------------------------------------------------------------------------ | ||
| # This workflow runs whenever a pull request in the repository is marked as | ||
| # "Ready for review" (i.e., a non-Draft PR). | ||
| name: Track Review Project | ||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| runner: | ||
| description: 'The runner to use for the job' | ||
| required: false | ||
| type: string | ||
| default: 'ubuntu-24.04' | ||
| project_org: | ||
| description: 'The organization that owns the project' | ||
| required: false | ||
| type: string | ||
| default: 'MetOffice' | ||
| project_number: | ||
| description: 'The project number' | ||
| required: false | ||
| type: number | ||
| default: 376 # Simulation Systems Review Tracker | ||
| permissions: | ||
| actions: read | ||
| contents: read | ||
| pull-requests: write | ||
| jobs: | ||
| track_project: | ||
| runs-on: ${{ inputs.runner }} | ||
| timeout-minutes: 5 | ||
| env: | ||
| REPO: ${{ github.repository }} | ||
| steps: | ||
| # Required as running from the workflow_run trigger | ||
| - name: Checkout repo | ||
| uses: actions/checkout@v6 | ||
| - name: Download PR data artifact | ||
| uses: actions/github-script@v8 | ||
| with: | ||
| github-token: ${{ secrets.GITHUB_TOKEN }} | ||
| script: | | ||
| let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| run_id: context.payload.workflow_run.id, | ||
| }); | ||
| let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => { | ||
| return artifact.name == "context.json" | ||
| })[0]; | ||
| let download = await github.rest.actions.downloadArtifact({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| artifact_id: matchArtifact.id, | ||
| archive_format: 'zip', | ||
| }); | ||
| let fs = require('fs'); | ||
| fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/context.zip`, Buffer.from(download.data)); | ||
| - name: Extract PR Data | ||
| run: | | ||
| unzip context.zip | ||
| pr_num=$(jq -r '.pr_num' context.json) | ||
| pr_id=$(jq -r '.pr_id' context.json) | ||
| echo "PR_NUM=$pr_num" >> $GITHUB_ENV | ||
| unzip -q context.zip | ||
| jq -r '"PR_NUM=\(.pr_num)\nPR_ID=\(.pr_id)"' context.json >> $GITHUB_ENV | ||
| - name: Assign Author if no assignees | ||
| continue-on-error: true | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| pr_data=$(gh pr view -R "$REPO" "$PR_NUM" --json assignees,author) | ||
| if [[ $(echo "$pr_data" | jq '.assignees | length') -eq 0 ]]; then | ||
| author=$(echo "$pr_data" | jq -r '.author.login') | ||
| gh pr edit -R "$REPO" "$PR_NUM" --add-assignee "$author" | ||
| fi | ||
| - name: Extract reviewers and determine state | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| pr_data=$(gh pr view -R "$REPO" "$PR_NUM" --json body,reviews,reviewRequests) | ||
| # Extract reviewers from PR body | ||
| pr_body=$(echo "$pr_data" | jq -r '.body') | ||
| scitech_reviewer=$(echo "$pr_body" | grep -oP "Sci/Tech Reviewer:\s?@\K([\w\-]{1,39})\b" || true) | ||
| code_reviewer=$(echo "$pr_body" | grep -oP "Code Reviewer:\s?@\K([\w\-]{1,39})\b" || true) | ||
| echo "Expected SciTech Reviewer: ${scitech_reviewer:-None}" | ||
| echo "Expected Code Reviewer: ${code_reviewer:-None}" | ||
| echo "SCITECH_REVIEWER=$scitech_reviewer" >> $GITHUB_ENV | ||
| echo "CODE_REVIEWER=$code_reviewer" >> $GITHUB_ENV | ||
| # Determine review state | ||
| reviews=$(echo "$pr_data" | jq -c '{reviews, reviewRequests}') | ||
| sr_requested=$(echo "$reviews" | jq --arg sr "$scitech_reviewer" '[.reviewRequests[] | select(.login == $sr)] | length') | ||
| cr_requested=$(echo "$reviews" | jq --arg cr "$code_reviewer" '[.reviewRequests[] | select(.login == $cr)] | length') | ||
| # Determine required state | ||
| sr_approved=$(echo "$reviews" | jq --arg sr "$scitech_reviewer" '[.reviews[] | select(.author.login == $sr and .state == "APPROVED")] | length') | ||
| sr_changes=$(echo "$reviews" | jq --arg sr "$scitech_reviewer" '[.reviews[] | select(.author.login == $sr and (.state == "CHANGES_REQUESTED" or .state == "COMMENTED"))] | length') | ||
| [[ ! -z "$scitech_reviewer" ]] && [[ "$sr_requested" -eq 0 ]] && [[ "$sr_approved" -eq 0 ]] && [[ "$sr_changes" -eq 0 ]] && echo "SR_REQUEST_REQUIRED=true" >> $GITHUB_ENV | ||
| if [[ -z "$scitech_reviewer" ]] && [[ "$cr_requested" -gt 0 ]]; then | ||
| echo "No SR set and CR requested so assuming this is trivial" | ||
| else | ||
| if [[ "$sr_approved" -eq 0 ]]; then | ||
| if [[ -z "$scitech_reviewer" ]] && [[ "$sr_changes" -eq 0 ]]; then | ||
| required_state="In Progress" | ||
| elif [[ "$sr_changes" -gt 0 ]] && [[ "$sr_requested" -eq 0 ]]; then | ||
| required_state="Changes Requested" | ||
| else | ||
| required_state="SciTech Review" | ||
| fi | ||
| echo "REQUIRED_STATE=$required_state" >> $GITHUB_ENV | ||
| exit 0 | ||
| fi | ||
| fi | ||
| cr_approved=$(echo "$reviews" | jq --arg cr "$code_reviewer" '[.reviews[] | select(.author.login == $cr and .state == "APPROVED")] | length') | ||
| cr_changes=$(echo "$reviews" | jq --arg cr "$code_reviewer" '[.reviews[] | select(.author.login == $cr and (.state == "CHANGES_REQUESTED" or .state == "COMMENTED"))] | length') | ||
| if [[ "$cr_approved" -gt 0 ]]; then | ||
| required_state="Approved" | ||
| elif [[ "cr_changes" -eq 0 ]] && [[ "$cr_requested" -eq 0 ]] && [[ "$sr_approved" -eq 0 ]]; then | ||
| required_state="In Progress" | ||
| elif [[ "$cr_changes" -gt 0 ]] && [[ "$cr_requested" -eq 0 ]]; then | ||
| required_state="Changes Requested" | ||
| else | ||
| required_state="Code Review" | ||
| [[ "$cr_requested" -eq 0 ]] && [[ "$cr_approved" -eq 0 ]] && [[ "$cr_changes" -eq 0 ]] && echo "CR_REQUEST_REQUIRED=true" >> $GITHUB_ENV | ||
| fi | ||
| echo "REQUIRED_STATE=$required_state" >> $GITHUB_ENV | ||
| - name: Request SciTech review, if needed | ||
| if: env.SR_REQUEST_REQUIRED == 'true' && env.REQUIRED_STATE == 'SciTech Review' && env.SCITECH_REVIEWER != '' | ||
| continue-on-error: true | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: gh pr edit -R "$REPO" "$PR_NUM" --add-reviewer "$SCITECH_REVIEWER" | ||
| - name: Request code review, if needed | ||
| if: env.CR_REQUEST_REQUIRED == 'true' && env.REQUIRED_STATE == 'Code Review' && env.CODE_REVIEWER != '' | ||
| continue-on-error: true | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| gh pr edit -R "$REPO" "$PR_NUM" --add-reviewer "$CODE_REVIEWER" | ||
| - name: Update project tracking | ||
| env: | ||
| GH_TOKEN: ${{ secrets.PROJECT_ACTION_PAT }} | ||
| run: | | ||
| # Get project data | ||
| project_data=$(gh api graphql -f query=' | ||
| query($org: String!, $number: Int!) { | ||
| organization(login: $org){ | ||
| projectV2(number: $number) { | ||
| id | ||
| fields(first:20) { | ||
| nodes { | ||
| ... on ProjectV2Field { id name } | ||
| ... on ProjectV2SingleSelectField { id name options { id name } } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }' -f org=${{ inputs.project_org }} -F number=${{ inputs.project_number }}) | ||
| project_id=$(echo "$project_data" | jq -r '.data.organization.projectV2.id') | ||
| status_field_id=$(echo "$project_data" | jq -r '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id') | ||
| status_option_id=$(echo "$project_data" | jq -r --arg status "$REQUIRED_STATE" '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name==$status) | .id') | ||
| scitech_field_id=$(echo "$project_data" | jq -r '.data.organization.projectV2.fields.nodes[] | select(.name== "SciTech Review") | .id') | ||
| code_field_id=$(echo "$project_data" | jq -r '.data.organization.projectV2.fields.nodes[] | select(.name== "Code Review") | .id') | ||
| # Add PR to project and update fields | ||
| item_id=$(gh api graphql -f query=' | ||
| mutation($project:ID!, $pr:ID!) { | ||
| addProjectV2ItemById(input: {projectId: $project, contentId: $pr}) { | ||
| item { id } | ||
| } | ||
| }' -f project="$project_id" -f pr="$PR_ID" --jq '.data.addProjectV2ItemById.item.id') | ||
| # Update all fields in single mutation | ||
| gh api graphql -f query=' | ||
| mutation($project: ID!, $item: ID!, $status_field: ID!, $status_value: String!, $cr_field: ID!, $cr_value: String!, $sr_field: ID!, $sr_value: String!) { | ||
| set_status: updateProjectV2ItemFieldValue(input: {projectId: $project, itemId: $item, fieldId: $status_field, value: {singleSelectOptionId: $status_value}}) { projectV2Item { id } } | ||
| set_code_review: updateProjectV2ItemFieldValue(input: {projectId: $project, itemId: $item, fieldId: $cr_field, value: {text: $cr_value}}) { projectV2Item { id } } | ||
| set_scitech_review: updateProjectV2ItemFieldValue(input: {projectId: $project, itemId: $item, fieldId: $sr_field, value: {text: $sr_value}}) { projectV2Item { id } } | ||
| }' -f project="$project_id" -f item="$item_id" -f status_field="$status_field_id" -f status_value="$status_option_id" -f cr_field="$code_field_id" -f cr_value="$CODE_REVIEWER" -f sr_field="$scitech_field_id" -f sr_value="$SCITECH_REVIEWER" --silent | ||