Skip to content

Commit

Permalink
Improve Asana integration for failed PR checks
Browse files Browse the repository at this point in the history
  • Loading branch information
ayoy committed Sep 25, 2023
1 parent c340049 commit 9447cbd
Show file tree
Hide file tree
Showing 4 changed files with 308 additions and 13 deletions.
49 changes: 49 additions & 0 deletions .github/actions/asana-failed-pr-checks/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Report Failed PR Checks to Asana
description: Integrates with Asana to report failed PR checks as a task.
inputs:
asana-access-token:
description: "Asana access token"
required: true
type: string
asana-section-id:
description: "Asana project's section ID"
required: true
type: string
action:
description: "Action to perform: choose between 'create-task' and 'close-task'"
required: true
type: string
commit-author:
description: "Last commit author's GitHub handle"
required: false
type: string
runs:
using: "composite"
steps:
- env:
WORKFLOW_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
GITHUB_REF_NAME: ${{ github.ref_name }}
GITHUB_RUN_ID: ${{ github.run_id }}
ASANA_SECTION_ID: ${{ inputs.asana-section-id }}
ASANA_ACCESS_TOKEN: ${{ inputs.asana-access-token }}
shell: bash
run: |
case "${{ inputs.action }}" in
"create-task")
if [[ -n "${{ inputs.commit-author }}" ]]; then
export ASANA_ASSIGNEE=$(jq -r .${{ inputs.commit-author }} < ${{ github.action_path }}/user_ids.json)
fi
${{ github.action_path }}/report-failed-pr-checks.sh create-task \
-t "PR Check is failing on ${{ env.GITHUB_REF_NAME }}" \
-d "PR Checks conducted after merging have failed. See ${{ env.WORKFLOW_URL }}. Follow the steps on https://app.asana.com/0/1202500774821704/1205317064731691 to resolve this issue."
;;
"close-task")
${{ github.action_path }}/report-failed-pr-checks.sh close-task \
-m "Closing this one as checks are passing after a re-run. See ${{ env.WORKFLOW_URL }}/attempts/${{ github.run_attempt }} for details."
;;
*)
echo "::error::Invalid action '${{ inputs.action }}'."
exit 1
;;
esac
192 changes: 192 additions & 0 deletions .github/actions/asana-failed-pr-checks/report-failed-pr-checks.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#!/bin/bash

set -eo pipefail

workspace_id="137249556945"
project_id="1205237866452338"
workflow_id_custom_field_id="1205563320492190"
asana_api_url="https://app.asana.com/api/1.0"

print_usage_and_exit() {
local reason=$1

cat <<- EOF
Usage:
$ $(basename "$0") <create-task|close-task> [-h] [-t <title>] [-d <description>] [-m <closing_comment_message>]
Actions:
create-task Create a new task in Asana
close-task Close an existing Asana task for the workflow ID provided in environment variable WORKFLOW_ID
Options (only used for create-task):
-t <title> Asana task title
-d <description> Asana task description
-m <closing_comment_message> Closing comment message
Note: This script is intended for CI use only. You shouldn't call it directly.
EOF

echo "${reason}"
exit 1
}

read_command_line_arguments() {
action="$1"
case "${action}" in
create-task)
;;
close-task)
;;
*)
print_usage_and_exit "Unknown action '${action}'"
;;
esac

shift 1

case "${action}" in
create-task)
if (( $# < 2 )); then
print_usage_and_exit "Missing arguments"
fi
;;
close-task)
if (( $# < 1 )); then
print_usage_and_exit "Missing message argument"
fi
;;
esac

while getopts 'd:hm:t:' OPTION; do
case "${OPTION}" in
d)
description="${OPTARG}"
;;
h)
print_usage_and_exit
;;
m)
message="${OPTARG}"
;;
t)
title="${OPTARG}"
;;
*)
print_usage_and_exit "Unknown option '${OPTION}'"
;;
esac
done

shift $((OPTIND-1))
}

create_task() {
local task_name=$1
local description
local task_id
local assignee_param
description=$(sed -E -e 's/\\/\\\\/g' -e 's/"/\\"/g' <<< "$2")
if [[ -n "${assignee}" ]]; then
assignee_param="\"${assignee}\""
else
assignee_param="null"
fi

task_id=$(curl -X POST -s "${asana_api_url}/tasks?opt_fields=gid" \
-H "Authorization: Bearer ${asana_personal_access_token}" \
-H 'content-type: application/json' \
-d "{
\"data\": {
\"assignee\": ${assignee_param},
\"name\": \"${task_name}\",
\"resource_subtype\": \"default_task\",
\"notes\": \"${description}\",
\"projects\": [
\"${project_id}\"
],
\"custom_fields\": {
\"${workflow_id_custom_field_id}\": \"${workflow_id}\"
}
}
}" \
| jq -r '.data.gid')

return_code="$(curl -X POST -s "${asana_api_url}/sections/${section_id}/addTask" \
-H "Authorization: Bearer ${asana_personal_access_token}" \
-H 'content-type: application/json' \
--write-out '%{http_code}' \
--output /dev/null \
-d "{\"data\": {\"task\": \"${task_id}\"}}")"

[[ ${return_code} -eq 200 ]]
}

find_task_for_workflow_id() {
local workflow_id=$1
curl -s "${asana_api_url}/workspaces/${workspace_id}/tasks/search?opt_fields=gid&resource_subtype=default_task&projects.any=${project_id}&limit=1&custom_fields.${workflow_id_custom_field_id}.value=${workflow_id}" \
-H "Authorization: Bearer ${asana_personal_access_token}" \
| jq -r "if (.data | length) != 0 then .data[0].gid else empty end"
}

