issue #168: fix issue-status workflow #13
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
name: Workflow issue status | |
on: | |
workflow_call: | |
issues: | |
types: | |
- opened | |
pull_request: | |
types: | |
- opened | |
- synchronize | |
- reopened | |
- closed | |
- ready_for_review | |
- converted_to_draft | |
env: | |
OWNER: ai-cfia | |
REPO_PROJECT: github-workflows/repo_project.txt | |
jobs: | |
handle_issue_events: | |
runs-on: ubuntu-latest | |
permissions: write-all | |
steps: | |
# https://docs.github.com/en/issues/planning-and-tracking-with-projects/automating-your-project/automating-projects-using-actions | |
# GITHUB_TOKEN is scoped to the repository level and cannot access projects. | |
# To access projects you can either create a GitHub App (recommended for organization projects) | |
# or a personal access token (recommended for user projects). | |
# Workflow examples for both approaches are shown below. | |
- name: Generate token from Github application (GH app for workflows) | |
id: generate-token | |
uses: actions/create-github-app-token@v1 | |
with: | |
app-id: ${{ secrets.GH_WORKFLOW_APP_ID }} | |
private-key: ${{ secrets.GH_WORKFLOW_APP_PEM }} | |
- name: Checkout current repository | |
uses: actions/checkout@v3 | |
- name: Checkout ai-cfia/github-workflows repo (for repo_project.txt file) | |
uses: actions/checkout@v3 | |
with: | |
repository: ai-cfia/github-workflows | |
token: ${{ secrets.GITHUB_TOKEN }} | |
path: github-workflows | |
- name: Extract issue number | |
run: | | |
if [ "${{ github.event_name }}" == "issues" ] && [ "${{ github.event.action }}" == "opened" ]; then | |
ISSUE_NUMBER="${{ github.event.issue.number }}" | |
elif [ "${{ github.event_name }}" == "pull_request" ]; then | |
ISSUE_NUMBER=$(echo "${{ github.event.pull_request.head.ref }}" | grep -oE '^[0-9]+') | |
fi | |
if [ -n "$ISSUE_NUMBER" ]; then | |
echo "Issue number '$ISSUE_NUMBER'" | |
echo "ISSUE_NUMBER=$ISSUE_NUMBER" >> $GITHUB_ENV | |
else | |
echo "1. No issue found. The branch must be created from the issue page so that its name is in the correct format (e.g., 123-my-new-feature)." | |
echo "2. This pull request was generated by a bot (e.g., Renovate or Dependabot), so it is expected that there isn't an associated issue linked to the pull request branch." | |
fi | |
- name: Fetch issue project(s) information(s) | |
if: ${{ env.ISSUE_NUMBER != '' }} | |
run: | | |
ISSUE_NUMBER="${ISSUE_NUMBER}" | |
OWNER="${OWNER}" | |
REPO_NAME="${{ github.repository }}" | |
FILE="${REPO_PROJECT}" | |
declare -A PROJECT_NUMBER_ID_MAP ITEM_ID_MAP STATUS_FIELD_ID_MAP STATUS_OPTIONS_MAP | |
echo "1. Project associated with this issue number #$ISSUE_NUMBER" | |
PROJECT_ITEMS_JSON=$(gh issue view "$ISSUE_NUMBER" --json projectItems) | |
if [ "$(echo "$PROJECT_ITEMS_JSON" | jq '.projectItems | length')" -eq 0 ]; then | |
echo "The projectItems list is empty. Assigning project from '$REPO_PROJECT'" | |
PROJECT=$(grep "^${REPO_NAME}_" "$FILE" | cut -d'_' -f2) | |
if [ -n "$PROJECT" ]; then | |
gh issue edit "$ISSUE_NUMBER" --add-project "$PROJECT" | |
PROJECT_ITEMS_JSON=$(gh issue view "$ISSUE_NUMBER" --json projectItems) | |
echo "Assigned project $PROJECT to issue $ISSUE_NUMBER." | |
else | |
echo "No matching project found for repository $REPO_NAME." | |
fi | |
fi | |
PROJECT_TITLES=$(echo "$PROJECT_ITEMS_JSON" | jq -r '.projectItems[].title') | |
echo "$PROJECT_TITLES" | |
echo "2. Project information" | |
PROJECT_LIST=$(gh project list --owner "$OWNER") | |
while read -r TITLE; do | |
PROJECT_LINE=$(echo "$PROJECT_LIST" | grep -F "$TITLE") | |
if [ -z "$PROJECT_LINE" ]; then | |
echo "Project '$TITLE' not found. Skipping..." | |
continue | |
fi | |
PROJECT_NUMBER=$(echo "$PROJECT_LINE" | awk '{print $1}') | |
PROJECT_ID=$(echo "$PROJECT_LINE" | awk '{print $NF}') | |
echo "Project '$TITLE' found with # '$PROJECT_NUMBER' and ID '$PROJECT_ID'" | |
PROJECT_NUMBER_ID_MAP[$PROJECT_NUMBER]=$PROJECT_ID | |
done <<< "$PROJECT_TITLES" | |
echo "3. Item id associated to each project for a specific issue and keep the associated ID of the related-project" | |
CURRENT_REPOSITORY="${{ github.repository }}" | |
echo "3.5 Current repository: '$CURRENT_REPOSITORY'" | |
for PROJECT_NUMBER in "${!PROJECT_NUMBER_ID_MAP[@]}"; do | |
PROJECT_ID=${PROJECT_NUMBER_ID_MAP[$PROJECT_NUMBER]} | |
echo "Searching #$ISSUE_NUMBER in '$PROJECT_NUMBER'" | |
ITEMS_JSON=$(gh project item-list "$PROJECT_NUMBER" --limit 2000 --owner "$OWNER" --format json) | |
ITEM_ENTRY=$(echo "$ITEMS_JSON" | jq -r --argjson ISSUE_NUMBER "$ISSUE_NUMBER" --arg REPO "$CURRENT_REPOSITORY" \ | |
'.items[] | select(.content.number == $ISSUE_NUMBER and (.content.repository == $REPO))') | |
ITEM_ID=$(echo "$ITEM_ENTRY" | jq -r '.id') | |
if [ -z "$ITEM_ENTRY" ]; then | |
echo "Issue #$ISSUE_NUMBER with repository '$CURRENT_REPOSITORY' not found in '$PROJECT_NUMBER'. Skipping..." | |
continue | |
fi | |
echo "Item found #$ISSUE_NUMBER in '$PROJECT_NUMBER' with ID: '$ITEM_ID'" | |
ITEM_ID_MAP[$PROJECT_ID]=$ITEM_ID | |
done | |
echo "4. Find 'Status' field" | |
for PROJECT_ID in "${!ITEM_ID_MAP[@]}"; do | |
FIELDS_JSON=$(gh api graphql -f query=' | |
query { | |
node(id: "'"$PROJECT_ID"'") { | |
... on ProjectV2 { | |
fields(first: 20) { | |
nodes { | |
... on ProjectV2SingleSelectField { | |
id | |
name | |
options { | |
id | |
name | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
') | |
STATUS_FIELD_ENTRY=$(echo "$FIELDS_JSON" | jq '.data.node.fields.nodes[] | select(.name=="Status")') | |
STATUS_FIELD_ID=$(echo "$STATUS_FIELD_ENTRY" | jq -r '.id') | |
if [ -z "$STATUS_FIELD_ID" ] || [ "$STATUS_FIELD_ID" == "null" ]; then | |
echo "Status not found in '$PROJECT_ID'. Skipping..." | |
continue | |
fi | |
echo "'Status' field found for project ID '$PROJECT_ID': '$STATUS_FIELD_ID'" | |
OPTIONS_JSON=$(echo "$STATUS_FIELD_ENTRY" | jq -c '.options') | |
STATUS_FIELD_ID_MAP[$PROJECT_ID]=$STATUS_FIELD_ID | |
STATUS_OPTIONS_MAP[$PROJECT_ID]=$OPTIONS_JSON | |
done | |
echo "5. Update item card for each project" | |
EVENT_NAME="${{ github.event_name }}" | |
EVENT_ACTION="${{ github.event.action }}" | |
for PROJECT_ID in "${!ITEM_ID_MAP[@]}"; do | |
ITEM_ID=${ITEM_ID_MAP[$PROJECT_ID]} | |
STATUS_FIELD_ID=${STATUS_FIELD_ID_MAP[$PROJECT_ID]} | |
OPTIONS=${STATUS_OPTIONS_MAP[$PROJECT_ID]} | |
if [ -z "$STATUS_FIELD_ID" ]; then | |
echo "STATUS_FIELD_ID for project $PROJECT_ID not found. Skipping..." | |
continue | |
fi | |
TARGET_STATUS_NAME="" | |
case "$EVENT_NAME" in | |
"issues") | |
case "$EVENT_ACTION" in | |
"opened") | |
echo "Targeted field name: Todo" | |
TARGET_STATUS_NAME="Todo" | |
;; | |
esac | |
;; | |
"pull_request") | |
case "$EVENT_ACTION" in | |
"opened") | |
if [ "${{ github.event.pull_request.draft }}" == "true" ]; then | |
echo "Targeted field name: In Progress" | |
TARGET_STATUS_NAME="In Progress" | |
else | |
echo "Targeted field name: In Review" | |
TARGET_STATUS_NAME="In Review" | |
fi | |
;; | |
"ready_for_review") | |
echo "Targeted field name: In Review" | |
TARGET_STATUS_NAME="In Review" | |
;; | |
"converted_to_draft") | |
echo "Targeted field name: In Progress" | |
TARGET_STATUS_NAME="In Progress" | |
;; | |
"reopened") | |
echo "Targeted field name: In Progress" | |
TARGET_STATUS_NAME="In Progress" | |
;; | |
"closed") | |
if [ "${{ github.event.pull_request.merged }}" == "true" ]; then | |
echo "Targeted field name: Done" | |
TARGET_STATUS_NAME="Done" | |
else | |
echo "Targeted field name: Won't do" | |
TARGET_STATUS_NAME="Won't do" | |
fi | |
;; | |
"synchronize") | |
STATUS=$(gh pr view ${{ github.event.pull_request.number }} --json isDraft --jq '.isDraft') | |
if [ "$STATUS" == "false" ]; then | |
echo "Targeted field name: In Review" | |
TARGET_STATUS_NAME="In Review" | |
else | |
echo "Targeted field name: In Progress" | |
TARGET_STATUS_NAME="In Progress" | |
fi | |
;; | |
esac | |
;; | |
esac | |
if [ -z "$TARGET_STATUS_NAME" ]; then | |
echo "No appropriate status for event $EVENT_NAME/$EVENT_ACTION. Use case not managed." | |
exit 1 | |
fi | |
TARGET_STATUS_NAME_LOWER=$(echo "$TARGET_STATUS_NAME" | tr '[:upper:]' '[:lower:]') | |
NEW_OPTION_ID=$(echo "$OPTIONS" | jq -r --arg name "$TARGET_STATUS_NAME_LOWER" '.[] | select(.name | ascii_downcase == $name) | .id') | |
if [ -z "$NEW_OPTION_ID" ]; then | |
echo "Could not find status '$TARGET_STATUS_NAME'. Using default ID." | |
exit 1 | |
else | |
echo "Found status '$TARGET_STATUS_NAME' with ID: $NEW_OPTION_ID" | |
fi | |
echo "Updating..." | |
gh project item-edit --id "$ITEM_ID" --project-id "$PROJECT_ID" --field-id "$STATUS_FIELD_ID" --single-select-option-id "$NEW_OPTION_ID" | |
echo "Done!" | |
done | |
env: | |
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} | |
ISSUE_NUMBER: ${{ env.ISSUE_NUMBER }} | |
OWNER: ${{ env.OWNER }} |