Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move GitHub custom actions from apple-infra #6

Merged
merged 2 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions actions/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// swift-tools-version:5.7

// Leave blank. This is only here so that Xcode doesn't display it.

import PackageDescription

let package = Package(
name: "actions",
products: [],
targets: []
)
45 changes: 45 additions & 0 deletions actions/asana-create-pr-subtask/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Create Asana PR subtask
description: |
Creates a PR subtask and assigns it to the team member in charge of the GitHub review.
If the PR subtask already exists, it ensures the reviewer is assigned and the task is not marked 'completed'.
inputs:
access-token:
description: "Asana access token"
required: true
type: string
asana-task-id:
description: "Asana Task ID for the PR"
required: string
type: string
github-reviewer-user:
description: "GitHub username for the requested reviewer"
required: true
type: string
runs:
using: "composite"
steps:
- name: Get Asana user ID
id: get-asana-user-id
uses: duckduckgo/apple-infra/actions/asana-get-user-id-for-github-handle@main
with:
access-token: ${{ inputs.access-token }}
github-handle: ${{ inputs.github-reviewer-user }}

- name: Get PR URL
id: get-pr-url
shell: bash
run: |
echo "url=https://github.com/${{ github.repository }}/pull/${{ github.event.number }}" >> "$GITHUB_OUTPUT"
- name: "Create PR Subtask"
shell: bash
env:
ASANA_ACCESS_TOKEN: ${{ inputs.access-token }}
run: |
# If the Asana User ID is not found, exit with an error
if [[ -z "${{ steps.get-asana-user-id.outputs.user-id }}" ]]; then
echo "Error: Failed to get Asana user ID for GitHub user ${{ inputs.github-reviewer-user }}"
exit 1
else
${{ github.action_path }}/asana-create-pr-subtask.sh ${{ inputs.asana-task-id }} ${{ steps.get-asana-user-id.outputs.user-id }} ${{ steps.get-pr-url.outputs.url }}
fi
149 changes: 149 additions & 0 deletions actions/asana-create-pr-subtask/asana-create-pr-subtask.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#!/bin/bash
#
# This script creates a PR subtask and assign it to a team member when a review is requested on GitHub
#

set -e -o pipefail

asana_api_url="https://app.asana.com/api/1.0"
pr_prefix="PR:"

# Fetch the subtasks of a task with the given Asana task ID.
_fetch_subtasks() {
local asana_task_id="$1"
local url="${asana_api_url}/tasks/${asana_task_id}/subtasks?opt_fields=name,completed,parent.name,assignee"

local response
response="$(curl -fLSs "$url" -H "Authorization: Bearer ${ASANA_ACCESS_TOKEN}")"

# extract the task id, task name, task completed status, assignee id and parent name
jq -c '[
.data[] | {
task_id: .gid,
task_name: .name,
task_completed: .completed,
assignee: .assignee.gid,
parent_name: .parent.name
}
]' <<< "$response"
}

# Sets the parent task name
_set_parent_task_name() {
local subtasks="$1"
local asana_task_id="$2"

# extracts the parent name from the first object
parent_task_name=$(echo "$subtasks" | jq -r '.[0].parent_name')

# if parent_task_name is nil it means that there are no subtask in the current task so we fetch the parent name
if [ -z "$parent_task_name" ] || [ "$parent_task_name" = "null" ]; then
local url="${asana_api_url}/tasks/${asana_task_id}"
parent_task_name="$(curl -fLSs "$url" -H "Authorization: Bearer ${ASANA_ACCESS_TOKEN}" | jq -r '.data.name')"
fi
}

# Checks if a subtask for the PR already exists.
_check_pr_subtask_exist() {
local response="$1"
local asana_assignee_id="$2"

# read each line of the array
# extract the task name and trim leading and trailing white spaces
# extract the parent name and trim leading and trailing white spaces
# extract the assignee name
# checks if the task name has 'PR:' prefix and if contains the parent name and if it's assigned to the reviewer
echo "$response" | jq -c '.[]' | while read -r item; do
task_name=$(jq -r '.task_name' <<< "$item" | awk '{$1=$1};1')
parent_name=$(jq -r '.parent_name' <<< "$item" | awk '{$1=$1};1')
assignee=$(jq -r '.assignee' <<< "$item")

if [[ "$task_name" == "${pr_prefix}"* && "$task_name" == *"$parent_name"* && "$assignee" == "$asana_assignee_id" ]]; then
echo "$item"
fi
done

}

