gitlink-cli/workflows/01a-issue-triage.ps1

207 lines
8.0 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ----------------------------------------------------------------
# Scenario 1a: Real-Time Issue Triage (Webhook触发的单条Issue分类)
# Flow: Webhook触发 → 拉取Issue详情 → 动态收集标签ID → 分类 → 打tags → 分配
#
# 10 个标准中文分类: 缺陷/功能/文档/任务/测试/支持/重复/疑问/协助/搁置
# ----------------------------------------------------------------
#Requires -Version 5.1
param(
[string]$Owner = "",
[string]$Repo = "",
[string]$IssueNumber = "",
[string]$Assignee = "",
[switch]$DryRun,
[switch]$Help
)
$ErrorActionPreference = "Stop"
Import-Module "$PSScriptRoot/lib/common.psm1" -Force -WarningAction SilentlyContinue
if ($Help) {
Write-Host "Usage: powershell 01a-issue-triage.ps1 -IssueNumber N [-Owner OWNER] [-Repo REPO] [-Assignee USER] [-DryRun]"
exit 0
}
if (-not $IssueNumber) { Log-Err "IssueNumber is required (use -Help for usage)"; exit 1 }
Check-Auth
$r = Resolve-OwnerRepo $Owner $Repo
$Owner = $r.Owner; $Repo = $r.Repo
# 10 个标准中文分类标签
$STANDARD_LABELS = @("缺陷","功能","文档","任务","测试","支持","重复","疑问","协助","搁置")
# ================================================================
Log-Title "Issue Triage: #$IssueNumber ($Owner/$Repo)"
# -- Phase 1: 拉取Issue详情 --
Log-Step "Fetching issue #$IssueNumber details..."
$issueJson = Invoke-GLCheck issue,+view,--owner,$Owner,--repo,$Repo,--number,$IssueNumber
if (-not $issueJson) { Log-Err "Failed to fetch issue #$IssueNumber"; exit 1 }
$issueData = $issueJson.data
$issueId = if ($issueData.id) { $issueData.id } else { $IssueNumber }
$issueTitle = if ($issueData.subject) { $issueData.subject } elseif ($issueData.title) { $issueData.title } else { "N/A" }
$issueDesc = if ($issueData.description) { $issueData.description } else { "" }
$issueAuthor = if ($issueData.author.login) { $issueData.author.login } elseif ($issueData.author.username) { $issueData.author.username } else { "" }
$existingTags = @()
if ($issueData.tags) { $existingTags = @($issueData.tags | ForEach-Object { $_.name }) }
if ($existingTags.Count -gt 0) { Log-Info "Already tagged: $($existingTags -join ', ')" }
Log-Ok "Issue `"$issueTitle`" by @$issueAuthor"
# -- Phase 2: 动态收集标签 ID 映射 --
Log-Step "Discovering label IDs..."
$tagIdMap = @{}
# 优先从项目 issue_tags API 获取
$tagsApi = Invoke-GL api,GET,"/v1/$Owner/$Repo/issue_tags"
if ($tagsApi) {
try {
$tData = ($tagsApi | ConvertFrom-Json).data
$tagList = if ($tData.issue_tags) { @($tData.issue_tags) } elseif ($tData -is [array]) { @($tData) } else { @() }
foreach ($t in $tagList) {
if ($t.id -and $t.name) { $tagIdMap[$t.name] = $t.id }
}
} catch {}
}
# 补充:从已有 issue 的 tags 字段收集
foreach ($state in @("open","closed")) {
$sample = Invoke-GL issue,+list,--owner,$Owner,--repo,$Repo,--state,$state,--limit,50
if ($sample) {
try {
$sData = ($sample | ConvertFrom-Json).data
$issues = if ($sData.issues) { @($sData.issues) } elseif ($sData -is [array]) { @($sData) } else { @() }
foreach ($iss in $issues) {
if ($iss.tags) {
foreach ($t in $iss.tags) {
if ($t.id -and $t.name) { $tagIdMap[$t.name] = $t.id }
}
}
}
} catch {}
}
}
$found = @(); $missing = @()
foreach ($lbl in $STANDARD_LABELS) {
if ($tagIdMap.ContainsKey($lbl)) { $found += "$lbl($($tagIdMap[$lbl]))" } else { $missing += $lbl }
}
Log-Ok "Found tags: $($found -join ' ')"
if ($missing.Count -gt 0) { Log-Warn "Not in repo yet: $($missing -join ' ')" }
# -- Phase 3: 关键词分类 (标题+描述) --
Log-Step "Analyzing issue content..."
$combined = "$issueTitle $issueDesc".ToLower()
$chosenLabel = ""
if ($combined -match '(?i)bug|error|crash|fault|fix|缺陷|错误|异常|崩溃|修复|故障') {
$chosenLabel = "缺陷"
} elseif ($combined -match '(?i)feature|enhancement|add|新增|建议|功能|特性|新功能') {
$chosenLabel = "功能"
} elseif ($combined -match '(?i)doc|readme|guide|wiki|文档|说明|教程') {
$chosenLabel = "文档"
} elseif ($combined -match '(?i)test|测试|用例|覆盖') {
$chosenLabel = "测试"
} elseif ($combined -match '(?i)duplicate|重复|重复的|和.*重复') {
$chosenLabel = "重复"
} elseif ($combined -match '(?i)question|疑问|不确定|讨论|澄清') {
$chosenLabel = "疑问"
} elseif ($combined -match '(?i)help|协助|帮助|协作|请求帮助') {
$chosenLabel = "协助"
} elseif ($combined -match '(?i)postpone|wontfix|暂缓|搁置|低优|不重要|不紧急') {
$chosenLabel = "搁置"
} elseif ($combined -match '(?i)support|兼容|环境|依赖|支持') {
$chosenLabel = "支持"
} elseif ($combined -match '(?i)task|todo|任务|待办|计划') {
$chosenLabel = "任务"
}
if ($chosenLabel) {
Log-Ok "Classified: $chosenLabel"
} else {
Log-Info "No matching label — skipping"
}
# -- Phase 4: 打标签 (raw API PATCH tags) --
if (-not $chosenLabel) {
Log-Info "No label assigned"
} elseif ($DryRun) {
Log-Warn "[DRY RUN] Would tag #$IssueNumber with '$chosenLabel'"
} else {
$tgtId = $tagIdMap[$chosenLabel]
if (-not $tgtId) {
Log-Warn "Label '$chosenLabel' not found in repo tags — create it on website first"
Log-Info " https://www.gitlink.org.cn/$Owner/$Repo/settings/labels"
} else {
Log-Step "Tagging with '$chosenLabel' (ID:$tgtId)..."
# GitLink v1 API 使用 issue_tag_ids (整数ID数组),不是 tags (对象数组)
$bodyJson = "{`"issue_tag_ids`":[$tgtId]}"
$tagResult = Invoke-GL api,PATCH,"/v1/$Owner/$Repo/issues/$issueId",--body,$bodyJson
if ($tagResult) {
try {
$tagOk = (($tagResult | ConvertFrom-Json).ok -eq $true)
} catch { $tagOk = $false }
if ($tagOk) { Log-Ok "Tagged: $chosenLabel" } else { Log-Warn "Tag failed" }
} else { Log-Warn "Tag failed" }
}
}
# -- Phase 5: 分配责任人 --
$targetAssignee = if ($Assignee) { $Assignee } else { $issueAuthor }
Log-Step "Assigning..."
if (-not $targetAssignee) {
Log-Warn "No assignee, skipping"
} elseif ($DryRun) {
Log-Warn "[DRY RUN] Would assign @$targetAssignee"
} else {
# assigned_to_id 需要数字用户 ID不是登录名
# 先尝试把输入当纯数字;否则通过 /users/{login} 查询
$assigneeID = $null
$isNumeric = $false
try { [void][int]$targetAssignee; $isNumeric = $true } catch {}
if ($isNumeric) {
$assigneeID = $targetAssignee
} else {
Log-Info "Resolving user ID for '$targetAssignee'..."
$userJson = Invoke-GL api,GET,"/users/$targetAssignee"
if ($userJson) {
try {
$userData = ($userJson | ConvertFrom-Json).data
if ($userData.id) {
$assigneeID = [int]$userData.id
} elseif ($userData.user_id) {
$assigneeID = [int]$userData.user_id
}
} catch {}
}
}
if (-not $assigneeID) {
Log-Warn "Cannot resolve user ID for '$targetAssignee' — skipping assign"
} else {
$escTitle = $issueTitle -replace '"','\"'
$escDesc = $issueDesc -replace '"','\"'
$bodyJson = "{`"subject`":`"$escTitle`",`"description`":`"$escDesc`",`"assigner_ids`":[$assigneeID]}"
$result = Invoke-GL api,PATCH,"/v1/$Owner/$Repo/issues/$issueId",--body,$bodyJson
if ($result -and ((($result | ConvertFrom-Json).ok -eq $true))) {
Log-Ok "Assigned: @$targetAssignee (ID:$assigneeID)"
} else {
Log-Warn "Assign failed"
}
}
}
Log-Title "Triage Complete"
Write-Host " Issue: #$IssueNumber - $issueTitle" -ForegroundColor Green
Write-Host " Author: @$issueAuthor" -ForegroundColor Green
Write-Host " Label: $(if($chosenLabel){$chosenLabel}else{'无'})" -ForegroundColor Green
Write-Host " Assignee: @$targetAssignee" -ForegroundColor Green