feat(issue): add metadata lookup shortcuts

This commit is contained in:
wangyue789 2026-06-02 12:45:30 +08:00
parent e3bb4d2711
commit 34be160d96
8 changed files with 252 additions and 0 deletions

View File

@ -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

View File

@ -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
```
### 标签管理

View File

@ -266,6 +266,81 @@ func Shortcuts() []*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)
},
},
}
}

View File

@ -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) {

View File

@ -29,6 +29,9 @@ metadata:
| `issue +comment` | 添加评论 | 是 |
| `issue +assigners` | 查询 Issue 负责人列表 | 否(公开项目) |
| `issue +authors` | 查询 Issue 发布人列表 | 否(公开项目) |
| `issue +statuses` | 查询 Issue 状态列表 | 否(公开项目) |
| `issue +tags` | 查询 Issue 标签列表 | 否(公开项目) |
| `issue +priorities` | 查询 Issue 优先级列表 | 否(公开项目) |
## 使用示例

View File

@ -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
```

View File

@ -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.

View File

@ -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`.