chore: sync from fn-registry agent
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// cmdFlow dispatches flow subcommands.
|
||||
func cmdFlow(args []string, flags Flags) {
|
||||
if len(args) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "usage: dev_console flow <list|show|status> [args]")
|
||||
os.Exit(1)
|
||||
}
|
||||
sub := args[0]
|
||||
rest := args[1:]
|
||||
|
||||
switch sub {
|
||||
case "list":
|
||||
flowList(rest, flags)
|
||||
case "show":
|
||||
flowShow(rest, flags)
|
||||
case "status":
|
||||
flowStatus(rest, flags)
|
||||
// v2 stubs
|
||||
case "create", "dod", "trace", "user-test", "run", "chain", "done":
|
||||
fmt.Fprintf(os.Stderr, "TODO v2: flow %s not yet implemented\n", sub)
|
||||
os.Exit(2)
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown flow subcommand: %s\n", sub)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// flowList lists flows with optional filters.
|
||||
func flowList(args []string, flags Flags) {
|
||||
root := mustRegistryRoot()
|
||||
flows, err := LoadAllFlows(root)
|
||||
if err != nil {
|
||||
fatalf("load flows: %v", err)
|
||||
}
|
||||
|
||||
// Apply filters
|
||||
var filtered []Flow
|
||||
for _, fl := range flows {
|
||||
if flags.App != "" && !matchApp(fl.Apps, flags.App) {
|
||||
continue
|
||||
}
|
||||
if flags.Pattern != "" && !matchStr(fl.Pattern, flags.Pattern) {
|
||||
continue
|
||||
}
|
||||
if flags.Risk != "" && !matchStr(fl.Risk, flags.Risk) {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, fl)
|
||||
}
|
||||
|
||||
sort.Slice(filtered, func(i, j int) bool {
|
||||
return filtered[i].ID < filtered[j].ID
|
||||
})
|
||||
|
||||
if flags.JSON {
|
||||
printJSON(filtered)
|
||||
return
|
||||
}
|
||||
|
||||
headers := []string{"ID", "NAME", "STATUS", "PRIO", "RISK", "APPS", "ACCEPTANCE", "DOD", "USER-FACING"}
|
||||
var rows [][]string
|
||||
for _, fl := range filtered {
|
||||
rows = append(rows, []string{
|
||||
fl.ID,
|
||||
truncate(fl.Name, 30),
|
||||
statusShort(fl.Status),
|
||||
fl.Priority,
|
||||
fl.Risk,
|
||||
truncate(joinStrings(fl.Apps), 30),
|
||||
pctBar(fl.AcceptancePct),
|
||||
pctBar(fl.DoDPct),
|
||||
pctBar(fl.UserFacingPct),
|
||||
})
|
||||
}
|
||||
printTable(os.Stdout, headers, rows)
|
||||
fmt.Printf("\nTotal: %d flows\n", len(filtered))
|
||||
}
|
||||
|
||||
// flowShow prints the full content of a flow.
|
||||
func flowShow(args []string, flags Flags) {
|
||||
if len(args) == 0 {
|
||||
fatalf("usage: dev_console flow show NNNN")
|
||||
}
|
||||
id := normalizeID(args[0])
|
||||
root := mustRegistryRoot()
|
||||
fl, err := findFlowByID(root, id)
|
||||
if err != nil {
|
||||
fatalf("flow %s: %v", id, err)
|
||||
}
|
||||
|
||||
if flags.JSON {
|
||||
printJSON(fl)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("# Flow %s — %s\n\n", fl.ID, fl.Name)
|
||||
fmt.Printf("Status: %s\n", statusShort(fl.Status))
|
||||
fmt.Printf("Priority: %s\n", fl.Priority)
|
||||
fmt.Printf("Risk: %s\n", fl.Risk)
|
||||
fmt.Printf("Apps: %s\n", joinStrings(fl.Apps))
|
||||
fmt.Printf("Trigger: %s\n", fl.Trigger)
|
||||
fmt.Printf("Schedule: %s\n", fl.Schedule)
|
||||
fmt.Printf("ExpectedRuntime: %ds\n", fl.ExpectedRuntimeS)
|
||||
fmt.Printf("Tags: %s\n", joinStrings(fl.Tags))
|
||||
fmt.Printf("Created: %s\n", fl.Created)
|
||||
fmt.Printf("Updated: %s\n", fl.Updated)
|
||||
fmt.Printf("Path: %s\n", fl.Path)
|
||||
fmt.Printf("\nAcceptance: %s\n", pctBar(fl.AcceptancePct))
|
||||
fmt.Printf("DoD: %s\n", pctBar(fl.DoDPct))
|
||||
fmt.Printf("User-facing: %s\n", pctBar(fl.UserFacingPct))
|
||||
fmt.Printf("\n---\n%s\n", fl.Body)
|
||||
}
|
||||
|
||||
// flowStatus prints acceptance + DoD + user-facing % for a flow.
|
||||
func flowStatus(args []string, flags Flags) {
|
||||
if len(args) == 0 {
|
||||
fatalf("usage: dev_console flow status NNNN")
|
||||
}
|
||||
id := normalizeID(args[0])
|
||||
root := mustRegistryRoot()
|
||||
fl, err := findFlowByID(root, id)
|
||||
if err != nil {
|
||||
fatalf("flow %s: %v", id, err)
|
||||
}
|
||||
|
||||
type statusOut struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
AcceptancePct int `json:"acceptance_pct"`
|
||||
DoDPct int `json:"dod_pct"`
|
||||
UserFacingPct int `json:"user_facing_pct"`
|
||||
DoDComplete bool `json:"dod_complete"`
|
||||
}
|
||||
out := statusOut{
|
||||
ID: fl.ID,
|
||||
Name: fl.Name,
|
||||
Status: fl.Status,
|
||||
AcceptancePct: fl.AcceptancePct,
|
||||
DoDPct: fl.DoDPct,
|
||||
UserFacingPct: fl.UserFacingPct,
|
||||
DoDComplete: fl.DoDPct == 100,
|
||||
}
|
||||
|
||||
if flags.JSON {
|
||||
printJSON(out)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Flow %s — %s\n", fl.ID, fl.Name)
|
||||
fmt.Printf("Status: %s\n", statusShort(fl.Status))
|
||||
fmt.Printf("Acceptance: %s\n", pctBar(fl.AcceptancePct))
|
||||
fmt.Printf("DoD: %s\n", pctBar(fl.DoDPct))
|
||||
fmt.Printf("User-facing: %s\n", pctBar(fl.UserFacingPct))
|
||||
if fl.DoDPct == 100 {
|
||||
fmt.Println("DoD: COMPLETE (100%)")
|
||||
} else {
|
||||
fmt.Printf("DoD: INCOMPLETE (%d%%)\n", fl.DoDPct)
|
||||
}
|
||||
}
|
||||
|
||||
// findFlowByID searches for a flow by ID.
|
||||
func findFlowByID(root, id string) (Flow, error) {
|
||||
flows, err := LoadAllFlows(root)
|
||||
if err != nil {
|
||||
return Flow{}, err
|
||||
}
|
||||
for _, fl := range flows {
|
||||
if fl.ID == id {
|
||||
return fl, nil
|
||||
}
|
||||
}
|
||||
return Flow{}, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
// matchApp returns true if any of the flow's apps matches the filter.
|
||||
func matchApp(apps []string, filter string) bool {
|
||||
filter = strings.ToLower(filter)
|
||||
for _, a := range apps {
|
||||
if strings.ToLower(a) == filter {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user