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

199 lines
8.1 KiB
Bash
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.

#!/usr/bin/env bash
# ================================================================
# Scenario 1a: Real-Time Issue Triage (Linux Webhook 版)
# Flow: 接收 Issue 编号 → 拉取详情 → 分类 → 打 tags → 分配人
# ================================================================
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib/common.sh"
ISSUE_NUMBER=""
OWNER=""
REPO=""
ASSIGNEE=""
DRY_RUN=false
usage() {
echo "Usage: $0 --issue-number N [--owner OWNER] [--repo REPO] [--assignee USER] [--dry-run]"
exit 1
}
while [[ $# -gt 0 ]]; do
case "$1" in
--issue-number) ISSUE_NUMBER="$2"; shift 2 ;;
--owner) OWNER="$2"; shift 2 ;;
--repo) REPO="$2"; shift 2 ;;
--assignee) ASSIGNEE="$2"; shift 2 ;;
--dry-run) DRY_RUN="true"; shift ;;
--help|-h) usage ;;
*) log_err "Unknown arg: $1"; usage ;;
esac
done
[[ -z "$ISSUE_NUMBER" ]] && { log_err "--issue-number is required"; usage; }
check_auth
require_owner_repo
# 10 个标准中文分类标签(必须和仓库实际标签名一致)
STANDARD_LABELS="缺陷 功能 文档 任务 测试 支持 重复 疑问 协助 搁置"
# ================================================================
log_title "Issue Triage: #$ISSUE_NUMBER ($OWNER/$REPO)"
# ── Phase 1: 拉取 Issue 详情 ─────────────────────────────────────
log_step "Fetching issue #$ISSUE_NUMBER details..."
ISSUE_JSON=$(gl_check issue +view --owner "$OWNER" --repo "$REPO" --number "$ISSUE_NUMBER")
[[ -z "$ISSUE_JSON" ]] && { log_err "Failed to fetch issue #$ISSUE_NUMBER"; exit 1; }
ISSUE_TITLE=$(echo "$ISSUE_JSON" | jq -r '.data.subject // .data.title // "N/A"')
ISSUE_DESC=$(echo "$ISSUE_JSON" | jq -r '.data.description // ""' | head -c 2000)
ISSUE_AUTHOR=$(echo "$ISSUE_JSON" | jq -r '.data.author.login // .data.author.username // ""')
ISSUE_STATE=$(echo "$ISSUE_JSON" | jq -r '.data.status.name // .data.state.name // .data.state // "N/A"')
EXISTING_TAGS=$(echo "$ISSUE_JSON" | jq -r '[.data.tags[]?.name // ""] | join(", ")' 2>/dev/null || echo "")
[[ -n "$EXISTING_TAGS" ]] && log_info "Already tagged: $EXISTING_TAGS"
log_ok "Issue \"$ISSUE_TITLE\" by @$ISSUE_AUTHOR"
# ── Phase 2: 动态收集标签 ID 映射 ─────────────────────────────────
log_step "Discovering label IDs..."
ID_MAP_FILE=$(mktemp)
# 优先从项目 issue_tags API 获取
TAGS_JSON=$(gl_run api GET "/v1/$OWNER/$REPO/issue_tags" 2>/dev/null || true)
if [[ -n "$TAGS_JSON" ]]; then
echo "$TAGS_JSON" | jq -r '(.data.issue_tags // .data // [])[]? | "\(.id)|\(.name)"' 2>/dev/null >> "$ID_MAP_FILE" || true
fi
# 补充:从已有 issue 的 tags 字段收集(覆盖 issue_tags API 未返回的情况)
for state in open closed; do
SAMPLE=$(gl_run issue +list --owner "$OWNER" --repo "$REPO" --state "$state" --limit 50 2>/dev/null || true)
echo "$SAMPLE" | jq -r '(.data.issues // .data // [])[]?.tags[]? | "\(.id)|\(.name)"' 2>/dev/null >> "$ID_MAP_FILE" || true
done
declare -A TAG_ID
while IFS='|' read -r tid tname || [[ -n "$tid" ]]; do
[[ -n "$tid" && -n "$tname" ]] && TAG_ID["$tname"]="$tid"
done < "$ID_MAP_FILE"
rm -f "$ID_MAP_FILE"
FOUND=""; MISSING=""
for lbl in $STANDARD_LABELS; do
if [[ -n "${TAG_ID[$lbl]:-}" ]]; then
FOUND="$FOUND $lbl(${TAG_ID[$lbl]})"
else
MISSING="$MISSING $lbl"
fi
done
log_ok "Found tags:${FOUND:- (none)}"
[[ -n "$MISSING" ]] && log_warn "Not in repo yet:${MISSING}"
# ── Phase 3: 分类(关键词 → 中文标签)─────────────────────────────
log_step "Analyzing issue content..."
COMBINED=$(echo "$ISSUE_TITLE $ISSUE_DESC" | tr '[:upper:]' '[:lower:]')
CHOSEN_LABEL=""
CONFIDENCE=""
# 优先匹配具体意图,再匹配通用
if echo "$COMBINED" | grep -qiE 'bug|error|crash|fault|fix|错误|异常|崩溃|缺陷|修复|故障'; then
CHOSEN_LABEL="缺陷"
elif echo "$COMBINED" | grep -qiE 'feature|enhancement|add|新增|建议|功能|特性|新功能'; then
CHOSEN_LABEL="功能"
elif echo "$COMBINED" | grep -qiE 'doc|readme|guide|wiki|文档|说明|教程'; then
CHOSEN_LABEL="文档"
elif echo "$COMBINED" | grep -qiE 'test|测试|用例|覆盖'; then
CHOSEN_LABEL="测试"
elif echo "$COMBINED" | grep -qiE 'duplicate|重复|重复的|和.*重复'; then
CHOSEN_LABEL="重复"
elif echo "$COMBINED" | grep -qiE 'question|疑问|不确定|讨论|澄清'; then
CHOSEN_LABEL="疑问"
elif echo "$COMBINED" | grep -qiE 'help|协助|帮助|协作|请求帮助'; then
CHOSEN_LABEL="协助"
elif echo "$COMBINED" | grep -qiE 'postpone|wontfix|暂缓|搁置|低优|不重要|不紧急'; then
CHOSEN_LABEL="搁置"
elif echo "$COMBINED" | grep -qiE 'support|兼容|环境|依赖|支持'; then
CHOSEN_LABEL="支持"
elif echo "$COMBINED" | grep -qiE 'task|todo|任务|待办|计划'; then
CHOSEN_LABEL="任务"
elif echo "$COMBINED" | grep -qiE 'how|怎么|如何|求助|使用|用法'; then
CHOSEN_LABEL="疑问"
fi
if [[ -n "$CHOSEN_LABEL" ]]; then
CONFIDENCE="keyword"
log_ok "Classified: $CHOSEN_LABEL"
else
log_info "No matching label — skipping"
fi
# ── Phase 4: 打标签raw API PATCH tags─────────────────────────
if [[ -z "$CHOSEN_LABEL" ]]; then
log_info "No label assigned"
elif [[ "$DRY_RUN" == "true" ]]; then
log_warn "[DRY RUN] Would tag #$ISSUE_NUMBER with '$CHOSEN_LABEL'"
else
TGT_ID="${TAG_ID[$CHOSEN_LABEL]:-}"
if [[ -z "$TGT_ID" ]]; then
log_warn "Label '$CHOSEN_LABEL' 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 '$CHOSEN_LABEL' (ID:$TGT_ID)..."
TAG_RESP=$(gl_run api PATCH "/v1/$OWNER/$REPO/issues/$ISSUE_NUMBER" \
--body "{\"issue_tag_ids\":[$TGT_ID]}" 2>&1) || true
if echo "$TAG_RESP" | jq -e '.ok == true' &>/dev/null; then
log_ok "Tagged: $CHOSEN_LABEL"
else
ERR=$(echo "$TAG_RESP" | jq -r '.error.message // "unknown"' 2>/dev/null || echo "unknown")
log_warn "Tag failed: $ERR"
fi
fi
fi
# ── Phase 5: 分配责任人 ──────────────────────────────────────────
TARGET_ASSIGNEE="${ASSIGNEE:-$ISSUE_AUTHOR}"
log_step "Assigning..."
if [[ -z "$TARGET_ASSIGNEE" ]]; then
log_warn "No assignee, skipping"
elif [[ "$DRY_RUN" == "true" ]]; then
log_warn "[DRY RUN] Would assign @$TARGET_ASSIGNEE"
else
# Resolve login name to numeric user ID
ASSIGNEE_ID=""
if [[ "$TARGET_ASSIGNEE" =~ ^[0-9]+$ ]]; then
ASSIGNEE_ID="$TARGET_ASSIGNEE"
else
USER_JSON=$(gl_run api GET "/users/$TARGET_ASSIGNEE" 2>/dev/null || true)
if [[ -n "$USER_JSON" ]]; then
ASSIGNEE_ID=$(echo "$USER_JSON" | jq -r '.data.id // .data.user_id // ""' 2>/dev/null || echo "")
fi
fi
if [[ -z "$ASSIGNEE_ID" ]]; then
log_warn "Cannot resolve user ID for '$TARGET_ASSIGNEE' — skipping assign"
else
BODY="{\"assigner_ids\":[$ASSIGNEE_ID]}"
ARESP=$(gl_run api PATCH "/v1/$OWNER/$REPO/issues/$ISSUE_NUMBER" --body "$BODY" 2>&1) || true
if echo "$ARESP" | jq -e '.ok == true' &>/dev/null; then
log_ok "Assigned: @$TARGET_ASSIGNEE (ID:$ASSIGNEE_ID)"
else
ERR2=$(echo "$ARESP" | jq -r '.error.message // "unknown"' 2>/dev/null || echo "unknown")
log_warn "Assign failed: $ERR2"
fi
fi
fi
# ── Complete ──────────────────────────────────────────────────────
log_title "Triage Complete"
echo -e "${GREEN} Issue: #$ISSUE_NUMBER - $ISSUE_TITLE${NC}"
echo -e "${GREEN} Author: @$ISSUE_AUTHOR${NC}"
echo -e "${GREEN} Label: ${CHOSEN_LABEL:-}${NC}"
echo -e "${GREEN} Assignee: @$TARGET_ASSIGNEE${NC}"