diff --git a/README.md b/README.md index 77ec2d6..f63c0a8 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,15 @@ gitlink-cli issue +assigners --owner Gitlink --repo forgeplus # List issue authors gitlink-cli issue +authors --owner Gitlink --repo forgeplus + +# List issue priorities +gitlink-cli issue +priorities --owner Gitlink --repo forgeplus + +# List issue tags +gitlink-cli issue +tags --owner Gitlink --repo forgeplus --only-name + +# List issue statuses +gitlink-cli issue +statuses --owner Gitlink --repo forgeplus ``` ### Label Management diff --git a/README.zh-CN.md b/README.zh-CN.md index db3547b..c29899e 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -278,6 +278,15 @@ gitlink-cli issue +assigners --owner Gitlink --repo forgeplus # 列出 Issue 发布人 gitlink-cli issue +authors --owner Gitlink --repo forgeplus + +# 列出 Issue 优先级 +gitlink-cli issue +priorities --owner Gitlink --repo forgeplus + +# 列出 Issue 标签 +gitlink-cli issue +tags --owner Gitlink --repo forgeplus --only-name + +# 列出 Issue 状态 +gitlink-cli issue +statuses --owner Gitlink --repo forgeplus ``` ### 标签管理 diff --git a/shortcuts/issue/issue.go b/shortcuts/issue/issue.go index ad67c8a..ccf8737 100644 --- a/shortcuts/issue/issue.go +++ b/shortcuts/issue/issue.go @@ -268,6 +268,81 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut { return ctx.Output(env) }, }, + { + Name: "priorities", + Description: "List issue priorities", + Flags: []common.Flag{ + {Name: "keyword", Short: "k", Usage: "Search keyword"}, + }, + Run: func(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + q := url.Values{} + if keyword := ctx.Arg("keyword"); keyword != "" { + q.Set("keyword", keyword) + } + env, err := ctx.CallAPIWithQuery("GET", v1RepoPath(ctx)+"/issue_priorities", q) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, + { + Name: "tags", + Description: "List issue tags", + Flags: []common.Flag{ + {Name: "keyword", Short: "k", Usage: "Search keyword"}, + {Name: "only-name", Usage: "Only return tag names and IDs", Bool: true, Default: "false"}, + {Name: "order-by", Usage: "Order by: updated_on, created_on, issues_count"}, + {Name: "order-direction", Usage: "Order direction: asc or desc"}, + }, + Run: func(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + q := url.Values{} + if keyword := ctx.Arg("keyword"); keyword != "" { + q.Set("keyword", keyword) + } + if parseBool(ctx.Arg("only-name")) { + q.Set("only_name", "true") + } + if orderBy := ctx.Arg("order-by"); orderBy != "" { + q.Set("order_by", orderBy) + } + if orderDirection := ctx.Arg("order-direction"); orderDirection != "" { + q.Set("order_direction", orderDirection) + } + env, err := ctx.CallAPIWithQuery("GET", v1RepoPath(ctx)+"/issue_tags", q) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, + { + Name: "statuses", + Description: "List issue statuses", + Flags: []common.Flag{ + {Name: "page", Short: "p", Usage: "Page number", Default: "1"}, + {Name: "limit", Short: "l", Usage: "Items per page", Default: "20"}, + }, + Run: func(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + q := url.Values{} + q.Set("page", ctx.Arg("page")) + q.Set("limit", ctx.Arg("limit")) + env, err := ctx.CallAPIWithQuery("GET", v1RepoPath(ctx)+"/issue_statues", q) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, } } diff --git a/shortcuts/issue/issue_test.go b/shortcuts/issue/issue_test.go index c821e54..ddaf5c6 100644 --- a/shortcuts/issue/issue_test.go +++ b/shortcuts/issue/issue_test.go @@ -470,6 +470,78 @@ func TestBatchCloseWithFailedClose(t *testing.T) { } } +// --- metadata lookup shortcuts --- + +func TestIssuePrioritiesShortcut(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/issue_priorities.json" { + t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path) + } + assertEqual(t, r.URL.Query().Get("keyword"), "normal") + writeJSON(w, []map[string]interface{}{ + {"id": 2, "name": "Normal"}, + }) + })) + defer server.Close() + + err := runShortcut(t, server, "priorities", map[string]string{"keyword": "normal"}) + if err != nil { + t.Fatalf("priorities shortcut failed: %v", err) + } +} + +func TestIssueTagsShortcutWithFilters(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/issue_tags.json" { + t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path) + } + query := r.URL.Query() + assertEqual(t, query.Get("keyword"), "bug") + assertEqual(t, query.Get("only_name"), "true") + assertEqual(t, query.Get("order_by"), "issues_count") + assertEqual(t, query.Get("order_direction"), "desc") + writeJSON(w, map[string]interface{}{ + "total_count": 1, + "issue_tags": []map[string]interface{}{ + {"id": 3, "name": "bug"}, + }, + }) + })) + defer server.Close() + + err := runShortcut(t, server, "tags", map[string]string{ + "keyword": "bug", + "only-name": "true", + "order-by": "issues_count", + "order-direction": "desc", + }) + if err != nil { + t.Fatalf("tags shortcut failed: %v", err) + } +} + +func TestIssueStatusesShortcut(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/issue_statues.json" { + t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path) + } + assertEqual(t, r.URL.Query().Get("page"), "2") + assertEqual(t, r.URL.Query().Get("limit"), "10") + writeJSON(w, map[string]interface{}{ + "total_count": 1, + "statues": []map[string]interface{}{ + {"id": 1, "name": "Open"}, + }, + }) + })) + defer server.Close() + + err := runShortcut(t, server, "statuses", map[string]string{"page": "2", "limit": "10"}) + if err != nil { + t.Fatalf("statuses shortcut failed: %v", err) + } +} + // --- HTTP error paths --- func TestIssueListHTTPError(t *testing.T) { diff --git a/skills/gitlink-issue/SKILL.md b/skills/gitlink-issue/SKILL.md index 0928362..77c5955 100644 --- a/skills/gitlink-issue/SKILL.md +++ b/skills/gitlink-issue/SKILL.md @@ -29,6 +29,9 @@ metadata: | `issue +comment` | 添加评论 | 是 | | `issue +assigners` | 查询 Issue 负责人列表 | 否(公开项目) | | `issue +authors` | 查询 Issue 发布人列表 | 否(公开项目) | +| `issue +statuses` | 查询 Issue 状态列表 | 否(公开项目) | +| `issue +tags` | 查询 Issue 标签列表 | 否(公开项目) | +| `issue +priorities` | 查询 Issue 优先级列表 | 否(公开项目) | ## 使用示例 diff --git a/skills/gitlink-issue/references/gitlink-issue-priorities.md b/skills/gitlink-issue/references/gitlink-issue-priorities.md new file mode 100644 index 0000000..5a71a4e --- /dev/null +++ b/skills/gitlink-issue/references/gitlink-issue-priorities.md @@ -0,0 +1,25 @@ +# Issue Priorities + +Use `issue +priorities` to list the available Issue priority values for a repository. +This is useful before creating or updating an Issue that needs a specific `priority_id`. + +## Usage + +```bash +gitlink-cli issue +priorities --owner Gitlink --repo forgeplus +gitlink-cli issue +priorities --owner Gitlink --repo forgeplus --keyword normal +``` + +## Options + +| Option | Description | +|--------|-------------| +| `--owner` | Repository owner | +| `--repo` | Repository name | +| `--keyword`, `-k` | Optional search keyword | + +## API + +```http +GET /api/v1/{owner}/{repo}/issue_priorities.json +``` diff --git a/skills/gitlink-issue/references/gitlink-issue-statuses.md b/skills/gitlink-issue/references/gitlink-issue-statuses.md new file mode 100644 index 0000000..5f85237 --- /dev/null +++ b/skills/gitlink-issue/references/gitlink-issue-statuses.md @@ -0,0 +1,28 @@ +# Issue Statuses + +Use `issue +statuses` to list available Issue status values for a repository. +This helps users find `status_id` values before creating reports or updating Issues. + +## Usage + +```bash +gitlink-cli issue +statuses --owner Gitlink --repo forgeplus +gitlink-cli issue +statuses --owner Gitlink --repo forgeplus --page 1 --limit 50 +``` + +## Options + +| Option | Description | +|--------|-------------| +| `--owner` | Repository owner | +| `--repo` | Repository name | +| `--page`, `-p` | Page number | +| `--limit`, `-l` | Items per page | + +## API + +```http +GET /api/v1/{owner}/{repo}/issue_statues.json +``` + +The API path and response field use `statues` as documented by GitLink OpenAPI. diff --git a/skills/gitlink-issue/references/gitlink-issue-tags.md b/skills/gitlink-issue/references/gitlink-issue-tags.md new file mode 100644 index 0000000..2ed581e --- /dev/null +++ b/skills/gitlink-issue/references/gitlink-issue-tags.md @@ -0,0 +1,31 @@ +# Issue Tags + +Use `issue +tags` to list the Issue tags available in a repository. +This helps users find tag IDs before creating or updating Issues. + +## Usage + +```bash +gitlink-cli issue +tags --owner Gitlink --repo forgeplus +gitlink-cli issue +tags --owner Gitlink --repo forgeplus --only-name --keyword bug +gitlink-cli issue +tags --owner Gitlink --repo forgeplus --order-by issues_count --order-direction desc +``` + +## Options + +| Option | Description | +|--------|-------------| +| `--owner` | Repository owner | +| `--repo` | Repository name | +| `--keyword`, `-k` | Optional search keyword | +| `--only-name` | Return only tag names and IDs | +| `--order-by` | Sort field: `updated_on`, `created_on`, or `issues_count` | +| `--order-direction` | Sort direction: `asc` or `desc` | + +## API + +```http +GET /api/v1/{owner}/{repo}/issue_tags.json +``` + +Query parameters are mapped as `keyword`, `only_name`, `order_by`, and `order_direction`.