forked from Gitlink/gitlink-cli
feat: 添加reopen/batch-reopen命令 + 修复assign使用PATCH代替PUT
This commit is contained in:
parent
1004bddc8d
commit
623e065efa
|
|
@ -12,6 +12,7 @@ import (
|
|||
)
|
||||
|
||||
const closedIssueStatusID = 5
|
||||
const openIssueStatusID = 1
|
||||
|
||||
type batchResult struct {
|
||||
Number string `json:"number" yaml:"number"`
|
||||
|
|
@ -114,6 +115,90 @@ func closeIssue(ctx *common.RuntimeContext, number string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func newBatchReopenShortcut() *common.Shortcut {
|
||||
return &common.Shortcut{
|
||||
Name: "batch-reopen",
|
||||
Description: "Reopen multiple closed issues by issue numbers or a CSV file",
|
||||
Flags: []common.Flag{
|
||||
{Name: "numbers", Short: "n", Usage: "Comma-separated issue numbers from the web URL, for example: 1,2,3"},
|
||||
{Name: "from", Usage: "Read issue numbers from a CSV file. Supports a number/issue_number/project_issues_index column or first column without header"},
|
||||
{Name: "dry-run", Usage: "Preview the issues that would be reopened without changing them", Bool: true, Default: "false"},
|
||||
},
|
||||
Run: runBatchReopen,
|
||||
}
|
||||
}
|
||||
|
||||
func runBatchReopen(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return fmt.Errorf("解析仓库信息失败: %w", err)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
|
||||
numbers, err := collectIssueNumbers(ctx.Arg("numbers"), ctx.Arg("from"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(numbers) == 0 {
|
||||
return fmt.Errorf("未提供 Issue 编号,请使用 --numbers 1,2,3 或 --from issues.csv")
|
||||
}
|
||||
|
||||
dryRun := parseBool(ctx.Arg("dry-run"))
|
||||
summary := batchSummary{
|
||||
Repository: fmt.Sprintf("%s/%s", ctx.Owner, ctx.Repo),
|
||||
DryRun: dryRun,
|
||||
Total: len(numbers),
|
||||
Results: make([]batchResult, 0, len(numbers)),
|
||||
}
|
||||
|
||||
for _, number := range numbers {
|
||||
result := batchResult{Number: number, Action: "reopen"}
|
||||
if dryRun {
|
||||
result.Status = "planned"
|
||||
summary.Succeeded++
|
||||
summary.Results = append(summary.Results, result)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := reopenIssue(ctx, number); err != nil {
|
||||
result.Status = "failed"
|
||||
result.Error = err.Error()
|
||||
summary.Failed++
|
||||
} else {
|
||||
result.Status = "reopened"
|
||||
summary.Succeeded++
|
||||
}
|
||||
summary.Results = append(summary.Results, result)
|
||||
}
|
||||
|
||||
summary.Duration = time.Since(start).String()
|
||||
|
||||
if err := ctx.OutputData(summary); err != nil {
|
||||
return err
|
||||
}
|
||||
if summary.Failed > 0 {
|
||||
return fmt.Errorf("%d / %d 个 Issue 重新打开失败", summary.Failed, summary.Total)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func reopenIssue(ctx *common.RuntimeContext, number string) error {
|
||||
current, err := fetchExistingIssue(ctx, number)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取 Issue 详情: %w", err)
|
||||
}
|
||||
|
||||
body := map[string]interface{}{
|
||||
"subject": current.Subject,
|
||||
"description": current.Description,
|
||||
"status_id": openIssueStatusID,
|
||||
}
|
||||
if _, err := ctx.CallAPI("PATCH", fmt.Sprintf("%s/issues/%s", v1RepoPath(ctx), number), body); err != nil {
|
||||
return fmt.Errorf("重新打开 Issue: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newBatchAssignShortcut() *common.Shortcut {
|
||||
return &common.Shortcut{
|
||||
Name: "batch-assign",
|
||||
|
|
@ -121,7 +206,7 @@ func newBatchAssignShortcut() *common.Shortcut {
|
|||
Flags: []common.Flag{
|
||||
{Name: "numbers", Short: "n", Usage: "Comma-separated issue numbers from the web URL, for example: 1,2,3"},
|
||||
{Name: "from", Usage: "Read issue numbers from a CSV file. Supports a number/issue_number/project_issues_index column or first column without header"},
|
||||
{Name: "user", Short: "u", Usage: "Assignee user login", Required: true},
|
||||
{Name: "user", Short: "u", Usage: "Assignee user ID (numeric)", Required: true},
|
||||
{Name: "dry-run", Usage: "Preview the issues that would be assigned without changing them", Bool: true, Default: "false"},
|
||||
},
|
||||
Run: runBatchAssign,
|
||||
|
|
@ -184,10 +269,17 @@ func runBatchAssign(ctx *common.RuntimeContext) error {
|
|||
}
|
||||
|
||||
func assignIssue(ctx *common.RuntimeContext, number, user string) error {
|
||||
current, err := fetchExistingIssue(ctx, number)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取 Issue 详情: %w", err)
|
||||
}
|
||||
|
||||
body := map[string]interface{}{
|
||||
"subject": current.Subject,
|
||||
"description": current.Description,
|
||||
"assigned_to_id": user,
|
||||
}
|
||||
if _, err := ctx.CallAPI("PUT", fmt.Sprintf("%s/issues/%s/assignees", v1RepoPath(ctx), number), body); err != nil {
|
||||
if _, err := ctx.CallAPI("PATCH", fmt.Sprintf("%s/issues/%s", v1RepoPath(ctx), number), body); err != nil {
|
||||
return fmt.Errorf("分配 Issue: %w", err)
|
||||
}
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ type existingIssue struct {
|
|||
func Shortcuts() []*common.Shortcut {
|
||||
return []*common.Shortcut{
|
||||
newBatchCloseShortcut(),
|
||||
newBatchReopenShortcut(),
|
||||
newBatchAssignShortcut(),
|
||||
newBatchLabelShortcut(),
|
||||
newBatchMilestoneShortcut(),
|
||||
|
|
@ -143,6 +144,37 @@ func Shortcuts() []*common.Shortcut {
|
|||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "reopen",
|
||||
Description: "Reopen a closed issue",
|
||||
Flags: []common.Flag{
|
||||
{Name: "number", Short: "n", Usage: "Issue number (as shown in the web URL)", Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return fmt.Errorf("解析仓库信息失败: %w", err)
|
||||
}
|
||||
number, err := ctx.RequireArg("number")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
current, err := fetchExistingIssue(ctx, number)
|
||||
if err != nil {
|
||||
return fmt.Errorf("重新打开 Issue 失败: %w", err)
|
||||
}
|
||||
|
||||
body := map[string]interface{}{
|
||||
"subject": current.Subject,
|
||||
"description": current.Description,
|
||||
"status_id": 1, // 1 = open
|
||||
}
|
||||
env, err := ctx.CallAPI("PATCH", fmt.Sprintf("%s/issues/%s", v1RepoPath(ctx), number), body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("重新打开 Issue 失败: %w", err)
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update",
|
||||
Description: "Update an issue",
|
||||
|
|
@ -201,7 +233,7 @@ func Shortcuts() []*common.Shortcut {
|
|||
Description: "Assign an issue to a user",
|
||||
Flags: []common.Flag{
|
||||
{Name: "number", Short: "n", Usage: "Issue number (as shown in the web URL)", Required: true},
|
||||
{Name: "user", Short: "u", Usage: "Assignee user login", Required: true},
|
||||
{Name: "user", Short: "u", Usage: "Assignee user ID (numeric)", Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -215,10 +247,16 @@ func Shortcuts() []*common.Shortcut {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
current, err := fetchExistingIssue(ctx, number)
|
||||
if err != nil {
|
||||
return fmt.Errorf("分配 Issue 失败: %w", err)
|
||||
}
|
||||
body := map[string]interface{}{
|
||||
"subject": current.Subject,
|
||||
"description": current.Description,
|
||||
"assigned_to_id": user,
|
||||
}
|
||||
env, err := ctx.CallAPI("PUT", fmt.Sprintf("%s/issues/%s/assignees", v1RepoPath(ctx), number), body)
|
||||
env, err := ctx.CallAPI("PATCH", fmt.Sprintf("%s/issues/%s", v1RepoPath(ctx), number), body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("分配 Issue 失败: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -188,12 +188,16 @@ const MODULES = [
|
|||
]
|
||||
},
|
||||
{
|
||||
name: "issue", title: "批量 Issue 操作",
|
||||
desc: "批量管理疑修(Issue):批量关闭、分配、设置标签和里程碑,支持逗号列表或 CSV 文件导入",
|
||||
name: "issue", title: "Issue 管理",
|
||||
desc: "管理疑修(Issue):单个关闭/重新打开,批量关闭/重新打开/分配/设置标签和里程碑,支持逗号列表或 CSV 文件导入",
|
||||
color: "#13c2c2",
|
||||
commands: [
|
||||
{ name: "close", desc: "关闭 Issue", detail: "关闭指定编号的 Issue", params: [{k:"number",l:"Issue编号",v:"1"}] },
|
||||
{ name: "reopen", desc: "重新打开 Issue", detail: "重新打开已关闭的 Issue", params: [{k:"number",l:"Issue编号",v:"1"}] },
|
||||
{ name: "assign", desc: "分配 Issue", detail: "将 Issue 分配给指定用户。user 是用户数字ID,可从成员管理卡片查看", params: [{k:"number",l:"Issue编号",v:"1"},{k:"user",l:"负责人用户ID",v:"149027"}] },
|
||||
{ name: "batch-close", desc: "批量关闭 Issue", detail: "按编号批量关闭 Issue,支持 --numbers 逗号分隔或 --from CSV 文件。开启预览模式可先查看将执行的操作而不实际执行", params: [{k:"numbers",l:"Issue编号",v:"1,2,3"},{k:"dry-run",l:"预览模式",v:"false",bool:true}] },
|
||||
{ name: "batch-assign", desc: "批量分配 Issue", detail: "将多个 Issue 批量分配给指定用户处理,用户名为 GitLink 登录名", params: [{k:"numbers",l:"Issue编号",v:"1,2,3"},{k:"user",l:"负责人用户名",v:"chroe"},{k:"dry-run",l:"预览模式",v:"false",bool:true}] },
|
||||
{ name: "batch-reopen", desc: "批量重新打开 Issue", detail: "批量重新打开已关闭的 Issue,支持 --numbers 逗号分隔或 --from CSV 文件", params: [{k:"numbers",l:"Issue编号",v:"1,2,3"},{k:"dry-run",l:"预览模式",v:"false",bool:true}] },
|
||||
{ name: "batch-assign", desc: "批量分配 Issue", detail: "将多个 Issue 批量分配给指定用户。user 是用户数字ID,可从成员管理卡片查看", params: [{k:"numbers",l:"Issue编号",v:"1,2,3"},{k:"user",l:"负责人用户ID",v:"149027"},{k:"dry-run",l:"预览模式",v:"false",bool:true}] },
|
||||
{ name: "batch-label", desc: "批量设置标签", detail: "批量为 Issue 添加或移除标签。标签ID 先用 label +list 查看", params: [{k:"numbers",l:"Issue编号",v:"1,2,3"},{k:"add",l:"添加标签ID",v:""},{k:"remove",l:"移除标签ID",v:""},{k:"dry-run",l:"预览模式",v:"false",bool:true}] },
|
||||
{ name: "batch-milestone", desc: "批量设置里程碑", detail: "批量将 Issue 关联到指定的里程碑。里程碑ID 先用 milestone +list 查看", params: [{k:"numbers",l:"Issue编号",v:"1,2,3"},{k:"milestone",l:"里程碑ID",v:"2760"},{k:"dry-run",l:"预览模式",v:"false",bool:true}] },
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in New Issue