Skip to content

Project Board Automation #2159

Project Board Automation

Project Board Automation #2159

# 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]
# Configure the project specific variables
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' # See project settings (default is `Iteration`)
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:
#######
## Gather data, reopen closed issues and ensure work item is on the board
#######
- 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.PROJECT_MANAGE_ACTION}}
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.PROJECT_MANAGE_ACTION}}
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.PROJECT_MANAGE_ACTION}}
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.PROJECT_MANAGE_ACTION}}
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
# check the env vars before the update to help debugging
- run: env
#######
## Set the variables ready to update work item on project board
#######
- 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
#######
## Take the set item fields, update and move the item on the board
#######
- 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.PROJECT_MANAGE_ACTION }}