Skip to content

Commit 87f653b

Browse files
sumitmsftjahnvi480
andauthored
FEAT: AI-powered issue triage with GitHub Models (#477)
### Work Item / Issue Reference <!-- IMPORTANT: Please follow the PR template guidelines below. For mssql-python maintainers: Insert your ADO Work Item ID below For external contributors: Insert Github Issue number below Only one reference is required - either GitHub issue OR ADO Work Item. --> <!-- mssql-python maintainers: ADO Work Item --> > [AB#43191](https://sqlclientdrivers.visualstudio.com/c6d89619-62de-46a0-8b46-70b92a84d85e/_workitems/edit/43191) ------------------------------------------------------------------- ### Summary Adds an automated GitHub Actions workflow that uses AI (GitHub Models - GPT-4.1) to classify and analyze new issues, then sends formatted notifications to a Teams channel. **Changes:** - Add `issue-triage.yml` workflow β€” AI-powered issue classification and code-grounded analysis triggered on new issues or via manual dispatch - Add `issue-notify.yml` workflow β€” Formats triage results into rich HTML and posts to Teams via webhook - Add `test-triage-local.js` β€” Local test script that mirrors the workflow logic for development - Update `.gitignore` β€” Exclude triage test output files **Key capabilities:** - Classifies issues into one of four categories: BUG, BREAK_FIX, FEATURE_REQUEST, or DISCUSSION - Fetches actual source files before AI analysis for code-grounded accuracy - For BUG/BREAK_FIX: provides verdict, root cause, evidence, recommended fixes, and code locations - For FEATURE_REQUEST/DISCUSSION: provides engineer guidance with technical assessment, implementation approach, effort estimate, and risks - Verdict labels: Confirmed Bug, Likely Bug, Require More Analysis, Not a Bug - Uses GitHub Models API (gpt-4.1) with GITHUB_TOKEN β€” no Azure secrets needed - Teams notifications with bold labels, structured sections, and proper HTML rendering - HTML escaping on all untrusted/user-supplied values to prevent markup injection --------- Co-authored-by: Jahnvi Thakkar <61936179+jahnvi480@users.noreply.github.com>
1 parent 590af57 commit 87f653b

4 files changed

Lines changed: 961 additions & 0 deletions

File tree

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
name: Issue Notification
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
category:
7+
required: true
8+
type: string
9+
confidence:
10+
required: true
11+
type: string
12+
severity:
13+
required: true
14+
type: string
15+
justification:
16+
required: true
17+
type: string
18+
summary_for_maintainers:
19+
required: true
20+
type: string
21+
relevant_files:
22+
required: true
23+
type: string
24+
keywords:
25+
required: true
26+
type: string
27+
code_analysis:
28+
required: false
29+
type: string
30+
default: ''
31+
engineer_guidance:
32+
required: false
33+
type: string
34+
default: ''
35+
issue_number:
36+
required: true
37+
type: string
38+
issue_title:
39+
required: true
40+
type: string
41+
issue_url:
42+
required: true
43+
type: string
44+
issue_author:
45+
required: true
46+
type: string
47+
secrets:
48+
TEAMS_WEBHOOK_URL:
49+
required: true
50+
51+
permissions:
52+
contents: read
53+
54+
jobs:
55+
send-notification:
56+
runs-on: ubuntu-latest
57+
steps:
58+
- name: Send Teams Channel notification
59+
env:
60+
INPUT_CATEGORY: ${{ inputs.category }}
61+
INPUT_SEVERITY: ${{ inputs.severity }}
62+
INPUT_CONFIDENCE: ${{ inputs.confidence }}
63+
INPUT_ISSUE_NUMBER: ${{ inputs.issue_number }}
64+
INPUT_ISSUE_TITLE: ${{ inputs.issue_title }}
65+
INPUT_ISSUE_AUTHOR: ${{ inputs.issue_author }}
66+
INPUT_ISSUE_URL: ${{ inputs.issue_url }}
67+
INPUT_KEYWORDS: ${{ inputs.keywords }}
68+
INPUT_RELEVANT_FILES: ${{ inputs.relevant_files }}
69+
INPUT_SUMMARY: ${{ inputs.summary_for_maintainers }}
70+
INPUT_CODE_ANALYSIS: ${{ inputs.code_analysis }}
71+
INPUT_ENGINEER_GUIDANCE: ${{ inputs.engineer_guidance }}
72+
INPUT_JUSTIFICATION: ${{ inputs.justification }}
73+
TEAMS_WEBHOOK_URL: ${{ secrets.TEAMS_WEBHOOK_URL }}
74+
run: |
75+
CATEGORY="$INPUT_CATEGORY"
76+
SEVERITY="$INPUT_SEVERITY"
77+
78+
# Set emoji and action based on category
79+
case "$CATEGORY" in
80+
FEATURE_REQUEST)
81+
EMOJI="πŸ’‘"
82+
CATEGORY_DISPLAY="Feature Request"
83+
ACTION="Evaluate against roadmap. If approved, create ADO work item."
84+
;;
85+
BUG)
86+
EMOJI="πŸ›"
87+
CATEGORY_DISPLAY="Bug"
88+
ACTION="Validate bug, reproduce if possible, assign to developer."
89+
;;
90+
DISCUSSION)
91+
EMOJI="πŸ’¬"
92+
CATEGORY_DISPLAY="Discussion"
93+
ACTION="Respond with guidance. Re-classify if needed."
94+
;;
95+
BREAK_FIX)
96+
EMOJI="🚨"
97+
CATEGORY_DISPLAY="Break/Fix (Regression)"
98+
ACTION="URGENT: Assign to senior dev, create P0/P1 ADO item."
99+
;;
100+
*)
101+
EMOJI="❓"
102+
CATEGORY_DISPLAY="Unknown"
103+
ACTION="Review and manually classify this issue."
104+
;;
105+
esac
106+
107+
# Parse and format code analysis from JSON into readable text
108+
CODE_ANALYSIS_RAW="$INPUT_CODE_ANALYSIS"
109+
if [ -n "$CODE_ANALYSIS_RAW" ]; then
110+
# Try to parse as JSON and extract structured fields
111+
CODE_ANALYSIS=$(echo "$CODE_ANALYSIS_RAW" | jq -r '
112+
[
113+
(if .is_bug then "<b>Verdict:</b> " + (.is_bug | @html) else empty end),
114+
(if .root_cause then "<b>Root Cause:</b> " + (.root_cause | @html) else empty end),
115+
(if .affected_components and (.affected_components | length) > 0
116+
then "<b>Affected Components:</b><br>" + ([.affected_components[] | "&nbsp;&nbsp;β€’ " + (. | @html)] | join("<br>"))
117+
else empty end),
118+
(if .evidence_and_context then "<b>Evidence &amp; Context:</b> " + (.evidence_and_context | @html) else empty end),
119+
(if .recommended_fixes and (.recommended_fixes | length) > 0
120+
then "<b>Recommended Fixes:</b><br>" + ([.recommended_fixes | to_entries[] | "&nbsp;&nbsp;" + ((.key + 1) | tostring) + ". " + (.value | @html)] | join("<br>"))
121+
else empty end),
122+
(if .code_locations and (.code_locations | length) > 0
123+
then "<b>Code Locations:</b><br>" + ([.code_locations[] | "&nbsp;&nbsp;β€’ " + (. | @html)] | join("<br>"))
124+
else empty end),
125+
(if .risk_assessment then "<b>Risk Assessment:</b> " + (.risk_assessment | @html) else empty end)
126+
] | join("<br><br>")
127+
' 2>/dev/null || echo "$CODE_ANALYSIS_RAW" | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g')
128+
else
129+
CODE_ANALYSIS="N/A β€” classification did not require code analysis."
130+
fi
131+
132+
# Parse and format engineer guidance from JSON into readable text
133+
ENGINEER_GUIDANCE_RAW="$INPUT_ENGINEER_GUIDANCE"
134+
if [ -n "$ENGINEER_GUIDANCE_RAW" ]; then
135+
ENGINEER_GUIDANCE=$(echo "$ENGINEER_GUIDANCE_RAW" | jq -r '
136+
[
137+
(if .technical_assessment then "<b>Technical Assessment:</b> " + (.technical_assessment | @html) else empty end),
138+
(if .verdict then "<b>Verdict:</b> " + (.verdict | @html) else empty end),
139+
(if .effort_estimate then "Effort Estimate: " + (.effort_estimate | @html) else empty end),
140+
(if .affected_files and (.affected_files | length) > 0
141+
then "<b>Affected Files:</b><br>" + ([.affected_files[] | "&nbsp;&nbsp;β€’ " + (. | @html)] | join("<br>"))
142+
else empty end),
143+
(if .implementation_approach then "<b>Implementation Approach:</b> " + (.implementation_approach | @html) else empty end),
144+
(if .risks_and_tradeoffs then "<b>Risks &amp; Tradeoffs:</b> " + (.risks_and_tradeoffs | @html) else empty end),
145+
(if .suggested_response then "<b>Suggested Response to User:</b><br>" + (.suggested_response | @html) else empty end),
146+
(if .related_considerations and (.related_considerations | length) > 0
147+
then "<b>Related Considerations:</b><br>" + ([.related_considerations | to_entries[] | "&nbsp;&nbsp;" + ((.key + 1) | tostring) + ". " + (.value | @html)] | join("<br>"))
148+
else empty end)
149+
] | join("<br><br>")
150+
' 2>/dev/null || echo "$ENGINEER_GUIDANCE_RAW" | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g')
151+
else
152+
ENGINEER_GUIDANCE=""
153+
fi
154+
155+
# Set severity color indicator
156+
case "$SEVERITY" in
157+
critical) SEV_INDICATOR="πŸ”΄" ;;
158+
high) SEV_INDICATOR="🟠" ;;
159+
medium) SEV_INDICATOR="🟑" ;;
160+
*) SEV_INDICATOR="🟒" ;;
161+
esac
162+
163+
# Build well-formatted HTML message using jq for proper JSON escaping
164+
jq -n \
165+
--arg emoji "$EMOJI" \
166+
--arg category_display "$CATEGORY_DISPLAY" \
167+
--arg severity "$SEVERITY" \
168+
--arg sev_indicator "$SEV_INDICATOR" \
169+
--arg confidence "$INPUT_CONFIDENCE" \
170+
--arg issue_num "$INPUT_ISSUE_NUMBER" \
171+
--arg issue_title "$INPUT_ISSUE_TITLE" \
172+
--arg issue_author "$INPUT_ISSUE_AUTHOR" \
173+
--arg issue_url "$INPUT_ISSUE_URL" \
174+
--arg keywords "$INPUT_KEYWORDS" \
175+
--arg relevant_files "$INPUT_RELEVANT_FILES" \
176+
--arg summary "$INPUT_SUMMARY" \
177+
--arg code_analysis "$CODE_ANALYSIS" \
178+
--arg engineer_guidance "$ENGINEER_GUIDANCE" \
179+
--arg justification "$INPUT_JUSTIFICATION" \
180+
--arg action "$ACTION" \
181+
--arg repo_url "https://github.com/microsoft/mssql-python" \
182+
'{
183+
"text": (
184+
"<h2>" + $emoji + " mssql-python Issue Triage</h2>" +
185+
"<p><b>" + $category_display + "</b> &nbsp;|&nbsp; " +
186+
$sev_indicator + " Severity: <b>" + $severity + "</b> &nbsp;|&nbsp; " +
187+
"Confidence: <b>" + $confidence + "%</b></p>" +
188+
"<hr>" +
189+
"<p>" +
190+
"πŸ“Œ <b>Issue:</b> <a href=\"" + $issue_url + "\">#" + $issue_num + " β€” " + ($issue_title | @html) + "</a><br>" +
191+
"πŸ‘€ <b>Author:</b> @" + ($issue_author | @html) + "<br>" +
192+
"🏷️ <b>Keywords:</b> " + ($keywords | @html) + "<br>" +
193+
"πŸ“‚ <b>Relevant Files:</b> " + ($relevant_files | @html) + "<br>" +
194+
"πŸ’¬ <b>Justification:</b> " + ($justification | @html) +
195+
"</p>" +
196+
"<hr>" +
197+
"<h3>πŸ“ Analysis</h3>" +
198+
"<p>" + ($summary | @html) + "</p>" +
199+
"<h3>πŸ” Code Analysis</h3>" +
200+
"<p>" + $code_analysis + "</p>" +
201+
(if $engineer_guidance != "" then
202+
"<h3>πŸ’‘ Engineer Guidance</h3>" +
203+
"<p>" + $engineer_guidance + "</p>"
204+
else "" end) +
205+
"<hr>" +
206+
"<p>⚑ <b>Action Required:</b> " + $action + "</p>" +
207+
"<p><i>⚠️ AI-generated analysis β€” verified against source code but may contain inaccuracies. Review before acting.</i></p>" +
208+
"<p><a href=\"" + $issue_url + "\">πŸ“‹ View Issue</a>" +
209+
" &nbsp;|&nbsp; " +
210+
"<a href=\"" + $repo_url + "\">πŸ“‚ View Repository</a></p>"
211+
)
212+
}' > /tmp/teams_payload.json
213+
214+
echo "Sending notification to Teams Channel..."
215+
216+
HTTP_STATUS=$(curl -s -o /tmp/teams_response.txt -w "%{http_code}" \
217+
-H "Content-Type: application/json" \
218+
-d @/tmp/teams_payload.json \
219+
"$TEAMS_WEBHOOK_URL")
220+
221+
echo "Teams API response: $HTTP_STATUS"
222+
cat /tmp/teams_response.txt
223+
224+
if [ "$HTTP_STATUS" -lt 200 ] || [ "$HTTP_STATUS" -ge 300 ]; then
225+
echo "::error::Failed to send Teams notification. HTTP status: $HTTP_STATUS"
226+
exit 1
227+
fi
228+
229+
echo "βœ… Teams Channel notification sent successfully"

0 commit comments

Comments
Β (0)