Skip to content

Commit 4c95283

Browse files
committed
feat: manage sub-issues through "slash commands" in issue comments
related: #325 This new GitHub Actions workflow listens for issue comments and processes commands to add or remove sub-issues. It includes error handling and posts feedback to the issue if any errors occur during execution. Acceptable input formats (and multiple space-delimited arguments can be provided): ``` /add-sub-issue #1 /add-sub-issue 1 /add-sub-issue #1 ``` :information_source: Be mindful of underlying constraints enforced in GH regarding sub-issues: - An issue can only be a sub-issue to 0 or 1 issues - Trying to add an issue as a sub-issue when it is already assigned as a sub-issue results in error Also, in this commit, the ability to assign sub-issues is open to anyone. Restricting it to a set of users is certainly possible - but no enforcement is done (would need to build up some infrastructure to identify users with appropriate authorization). Signed-off-by: Andy Stoneberg <[email protected]>
1 parent 2c3e75e commit 4c95283

File tree

1 file changed

+165
-0
lines changed

1 file changed

+165
-0
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
name: Sub-Issue Slash Command Handler
2+
3+
on:
4+
issue_comment:
5+
types: [created]
6+
7+
permissions:
8+
issues: write
9+
10+
env:
11+
GH_TOKEN: ${{ github.token }}
12+
13+
jobs:
14+
handle-sub-issue-command:
15+
if: github.event.issue.pull_request == null # Only trigger on issues, not PRs
16+
runs-on: ubuntu-latest
17+
18+
steps:
19+
- name: Write comment body to file
20+
run: |
21+
echo "${{ github.event.comment.body }}" > comment.txt
22+
23+
- name: Parse and act on slash commands
24+
run: |
25+
set -euo pipefail
26+
27+
parse_issue_number() {
28+
input="${1:-}"
29+
30+
case "${input}" in
31+
[0-9]*)
32+
printf "%s" "${input}"
33+
;;
34+
\#*)
35+
printf "%s" "${input}" | sed -n 's/^#\([0-9]\+\)$/\1/p'
36+
;;
37+
*"/issues/"*)
38+
printf "%s" "${input}" | sed -n 's#.*/issues/\([0-9][0-9]*\)$#\1#p'
39+
;;
40+
*)
41+
echo "Error: Could not parse issue number from input: '${input}'" > error_summary.txt
42+
return 1
43+
;;
44+
esac
45+
}
46+
47+
execute_mutation() {
48+
mutation_body="${1:-}"
49+
50+
set +e
51+
response=$(gh api graphql -f query="${mutation_body}")
52+
rc=$?
53+
set -e
54+
55+
if ! [ "${rc}" = "0" ]; then
56+
jq -r '[ .errors[]?.message ] | if length > 0 then join ("\n") else empty end' <<< "${response}" > error_summary.txt
57+
exit "${rc}"
58+
fi
59+
}
60+
61+
perform_sub_issue_mutation() {
62+
action="${1:-}" # "add" or "remove"
63+
child_issue="${2:-}"
64+
65+
child_issue_number=$(parse_issue_number "${child_issue}")
66+
child_issue_node_id=$(gh api graphql -f query="
67+
query {
68+
repository(owner: \"${gh_owner}\", name: \"${gh_repo_name}\") {
69+
issue(number: ${child_issue_number}) {
70+
id
71+
}
72+
}
73+
}" | jq -r '.data.repository.issue.id')
74+
echo "child_issue_node_id: ${child_issue_node_id}"
75+
76+
mutation_field="${action}SubIssue"
77+
mutation_query="
78+
mutation {
79+
${mutation_field}(input: {
80+
issueId: \"${parent_issue_node_id}\",
81+
subIssueId: \"${child_issue_node_id}\"
82+
}) {
83+
clientMutationId
84+
issue {
85+
id
86+
title
87+
}
88+
subIssue {
89+
id
90+
title
91+
}
92+
}
93+
}"
94+
95+
execute_mutation "${mutation_query}"
96+
}
97+
98+
gh_owner="${GITHUB_REPOSITORY%/*}"
99+
gh_repo_name="${GITHUB_REPOSITORY#*/}"
100+
101+
parent_issue_number=${{ github.event.issue.number }}
102+
parent_issue_node_id=$(gh api graphql -f query="
103+
query {
104+
repository(owner: \"${gh_owner}\", name: \"${gh_repo_name}\") {
105+
issue(number: ${parent_issue_number}) {
106+
id
107+
}
108+
}
109+
}" | jq -r '.data.repository.issue.id')
110+
111+
echo "parent_issue_node_id: ${parent_issue_node_id}"
112+
113+
while IFS= read -r line; do
114+
if echo "$line" | grep -qE '^/add-sub-issue'; then
115+
args=$(echo "$line" | sed -E 's|^/add-sub-issue *||')
116+
for issue in $args; do
117+
perform_sub_issue_mutation "add" "$issue"
118+
done
119+
elif echo "$line" | grep -qE '^/remove-sub-issue'; then
120+
args=$(echo "$line" | sed -E 's|^/remove-sub-issue *||')
121+
for issue in $args; do
122+
perform_sub_issue_mutation "remove" "$issue"
123+
done
124+
fi
125+
done < comment.txt
126+
127+
- name: Post error comment if failure
128+
if: failure()
129+
env:
130+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
131+
ISSUE_NUMBER: ${{ github.event.issue.number }}
132+
REPO: ${{ github.repository }}
133+
RUN_ID: ${{ github.run_id }}
134+
COMMENT_URL: ${{ github.event.comment.html_url }}
135+
run: |
136+
error_summary=
137+
if [ -f "error_summary.txt" ]; then
138+
error_summary=$(<error_summary.txt)
139+
fi
140+
141+
{
142+
echo ":x: **GitHub Action Failed**"
143+
echo
144+
echo "The workflow encountered an error while processing [your comment](${COMMENT_URL}) to manage sub-issues."
145+
echo
146+
echo ":point_right: [View the run](https://github.com/${REPO}/actions/runs/${RUN_ID})"
147+
echo
148+
149+
if [ -n "$error_summary" ]; then
150+
echo "<details>"
151+
echo "<summary>Expand to see error summary</summary>"
152+
echo
153+
echo '```bash'
154+
echo "${error_summary}"
155+
echo '```'
156+
echo "</details>"
157+
echo
158+
fi
159+
160+
echo "Please check the logs and try again, or open a bug report if the issue persists."
161+
} > comment.md
162+
163+
gh api "repos/${REPO}/issues/${ISSUE_NUMBER}/comments" \
164+
--method POST \
165+
--raw-field body="$(<comment.md)"

0 commit comments

Comments
 (0)