Files
dev_console/flow.go
T
2026-05-17 02:44:02 +02:00

194 lines
4.8 KiB
Go

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
}