# Creates a subtask called PR: ${task_title}, set the PR URL as description and assign to the requested reviewer
_create_pr_subtask() {
local asana_task_id="$1"
local asana_assignee_id="$2"
local github_pr_url="$3"
local url="${asana_api_url}/tasks/${asana_task_id}/subtasks?opt_fields=gid"

local payload
payload=$(cat <<-EOF
{
"data": {
"assignee": "${asana_assignee_id}",
"notes": "${pr_prefix} ${github_pr_url}",
"name": "${pr_prefix} ${parent_task_name}"
}
}
EOF
)

_execute_create_or_update_asana_task_request POST "$url" "$payload"
}

# Assigns a reviewer to the existing PR subtask and update the task status if it is marked 'completed'
_mark_task_uncompleted_if_needed() {
local pr_subtask="$1"

# get the task id
local task_id
task_id=$(echo "$pr_subtask" | jq -r '.task_id')
# get the completed status
local task_status_completed
task_status_completed=$(echo "$pr_subtask" | jq -r '.task_completed')

# if the status is completed mark the task uncompleted.
if [ "$task_status_completed" = true ]; then
local url="${asana_api_url}/tasks/${task_id}?opt_fields=gid"
local payload='{"data":{"completed":false}}'
_execute_create_or_update_asana_task_request PUT "$url" "$payload"
fi
}

# Executes an Asana request to create or update a Subtask
_execute_create_or_update_asana_task_request() {
local method="$1"
local url="$2"
local payload="$3"

local task_id
task_id="$(curl -fLSs -X "$method" "$url" \
-H "Authorization: Bearer ${ASANA_ACCESS_TOKEN}" \
-H 'accept: application/json' \
-H 'content-type: application/json' \
--data "${payload}" \
| jq -r .data.gid)"
}

main() {
local asana_task_id="$1"
local asana_assignee_id="$2"
local github_pr_url="$3"

# fetch the task subtasks
local subtasks
subtasks=$(_fetch_subtasks "$asana_task_id")

# set the parent task name
_set_parent_task_name "$subtasks" "$asana_task_id"

# check if the PR subtask already exist
local pr_subtask
pr_subtask=$(_check_pr_subtask_exist "$subtasks" "$asana_assignee_id")

# if the PR subtask exist, mark the task uncompleted if it the task is marked completed
# otherwise, create the PR subtask and assign it to the reviewer
if [[ -n "$pr_subtask" ]]; then
_mark_task_uncompleted_if_needed "$pr_subtask"
else
_create_pr_subtask "$asana_task_id" "$asana_assignee_id" "$github_pr_url"
fi
}

main "$@"
27 changes: 27 additions & 0 deletions actions/asana-get-user-id-for-github-handle/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Get Asana user ID matching GitHub handle
description: Returns Asana user ID that matches GitHub user handle
inputs:
access-token:
description: "Asana access token"
required: true
type: string
github-handle:
description: "GitHub user handle"
required: true
type: string
outputs:
user-id:
description: "Asana user ID"
value: ${{ steps.get-asana-user-id.outputs.user-id }}
runs:
using: "composite"
steps:
- id: get-asana-user-id
run: |
user_id="$(yq ."${{ inputs.github-handle }}" ${{ github.action_path }}/github-asana-user-id-mapping.yml)"
if [[ -z "$user_id" || "$user_id" == "null" ]]; then
echo "::warning::Asana User ID not found for GitHub handle: ${{ inputs.github-handle }}"
else
echo "user-id=${user_id}" >> $GITHUB_OUTPUT
fi
shell: bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
aataraxiaa: "1206488453854239"
afterxleep: "1204051006248664"
alessandroboron: "1206329551987270"
amddg44: "1201462886797771"
ayoy: "1201621708091911"
brindy: "33604954490307"
Bunn: "1201011656757987"
bwaresiak: "856498666990313"
dharb: "246491496396026"
diegoreymendez: "1203108348749442"
dus7: "1206226850447383"
federicocappelli: "1205736672764360"
GioSensation: "1144596632862977"
graeme: "1202926619863343"
jaceklyp: "1201392122268514"
jonathanKingston: "1199237043579003"
jotaemepereira: "1203972458584419"
ladamski: "1171671023737946"
loremattei: "1202204871961729"
mallexxx: "1202406491309495"
miasma13: "1200848783415037"
muodov: "1201807839780469"
quanganhdo: "1205591970852428"
SabrinaTardio: "1204024359086948"
samsymons: "1193060753332998"
shakyShane: "1201141132903155"
THISISDINOSAUR: "1187352150204278"
tomasstrba: "1148564398679340"
viktorjansson: "970019367293722"
vinay-nadig-0042: "1202096681806170"