Skip to content

Commit b809551

Browse files
authored
Add workflow to sync issue labels with project fields
This workflow synchronizes issue labels with project status fields in GitHub Projects. It updates the project item status based on the labels applied to issues.
1 parent b7de420 commit b809551

File tree

1 file changed

+124
-0
lines changed

1 file changed

+124
-0
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
name: "Issue labels → Project Status"
2+
3+
on:
4+
issues:
5+
types: [labeled, unlabeled]
6+
7+
jobs:
8+
sync:
9+
runs-on: ubuntu-latest
10+
if: ${{ github.actor != 'github-actions[bot]' }}
11+
permissions:
12+
issues: write
13+
contents: read
14+
15+
steps:
16+
- name: Update Project Status from status:* label
17+
env:
18+
GH_TOKEN: ${{ secrets.PROJECTS_TOKEN }}
19+
ORG: alkem-io
20+
PROJECT_NUMBER: "53"
21+
REPO: ${{ github.repository }}
22+
ISSUE_NUMBER: ${{ github.event.issue.number }}
23+
LABEL_NAME: ${{ github.event.label.name }}
24+
25+
ALL_STATUS_LABELS: "status:incoming,status:todo,status:ready,status:in-progress,status:review,status:done"
26+
run: |
27+
set -euo pipefail
28+
29+
# Act only on your status labels
30+
case "$LABEL_NAME" in
31+
status:incoming|status:todo|status:ready|status:in-progress|status:review|status:done) ;;
32+
*) echo "Label '$LABEL_NAME' not managed; exit"; exit 0 ;;
33+
esac
34+
35+
# If unlabeled event removed a status label, we do nothing for now (optional: clear Status)
36+
if [ "${{ github.event.action }}" = "unlabeled" ]; then
37+
echo "Status label removed; not updating project (configurable)."
38+
exit 0
39+
fi
40+
41+
# Enforce mutual exclusion on labels in this repo issue (optional but helpful)
42+
IFS=',' read -ra labels <<< "$ALL_STATUS_LABELS"
43+
for l in "${labels[@]}"; do
44+
if [ "$l" != "$LABEL_NAME" ]; then
45+
gh issue edit "$ISSUE_NUMBER" --repo "$REPO" --remove-label "$l" || true
46+
fi
47+
done
48+
49+
# Map label -> status option name
50+
status=""
51+
case "$LABEL_NAME" in
52+
status:incoming) status="Incoming" ;;
53+
status:todo) status="Todo" ;;
54+
status:ready) status="Ready" ;;
55+
status:in-progress) status="In Progress" ;;
56+
status:review) status="Review" ;;
57+
status:done) status="Done" ;;
58+
esac
59+
echo "Setting project status to '$status'"
60+
61+
# Find the project item for this issue and update the single-select field "Status".
62+
# (Starter approach: query project items for this issue content, then mutate field value.)
63+
# You'll likely want to optimize later (pagination, caching field IDs/options).
64+
query='
65+
query($org:String!, $number:Int!, $repo:String!, $issue:Int!) {
66+
organization(login:$org) {
67+
projectV2(number:$number) {
68+
id
69+
fields(first:50) {
70+
nodes {
71+
... on ProjectV2SingleSelectField {
72+
id
73+
name
74+
options { id name }
75+
}
76+
}
77+
}
78+
items(first:100) {
79+
nodes {
80+
id
81+
content {
82+
... on Issue {
83+
number
84+
repository { nameWithOwner }
85+
}
86+
}
87+
}
88+
}
89+
}
90+
}
91+
}'
92+
data="$(gh api graphql -f query="$query" -f org="$ORG" -F number="$PROJECT_NUMBER" -f repo="$REPO" -F issue="$ISSUE_NUMBER")"
93+
94+
project_id="$(echo "$data" | jq -r '.data.organization.projectV2.id')"
95+
status_field_id="$(echo "$data" | jq -r '.data.organization.projectV2.fields.nodes[] | select(.name=="Status") | .id' | head -n1)"
96+
option_id="$(echo "$data" | jq -r --arg s "$status" '.data.organization.projectV2.fields.nodes[] | select(.name=="Status") | .options[] | select(.name==$s) | .id' | head -n1)"
97+
item_id="$(echo "$data" | jq -r --arg r "$REPO" --argjson n "$ISSUE_NUMBER" '
98+
.data.organization.projectV2.items.nodes[]
99+
| select(.content.repository.nameWithOwner==$r and .content.number==$n)
100+
| .id' | head -n1)"
101+
102+
if [ -z "$item_id" ] || [ "$item_id" = "null" ]; then
103+
echo "Issue is not in Project 53; nothing to update."
104+
exit 0
105+
fi
106+
107+
if [ -z "$status_field_id" ] || [ -z "$option_id" ] || [ "$status_field_id" = "null" ] || [ "$option_id" = "null" ]; then
108+
echo "Could not resolve Status field or option id."
109+
exit 1
110+
fi
111+
112+
mutation='
113+
mutation($project:ID!, $item:ID!, $field:ID!, $option: String!) {
114+
updateProjectV2ItemFieldValue(input: {
115+
projectId: $project
116+
itemId: $item
117+
fieldId: $field
118+
value: { singleSelectOptionId: $option }
119+
}) {
120+
projectV2Item { id }
121+
}
122+
}'
123+
124+
gh api graphql -f query="$mutation" -f project="$project_id" -f item="$item_id" -f field="$status_field_id" -f option="$option_id"

0 commit comments

Comments
 (0)