diff --git a/.github/workflows/add_all_pr_to_project_cron.yml b/.github/workflows/add_all_pr_to_project_cron.yml new file mode 100644 index 0000000..e94391c --- /dev/null +++ b/.github/workflows/add_all_pr_to_project_cron.yml @@ -0,0 +1,176 @@ +# This workflow finds all PRs in all repos and adds them to the +# project if they have the right label + +name: PR automation +on: + # schedule: + # # * is a special character in YAML so you have to quote this string + # - cron: '30 5,17 * * *' + workflow_dispatch: + +jobs: + get_repos: + name: Get all repositories + runs-on: ubuntu-latest + outputs: + repos: ${{ steps.get_repo.outputs.repositories }} + steps: + - name: Get current repositories + id: get_repo + env: + GH_TOKEN: ${{ secrets.LAB_PAT }} + MAX_REPO: 50 + run: | + repo_list=$(gh repo list $GITHUB_REPOSITORY_OWNER --no-archived -L ${MAX_REPO} --json owner,name --jq '.[] | "\(.owner.login)/\(.name)"' | jq -cnR '[inputs | select(length>0)]') + echo "repositories=${repo_list}" >> $GITHUB_OUTPUT + + add2project: + runs-on: ubuntu-latest + name: ${{ matrix.repo }} <- ${{ github.event.label.name }} (${{github.event.action}}) + needs: get_repos + strategy: + matrix: + repo: ${{fromJSON(needs.get_repos.outputs.repos)}} + + steps: + - name: find all PRs + id: repo_pr + run: | + echo ${{ matrix.repo }} + - name: Check if label of interest is applied + id: preview_label_check + uses: docker://agilepathway/pull-request-label-checker:latest + with: + any_of: _bot,_community + repo_token: ${{ secrets.NB_PROJECT_PAT }} + allow_failure: true + + - name: Detect if PR is in project + if: steps.preview_label_check.outputs.label_check == 'success' + id: in_project + env: + GH_TOKEN: ${{ secrets.NB_PROJECT_PAT }} + run: | + gh pr view ${{ github.event.pull_request.html_url }} --json projectItems | jq -r '.projectItems[].title' | grep Neurobagel + continue-on-error: true + + - name: Add PR to Project + if: steps.in_project.outcome == 'failure' && steps.preview_label_check.outputs.label_check == 'success' + uses: actions/add-to-project@main + with: + project-url: https://github.com/orgs/neurobagel/projects/1 + github-token: ${{ secrets.NB_PROJECT_PAT }} + + # Note that in contrast to the graphical github UI, + # once a PR (or other item) is added to a github project, + # project related changes are no longer applied directly to the PR + # but instead to the project card that contains the PR. + # Once the PR is added to the project (and thus has a project card) + # we therefore have to search for the node id of the containing project card + # and then set the Status and Community option on the project card. + # + # This step expects to find the id of the (parent) project card + # and will fail (crashing the entire workflow) otherwise + - name: Find project card container + if: steps.preview_label_check.outputs.label_check == 'success' + id: find_container + env: + GH_TOKEN: ${{ secrets.NB_PROJECT_PAT }} + run: | + out=$(newCursor="" + while true; do + + response=$(gh api graphql -f query='{ + organization(login: "neurobagel") { + projectV2(number: 1) { + items(first: 100, orderBy: {field: POSITION, direction: DESC}, after: "'"${newCursor}"'") { + edges { + cursor + node { + content { + ... on PullRequest { + childID: id + } + } + parentID: id + } + } + } + } + } + }') + + # Because we may not be able to find the parent ID in the first 100 items + # we have to keep advancing the cursor to move through the list + # Note: we use 100 items to balance speed and API limits, might have to be changed + while read -r pID cID cursor; do + + if [ "$cID" == "${{ github.event.pull_request.node_id }}" ]; + then + echo $pID; + exit 0; + fi + newCursor="$cursor" + # Note: we need to use the here string + # to avoid running the while loop in a subshell that would not let us access newCursor + # after the while loop has finished + # see: https://tldp.org/LDP/abs/html/subshells.html + done <<< $(echo "$response" | jq -r '.data.organization.projectV2.items.edges[] | "\(.node.parentID) \(.node.content.childID) \(.cursor)"') + + if [ ! -n "$newCursor" ]; then + # We have passed through the entire list of items + # and didn't find the project card for our PR. + # Something is wrong and we will now crash the workflow. + exit 1; + fi + done + ) + echo "parent_id=${out}" >> $GITHUB_OUTPUT + + # This step expects a custom field called "Status" + # with an option called "Community" to exist in the project. + # We need their IDs as input for our later call to the API + # to move our PR project card to the "Community" column. + # + # We make them available to other steps in this job + # by writing them to the GITHUB_OUTPUT environment variable. + # see: https://docs.github.com/en/actions/using-jobs/defining-outputs-for-jobs + - name: Get IDs for Status field and Community option + if: steps.preview_label_check.outputs.label_check == 'success' + id: get_id + env: + GH_TOKEN: ${{ secrets.NB_PROJECT_PAT }} + FIELD: "Status" + OPTION: "Community" + run: | + response=$(gh api graphql -f query='{ + organization(login: "neurobagel") { + projectV2(number: 1) { + field(name: "'"${FIELD}"'") { + ... on ProjectV2SingleSelectField { + fieldID: id + options(names: "'"${OPTION}"'") { + optionID: id + } + } + } + } + } + }' | jq '.data.organization.projectV2.field | "\(.fieldID) \(.options[0].optionID)"') + read fieldID optionID <<< "${response//\"}" + + echo "fieldID=${fieldID}" >> $GITHUB_OUTPUT + echo "optionID=${optionID}" >> $GITHUB_OUTPUT + + - name: Set "Status" of PR to "Community" + if: steps.preview_label_check.outputs.label_check == 'success' + env: + GH_TOKEN: ${{ secrets.NB_PROJECT_PAT }} + run: | + gh api graphql -f query='mutation { + updateProjectV2ItemFieldValue( + input: {projectId: "PVT_kwDOBaeejM4AAQiP", itemId: "${{ steps.find_container.outputs.parent_id }}", fieldId: "${{ steps.get_id.outputs.fieldID }}", value: {singleSelectOptionId: "${{ steps.get_id.outputs.optionID }}"}} + ) { + clientMutationId + } + }'