milestones:新增跨倉庫里程碑總覽命令(#31)
- teai milestones(不帶 --repo)輸出跨所屬組織各倉庫 open 里程碑概況, 對應 gitea.py milestones(管理者巡邏,agents AGENTS.md 10.1): 欄位 repo/id/title/url/open_issues/closed_issues/due_on/due_hours/ overdue/unassigned/updated_at,排序期限近者在前、無期限最後。 - internal/workflow.MilestoneOverview:判定核心(Source 注入、離線可測); MilestoneEntry 自訂 MarshalJSON,鍵集鍵序與 gitea.py 全等 (null 表示、due_hours 定點一位、unassigned PR 條目帶 author)。 - internal/workflow.APIClient 增 OpenMilestones/MilestoneOpenIssues (issues 端點刻意不帶 type=issues,PR 條目保留供覆蓋檢查,同 gitea.py)。 - milestones --repo a/b 維持現行單倉庫 list 語義不變。 - 空清單輸出 [](gitea.py 無輸出;#26 慣例差異,README 已文件化)。 - 交叉驗證:同帳號同時跑 gitea.py milestones 與 teai milestones, 含逾期/未到期/無期限里程碑與未分派 issue/PR 條目,輸出位元組全等。
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
// milestones_commands.go — 跨倉庫里程碑總覽命令的 CLI 接線(#31):
|
||||
// teai milestones(不帶 --repo)。
|
||||
//
|
||||
// 核心邏輯在 internal/workflow(MilestoneOverview),本檔只做旗標
|
||||
// 剖析、資料源接線與輸出格式化(與 stalled_commands.go 同模式)。
|
||||
// 輸出與 gitea.py milestones 位元組相容(MilestoneEntry 自訂
|
||||
// MarshalJSON;縮排 2、停用 HTML 轉義、空清單輸出 [],見 #26 慣例)。
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gitea.alterminal.com/alterminal/teai/internal/gitea"
|
||||
"gitea.alterminal.com/alterminal/teai/internal/workflow"
|
||||
)
|
||||
|
||||
// newMilestoneSourceFunc 建立總覽的資料源;宣告為變數讓接線層測試
|
||||
// 可注入假資料源(與 newStalledSourceFunc 同模式)。
|
||||
var newMilestoneSourceFunc = newMilestoneSource
|
||||
|
||||
// newMilestoneSource 依全域選項建立 workflow.MilestoneSource(API 實作)。
|
||||
func newMilestoneSource(env *Env) (workflow.MilestoneSource, error) {
|
||||
token, _, err := gitea.ResolveToken(gitea.AuthOptions{
|
||||
FlagToken: env.Globals.Token,
|
||||
SiteURL: env.Globals.URL,
|
||||
ConfigPath: env.Globals.ConfigPath,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := gitea.New(env.Globals.URL, token, env.Globals.Timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &workflow.APIClient{Client: client}, nil
|
||||
}
|
||||
|
||||
// milestonesNowFunc 回傳總覽的基準時刻;宣告為變數讓測試可固定時刻。
|
||||
var milestonesNowFunc = time.Now
|
||||
|
||||
// milestoneEntryRow 把 MilestoneEntry 轉成 table 列(unassigned 僅 JSON
|
||||
// 模式輸出完整摘要;table 給人工抽查用,取計數)。
|
||||
func milestoneEntryRow(e workflow.MilestoneEntry) gitea.Values {
|
||||
dueOn := ""
|
||||
if e.DueOn != nil {
|
||||
dueOn = *e.DueOn
|
||||
}
|
||||
dueHours := ""
|
||||
if e.DueHours != nil {
|
||||
dueHours = fmt.Sprintf("%.1f", *e.DueHours)
|
||||
}
|
||||
updatedAt := ""
|
||||
if e.UpdatedAt != nil {
|
||||
updatedAt = *e.UpdatedAt
|
||||
}
|
||||
id := ""
|
||||
if e.ID != nil {
|
||||
id = fmt.Sprintf("%d", *e.ID)
|
||||
}
|
||||
return gitea.Values{
|
||||
e.Repo, id, e.Title, e.URL,
|
||||
e.OpenIssues, e.ClosedIssues, dueOn, dueHours,
|
||||
e.Overdue, len(e.Unassigned), updatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// runMilestonesOverview:teai milestones(跨倉庫總覽)。
|
||||
// JSON 輸出與 gitea.py milestones 位元組相容;空清單輸出 [](#26 慣例:
|
||||
// gitea.py 對空清單不輸出,teai 輸出 [] 供 JSON 消費者明確判斷)。
|
||||
func runMilestonesOverview(env *Env) error {
|
||||
src, err := newMilestoneSourceFunc(env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entries, err := workflow.MilestoneOverview(src, env.Globals.URL, milestonesNowFunc())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := printer(env)
|
||||
if p.Format == gitea.FormatTable {
|
||||
rows := make([]gitea.Values, 0, len(entries))
|
||||
for i := range entries {
|
||||
rows = append(rows, milestoneEntryRow(entries[i]))
|
||||
}
|
||||
return p.PrintList([]gitea.Field{
|
||||
{Name: "repo"}, {Name: "id"}, {Name: "title"}, {Name: "url"},
|
||||
{Name: "open_issues"}, {Name: "closed_issues"}, {Name: "due_on"},
|
||||
{Name: "due_hours"}, {Name: "overdue"}, {Name: "unassigned"}, {Name: "updated_at"},
|
||||
}, rows)
|
||||
}
|
||||
if len(entries) == 0 {
|
||||
_, err := fmt.Fprintln(env.Out, "[]")
|
||||
return err
|
||||
}
|
||||
return printJSON(env.Out, entries)
|
||||
}
|
||||
Reference in New Issue
Block a user