add_comment_to_task() {
local task_id=$1
local message
local return_code
message=$(sed -E -e 's/\\/\\\\/g' -e 's/"/\\"/g' <<< "$2")

return_code="$(curl -X POST -s "${asana_api_url}/tasks/${task_id}/stories" \
-H "Authorization: Bearer ${asana_personal_access_token}" \
-H 'content-type: application/json' \
--write-out '%{http_code}' \
--output /dev/null \
-d "{\"data\": {\"text\": \"${message}\"}}")"

[[ ${return_code} -eq 201 ]]
}

close_task() {
local task_id=$1
local return_code

return_code="$(curl -X PUT -s "${asana_api_url}/tasks/${task_id}" \
-H "Authorization: Bearer ${asana_personal_access_token}" \
-H 'content-type: application/json' \
--write-out '%{http_code}' \
--output /dev/null \
-d "{\"data\": {\"completed\": true}}")"

[[ ${return_code} -eq 200 ]]
}

main() {
local asana_personal_access_token="${ASANA_ACCESS_TOKEN}"
local section_id="${ASANA_SECTION_ID}"
local assignee="${ASANA_ASSIGNEE}"
local workflow_id="${GITHUB_RUN_ID}"
local action
local title
local description
local message

read_command_line_arguments "$@"

case "${action}" in
create-task)
create_task "${title}" "${description}"
;;
close-task)
task_id=$(find_task_for_workflow_id "${workflow_id}")
if [[ -n "${task_id}" ]]; then
add_comment_to_task "${task_id}" "${message}"
close_task "${task_id}"
else
echo "No task found for workflow ID '${workflow_id}'"
fi
;;
*)
print_usage_and_exit "Unknown action '${action}'"
;;
esac
}

main "$@"
26 changes: 26 additions & 0 deletions .github/actions/asana-failed-pr-checks/user_ids.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"afterxleep": "1204051006248670",
"amddg44": "1201462886802326",
"ayoy": "1201621708108816",
"brindy": "392891325557408",
"bwaresiak": "856498667309057",
"Bunn": "1201011656765575",
"dharb": "246491496396030",
"diegoreymendez": "1203108348814444",
"GioSensation": "1144596633302696",
"graeme": "1202926619868495",
"jaceklyp": "1201392122286920",
"jonathanKingston": "1199237043596759",
"jotaemepereira": "1203972458584425",
"ladamski": "1171671347221380",
"mallexxx": "1202406491309505",
"miasma13": "1200848783436158",
"muodov": "1201807839802870",
"samsymons": "1193060753408019",
"shakyShane": "1201141132927824",
"SabrinaTardio": "1204024359086954",
"THISISDINOSAUR": "1187352150811184",
"tomasstrba": "1148564399176232",
"viktorjansson": "970019368055267",
"vinay-nadig-0042": "1202096681833210"
}
54 changes: 41 additions & 13 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ jobs:
runs-on: macos-13
timeout-minutes: 30

outputs:
commit_author: ${{ steps.fetch_commit_author.outputs.commit_author }}

steps:

- name: Check out the code
Expand Down Expand Up @@ -89,24 +92,49 @@ jobs:
name: build-log.txt
path: build-log.txt

asana:
- name: Fetch latest commit author
if: always() && github.ref_name == 'main'
id: fetch_commit_author
env:
GH_TOKEN: ${{ github.token }}
run: |
head_commit=$(git rev-parse HEAD)
author=$(gh api https://api.github.com/repos/${{ github.repository }}/commits/${head_commit} --jq .author.login)
echo "commit_author=${author}" >> $GITHUB_OUTPUT
create-asana-task:
name: Create Asana Task
needs: [swiftlint, unit-tests]

if: failure() && github.ref_name == 'main'

env:
WORKFLOW_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}

if: failure() && github.ref_name == 'main' && github.run_attempt == 1

runs-on: ubuntu-latest

steps:
- name: Check out the code
uses: actions/checkout@v3
- name: Create Asana Task
uses: malmstein/github-asana-action@master
uses: ./.github/actions/asana-failed-pr-checks
with:
action: create-task
asana-access-token: ${{ secrets.ASANA_ACCESS_TOKEN }}
asana-section-id: ${{ vars.APPLE_CI_FAILING_TESTS_BSK_POST_MERGE_SECTION_ID }}
commit-author: ${{ needs.unit-tests.outputs.commit_author }}

close-asana-task:
name: Close Asana Task
needs: [swiftlint, unit-tests]

if: success() && github.ref_name == 'main' && github.run_attempt > 1

runs-on: ubuntu-latest

steps:
- name: Check out the code
uses: actions/checkout@v3
- name: Close Asana Task
uses: ./.github/actions/asana-failed-pr-checks
with:
asana-pat: ${{ secrets.ASANA_ACCESS_TOKEN }}
asana-project: ${{ vars.APPLE_CI_FAILING_TESTS_PROJECT_ID }}
asana-section: ${{ vars.APPLE_CI_FAILING_TESTS_BSK_POST_MERGE_SECTION_ID }}
asana-task-name: 'PR Check is failing on main'
action: create-asana-task
asana-task-description: PR Checks conducted after merging have failed. See ${{ env.WORKFLOW_URL }}. Follow the steps on https://app.asana.com/0/1202500774821704/1205317064731691 to resolve this issue.
action: close-task
asana-access-token: ${{ secrets.ASANA_ACCESS_TOKEN }}
asana-section-id: ${{ vars.APPLE_CI_FAILING_TESTS_BSK_POST_MERGE_SECTION_ID }}

0 comments on commit 9447cbd

Please sign in to comment.