forked from Gitlink/gitlink-cli
207 lines
8.0 KiB
PowerShell
207 lines
8.0 KiB
PowerShell
# ----------------------------------------------------------------
|
||
# 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
|