feat(dataset): add dataset query shortcuts

Add a `dataset` shortcut group for querying GitLink research datasets,
which previously had no shortcut coverage:

- dataset +list --ids <ids>  -> GET /v1/project_datasets
- dataset +view              -> GET /v1/project_datasets (project ID
                                 resolved from --owner/--repo)

Only the platform-wide query endpoint is available on production
gitlink.org.cn; the documented per-repo dataset CRUD routes return 404
there (verified against the API), so this group wraps the query endpoint
for both listing and per-repo viewing.

Includes unit tests, bilingual (en-US/zh-CN) i18n help text, README
updates, and a change note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
luwanzhou 2026-06-14 11:48:22 +08:00
parent 52b7093846
commit 7f731eb04f
9 changed files with 423 additions and 17 deletions

View File

@ -107,6 +107,7 @@ The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans
| ⚙️ Pipeline | Run, inspect, enable, disable, delete pipeline workflows and logs |
| 🔔 Webhook | Manage repo webhooks and test deliveries |
| 🔍 Search | Search repositories, users |
| 📊 Dataset | Query research datasets by project |
| 👤 User | View user profiles and info |
| 📋 PM | Sprint management, kanban boards, weekly reports |
| 🤖 Workflow | AI-powered issue triage, PR review, release notes |
@ -576,6 +577,22 @@ Safety:
- `workflow +pr-summary` does not comment, approve, reject, or merge pull requests.
- `workflow +repo-report` aggregates health, issue triage, and PR review summary signals without remote writes.
### Dataset
`dataset` queries GitLink research datasets (title, description, paper content,
license, owning project).
```bash
# List datasets for one or more projects (by numeric project ID)
gitlink-cli dataset +list --ids 5988
# View a repository's dataset (project ID resolved from --owner/--repo)
gitlink-cli dataset +view --owner Gitlink --repo forgeplus
```
> Note: only the platform-wide dataset query endpoint is available on production
> gitlink.org.cn; the per-repo dataset CRUD routes are not yet deployed there.
### Raw API
For endpoints not covered by shortcuts, use the Raw API directly:

View File

