diff --git a/.github/actions/add-labels/action.yml b/.github/actions/add-labels/action.yml deleted file mode 100644 index 0f36aff91..000000000 --- a/.github/actions/add-labels/action.yml +++ /dev/null @@ -1,235 +0,0 @@ -name: Add Label(s) -description: adds label(s) to labelable -inputs: - gh_token: - description: gh api access token to use - required: true - repository: - description: the OWNER/REPOSITORY to operate on - default: ${{ github.repository }} - issues: - description: a single or comma separated list of issue numbers - required: true - labels: - description: a single or comma separated list of labels to apply to all listed issues - required: true - colors: - description: | - A single or comma separated list of colors to create the labels with if needed. - the list order is the same as `labels`. Missing or blank values (e.g. `FFFFFF,,FFFFFF`) use the `default-color` - default-color: - description: default color to create labels with - default: "#ffffff" - -runs: - using: "composite" - steps: - - name: Collect Repo Labels - id: collect_labels - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - REPOSITORY: ${{ inputs.repository }} - LABELS: ${{ inputs.labels }} - COLORS: ${{ inputs.colors }} - DEFAULT_COLOR: ${{ inputs.default-color }} - run: | - owner="$(dirname "$REPOSITORY")" - repo="$(basename "$REPOSITORY")" - query=$( - jq -nr \ - --arg labels "$LABELS" \ - ' - (reduce ($labels | split (", *"; null) | .[]) as $i ([]; . + [$i])) as $labels - | "query ($owner: String!, $repo: String!) {\n" + - " repository(owner: $owner, name: $repo) {\n" + - " id\n" + - ( - reduce $labels[] as $i ([0, ""]; [ - .[0] + 1, - .[1] + ( - " _" + (.[0] | tostring) + - ": label(name: \"" + $i + "\") {\n" + - " id\n" + - " name\n"+ - " }\n" - ) - ]) - | .[1] - )+ - " }\n" + - "}" - ' - ) - data=$( - gh api graphql \ - -f owner="$owner" \ - -f repo="$repo" \ - -f query="$query" \ - | jq -c \ - --arg labels "$LABELS" \ - --arg colors "$COLORS" \ - --arg defaultColor "$DEFAULT_COLOR" \ - ' - . as $in - | ($labels | split(", *"; null)) as $labels - | ($colors | split(", *"; null)) as $colors - | ( - reduce ( [$labels, $colors] | transpose)[] as $i ( - {}; - .[$i[0]] = ( - if ($i[1] and $i[1] != "") then - $i[1] - else - $defaultColor - end - | if startswith("#") then .[1:] else . end - ) - ) - ) as $colorMap - | ( - reduce ( - $in.data.repository[] - | select( objects | .name as $name | any($labels[]; . == $name ) ) - ) as $i ({}; .[$i.name] = {"id": $i.id}) - ) as $found - | ( - reduce ( - $labels[] - | select( . as $name | $found | has($name) | not ) - ) as $i ({}; .[$i] = {"color": $colorMap[$i]}) - ) as $missing - | { - "repoId": $in.data.repository.id, - "found": $found, - "missing": $missing, - } - ' - ) - echo "found=$(jq -c '.found' <<< "$data")" >> "$GITHUB_OUTPUT" - echo "missing=$(jq -c '.missing' <<< "$data")" >> "$GITHUB_OUTPUT" - echo "repo_id=$(jq -r '.repoId' <<< "$data")" >> "$GITHUB_OUTPUT" - - - name: Collect Item Node IDs - id: collect_ids - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - REPOSITORY: ${{ inputs.repository }} - ISSUES: ${{ inputs.issues }} - run: | - owner="$(dirname "$REPOSITORY")" - repo="$(basename "$REPOSITORY")" - query=$( - jq -nr \ - --arg issues "$ISSUES" \ - ' - (reduce ($issues | split (", *"; null) | .[]) as $i ([]; . + [$i])) as $issues - | - "query ($owner: String!, $repo: String!) {\n" + - " repository(owner: $owner, name: $repo) {\n" + - ( - reduce $issues[] as $i ([0, ""]; [ - .[0] + 1, - .[1] + ( - " _" + (.[0] | tostring) + - ": issueOrPullRequest(number: " + ($i | tostring) +") {\n" + - " ... on Issue {\n" + - " id\n" + - " }\n" + - " ... on PullRequest {\n"+ - " id\n"+ - " }\n" + - " }\n" - ) - ]) - | .[1] - )+ - " }\n" + - "}" - ' - ) - data=$( - gh api graphql \ - -f owner="$owner" \ - -f repo="$repo" \ - -f query="$query" \ - | jq -c 'reduce .data.repository[].id as $i ([]; . + [$i])' - ) - echo "issue_ids=$data" >> "$GITHUB_OUTPUT" - - - name: Create Missing Labels - id: create_missing - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - REPO_ID: ${{ steps.collect_labels.outputs.repo_id }} - EXISTING: ${{ steps.collect_labels.outputs.found }} - MISSING: ${{ steps.collect_labels.outputs.missing }} - run: | - query=$( - jq -nr \ - --argjson labels "$MISSING" \ - --arg repo "$REPO_ID" ' - "mutation {\n" + ( - reduce ($labels | keys | .[] | [., $labels[.]]) as $i ([0, ""]; [ - .[0] + 1, - .[1] + ( - " _" + (.[0] | tostring) + - ": createLabel(input: {repositoryId: \"" + $repo + - "\", name: \"" + $i[0] + - "\", color: \"" + $i[1].color + - "\"}) {\n" + - " clientMutationId\n" + - " label {\n" + - " id\n" + - " name\n" + - " color\n" + - " }\n" + - " }\n" - ) - ]) - | .[1] - ) + - "}" - ' - ) - data=$( - gh api graphql -f query="$query" | jq --argjson existing "$EXISTING" ' - reduce .data[].label as $i ({}; .[$i.name] = {"id": $i.id, "color": $i.color }) - | . + $existing - ' - ) - label_ids=$(jq -c '[.[].id]' <<< "$data") - echo "label_ids=$label_ids" >> "$GITHUB_OUTPUT" - - - name: Apply Labels - id: apply_labels - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - ISSUES: ${{ steps.collect_ids.outputs.issue_ids }} - LABELS: ${{ steps.create_missing.outputs.label_ids }} - run: | - query=$( - jq -nr \ - --argjson labels "$LABELS" \ - --argjson issues "$ISSUES" \ - ' - "mutation {\n" + ( - reduce $issues[] as $i ([0, ""]; [ - .[0] + 1, - .[1] + ( - " _" + (.[0] | tostring) + - ": addLabelsToLabelable(input: {labelableId: \"" + - $i + "\", labelIds: " + ($labels | tojson) + "}) {\n" + - " clientMutationId\n" + - " }\n" - ) - ]) - | .[1] - ) + - "}" - ' - ) - gh api graphql -f query="$query" diff --git a/.github/actions/delete-labels/action.yml b/.github/actions/delete-labels/action.yml deleted file mode 100644 index 65d3ba3d4..000000000 --- a/.github/actions/delete-labels/action.yml +++ /dev/null @@ -1,101 +0,0 @@ -name: Delete Label(s) -description: delete Label(s) -inputs: - gh_token: - description: gh api access token to use - required: true - repository: - description: the OWNER/REPOSITORY to operate on - default: ${{ github.repository }} - labels: - description: a single or comma separated list of labels to delete - required: true - -runs: - using: "composite" - steps: - - name: Collect Repo Labels - id: collect_labels - shell: bash - env: - GH_TOKEN: ${{ inputs.gh_token }} - REPOSITORY: ${{ inputs.repository }} - LABELS: ${{ inputs.labels }} - run: | - owner=$(echo "$REPOSITORY" | cut -d '/' -f 1) - repo=$(echo "$REPOSITORY" | cut -d '/' -f 2) - query=$( - jq -nr \ - --arg labels "$LABELS" \ - ' - (reduce ($labels | split (", *"; null) | .[]) as $i ([]; . + [$i])) as $labels - | "query ($owner: String!, $repo: String!) {\n" + - " repository(owner: $owner, name: $repo) {\n" + - ( - reduce $labels[] as $i ([0, ""]; [ - .[0] + 1, - .[1] + ( - " _" + (.[0] | tostring) + - ": label(name: \"" + $i + "\") {\n" + - " id\n" + - " name\n"+ - " }\n" - ) - ]) - | .[1] - )+ - " }\n" + - "}" - ' - ) - data=$( - gh api graphql \ - -f owner="$owner" \ - -f repo="$repo" \ - -f query="$query" \ - | jq -c \ - --arg labels "$LABELS" \ - --arg colors "$COLORS" \ - --arg defaultColor "$DEFAULT_COLOR" \ - ' - . as $in - | ($labels | split(", *"; null)) as $labels - | ( - reduce ( - $in.data.repository[] - | select( objects | .name as $name | any($labels[]; . == $name ) ) - ) as $i ({}; .[$i.name] = {"id": $i.id}) - ) as $found - | [$found[].id] - ' - ) - echo "label_ids=$data" >> "$GITHUB_OUTPUT" - echo "num_labels=$(jq -r 'length' <<< "$data")" >> "$GITHUB_OUTPUT" - - - name: Delete Labels - id: delete_labels - shell: bash - if: fromJSON( steps.collect_labels.outputs.num_labels ) > 0 - env: - GH_TOKEN: ${{ inputs.gh_token }} - LABELS: ${{ steps.collect_labels.outputs.label_ids }} - run: | - query=$(jq -r ' - . as $in - | ( - "mutation {\n" + ( - reduce $in[] as $id ([0, ""]; [ - .[0] + 1 , - .[1] + ( - " _" + (.[0] | tostring) + ": deleteLabel(input: {id: \"" + $id + "\"}) {\n" + - " clientMutationId\n" + - " }\n" - ) - ]) - | .[1] - ) + - "}" - ) - ' <<< "$LABELS" - ) - gh api graphql -f query="$query" diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index 3201e5f7a..21e73660d 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -60,12 +60,11 @@ jobs: id: dispatch_event_setup env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPOSITORY: ${{ github.repository }} PR_NUMBER: ${{ inputs.pr_id }} run: | # setup env for the rest of the workflow - OWNER=$(echo "$REPOSITORY" | cut -d '/' -f 1) - REPO=$(echo "$REPOSITORY" | cut -d '/' -f 2) + OWNER=$(dirname "${{ github.repository }}") + REPO=$(basename "${{ github.repository }}") PR_JSON=$( gh api \ -H "Accept: application/vnd.github.raw+json" \ @@ -155,33 +154,38 @@ jobs: ' done < <(jq -c '.blocking[]' <<< "$BLOCKING_PRS") | jq -c -s ) - blocked_by_labels=$(jq -c 'map( select( .merged | not ) | "blocked-by:" + (.number | tostring))' <<< "$blocked_pr_data" ) echo "data=$blocked_pr_data" >> "$GITHUB_OUTPUT" echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$blocked_pr_data")" >> "$GITHUB_OUTPUT" - echo "blocked_by_labels=$blocked_by_labels" >> "$GITHUB_OUTPUT" echo "current_blocking=$(jq -c 'map( select( .merged | not ) | .number )' <<< "$blocked_pr_data" )" >> "$GITHUB_OUTPUT" - - name: Apply Blocked by Labels + - name: Add 'blocked' Label is Missing id: label_blocked - if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && steps.blocking_data.outputs.blocked_by_labels != '' + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) continue-on-error: true - uses: ./.github/actions/add-labels - with: - repository: ${{ github.repository }} - gh_token: ${{ secrets.GITHUB_TOKEN }} - issues: ${{ env.PR_NUMBER }} - labels: ${{ join( fromJSON(steps.blocking_data.outputs.blocked_by_labels), ',' ) }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh -R ${{ github.repository }} issue edit --add-label 'blocked' $PR_NUMBER - - name: Apply 'blocking:' Label to Unmerged Dependencies + - name: Remove 'blocked' Label if All Dependencies Are Merged + id: unlabel_blocked + if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocking_data.outputs.all_merged) + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh -R ${{ github.repository }} issue edit --remove-label 'blocked' $PR_NUMBER + + - name: Apply 'blocking' Label to Unmerged Dependencies id: label_blocking if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 continue-on-error: true - uses: ./.github/actions/add-labels - with: - repository: ${{ github.repository }} - gh_token: ${{ secrets.GITHUB_TOKEN }} - issues: ${{ join( fromJSON(steps.blocking_data.outputs.current_blocking) , ',' ) }} - labels: ${{ format( 'blocking:{0}', env.PR_NUMBER ) }} + env: + BLOCKING_ISSUES: ${{ steps.blocking_data.outputs.current_blocking }} + run: | + while read -r pr ; do + gh -R ${{ github.repository }} issue edit --add-label 'blocking' "$pr" || true + done < <(jq -c '.[]' <<< "$BLOCKING_ISSUES") - name: Apply Blocking PR Status Check id: blocked_check diff --git a/.github/workflows/manual-add-label.yml b/.github/workflows/manual-add-label.yml deleted file mode 100644 index fdb69e389..000000000 --- a/.github/workflows/manual-add-label.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: "Manual: Apply Labels in Bulk" - -on: - workflow_dispatch: - inputs: - issues: - description: a single or comma separated list of issue numbers - required: true - type: string - labels: - description: a single or comma separated list of labels to apply to all listed issues - required: true - type: string - colors: - description: | - A single or comma separated list of colors to create the labels with if needed. - the list order is the same as `labels`. Missing or blank values (e.g. `FFFFFF,,FFFFFF`) use the `default_color` - type: string - default-color: - description: default color to create labels with - default: "#D4C5F9" - type: string - -jobs: - apply-labels: - name: Apply Labels - runs-on: ubuntu-latest - - permissions: - issues: write - pull-requests: write - - steps: - - name: Checkout Default Branch - uses: actions/checkout@v4 - with: - ref: ${{ github.event.repository.default_branch }} - - name: Run Label Action - uses: ./.github/actions/add-labels - with: - gh_token: ${{ secrets.GITHUB_TOKEN }} - issues: ${{ inputs.issues }} - labels: ${{ inputs.labels }} - colors: ${{ inputs.colors }} - default-color: ${{ inputs.default-color }} diff --git a/.github/workflows/manual-delete-labels.yml b/.github/workflows/manual-delete-labels.yml deleted file mode 100644 index 300c0b52a..000000000 --- a/.github/workflows/manual-delete-labels.yml +++ /dev/null @@ -1,30 +0,0 @@ - -name: "Manual: Delete labels in bulk" - -on: - workflow_dispatch: - inputs: - labels: - description: a single or comma separated list of labels to delete - required: true - type: string - -jobs: - delete-labels: - name: Delete Labels - runs-on: ubuntu-latest - - permissions: - issues: write - pull-requests: write - - steps: - - name: Checkout Default Branch - uses: actions/checkout@v4 - with: - ref: ${{ github.event.repository.default_branch }} - - name: Run Label Action - uses: ./.github/actions/delete-labels - with: - gh_token: ${{ secrets.GITHUB_TOKEN }} - labels: ${{ inputs.labels }} diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml new file mode 100644 index 000000000..6db3483d4 --- /dev/null +++ b/.github/workflows/merge-blocking-pr.yml @@ -0,0 +1,52 @@ +name: Merged Blocking Pull Request Automation + +on: + pull_request: + types: + - closed + +jobs: + update-blocked-status: + name: Update Blocked Status + runs-on: ubuntu-latest + + # a pr that was a `blocking:` label was merged. + # find the open pr's it was blocked by and trigger a refresh of their state + if: github.event.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), "blocking" ) + + permissions: + issues: write + pull-requests: write + actions: write + + steps: + - name: Gather Dependent PRs + id: gather_deps + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + blocked_prs=$( + gh -R ${{ github.repository }} pr list --label 'blocked' --json 'number,body' \ + | jq -c --argjson pr "${{ github.event.pull_request.number }}" ' + reduce ( .[] | select( + .body | + scan("(?:blocked (?:by|on)|stacked on):? #([0-9]+)") | + map(tonumber) | + any(.[]; . == $pr) + )) as $i ([]; . + [$i]) + ' + ) + echo "deps=$blocked_prs" >> "$GITHUB_OUTPUT" + echo "numdeps=$(jq -r '. | length' <<< "$blocked_prs")" >> "$GITHUB_OUTPUT" + + - name: Trigger Blocked PR Workflows for Dependants + if: fromJSON(steps.gather_deps.outputs.numdeps) > 0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DEPS: ${{ steps.gather_deps.outputs.deps }} + run: | + while read -r pr ; do + gh -R ${{ github.repository }} workflow run 'blocked-prs.yml' -r "${{ github.ref_name }}" -f pr_id="$pr" + done < <(jq -c '.[].number' <<< "$DEPS") + diff --git a/.github/workflows/merge-blocking_pr.yml b/.github/workflows/merge-blocking_pr.yml deleted file mode 100644 index a3f40a1a8..000000000 --- a/.github/workflows/merge-blocking_pr.yml +++ /dev/null @@ -1,102 +0,0 @@ -name: Merged Blocking Pull Request Automation - -on: - pull_request: - types: - - closed - -jobs: - update-blocked-status: - name: Update Blocked Status - runs-on: ubuntu-latest - - # a pr that was a `blocking:` label was merged. - # find the open pr's it was blocked by and trigger a refresh of their state - if: github.event.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), "blocking:" ) - - permissions: - issues: write - pull-requests: write - actions: write - - steps: - - name: Gather Dependent PRs - id: gather_deps - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - owner=$(echo "${{ github.repository }}" | cut -d '/' -f 1) - repo=$(echo "${{ github.repository }}" | cut -d '/' -f 2) - query=" - query(\$repo: String!, \$owner: String!, \$endCursor: String) { - repository(name: \$repo, owner: \$owner) { - pullRequests(first: 100, after: \$endCursor, states: [OPEN], labels: [\"blocked-by:${PR_NUMBER}\"]) { - nodes { - number - bodyText - merged - } - pageInfo { - hasNextPage - endCursor - } - } - } - } - " - blocked_prs=$( - gh api graphql \ - -f repo="$repo" \ - -f owner="$owner" \ - -f query="$query" \ - --paginate \ - --slurp \ - | jq -c --argjson pr "${{ github.event.pull_request.number }}" ' - reduce ( .[].data.repository.pullRequests.nodes[] | select( - .bodyText | - scan("(?:blocked (?:by|on)|stacked on):? #([0-9]+)") | - map(tonumber) | - any(.[]; . == $pr) - )) as $i ([]; . + [$i]) - ' - ) - echo "deps=$blocked_prs" >> "$GITHUB_OUTPUT" - echo "numdeps=$(jq -r '. | length' <<< "$blocked_prs")" >> "$GITHUB_OUTPUT" - - - name: Trigger Blocked PR Workflows for Dependants - if: fromJSON(steps.gather_deps.outputs.numdeps) > 0 - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DEPS: ${{ steps.gather_deps.outputs.deps }} - run: | - while read -r pr ; do - gh api \ - --method POST \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "/repos/${{ github.repository }}/actions/workflows/blocked-prs.yml/dispatches" \ - -f "ref=${{ github.ref_name }}" \ - -f "inputs[pr_id]=$pr" - done < <(jq -c '.[].number' <<< "$DEPS") - - label-cleanup: - # this pr is closed, no need for these anymore - name: "Cleanup Related blocking: or blocked-by: labels" - runs-on: ubuntu-latest - - permissions: - issues: write - pull-requests: write - - steps: - - name: Checkout Default Branch - uses: actions/checkout@v4 - with: - ref: ${{ github.event.repository.default_branch }} - - name: Delete Related Labels - uses: ./.github/actions/delete-labels - with: - repository: ${{ github.repository }} - gh_token: ${{ secrets.GITHUB_TOKEN }} - labels: ${{ format('blocking:{0},blocked-by:{0}', github.event.pull_request.number ) }}