Project Board Automation #38621
This file contains 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
# yamllint disable rule:line-length | |
--- | |
name: "Project Board Automation" | |
"on": | |
pull_request: | |
branches: [develop, master] | |
types: [synchronize, opened, reopened, labeled, unlabeled, ready_for_review, review_requested, converted_to_draft, closed] | |
pull_request_review: | |
types: [submitted] | |
env: | |
ORGANIZATION: vegaprotocol | |
PROJECT_NUMBER: 106 | |
PR_URL: ${{ github.event.pull_request.html_url }} | |
GH_TOKEN: ${{ secrets.PROJECT_MANAGE_ACTION }} | |
EXCLUDE_LABEL: 'no-issue' | |
IN_PROGRESS_COLUMN_NAME: '"In Progress"' | |
REVIEW_REQUIRED_COLUMN_NAME: '"Waiting Review"' | |
IN_REVIEW_COLUMN_NAME: '"In Review"' | |
APPROVED_COLUMN_NAME: '"Approved"' | |
MERGED_COLUMN_NAME: '"Merged"' | |
DONE_COLUMN_NAME: '"Done"' | |
ITERATION_FIELD_NAME: 'Sprint' | |
USER: ${{ github.actor }} | |
concurrency: | |
group: ${{ github.workflow }}-${{ github.ref }} | |
cancel-in-progress: true | |
jobs: | |
project-board-automation: | |
name: "Linked Issues & Project Board Automation" | |
runs-on: ubuntu-latest | |
permissions: write-all | |
steps: | |
- name: "Get linked issue id and state" | |
id: linked-issue | |
env: | |
GH_TOKEN: ${{ secrets.PROJECT_MANAGE_ACTION }} | |
run: | | |
gh api graphql -f query=' | |
query($pr_url: URI!) { | |
resource(url: $pr_url) { | |
... on PullRequest { | |
closingIssuesReferences(last: 1) { | |
nodes { | |
id | |
state | |
} | |
} | |
} | |
} | |
}' -f pr_url=$PR_URL > data.json | |
echo 'LINKED_ISSUE_STATE='$(jq '.data.resource.closingIssuesReferences.nodes[] | .state' data.json) >> $GITHUB_ENV | |
echo 'LINKED_ISSUE_ID='$(jq '.data.resource.closingIssuesReferences.nodes[] | .id' data.json) >> $GITHUB_ENV | |
- name: "Check if PR raised by bot" | |
id: bot-pr | |
if: | | |
((startsWith(github.head_ref, 'renovate/') == true || startsWith(github.head_ref, 'dependabot/') == true) | |
|| (github.actor == 'dependabot[bot]' || github.actor == 'renovate[bot]')) | |
run: | | |
echo 'BOT_PR=true' >> $GITHUB_ENV | |
- name: "Check for linked issue" | |
id: linked | |
if: | | |
env.LINKED_ISSUE_ID != '' && | |
contains(github.event.pull_request.labels.*.name, env.EXCLUDE_LABEL) != true | |
uses: actions/[email protected] | |
with: | |
github-token: ${{secrets.GITHUB_TOKEN}} | |
script: | | |
console.log("Linked Issue Found!"); | |
- name: "Check for linked issue exclusion label" | |
id: exclude-linked | |
if: | | |
steps.bot-pr.outcome == 'success' || env.LINKED_ISSUE_ID == '' && | |
contains(github.event.pull_request.labels.*.name, env.EXCLUDE_LABEL) == true | |
uses: actions/[email protected] | |
with: | |
github-token: ${{secrets.GITHUB_TOKEN}} | |
script: | | |
console.log("Exclusion label added, or a bot-PR no linked issue required!"); | |
- name: "Fail if no linked issue or exclusion label" | |
id: exclude-linked-error | |
if: steps.linked.outcome == 'skipped' && steps.exclude-linked.outcome == 'skipped' | |
uses: actions/[email protected] | |
with: | |
github-token: ${{secrets.GITHUB_TOKEN}} | |
script: | | |
console.log("No linked issue or exclusion label!"); | |
core.setFailed("Link an issue and rerun, or, add the exclusion label!"); | |
- name: "Fail if linked issue AND exclusion label" | |
id: linked-and-nochangelog | |
if: steps.linked.outcome == 'success' && steps.exclude-linked.outcome == 'success' | |
uses: actions/[email protected] | |
with: | |
github-token: ${{secrets.GITHUB_TOKEN}} | |
script: | | |
console.log("Remove exclusion label, linked issue found!"); | |
core.setFailed("Remove exclusion label, linked issue found!"); | |
- name: "Get project related data" | |
id: project-data | |
run: | | |
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 | |
} | |
} | |
... on ProjectV2IterationField { | |
name | |
id | |
configuration { | |
iterations { | |
id | |
title | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > data.json | |
echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' data.json) >> $GITHUB_ENV | |
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' data.json) >> $GITHUB_ENV | |
echo 'IN_PROGRESS_COLUMN='$(jq --arg in_progress ${{ env.IN_PROGRESS_COLUMN_NAME }} '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name==$in_progress) | .id' data.json) >> $GITHUB_ENV | |
echo 'REVIEW_REQUIRED_COLUMN='$(jq --arg review_required ${{ env.REVIEW_REQUIRED_COLUMN_NAME }} '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name==$review_required) | .id' data.json) >> $GITHUB_ENV | |
echo 'IN_REVIEW_COLUMN='$(jq --arg in_review ${{ env.IN_REVIEW_COLUMN_NAME }} '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name==$in_review) | .id' data.json) >> $GITHUB_ENV | |
echo 'APPROVED_COLUMN='$(jq --arg approved ${{ env.APPROVED_COLUMN_NAME }} '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name==$approved) | .id' data.json) >> $GITHUB_ENV | |
echo 'MERGED_COLUMN='$(jq --arg merged ${{ env.MERGED_COLUMN_NAME }} '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name==$merged) | .id' data.json) >> $GITHUB_ENV | |
echo 'DONE_COLUMN='$(jq --arg done ${{ env.DONE_COLUMN_NAME }} '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name==$done) | .id' data.json) >> $GITHUB_ENV | |
echo 'ITERATION_FIELD_ID='$(jq --arg iteration_field_name $ITERATION_FIELD_NAME '.data.organization.projectV2.fields.nodes[] | select(.name==$iteration_field_name) | .id' data.json) >> $GITHUB_ENV | |
echo 'CURRENT_ITERATION='$(jq --arg iteration_field_name $ITERATION_FIELD_NAME '.data.organization.projectV2.fields.nodes[] | select(.name==$iteration_field_name) | .configuration.iterations[0] | .id' data.json) >> $GITHUB_ENV | |
- name: "Get PR state, review decision and author" | |
id: pr-status | |
run: | | |
gh api graphql -f query=' | |
query($pr_url: URI!) { | |
resource(url: $pr_url) { | |
... on PullRequest { | |
reviewThreads(last: 100) { | |
edges { | |
node { | |
isResolved | |
} | |
} | |
} | |
reviewDecision | |
id | |
isDraft | |
state | |
author{ | |
login | |
} | |
reviews(last: 1) { | |
nodes { | |
state | |
} | |
} | |
} | |
} | |
}' -f pr_url=$PR_URL > data.json | |
echo 'REVIEW_DECISION='$(jq '.data.resource | .reviewDecision' data.json) >> $GITHUB_ENV | |
echo 'NUM_REVIEWS='$(jq '.data.resource.reviews.nodes | length' data.json) >> $GITHUB_ENV | |
echo 'LATEST_REVIEW_STATE='$(jq '.data.resource.reviews.nodes[] | .state' data.json) >> $GITHUB_ENV | |
echo 'IS_PR_DRAFT='$(jq '.data.resource | .isDraft' data.json) >> $GITHUB_ENV | |
echo 'PR_STATE='$(jq '.data.resource | .state' data.json) >> $GITHUB_ENV | |
echo 'PR_ID='$(jq '.data.resource | .id' data.json) >> $GITHUB_ENV | |
echo 'AUTHOR_NAME='$(jq '.data.resource.author | .login' data.json) >> $GITHUB_ENV | |
- name: "Get PR author id" | |
id: pr-author | |
if: steps.pr-status.outcome == 'success' | |
run: | | |
gh api graphql -f query=' | |
query($author_name: String!) { | |
search (query: $author_name, type: USER, first: 1){ | |
edges { | |
node { | |
... on User { | |
id | |
} | |
} | |
} | |
} | |
}' -f author_name=$AUTHOR_NAME > data.json | |
echo 'AUTHOR_ID='$(jq '.data.search.edges[] | .node | .id' data.json) >> $GITHUB_ENV | |
- name: "Add linked issue to the project" | |
id: issue-to-project | |
if: | | |
env.LINKED_ISSUE_ID != '' && | |
contains(github.event.pull_request.labels.*.name, env.EXCLUDE_LABEL) != true | |
run: | | |
issue_item_id="$( gh api graphql -f query=' | |
mutation($user:String!, $project:ID!, $issue:ID!) { | |
addProjectV2ItemById(input: {clientMutationId: $user, projectId: $project, contentId: $issue}) { | |
item { | |
id | |
} | |
} | |
}' -f project=$PROJECT_ID -f issue=$LINKED_ISSUE_ID -f user=$USER --jq '.data.addProjectV2ItemById.item.id')" | |
echo 'BOARD_ITEM_ID='$issue_item_id >> $GITHUB_ENV | |
- name: "Add exclusion labeled PR to the project" | |
id: pr-to-project | |
if: | | |
steps.issue-to-project.outcome == 'skipped' && | |
contains(github.event.pull_request.labels.*.name, env.EXCLUDE_LABEL) == true | |
run: | | |
pr_item_id="$( gh api graphql -f query=' | |
mutation($user:String!, $project:ID!, $pr:ID!) { | |
addProjectV2ItemById(input: {clientMutationId: $user, projectId: $project, contentId: $pr}) { | |
item { | |
id | |
} | |
} | |
}' -f project=$PROJECT_ID -f pr=$PR_ID -f user=$USER --jq '.data.addProjectV2ItemById.item.id')" | |
echo 'BOARD_ITEM_ID='$pr_item_id >> $GITHUB_ENV | |
- name: "Add BOT PR to the project" | |
id: bot-pr-to-project | |
if: steps.bot-pr.outcome == 'success' | |
run: | | |
pr_item_id="$( gh api graphql -f query=' | |
mutation($user:String!, $project:ID!, $pr:ID!) { | |
addProjectV2ItemById(input: {clientMutationId: $user, projectId: $project, contentId: $pr}) { | |
item { | |
id | |
} | |
} | |
}' -f project=$PROJECT_ID -f pr=$PR_ID -f user=$USER --jq '.data.addProjectV2ItemById.item.id')" | |
echo 'BOARD_ITEM_ID='$pr_item_id >> $GITHUB_ENV | |
- name: "Reopen if the linked issue closed" | |
id: reopen-issue | |
if: steps.issue-to-project.outcome == 'success' && env.LINKED_ISSUE_STATE == '"CLOSED"' && env.PR_STATE != '"MERGED"' | |
run: | | |
gh api graphql -f query=' | |
mutation($clientMutationId:String!, $issueId:ID!) { | |
reopenIssue(input: {clientMutationId:$clientMutationId, issueId:$issueId}) { | |
issue{ | |
id | |
} | |
} | |
}' -f clientMutationId=$AUTHOR_ID -f issueId=$LINKED_ISSUE_ID | |
- run: env | |
- name: "Set draft work item for progress column" | |
id: draft-pr | |
if: github.event.pull_request.draft == 'true' || env.IS_PR_DRAFT == 'true' | |
run: | | |
echo 'ITEM_ID='${{ env.BOARD_ITEM_ID }} >> $GITHUB_ENV | |
echo 'CURRENT_ITERATION='$CURRENT_ITERATION >> $GITHUB_ENV | |
echo 'CURRENT_STATUS='${{ env.IN_PROGRESS_COLUMN }} >> $GITHUB_ENV | |
- name: "Set work item for review required column" | |
id: review-required | |
if: | | |
github.event.action == 'ready_for_review' || | |
env.IS_PR_DRAFT != 'true' && env.NUM_REVIEWS == 0 && env.REVIEW_DECISION == '"REVIEW_REQUIRED"' | |
run: | | |
echo 'ITEM_ID='${{ env.BOARD_ITEM_ID }} >> $GITHUB_ENV | |
echo 'CURRENT_STATUS='${{ env.REVIEW_REQUIRED_COLUMN }} >> $GITHUB_ENV | |
- name: "Set work item for in review column" | |
id: changes-requested | |
if: | | |
(steps.draft-pr.outcome == 'skipped' && env.NUM_REVIEWS > 0 && | |
(env.REVIEW_DECISION == '"CHANGES_REQUESTED"' || env.LATEST_REVIEW_STATE == '"COMMENTED"' | |
|| env.LATEST_REVIEW_STATE == '"DISMISSED"')) | |
run: | | |
echo 'ITEM_ID='${{ env.BOARD_ITEM_ID }} >> $GITHUB_ENV | |
echo 'CURRENT_STATUS='${{ env.IN_REVIEW_COLUMN }} >> $GITHUB_ENV | |
- name: "Set work item for approved column" | |
id: approved | |
if: | | |
(steps.draft-pr.outcome == 'skipped' && (env.REVIEW_DECISION == '"APPROVED"' | |
|| env.LATEST_REVIEW_STATE == '"APPROVED"')) | |
run: | | |
echo 'ITEM_ID='${{ env.BOARD_ITEM_ID }} >> $GITHUB_ENV | |
echo 'CURRENT_STATUS='${{ env.APPROVED_COLUMN }} >> $GITHUB_ENV | |
- name: "Set work item for merged column" | |
id: merged | |
if: env.PR_STATE == '"MERGED"' || github.event.pull_request.merged == true | |
run: | | |
echo 'ITEM_ID='${{ env.BOARD_ITEM_ID }} >> $GITHUB_ENV | |
echo 'CURRENT_STATUS='${{ env.MERGED_COLUMN }} >> $GITHUB_ENV | |
- name: "Close linked issue when PR merged" | |
id: close-issue | |
if: steps.merged.outcome == 'success' && steps.linked.outcome == 'success' | |
run: | | |
gh api graphql -f query=' | |
mutation($clientMutationId:String!, $issueId:ID!) { | |
closeIssue(input: {clientMutationId:$clientMutationId, issueId:$issueId}) { | |
issue{ | |
id | |
} | |
} | |
}' -f clientMutationId=$AUTHOR_ID -f issueId=$LINKED_ISSUE_ID | |
- name: "Set closed PR for done column" | |
id: closed-pr | |
if: steps.exclude-linked.outcome == 'success' && github.event.pull_request.closed == true | |
run: | | |
echo 'ITEM_ID='${{ env.BOARD_ITEM_ID }} >> $GITHUB_ENV | |
echo 'CURRENT_STATUS='${{ env.DONE_COLUMN }} >> $GITHUB_ENV | |
- name: "Set closed PR issue to in progress column" | |
id: closed-pr-with-issue | |
if: steps.linked.outcome == 'success' && github.event.pull_request.merged == true && env.PR_STATE != '"MERGED"' | |
run: | | |
echo 'ITEM_ID='${{ env.BOARD_ITEM_ID }} >> $GITHUB_ENV | |
echo 'CURRENT_STATUS='${{ env.IN_PROGRESS_COLUMN }} >> $GITHUB_ENV | |
- name: Move project board item | |
run: | | |
gh api graphql -f query=' | |
mutation ( | |
$project: ID! | |
$item: ID! | |
$status_field: ID! | |
$status_value: String! | |
$iteration_field: ID! | |
$iteration_value: String! | |
) { | |
set_status: updateProjectV2ItemFieldValue(input: { | |
projectId: $project | |
itemId: $item | |
fieldId: $status_field | |
value: { | |
singleSelectOptionId: $status_value | |
} | |
}) { | |
projectV2Item { | |
id | |
} | |
} | |
set_iteration: updateProjectV2ItemFieldValue(input: { | |
projectId: $project | |
itemId: $item | |
fieldId: $iteration_field | |
value: { | |
iterationId: $iteration_value | |
} | |
}) { | |
projectV2Item { | |
id | |
} | |
} | |
}' -f project=$PROJECT_ID \ | |
-f item=$ITEM_ID \ | |
-f status_field=$STATUS_FIELD_ID \ | |
-f status_value=${{ env.CURRENT_STATUS }} \ | |
-f iteration_field=$ITERATION_FIELD_ID \ | |
-f iteration_value=${{ env.CURRENT_ITERATION }} | |
verify-changelog-updated: | |
name: "Verify CHANGELOG Updated" | |
needs: project-board-automation | |
runs-on: ubuntu-latest | |
if: | | |
github.event.pull_request.draft != true | |
&& contains(github.event.pull_request.labels.*.name, 'no-changelog') != true | |
steps: | |
- name: "Checkout" | |
id: checkout | |
uses: actions/[email protected] | |
- name: "Check changelog entry" | |
id: check-changelog | |
uses: Zomzog/[email protected] | |
with: | |
fileName: CHANGELOG.md | |
noChangelogLabel: "no-changelog" | |
checkNotification: Simple | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
# yamllint enable rule:line-length |