@ -106,6 +106,7 @@
| 🔧 CI | 查看构建、日志、CI/CD 操作 |
| ⚙️ Pipeline | 运行、查看、启停、删除流水线工作流并查询日志 |
| 🔍 搜索 | 搜索仓库、用户 |
| 📊 数据集 | 按项目查询科研数据集 |
| 👤 用户 | 查看用户资料和信息 |
| 📋 项目管理 | Sprint 管理、看板、周报 |
| 🤖 工作流 | AI 驱动的 Issue 分类、PR Review、Release Notes |
@ -455,6 +456,20 @@ gitlink-cli search +repos -k "machine learning"
gitlink-cli search +users -k "zhangsan"
```
### 数据集
`dataset` 查询 GitLink 科研数据集(标题、描述、论文内容、许可证、所属项目)。
```bash
# 按数字项目 ID 列出一个或多个项目的数据集
gitlink-cli dataset +list --ids 5988
# 查看仓库的数据集project_id 从 --owner/--repo 解析)
gitlink-cli dataset +view --owner Gitlink --repo forgeplus
```
> 注意:生产环境 gitlink.org.cn 仅提供平台级数据集查询端点;按仓库的数据集增删改查路由尚未在生产部署。
### Raw API
Shortcuts 未覆盖的接口可通过 Raw API 直接调用:

View File

@ -0,0 +1,55 @@
# Dataset Shortcuts
## Summary
Adds a new `dataset` shortcut group for querying GitLink research datasets,
which previously had no shortcut coverage. Datasets carry research-oriented
metadata (title, description, `paper_content`, license, owning project) that is
valuable for research/scientometric scenarios.
## Commands
| Command | Purpose | Endpoint |
|---------|---------|----------|
| `gitlink-cli dataset +list --ids <ids>` | List datasets for one or more projects | `GET /v1/project_datasets` |
| `gitlink-cli dataset +view` | View a repository's dataset | `GET /v1/project_datasets` (project ID resolved from `--owner/--repo`) |
## Behaviour
- `dataset +list --ids 1,2,3` queries datasets by comma-separated numeric
project IDs. IDs are validated client-side before the request.
- `dataset +view --owner X --repo Y` resolves the repository's numeric project
ID from the repository info endpoint, then queries the dataset for that
project. Pass `--project-id` to skip resolution.
## Scope note (verified against production)
The dataset CRUD routes documented under `/api/v1/{owner}/{repo}/dataset`
(`POST`/`PUT`/`GET`) are **not deployed on the production `gitlink.org.cn`
host** — they return `404 您访问的页面不存在` even for a repository's owner. Only
the platform-wide query endpoint `GET /api/v1/project_datasets` is available in
production, so this group wraps that endpoint for both listing and per-repo
viewing. Create/update/attachment-delete can be added once the corresponding
routes are live on production.
## Tests
Unit tests cover `--ids` normalization (whitespace, ordering) and validation,
the missing/invalid `--ids` guards, project ID auto-resolution from repo info
for `+view`, explicit/invalid `--project-id`, and HTTP error handling.
## 中文说明
### 变更内容
- 新增 `dataset` 命令组:
- `dataset +list --ids 1,2,3` 按数字项目 ID 查询数据集
- `dataset +view --owner X --repo Y` 自动解析仓库 project_id 后查询该仓库数据集(可用 `--project-id` 跳过解析)
- 数据集含 `paper_content`、license、所属项目等科研相关元数据服务科研数据发现场景。
### 范围说明(已对生产环境验证)
文档中 `/api/v1/{owner}/{repo}/dataset` 的增删改查路由在生产 `gitlink.org.cn`
**未部署**(即使对仓库 owner 也返回 `404 页面不存在`)。生产可用的只有平台级查询
端点 `GET /api/v1/project_datasets`,故本命令组基于该端点实现列表与按仓库查看。待
对应路由在生产上线后,可补充创建/更新/附件删除。

View File

@ -21,6 +21,11 @@
"cmd.config.list.short": "List all configuration values",
"cmd.config.set.short": "Set a configuration value",
"cmd.config.short": "Manage gitlink-cli configuration",
"cmd.dataset.list.long": "List datasets for one or more GitLink projects by their numeric project IDs.",
"cmd.dataset.list.short": "List datasets by project IDs",
"cmd.dataset.short": "Dataset operations",
"cmd.dataset.view.long": "View the dataset of a repository. The project ID is resolved from --owner/--repo, or pass --project-id.",
"cmd.dataset.view.short": "View a repository's dataset",
"cmd.doctor.long": "Run local diagnostics for gitlink-cli configuration, authentication, repository context and API connectivity.",
"cmd.doctor.short": "Diagnose gitlink-cli environment problems",
"cmd.issue.batch_close.long": "Close filtered issues in bulk.\n\nThis command defaults to dry-run mode and only prints matching issues.\nPass --yes to execute remote close operations. Use restrictive filters and a small limit.\n\nExamples:\n gitlink-cli issue +batch-close --owner Gitlink --repo gitlink-cli --older-than-days 60 --limit 20\n gitlink-cli issue +batch-close --owner Gitlink --repo gitlink-cli --older-than-days 60 --limit 20 --yes",
@ -90,13 +95,13 @@
"error.config.save_failed": "failed to save config: {message}",
"error.missing_required_flag": "required flag --{name} is missing",
"error.unsupported_language": "unsupported language: {lang}",
"flag.api.body": "Request body (JSON string)",
"flag.api.body_file": "Read request body JSON from a file",
"flag.api.body_stdin": "Read request body JSON from stdin",
"flag.api.batch_continue_on_error": "Continue running remaining batch requests after a failure",
"flag.api.batch_dry_run": "Preview batch requests without sending remote requests",
"flag.api.batch_file": "Read an API batch plan from a JSON file",
"flag.api.batch_var": "Override a batch template variable (key=value, repeatable)",
"flag.api.body": "Request body (JSON string)",
"flag.api.body_file": "Read request body JSON from a file",
"flag.api.body_stdin": "Read request body JSON from stdin",
"flag.api.header": "Additional headers (key:value)",
"flag.api.query": "Query parameters (key=val&key2=val2)",
"flag.auth.token": "Login by pasting an existing token",
@ -106,6 +111,8 @@
"flag.ci.stage": "Stage number",
"flag.ci.step": "Step number",
"flag.comment.body": "Comment body",
"flag.dataset.ids": "Comma-separated project IDs to query datasets for",
"flag.dataset.project_id": "GitLink project ID. If omitted, it is resolved from --owner/--repo.",
"flag.debug": "Enable debug output",
"flag.description": "Description",
"flag.doctor.skip_network": "Skip authenticated API connectivity checks",
@ -113,6 +120,8 @@
"flag.format": "Output format: json, table, yaml (default: table)",
"flag.issue.add_label": "Label to add to each matching issue",
"flag.issue.assignee": "Assignee login",
"flag.issue.assignee_id": "Assignee user ID",
"flag.issue.author_id": "Author user ID",
"flag.issue.batch.reason": "Optional reason shown in the batch result",
"flag.issue.batch.yes": "Execute remote operations. Without this flag the command is dry-run only.",
"flag.issue.batch_close.older_than_days": "Required safety filter; must be at least 7",
@ -120,8 +129,6 @@
"flag.issue.batch_label.state": "Filter by issue state",
"flag.issue.batch_list.limit": "Maximum issues to return, capped at 100",
"flag.issue.batch_process.limit": "Maximum issues to process, capped at 100",
"flag.issue.assignee_id": "Assignee user ID",
"flag.issue.author_id": "Author user ID",
"flag.issue.body": "Issue description",
"flag.issue.label": "Label ID",
"flag.issue.label_filter": "Filter by existing label",
@ -149,14 +156,14 @@
"flag.pr.file": "Filter diff by file path",
"flag.pr.head": "Source branch",
"flag.pr.id": "PR number",
"flag.pr.milestone_id": "Milestone ID",
"flag.pr.merge_method": "Merge method: merge, rebase, squash",
"flag.pr.milestone_id": "Milestone ID",
"flag.pr.priority_id": "Priority ID",
"flag.pr.review_commit": "Commit SHA to attach the review to",
"flag.pr.review_content": "Review content",
"flag.pr.reviewer_id": "Reviewer user ID",
"flag.pr.review_status": "Review status: common, approved, rejected",
"flag.pr.review_status_filter": "Filter review status: common, approved, rejected",
"flag.pr.reviewer_id": "Reviewer user ID",
"flag.pr.state": "Filter: open, merged, closed",
"flag.pr.tag_id": "Issue tag ID",
"flag.pr.title": "PR title",
@ -194,7 +201,6 @@
"output.auth.login_hint": " Run: gitlink-cli auth login",
"output.config.file": "Config file: {path}",
"output.config.not_set": "(not set)",
"output.version": "gitlink-cli {version}",
"output.doctor.api_auth.config_skipped": "API authentication check skipped because the configuration file is invalid.",
"output.doctor.api_auth.failed": "Authenticated API request failed: {message}",
"output.doctor.api_auth.no_login": "Authenticated API response did not include a login field.",
@ -217,6 +223,7 @@
"output.doctor.suggestion.check_token": "Check whether the stored token is valid, or run gitlink-cli auth login again.",
"output.doctor.suggestion.fix_config_yaml": "Fix the YAML syntax in the gitlink-cli config file.",
"output.doctor.suggestion.pass_owner_repo": "Run the command with --owner and --repo when not inside a GitLink repository.",
"output.version": "gitlink-cli {version}",
"prompt.auth.password": "Password: ",
"prompt.auth.token": "Paste your access token: ",
"prompt.auth.username": "Username/Email/Phone: ",

View File

@ -21,6 +21,11 @@
"cmd.config.list.short": "列出所有配置项",
"cmd.config.set.short": "设置配置项",
"cmd.config.short": "管理 gitlink-cli 配置",
"cmd.dataset.list.long": "按数字项目 ID 列出一个或多个 GitLink 项目的数据集。",
"cmd.dataset.list.short": "按项目 ID 列出数据集",
"cmd.dataset.short": "数据集操作",
"cmd.dataset.view.long": "查看仓库的数据集。项目 ID 从 --owner/--repo 解析,也可用 --project-id 指定。",
"cmd.dataset.view.short": "查看仓库数据集",
"cmd.doctor.long": "诊断 gitlink-cli 的配置、认证、仓库上下文和 API 连通性问题。",
"cmd.doctor.short": "诊断 gitlink-cli 环境问题",
"cmd.issue.batch_close.long": "批量关闭筛选后的议题。\n\n该命令默认处于 dry-run 模式,只打印匹配的议题。\n传入 --yes 后执行远端关闭操作。请使用严格筛选条件和较小 limit。\n\n示例\n gitlink-cli issue +batch-close --owner Gitlink --repo gitlink-cli --older-than-days 60 --limit 20\n gitlink-cli issue +batch-close --owner Gitlink --repo gitlink-cli --older-than-days 60 --limit 20 --yes",
@ -90,13 +95,13 @@
"error.config.save_failed": "保存配置失败:{message}",
"error.missing_required_flag": "缺少必需参数 --{name}",
"error.unsupported_language": "不支持的语言:{lang}",
"flag.api.body": "请求体JSON 字符串)",
"flag.api.body_file": "从文件读取 JSON 请求体",
"flag.api.body_stdin": "从标准输入读取 JSON 请求体",
"flag.api.batch_continue_on_error": "批处理请求失败后继续执行后续请求",
"flag.api.batch_dry_run": "预览批处理请求,不发送远端请求",
"flag.api.batch_file": "从 JSON 文件读取 API 批处理计划",
"flag.api.batch_var": "覆盖批处理模板变量key=value可重复",
"flag.api.body": "请求体JSON 字符串)",
"flag.api.body_file": "从文件读取 JSON 请求体",
"flag.api.body_stdin": "从标准输入读取 JSON 请求体",
"flag.api.header": "附加请求头key:value",
"flag.api.query": "查询参数key=val&key2=val2",
"flag.auth.token": "通过粘贴已有 Token 登录",
@ -106,6 +111,8 @@
"flag.ci.stage": "阶段编号",
"flag.ci.step": "步骤编号",
"flag.comment.body": "评论内容",
"flag.dataset.ids": "用于查询数据集的项目 ID逗号分隔",
"flag.dataset.project_id": "GitLink 项目 ID。省略时从 --owner/--repo 解析。",
"flag.debug": "启用调试输出",
"flag.description": "描述",
"flag.doctor.skip_network": "跳过需要访问 GitLink 的认证连通性检查",
@ -113,6 +120,8 @@
"flag.format": "输出格式json、table、yaml默认table",
"flag.issue.add_label": "要添加到每个匹配议题的标签",
"flag.issue.assignee": "负责人登录名",
"flag.issue.assignee_id": "负责人用户 ID",
"flag.issue.author_id": "作者用户 ID",
"flag.issue.batch.reason": "批量结果中显示的可选原因",
"flag.issue.batch.yes": "执行远端操作。未传入该参数时仅 dry-run。",
"flag.issue.batch_close.older_than_days": "必需的安全筛选条件;至少为 7",
@ -120,8 +129,6 @@
"flag.issue.batch_label.state": "按议题状态筛选",
"flag.issue.batch_list.limit": "最多返回的议题数,上限 100",
"flag.issue.batch_process.limit": "最多处理的议题数,上限 100",
"flag.issue.assignee_id": "负责人用户 ID",
"flag.issue.author_id": "作者用户 ID",
"flag.issue.body": "议题描述",
"flag.issue.label": "标签 ID",
"flag.issue.label_filter": "按已有标签筛选",
@ -149,14 +156,14 @@
"flag.pr.file": "按文件路径筛选 diff",
"flag.pr.head": "源分支",
"flag.pr.id": "PR 编号",
"flag.pr.milestone_id": "里程碑 ID",
"flag.pr.merge_method": "合并方式merge、rebase、squash",
"flag.pr.milestone_id": "里程碑 ID",
"flag.pr.priority_id": "优先级 ID",
"flag.pr.review_commit": "关联评审的 Commit SHA",
"flag.pr.review_content": "评审内容",
"flag.pr.reviewer_id": "评审人用户 ID",
"flag.pr.review_status": "评审状态common、approved、rejected",
"flag.pr.review_status_filter": "按评审状态筛选common、approved、rejected",
"flag.pr.reviewer_id": "评审人用户 ID",
"flag.pr.state": "筛选open、merged、closed",
"flag.pr.tag_id": "议题标签 ID",
"flag.pr.title": "PR 标题",
@ -194,7 +201,6 @@
"output.auth.login_hint": " 运行gitlink-cli auth login",
"output.config.file": "配置文件:{path}",
"output.config.not_set": "(未设置)",
"output.version": "gitlink-cli {version}",
"output.doctor.api_auth.config_skipped": "配置文件无效,已跳过 API 认证检查。",
"output.doctor.api_auth.failed": "认证 API 请求失败:{message}",
"output.doctor.api_auth.no_login": "认证 API 响应中缺少 login 字段。",
@ -217,6 +223,7 @@
"output.doctor.suggestion.check_token": "检查已保存的 Token 是否有效,或重新运行 gitlink-cli auth login。",
"output.doctor.suggestion.fix_config_yaml": "修复 gitlink-cli 配置文件中的 YAML 语法。",
"output.doctor.suggestion.pass_owner_repo": "不在 GitLink 仓库目录内时,请通过 --owner 和 --repo 指定仓库。",
"output.version": "gitlink-cli {version}",
"prompt.auth.password": "密码:",
"prompt.auth.token": "粘贴你的访问 Token",
"prompt.auth.username": "用户名/邮箱/手机号:",

View File

@ -0,0 +1,152 @@
// Package dataset implements shortcuts for querying GitLink research datasets.
//
// GitLink exposes dataset metadata (title, description, paper_content, license,
// owning project) through the platform-wide query endpoint
// GET /api/v1/project_datasets. The per-repository dataset CRUD routes
// documented under /api/v1/{owner}/{repo}/dataset are not deployed on the
// production gitlink.org.cn host, so this package wraps the query endpoint and
// resolves a repository's project ID to offer both a list and a per-repo view.
package dataset
import (
"fmt"
"net/url"
"strconv"
"strings"
"github.com/gitlink-org/gitlink-cli/internal/i18n"
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
)
// Shortcuts returns dataset query shortcuts.
func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
tr := shortcutTranslator(translators...)
return []*common.Shortcut{
{
Name: "list",
Description: tr.T("cmd.dataset.list.short"),
Long: tr.T("cmd.dataset.list.long"),
Flags: []common.Flag{
{Name: "ids", Usage: tr.T("flag.dataset.ids"), Required: true},
},
Run: func(ctx *common.RuntimeContext) error {
ids, err := ctx.RequireArg("ids")
if err != nil {
return err
}
normalized, err := normalizeIDs(ids)
if err != nil {
return err
}
return queryDatasets(ctx, normalized)
},
},
{
Name: "view",
Description: tr.T("cmd.dataset.view.short"),
Long: tr.T("cmd.dataset.view.long"),
Flags: []common.Flag{
{Name: "project-id", Usage: tr.T("flag.dataset.project_id")},
},
Run: func(ctx *common.RuntimeContext) error {
projectID := strings.TrimSpace(ctx.Arg("project-id"))
if projectID == "" {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
resolved, err := resolveProjectID(ctx)
if err != nil {
return err
}
projectID = strconv.FormatInt(resolved, 10)
} else if _, err := strconv.ParseInt(projectID, 10, 64); err != nil {
return fmt.Errorf("invalid --project-id %q: use a numeric project ID", projectID)
}
return queryDatasets(ctx, projectID)
},
},
}
}
// queryDatasets calls the platform dataset query endpoint with a comma-separated
// list of project IDs.
func queryDatasets(ctx *common.RuntimeContext, ids string) error {
q := url.Values{}
q.Set("ids", ids)
env, err := ctx.CallAPIWithQuery("GET", "/v1/project_datasets", q)
if err != nil {
return err
}
return ctx.Output(env)
}
// normalizeIDs validates a comma-separated list of positive integer project IDs
// and returns it without surrounding whitespace.
func normalizeIDs(raw string) (string, error) {
parts := strings.Split(raw, ",")
cleaned := make([]string, 0, len(parts))
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
n, err := strconv.ParseInt(part, 10, 64)
if err != nil || n <= 0 {
return "", fmt.Errorf("invalid --ids %q: use comma-separated positive project IDs", raw)
}
cleaned = append(cleaned, strconv.FormatInt(n, 10))
}
if len(cleaned) == 0 {
return "", fmt.Errorf("invalid --ids %q: provide at least one project ID", raw)
}
return strings.Join(cleaned, ","), nil
}
// resolveProjectID resolves the numeric GitLink project ID from the current
// owner/repo via the repository info endpoint.
func resolveProjectID(ctx *common.RuntimeContext) (int64, error) {
env, err := ctx.CallAPI("GET", ctx.RepoPath(), nil)
if err != nil {
return 0, fmt.Errorf("resolve project id: %w", err)
}
data, ok := env.Data.(map[string]interface{})
if !ok {
return 0, fmt.Errorf("resolve project id: unexpected repository response")
}
for _, key := range []string{"id", "project_id"} {
if id, ok := projectIDValue(data[key]); ok {
return id, nil
}
}
return 0, fmt.Errorf("resolve project id: repository response did not include id")
}
func projectIDValue(value interface{}) (int64, bool) {
switch v := value.(type) {
case float64:
if v > 0 {
return int64(v), true
}
case int:
if v > 0 {
return int64(v), true
}
case int64:
if v > 0 {
return v, true
}
case string:
if n, err := strconv.ParseInt(strings.TrimSpace(v), 10, 64); err == nil && n > 0 {
return n, true
}
}
return 0, false
}
func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
if len(translators) > 0 && translators[0] != nil {
return translators[0]
}
return i18n.Default()
}

View File

@ -0,0 +1,150 @@
package dataset
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gitlink-org/gitlink-cli/internal/client"
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
)
func runShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error {
t.Helper()
shortcut := findShortcut(t, name)
ctx := &common.RuntimeContext{
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
Owner: "alice",
Repo: "demo",
Format: "json",
Args: args,
}
return shortcut.Run(ctx)
}
func findShortcut(t *testing.T, name string) *common.Shortcut {
t.Helper()
for _, s := range Shortcuts() {
if s.Name == name {
return s
}
}
t.Fatalf("shortcut %q not found", name)
return nil
}
func writeJSON(w http.ResponseWriter, v interface{}) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(v)
}
// --- list ---
func TestDatasetListNormalizesIDs(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/project_datasets.json" {
t.Fatalf("unexpected path: %s", r.URL.Path)
}
if got := r.URL.Query().Get("ids"); got != "1,2,3" {
t.Fatalf("ids = %q, want 1,2,3", got)
}
writeJSON(w, map[string]interface{}{"total_count": float64(0), "project_datasets": []interface{}{}})
}))
defer server.Close()
if err := runShortcut(t, server, "list", map[string]string{"ids": " 1, 2 ,3 "}); err != nil {
t.Fatalf("list failed: %v", err)
}
}
func TestDatasetListMissingIDs(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Fatal("no API call expected when ids missing")
}))
defer server.Close()
if err := runShortcut(t, server, "list", map[string]string{}); err == nil {
t.Fatal("expected error for missing --ids")
}
}
func TestDatasetListInvalidIDs(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Fatal("no API call expected for invalid ids")
}))
defer server.Close()
if err := runShortcut(t, server, "list", map[string]string{"ids": "abc"}); err == nil {
t.Fatal("expected error for non-numeric --ids")
}
}
// --- view ---
func TestDatasetViewResolvesProjectID(t *testing.T) {
var sawRepo, sawQuery bool
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/alice/demo.json":
sawRepo = true
writeJSON(w, map[string]interface{}{"id": float64(5988)})
case "/v1/project_datasets.json":
sawQuery = true
if got := r.URL.Query().Get("ids"); got != "5988" {
t.Fatalf("ids = %q, want 5988", got)
}
writeJSON(w, map[string]interface{}{"total_count": float64(1), "project_datasets": []interface{}{}})
default:
t.Fatalf("unexpected path: %s", r.URL.Path)
}
}))
defer server.Close()
if err := runShortcut(t, server, "view", map[string]string{}); err != nil {
t.Fatalf("view failed: %v", err)
}
if !sawRepo || !sawQuery {
t.Fatalf("expected repo+query calls, got repo=%v query=%v", sawRepo, sawQuery)
}
}
func TestDatasetViewExplicitProjectID(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/project_datasets.json" {
t.Fatalf("unexpected path: %s", r.URL.Path)
}
if got := r.URL.Query().Get("ids"); got != "42" {
t.Fatalf("ids = %q, want 42", got)
}
writeJSON(w, map[string]interface{}{"total_count": float64(0), "project_datasets": []interface{}{}})
}))
defer server.Close()
if err := runShortcut(t, server, "view", map[string]string{"project-id": "42"}); err != nil {
t.Fatalf("view failed: %v", err)
}
}
func TestDatasetViewInvalidProjectID(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Fatal("no API call expected for invalid project id")
}))
defer server.Close()
if err := runShortcut(t, server, "view", map[string]string{"project-id": "x"}); err == nil {
t.Fatal("expected error for invalid --project-id")
}
}
func TestDatasetViewHTTPError(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("server error"))
}))
defer server.Close()
if err := runShortcut(t, server, "view", map[string]string{"project-id": "42"}); err == nil {
t.Fatal("expected error for HTTP 500")
}
}

View File

@ -8,6 +8,7 @@ import (
"github.com/gitlink-org/gitlink-cli/shortcuts/ci"
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
"github.com/gitlink-org/gitlink-cli/shortcuts/compare"
"github.com/gitlink-org/gitlink-cli/shortcuts/dataset"
"github.com/gitlink-org/gitlink-cli/shortcuts/health"
"github.com/gitlink-org/gitlink-cli/shortcuts/issue"
"github.com/gitlink-org/gitlink-cli/shortcuts/label"
@ -47,6 +48,7 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) {
"search": search.Shortcuts(tr),
"ci": ci.Shortcuts(tr),
"compare": compare.Shortcuts(),
"dataset": dataset.Shortcuts(tr),
"webhook": webhook.Shortcuts(tr),
"health": health.Shortcuts(tr),
"workflow": workflow.Shortcuts(),
@ -68,6 +70,7 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) {
"search": tr.T("cmd.search.short"),
"ci": tr.T("cmd.ci.short"),
"compare": "Compare branches, tags, or commits",
"dataset": tr.T("cmd.dataset.short"),
"webhook": tr.T("cmd.webhook.short"),
"health": "Project health data collection",
"workflow": "AI agent workflow analysis",

View File

@ -14,7 +14,7 @@ func TestRegisterAll(t *testing.T) {
"repo", "issue", "label", "license", "pr", "release", "branch",
"org", "user", "search", "ci", "workflow",
"compare", "member", "milestone", "pipeline", "webhook",
"health",
"dataset", "health",
}
groupSet := map[string]bool{}