Skip to content

Commit 2b92f07

Browse files
authored
Merge pull request community#160756 from community/samus-aran-patch-1
Create actions_labeller.yml (New Workflow to assign multiple labels)
2 parents e08f8b1 + 53f4ba6 commit 2b92f07

File tree

1 file changed

+303
-0
lines changed

1 file changed

+303
-0
lines changed
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
name: Auto-Label Discussions for Actions Category
2+
3+
on:
4+
discussion:
5+
types: [created]
6+
7+
jobs:
8+
label-actions-discussion:
9+
if: ${{ contains(github.event.discussion.category.name, 'Actions') }}
10+
runs-on: ubuntu-latest
11+
env:
12+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Global authentication for gh CLI
13+
steps:
14+
- name: Get discussion body html
15+
id: get_discussion_body_html
16+
env:
17+
OWNER: ${{ github.repository_owner }}
18+
REPO: ${{ github.event.repository.name }}
19+
DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
20+
run: |
21+
gh api graphql -F owner=$OWNER -F name=$REPO -F number=$DISCUSSION_NUMBER -f query='
22+
query($owner: String!, $name: String!, $number: Int!) {
23+
repository(owner: $owner, name: $name){
24+
discussion(number: $number) {
25+
bodyHTML
26+
id
27+
}
28+
}
29+
}' > discussion_data.json
30+
31+
echo 'DISCUSSION_BODY_HTML='$(jq -r '.data.repository.discussion.bodyHTML' discussion_data.json) >> $GITHUB_ENV
32+
echo 'DISCUSSION_ID='$(jq -r '.data.repository.discussion.id' discussion_data.json) >> $GITHUB_ENV
33+
34+
- run: npm install jsdom
35+
36+
37+
- name: Extract Title and Body Text
38+
id: extract_text
39+
uses: actions/github-script@v6
40+
env:
41+
DISCUSSION_BODY_HTML: ${{ env.DISCUSSION_BODY_HTML }}
42+
DISCUSSION_TITLE: ${{ github.event.discussion.title }}
43+
with:
44+
result-encoding: string
45+
script: |
46+
const jsdom = require('jsdom');
47+
const { JSDOM } = jsdom;
48+
const { DISCUSSION_BODY_HTML } = process.env;
49+
const fragment = JSDOM.fragment(DISCUSSION_BODY_HTML);
50+
let body = '';
51+
// Find all <h3> and <p> pairs
52+
const h3s = Array.from(fragment.querySelectorAll('h3'));
53+
h3s.forEach(h3 => {
54+
const heading = h3.textContent.trim();
55+
let p = h3.nextElementSibling;
56+
while (p && p.tagName !== 'P') p = p.nextElementSibling;
57+
if (!p) return;
58+
if (heading === 'Discussion Details') {
59+
body = p.textContent.trim();
60+
}
61+
});
62+
// Remove leading/trailing quotes from body
63+
body = body.replace(/^['\"]+|['\"]+$/g, '');
64+
const title = process.env.DISCUSSION_TITLE || '';
65+
core.info(`Extracted title: ${title}`);
66+
core.info(`Extracted body: ${body}`);
67+
return JSON.stringify({ title, body });
68+
69+
- name: Extract Primary and Secondary Topic Areas
70+
id: extract_topics
71+
uses: actions/github-script@v6
72+
with:
73+
result-encoding: string
74+
script: |
75+
const jsdom = require('jsdom');
76+
const { JSDOM } = jsdom;
77+
const { DISCUSSION_BODY_HTML } = process.env;
78+
const fragment = JSDOM.fragment(DISCUSSION_BODY_HTML);
79+
let primary = '';
80+
let secondary = '';
81+
// Find all <h3> and <p> pairs (form headings as h3, answers as p)
82+
const h3s = Array.from(fragment.querySelectorAll('h3'));
83+
h3s.forEach(h3 => {
84+
const heading = h3.textContent.trim();
85+
// Look for the next <p> sibling after each heading
86+
let p = h3.nextElementSibling;
87+
while (p && p.tagName !== 'P') p = p.nextElementSibling;
88+
if (!p) return;
89+
if (heading === 'Why are you starting this discussion?') {
90+
primary = p.textContent.trim();
91+
}
92+
if (heading === 'What GitHub Actions topic or product is this about?') {
93+
secondary = p.textContent.trim();
94+
}
95+
});
96+
core.info(`Extracted primary topic: ${primary}`);
97+
core.info(`Extracted secondary topic: ${secondary}`);
98+
return JSON.stringify({ primary, secondary });
99+
100+
- name: Auto-label by keyword search
101+
id: auto_label_keywords
102+
uses: actions/github-script@v6
103+
with:
104+
result-encoding: string
105+
script: |
106+
// Keyphrase to label mapping
107+
const labelMap = [
108+
{
109+
label: 'Workflow Deployment',
110+
keywords: [
111+
"deployment error",
112+
"publish artifact",
113+
"release failure",
114+
"deployment target",
115+
"github pages",
116+
"deployment issue",
117+
"release workflow",
118+
"target environment"
119+
]
120+
},
121+
{
122+
label: 'Workflow Configuration',
123+
keywords: [
124+
"yaml syntax",
125+
"job dependency",
126+
"setup error",
127+
"workflow file",
128+
"configuration issue",
129+
"matrix strategy",
130+
"define env",
131+
"secret management",
132+
"environment setup",
133+
"config job"
134+
]
135+
},
136+
{
137+
label: 'Schedule & Cron Jobs',
138+
keywords: [
139+
"cron job",
140+
"scheduled workflow",
141+
"timing issue",
142+
"delay trigger",
143+
"timezone error",
144+
"periodic run",
145+
"recurring schedule",
146+
"interval workflow",
147+
"scheduled trigger",
148+
"cron expression"
149+
]
150+
},
151+
{
152+
label: 'Metrics & Insights',
153+
keywords: [
154+
"usage metrics",
155+
"performance trend",
156+
"analytics graph",
157+
"stats dashboard",
158+
"timeseries graph",
159+
"insight report",
160+
"metric tracking",
161+
"workflow analytics",
162+
"performance metric",
163+
"statistics report"
164+
]
165+
}
166+
];
167+
const miscLabel = 'Misc';
168+
let title = '';
169+
let body = '';
170+
try {
171+
const parsed = JSON.parse(`${{ steps.extract_text.outputs.result }}`);
172+
title = parsed.title || '';
173+
body = parsed.body || '';
174+
} catch (e) {}
175+
const text = (title + ' ' + body).toLowerCase();
176+
let foundLabel = miscLabel;
177+
core.info(`Auto-label debug: text to match: '${text}'`);
178+
for (const map of labelMap) {
179+
core.info(`Auto-label debug: checking label '${map.label}' with keywords: ${map.keywords.join(', ')}`);
180+
for (const k of map.keywords) {
181+
if (text.includes(k)) {
182+
core.info(`Auto-label debug: matched keyword '${k}' for label '${map.label}'`);
183+
foundLabel = map.label;
184+
break;
185+
}
186+
}
187+
if (foundLabel !== miscLabel) break;
188+
}
189+
core.info(`Auto-label debug: selected label: '${foundLabel}'`);
190+
return foundLabel;
191+
- name: Fetch label ID for primary topic
192+
id: fetch_primary_label_id
193+
env:
194+
OWNER: ${{ github.repository_owner }}
195+
REPO: ${{ github.event.repository.name }}
196+
TOPIC: ${{ fromJson(steps.extract_topics.outputs.result).primary }}
197+
run: |
198+
echo "DEBUG: Fetching label for primary topic: $TOPIC"
199+
gh api graphql -F owner=$OWNER -F name=$REPO -F topic="$TOPIC" -f query='
200+
query($owner: String!, $name: String!, $topic: String) {
201+
repository(owner: $owner, name: $name) {
202+
labels(first: 1, query: $topic) {
203+
edges {
204+
node {
205+
id
206+
name
207+
}
208+
}
209+
}
210+
}
211+
}
212+
' > primary_label_data.json
213+
214+
PRIMARY_LABEL_ID=$(jq -r '.data.repository.labels.edges[0]?.node?.id // empty' primary_label_data.json)
215+
echo "PRIMARY_LABEL_ID=$PRIMARY_LABEL_ID" >> $GITHUB_ENV
216+
217+
- name: Fetch label ID for secondary topic
218+
id: fetch_secondary_label_id
219+
env:
220+
OWNER: ${{ github.repository_owner }}
221+
REPO: ${{ github.event.repository.name }}
222+
TOPIC: ${{ fromJson(steps.extract_topics.outputs.result).secondary }}
223+
run: |
224+
echo "DEBUG: Fetching label for secondary topic: $TOPIC"
225+
gh api graphql -F owner=$OWNER -F name=$REPO -F topic="$TOPIC" -f query='
226+
query($owner: String!, $name: String!, $topic: String) {
227+
repository(owner: $owner, name: $name) {
228+
labels(first: 1, query: $topic) {
229+
edges {
230+
node {
231+
id
232+
name
233+
}
234+
}
235+
}
236+
}
237+
}
238+
' > secondary_label_data.json
239+
240+
SECONDARY_LABEL_ID=$(jq -r '.data.repository.labels.edges[0]?.node?.id // empty' secondary_label_data.json)
241+
echo "SECONDARY_LABEL_ID=$SECONDARY_LABEL_ID" >> $GITHUB_ENV
242+
- name: Fetch label ID for auto-label
243+
id: fetch_auto_label_id
244+
env:
245+
OWNER: ${{ github.repository_owner }}
246+
REPO: ${{ github.event.repository.name }}
247+
TOPIC: ${{ steps.auto_label_keywords.outputs.result }}
248+
run: |
249+
gh api graphql -F owner=$OWNER -F name=$REPO -F topic="$TOPIC" -f query='
250+
query($owner: String!, $name: String!, $topic: String) {
251+
repository(owner: $owner, name: $name) {
252+
labels(first: 1, query: $topic) {
253+
edges {
254+
node {
255+
id
256+
name
257+
}
258+
}
259+
}
260+
}
261+
}' > auto_label_data.json
262+
263+
AUTO_LABEL_ID=$(jq -r '.data.repository.labels.edges[0]?.node?.id // empty' auto_label_data.json)
264+
echo "AUTO_LABEL_ID=$AUTO_LABEL_ID" >> $GITHUB_ENV
265+
266+
- name: Apply labels to discussion
267+
if: ${{ env.PRIMARY_LABEL_ID != '' || env.SECONDARY_LABEL_ID != '' || env.AUTO_LABEL_ID != '' }}
268+
run: |
269+
echo "DEBUG: PRIMARY_LABEL_ID=$PRIMARY_LABEL_ID"
270+
echo "DEBUG: SECONDARY_LABEL_ID=$SECONDARY_LABEL_ID"
271+
echo "DEBUG: AUTO_LABEL_ID=$AUTO_LABEL_ID"
272+
LABEL_IDS=()
273+
if [ -n "$PRIMARY_LABEL_ID" ]; then
274+
LABEL_IDS+=("$PRIMARY_LABEL_ID")
275+
fi
276+
if [ -n "$SECONDARY_LABEL_ID" ]; then
277+
LABEL_IDS+=("$SECONDARY_LABEL_ID")
278+
fi
279+
if [ -n "$AUTO_LABEL_ID" ]; then
280+
LABEL_IDS+=("$AUTO_LABEL_ID")
281+
fi
282+
283+
# Deduplicate LABEL_IDS
284+
LABEL_IDS=($(printf "%s\n" "${LABEL_IDS[@]}" | awk '!seen[$0]++'))
285+
echo "DEBUG: LABEL_IDS to apply: ${LABEL_IDS[@]}"
286+
287+
# Apply labels
288+
gh api graphql -f query='
289+
mutation($labelableId: ID!, $labelIds: [ID!]!) {
290+
addLabelsToLabelable(input: {labelableId: $labelableId, labelIds: $labelIds}) {
291+
labelable {
292+
labels(first: 10) {
293+
edges {
294+
node {
295+
id
296+
name
297+
}
298+
}
299+
}
300+
}
301+
}
302+
}
303+
' -f labelableId=$DISCUSSION_ID $(printf -- "-f labelIds[]=%s " "${LABEL_IDS[@]}")

0 commit comments

Comments
 (0)