Skip to content

issue #168: fix issue-status workflow #13

issue #168: fix issue-status workflow

issue #168: fix issue-status workflow #13

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 }}