1
0

workflow.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995
  1. package cmd
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "os"
  7. "strings"
  8. "github.com/AlecAivazis/survey/v2"
  9. "github.com/olekukonko/tablewriter"
  10. "github.com/urfave/cli/v3"
  11. )
  12. // WorkflowCommand returns the workflow command
  13. func WorkflowCommand() *cli.Command {
  14. return &cli.Command{
  15. Name: "workflow",
  16. Usage: "Manage workflows",
  17. Description: `Manage ARP workflows. Workflows are automated processes defined by templates.
  18. Workflow templates define the structure of automated workflows as DAGs (Directed Acyclic Graphs).
  19. Workflow instances are running executions of templates.
  20. Workflow nodes represent individual steps in a running workflow.`,
  21. Commands: []*cli.Command{
  22. // WorkflowTemplate commands
  23. {
  24. Name: "template",
  25. Usage: "Manage workflow templates",
  26. Commands: []*cli.Command{
  27. {
  28. Name: "list",
  29. Aliases: []string{"ls"},
  30. Usage: "List all workflow templates",
  31. Flags: []cli.Flag{
  32. &cli.BoolFlag{
  33. Name: "json",
  34. Aliases: []string{"j"},
  35. Usage: "Output as JSON",
  36. },
  37. },
  38. Action: workflowTemplateList,
  39. },
  40. {
  41. Name: "get",
  42. Usage: "Get a workflow template by ID",
  43. Flags: []cli.Flag{
  44. &cli.StringFlag{
  45. Name: "id",
  46. Aliases: []string{"i"},
  47. Usage: "Template ID",
  48. Required: true,
  49. },
  50. &cli.BoolFlag{
  51. Name: "json",
  52. Aliases: []string{"j"},
  53. Usage: "Output as JSON",
  54. },
  55. },
  56. Action: workflowTemplateGet,
  57. },
  58. {
  59. Name: "create",
  60. Usage: "Create a new workflow template",
  61. Action: workflowTemplateCreate,
  62. Flags: []cli.Flag{
  63. &cli.StringFlag{
  64. Name: "name",
  65. Aliases: []string{"n"},
  66. Usage: "Template name",
  67. },
  68. &cli.StringFlag{
  69. Name: "description",
  70. Aliases: []string{"d"},
  71. Usage: "Template description",
  72. },
  73. &cli.StringFlag{
  74. Name: "definition",
  75. Aliases: []string{"f"},
  76. Usage: "Workflow definition (JSON string or @filename)",
  77. },
  78. &cli.BoolFlag{
  79. Name: "active",
  80. Usage: "Set template as active",
  81. Value: true,
  82. },
  83. },
  84. },
  85. {
  86. Name: "update",
  87. Usage: "Update a workflow template",
  88. Action: workflowTemplateUpdate,
  89. Flags: []cli.Flag{
  90. &cli.StringFlag{
  91. Name: "id",
  92. Aliases: []string{"i"},
  93. Usage: "Template ID",
  94. Required: true,
  95. },
  96. &cli.StringFlag{
  97. Name: "name",
  98. Aliases: []string{"n"},
  99. Usage: "Template name",
  100. },
  101. &cli.StringFlag{
  102. Name: "description",
  103. Aliases: []string{"d"},
  104. Usage: "Template description",
  105. },
  106. &cli.StringFlag{
  107. Name: "definition",
  108. Aliases: []string{"f"},
  109. Usage: "Workflow definition (JSON string or @filename)",
  110. },
  111. &cli.BoolFlag{
  112. Name: "active",
  113. Usage: "Set template as active",
  114. },
  115. },
  116. },
  117. {
  118. Name: "delete",
  119. Usage: "Delete a workflow template",
  120. Action: workflowTemplateDelete,
  121. Flags: []cli.Flag{
  122. &cli.StringFlag{
  123. Name: "id",
  124. Aliases: []string{"i"},
  125. Usage: "Template ID",
  126. Required: true,
  127. },
  128. &cli.BoolFlag{
  129. Name: "yes",
  130. Aliases: []string{"y"},
  131. Usage: "Skip confirmation",
  132. },
  133. },
  134. },
  135. },
  136. },
  137. // WorkflowInstance commands
  138. {
  139. Name: "instance",
  140. Usage: "Manage workflow instances",
  141. Commands: []*cli.Command{
  142. {
  143. Name: "list",
  144. Aliases: []string{"ls"},
  145. Usage: "List all workflow instances",
  146. Flags: []cli.Flag{
  147. &cli.BoolFlag{
  148. Name: "json",
  149. Aliases: []string{"j"},
  150. Usage: "Output as JSON",
  151. },
  152. },
  153. Action: workflowInstanceList,
  154. },
  155. {
  156. Name: "get",
  157. Usage: "Get a workflow instance by ID",
  158. Flags: []cli.Flag{
  159. &cli.StringFlag{
  160. Name: "id",
  161. Aliases: []string{"i"},
  162. Usage: "Instance ID",
  163. Required: true,
  164. },
  165. &cli.BoolFlag{
  166. Name: "json",
  167. Aliases: []string{"j"},
  168. Usage: "Output as JSON",
  169. },
  170. },
  171. Action: workflowInstanceGet,
  172. },
  173. {
  174. Name: "start",
  175. Usage: "Start a new workflow instance from a template",
  176. Action: workflowInstanceStart,
  177. Flags: []cli.Flag{
  178. &cli.StringFlag{
  179. Name: "template",
  180. Aliases: []string{"t"},
  181. Usage: "Template ID",
  182. Required: true,
  183. },
  184. &cli.StringFlag{
  185. Name: "service",
  186. Aliases: []string{"s"},
  187. Usage: "Service ID to associate with the workflow",
  188. },
  189. &cli.StringFlag{
  190. Name: "context",
  191. Aliases: []string{"c"},
  192. Usage: "Initial workflow context (JSON string)",
  193. },
  194. },
  195. },
  196. {
  197. Name: "cancel",
  198. Usage: "Cancel a running workflow instance",
  199. Action: workflowInstanceCancel,
  200. Flags: []cli.Flag{
  201. &cli.StringFlag{
  202. Name: "id",
  203. Aliases: []string{"i"},
  204. Usage: "Instance ID",
  205. Required: true,
  206. },
  207. },
  208. },
  209. },
  210. },
  211. // WorkflowNode commands
  212. {
  213. Name: "node",
  214. Usage: "Manage workflow nodes",
  215. Commands: []*cli.Command{
  216. {
  217. Name: "list",
  218. Aliases: []string{"ls"},
  219. Usage: "List all nodes for a workflow instance",
  220. Flags: []cli.Flag{
  221. &cli.StringFlag{
  222. Name: "instance",
  223. Aliases: []string{"i"},
  224. Usage: "Instance ID",
  225. Required: true,
  226. },
  227. &cli.BoolFlag{
  228. Name: "json",
  229. Aliases: []string{"j"},
  230. Usage: "Output as JSON",
  231. },
  232. },
  233. Action: workflowNodeList,
  234. },
  235. {
  236. Name: "get",
  237. Usage: "Get a workflow node by ID",
  238. Flags: []cli.Flag{
  239. &cli.StringFlag{
  240. Name: "id",
  241. Aliases: []string{"i"},
  242. Usage: "Node ID",
  243. Required: true,
  244. },
  245. &cli.BoolFlag{
  246. Name: "json",
  247. Aliases: []string{"j"},
  248. Usage: "Output as JSON",
  249. },
  250. },
  251. Action: workflowNodeGet,
  252. },
  253. {
  254. Name: "retry",
  255. Usage: "Retry a failed workflow node",
  256. Action: workflowNodeRetry,
  257. Flags: []cli.Flag{
  258. &cli.StringFlag{
  259. Name: "id",
  260. Aliases: []string{"i"},
  261. Usage: "Node ID",
  262. Required: true,
  263. },
  264. },
  265. },
  266. },
  267. },
  268. },
  269. }
  270. }
  271. // WorkflowTemplate represents a workflow template
  272. type WorkflowTemplate struct {
  273. ID string `json:"id"`
  274. Name string `json:"name"`
  275. Description string `json:"description"`
  276. Definition string `json:"definition"`
  277. IsActive bool `json:"isActive"`
  278. CreatedBy *User `json:"createdBy"`
  279. CreatedAt string `json:"createdAt"`
  280. UpdatedAt string `json:"updatedAt"`
  281. }
  282. // WorkflowInstance represents a running workflow instance
  283. type WorkflowInstance struct {
  284. ID string `json:"id"`
  285. Template *WorkflowTemplate `json:"template"`
  286. Status string `json:"status"`
  287. Context string `json:"context"`
  288. Service *Service `json:"service"`
  289. CreatedAt string `json:"createdAt"`
  290. UpdatedAt string `json:"updatedAt"`
  291. CompletedAt *string `json:"completedAt"`
  292. }
  293. // WorkflowNode represents a node in a workflow instance
  294. type WorkflowNode struct {
  295. ID string `json:"id"`
  296. NodeKey string `json:"nodeKey"`
  297. NodeType string `json:"nodeType"`
  298. Status string `json:"status"`
  299. Task *Task `json:"task"`
  300. InputData string `json:"inputData"`
  301. OutputData string `json:"outputData"`
  302. RetryCount int `json:"retryCount"`
  303. CreatedAt string `json:"createdAt"`
  304. UpdatedAt string `json:"updatedAt"`
  305. StartedAt *string `json:"startedAt"`
  306. CompletedAt *string `json:"completedAt"`
  307. }
  308. // WorkflowTemplate CRUD operations
  309. func workflowTemplateList(ctx context.Context, cmd *cli.Command) error {
  310. c, cfg, err := GetClient(ctx, cmd)
  311. if err != nil {
  312. return err
  313. }
  314. if err := RequireAuth(cfg); err != nil {
  315. return err
  316. }
  317. query := `query WorkflowTemplates { workflowTemplates { id name description definition isActive createdBy { id email } createdAt updatedAt } }`
  318. resp, err := c.Query(query, nil)
  319. if err != nil {
  320. return err
  321. }
  322. var result struct {
  323. WorkflowTemplates []WorkflowTemplate `json:"workflowTemplates"`
  324. }
  325. if err := json.Unmarshal(resp.Data, &result); err != nil {
  326. return err
  327. }
  328. if cmd.Bool("json") {
  329. enc := json.NewEncoder(os.Stdout)
  330. enc.SetIndent("", " ")
  331. return enc.Encode(result.WorkflowTemplates)
  332. }
  333. if len(result.WorkflowTemplates) == 0 {
  334. fmt.Println("No workflow templates found.")
  335. return nil
  336. }
  337. table := tablewriter.NewWriter(os.Stdout)
  338. table.Header([]string{"ID", "Name", "Description", "Active", "Created By", "Created At"})
  339. for _, t := range result.WorkflowTemplates {
  340. desc := t.Description
  341. if len(desc) > 30 {
  342. desc = desc[:27] + "..."
  343. }
  344. createdBy := ""
  345. if t.CreatedBy != nil {
  346. createdBy = t.CreatedBy.Email
  347. }
  348. active := "yes"
  349. if !t.IsActive {
  350. active = "no"
  351. }
  352. table.Append([]string{t.ID, t.Name, desc, active, createdBy, t.CreatedAt})
  353. }
  354. table.Render()
  355. return nil
  356. }
  357. func workflowTemplateGet(ctx context.Context, cmd *cli.Command) error {
  358. c, cfg, err := GetClient(ctx, cmd)
  359. if err != nil {
  360. return err
  361. }
  362. if err := RequireAuth(cfg); err != nil {
  363. return err
  364. }
  365. id := cmd.String("id")
  366. query := `query WorkflowTemplate($id: ID!) { workflowTemplate(id: $id) { id name description definition isActive createdBy { id email } createdAt updatedAt } }`
  367. resp, err := c.Query(query, map[string]interface{}{"id": id})
  368. if err != nil {
  369. return err
  370. }
  371. var result struct {
  372. WorkflowTemplate *WorkflowTemplate `json:"workflowTemplate"`
  373. }
  374. if err := json.Unmarshal(resp.Data, &result); err != nil {
  375. return err
  376. }
  377. if result.WorkflowTemplate == nil {
  378. return fmt.Errorf("workflow template not found")
  379. }
  380. if cmd.Bool("json") {
  381. enc := json.NewEncoder(os.Stdout)
  382. enc.SetIndent("", " ")
  383. return enc.Encode(result.WorkflowTemplate)
  384. }
  385. t := result.WorkflowTemplate
  386. fmt.Printf("ID: %s\n", t.ID)
  387. fmt.Printf("Name: %s\n", t.Name)
  388. fmt.Printf("Description: %s\n", t.Description)
  389. fmt.Printf("Active: %v\n", t.IsActive)
  390. if t.CreatedBy != nil {
  391. fmt.Printf("Created By: %s\n", t.CreatedBy.Email)
  392. }
  393. fmt.Printf("Created At: %s\n", t.CreatedAt)
  394. fmt.Printf("Updated At: %s\n", t.UpdatedAt)
  395. fmt.Printf("\nDefinition:\n%s\n", t.Definition)
  396. return nil
  397. }
  398. func workflowTemplateCreate(ctx context.Context, cmd *cli.Command) error {
  399. c, cfg, err := GetClient(ctx, cmd)
  400. if err != nil {
  401. return err
  402. }
  403. if err := RequireAuth(cfg); err != nil {
  404. return err
  405. }
  406. name := cmd.String("name")
  407. description := cmd.String("description")
  408. definition := cmd.String("definition")
  409. isActive := cmd.Bool("active")
  410. if name == "" {
  411. prompt := &survey.Input{Message: "Template name:"}
  412. if err := survey.AskOne(prompt, &name, survey.WithValidator(survey.Required)); err != nil {
  413. return err
  414. }
  415. }
  416. if description == "" {
  417. prompt := &survey.Input{Message: "Description (optional):"}
  418. survey.AskOne(prompt, &description)
  419. }
  420. if definition == "" {
  421. prompt := &survey.Multiline{Message: "Workflow definition (JSON):"}
  422. if err := survey.AskOne(prompt, &definition, survey.WithValidator(survey.Required)); err != nil {
  423. return err
  424. }
  425. }
  426. // Handle file input for definition
  427. definition = ReadFileOrString(definition)
  428. mutation := `mutation CreateWorkflowTemplate($input: NewWorkflowTemplate!) { createWorkflowTemplate(input: $input) { id name description definition isActive createdBy { id email } createdAt updatedAt } }`
  429. input := map[string]interface{}{
  430. "name": name,
  431. "description": description,
  432. "definition": definition,
  433. "isActive": isActive,
  434. }
  435. resp, err := c.Mutation(mutation, map[string]interface{}{"input": input})
  436. if err != nil {
  437. return err
  438. }
  439. var result struct {
  440. CreateWorkflowTemplate *WorkflowTemplate `json:"createWorkflowTemplate"`
  441. }
  442. if err := json.Unmarshal(resp.Data, &result); err != nil {
  443. return err
  444. }
  445. if result.CreateWorkflowTemplate == nil {
  446. return fmt.Errorf("failed to create workflow template")
  447. }
  448. fmt.Printf("Workflow template created successfully!\n")
  449. fmt.Printf("ID: %s\n", result.CreateWorkflowTemplate.ID)
  450. fmt.Printf("Name: %s\n", result.CreateWorkflowTemplate.Name)
  451. return nil
  452. }
  453. func workflowTemplateUpdate(ctx context.Context, cmd *cli.Command) error {
  454. c, cfg, err := GetClient(ctx, cmd)
  455. if err != nil {
  456. return err
  457. }
  458. if err := RequireAuth(cfg); err != nil {
  459. return err
  460. }
  461. id := cmd.String("id")
  462. name := cmd.String("name")
  463. description := cmd.String("description")
  464. definition := cmd.String("definition")
  465. // Check if active flag was explicitly set
  466. var isActive *bool
  467. if cmd.IsSet("active") {
  468. val := cmd.Bool("active")
  469. isActive = &val
  470. }
  471. if name == "" && description == "" && definition == "" && isActive == nil {
  472. fmt.Println("No updates provided. Use flags to specify what to update.")
  473. return nil
  474. }
  475. input := make(map[string]interface{})
  476. if name != "" {
  477. input["name"] = name
  478. }
  479. if description != "" {
  480. input["description"] = description
  481. }
  482. if definition != "" {
  483. input["definition"] = ReadFileOrString(definition)
  484. }
  485. if isActive != nil {
  486. input["isActive"] = *isActive
  487. }
  488. mutation := `mutation UpdateWorkflowTemplate($id: ID!, $input: UpdateWorkflowTemplateInput!) { updateWorkflowTemplate(id: $id, input: $input) { id name description definition isActive createdAt updatedAt } }`
  489. resp, err := c.Mutation(mutation, map[string]interface{}{"id": id, "input": input})
  490. if err != nil {
  491. return err
  492. }
  493. var result struct {
  494. UpdateWorkflowTemplate *WorkflowTemplate `json:"updateWorkflowTemplate"`
  495. }
  496. if err := json.Unmarshal(resp.Data, &result); err != nil {
  497. return err
  498. }
  499. if result.UpdateWorkflowTemplate == nil {
  500. return fmt.Errorf("workflow template not found")
  501. }
  502. fmt.Printf("Workflow template updated successfully!\n")
  503. fmt.Printf("ID: %s\n", result.UpdateWorkflowTemplate.ID)
  504. fmt.Printf("Name: %s\n", result.UpdateWorkflowTemplate.Name)
  505. return nil
  506. }
  507. func workflowTemplateDelete(ctx context.Context, cmd *cli.Command) error {
  508. c, cfg, err := GetClient(ctx, cmd)
  509. if err != nil {
  510. return err
  511. }
  512. if err := RequireAuth(cfg); err != nil {
  513. return err
  514. }
  515. id := cmd.String("id")
  516. skipConfirm := cmd.Bool("yes")
  517. if !skipConfirm {
  518. confirm := false
  519. prompt := &survey.Confirm{
  520. Message: fmt.Sprintf("Are you sure you want to delete workflow template %s?", id),
  521. Default: false,
  522. }
  523. if err := survey.AskOne(prompt, &confirm); err != nil {
  524. return err
  525. }
  526. if !confirm {
  527. fmt.Println("Deletion cancelled.")
  528. return nil
  529. }
  530. }
  531. mutation := `mutation DeleteWorkflowTemplate($id: ID!) { deleteWorkflowTemplate(id: $id) }`
  532. resp, err := c.Mutation(mutation, map[string]interface{}{"id": id})
  533. if err != nil {
  534. return err
  535. }
  536. var result struct {
  537. DeleteWorkflowTemplate bool `json:"deleteWorkflowTemplate"`
  538. }
  539. if err := json.Unmarshal(resp.Data, &result); err != nil {
  540. return err
  541. }
  542. if result.DeleteWorkflowTemplate {
  543. fmt.Printf("Workflow template %s deleted successfully.\n", id)
  544. } else {
  545. fmt.Printf("Failed to delete workflow template %s.\n", id)
  546. }
  547. return nil
  548. }
  549. // WorkflowInstance operations
  550. func workflowInstanceList(ctx context.Context, cmd *cli.Command) error {
  551. c, cfg, err := GetClient(ctx, cmd)
  552. if err != nil {
  553. return err
  554. }
  555. if err := RequireAuth(cfg); err != nil {
  556. return err
  557. }
  558. query := `query WorkflowInstances { workflowInstances { id template { id name } status context service { id name } createdAt updatedAt completedAt } }`
  559. resp, err := c.Query(query, nil)
  560. if err != nil {
  561. return err
  562. }
  563. var result struct {
  564. WorkflowInstances []WorkflowInstance `json:"workflowInstances"`
  565. }
  566. if err := json.Unmarshal(resp.Data, &result); err != nil {
  567. return err
  568. }
  569. if cmd.Bool("json") {
  570. enc := json.NewEncoder(os.Stdout)
  571. enc.SetIndent("", " ")
  572. return enc.Encode(result.WorkflowInstances)
  573. }
  574. if len(result.WorkflowInstances) == 0 {
  575. fmt.Println("No workflow instances found.")
  576. return nil
  577. }
  578. table := tablewriter.NewWriter(os.Stdout)
  579. table.Header([]string{"ID", "Template", "Status", "Service", "Created At"})
  580. for _, i := range result.WorkflowInstances {
  581. templateName := ""
  582. if i.Template != nil {
  583. templateName = i.Template.Name
  584. }
  585. serviceName := ""
  586. if i.Service != nil {
  587. serviceName = i.Service.Name
  588. }
  589. table.Append([]string{i.ID, templateName, i.Status, serviceName, i.CreatedAt})
  590. }
  591. table.Render()
  592. return nil
  593. }
  594. func workflowInstanceGet(ctx context.Context, cmd *cli.Command) error {
  595. c, cfg, err := GetClient(ctx, cmd)
  596. if err != nil {
  597. return err
  598. }
  599. if err := RequireAuth(cfg); err != nil {
  600. return err
  601. }
  602. id := cmd.String("id")
  603. query := `query WorkflowInstance($id: ID!) { workflowInstance(id: $id) { id template { id name description } status context service { id name } createdAt updatedAt completedAt } }`
  604. resp, err := c.Query(query, map[string]interface{}{"id": id})
  605. if err != nil {
  606. return err
  607. }
  608. var result struct {
  609. WorkflowInstance *WorkflowInstance `json:"workflowInstance"`
  610. }
  611. if err := json.Unmarshal(resp.Data, &result); err != nil {
  612. return err
  613. }
  614. if result.WorkflowInstance == nil {
  615. return fmt.Errorf("workflow instance not found")
  616. }
  617. if cmd.Bool("json") {
  618. enc := json.NewEncoder(os.Stdout)
  619. enc.SetIndent("", " ")
  620. return enc.Encode(result.WorkflowInstance)
  621. }
  622. i := result.WorkflowInstance
  623. fmt.Printf("ID: %s\n", i.ID)
  624. if i.Template != nil {
  625. fmt.Printf("Template: %s (%s)\n", i.Template.Name, i.Template.ID)
  626. }
  627. fmt.Printf("Status: %s\n", i.Status)
  628. if i.Service != nil {
  629. fmt.Printf("Service: %s (%s)\n", i.Service.Name, i.Service.ID)
  630. }
  631. fmt.Printf("Created At: %s\n", i.CreatedAt)
  632. fmt.Printf("Updated At: %s\n", i.UpdatedAt)
  633. if i.CompletedAt != nil {
  634. fmt.Printf("Completed At: %s\n", *i.CompletedAt)
  635. }
  636. if i.Context != "" {
  637. fmt.Printf("\nContext:\n%s\n", i.Context)
  638. }
  639. return nil
  640. }
  641. func workflowInstanceStart(ctx context.Context, cmd *cli.Command) error {
  642. c, cfg, err := GetClient(ctx, cmd)
  643. if err != nil {
  644. return err
  645. }
  646. if err := RequireAuth(cfg); err != nil {
  647. return err
  648. }
  649. templateID := cmd.String("template")
  650. serviceID := cmd.String("service")
  651. contextJSON := cmd.String("context")
  652. mutation := `mutation StartWorkflow($templateId: ID!, $input: StartWorkflowInput!) { startWorkflow(templateId: $templateId, input: $input) { id template { id name } status context service { id name } createdAt } }`
  653. input := make(map[string]interface{})
  654. if serviceID != "" {
  655. input["serviceId"] = serviceID
  656. }
  657. if contextJSON != "" {
  658. input["context"] = contextJSON
  659. }
  660. resp, err := c.Mutation(mutation, map[string]interface{}{"templateId": templateID, "input": input})
  661. if err != nil {
  662. return err
  663. }
  664. var result struct {
  665. StartWorkflow *WorkflowInstance `json:"startWorkflow"`
  666. }
  667. if err := json.Unmarshal(resp.Data, &result); err != nil {
  668. return err
  669. }
  670. if result.StartWorkflow == nil {
  671. return fmt.Errorf("failed to start workflow")
  672. }
  673. fmt.Printf("Workflow started successfully!\n")
  674. fmt.Printf("Instance ID: %s\n", result.StartWorkflow.ID)
  675. if result.StartWorkflow.Template != nil {
  676. fmt.Printf("Template: %s\n", result.StartWorkflow.Template.Name)
  677. }
  678. fmt.Printf("Status: %s\n", result.StartWorkflow.Status)
  679. return nil
  680. }
  681. func workflowInstanceCancel(ctx context.Context, cmd *cli.Command) error {
  682. c, cfg, err := GetClient(ctx, cmd)
  683. if err != nil {
  684. return err
  685. }
  686. if err := RequireAuth(cfg); err != nil {
  687. return err
  688. }
  689. id := cmd.String("id")
  690. mutation := `mutation CancelWorkflow($id: ID!) { cancelWorkflow(id: $id) { id status completedAt } }`
  691. resp, err := c.Mutation(mutation, map[string]interface{}{"id": id})
  692. if err != nil {
  693. return err
  694. }
  695. var result struct {
  696. CancelWorkflow *WorkflowInstance `json:"cancelWorkflow"`
  697. }
  698. if err := json.Unmarshal(resp.Data, &result); err != nil {
  699. return err
  700. }
  701. if result.CancelWorkflow == nil {
  702. return fmt.Errorf("workflow instance not found")
  703. }
  704. fmt.Printf("Workflow cancelled successfully!\n")
  705. fmt.Printf("Instance ID: %s\n", result.CancelWorkflow.ID)
  706. fmt.Printf("Status: %s\n", result.CancelWorkflow.Status)
  707. return nil
  708. }
  709. // WorkflowNode operations
  710. func workflowNodeList(ctx context.Context, cmd *cli.Command) error {
  711. c, cfg, err := GetClient(ctx, cmd)
  712. if err != nil {
  713. return err
  714. }
  715. if err := RequireAuth(cfg); err != nil {
  716. return err
  717. }
  718. instanceID := cmd.String("instance")
  719. query := `query WorkflowInstance($id: ID!) { workflowInstance(id: $id) { id status nodes: workflowNodes { id nodeKey nodeType status task { id title } retryCount createdAt startedAt completedAt } } }`
  720. resp, err := c.Query(query, map[string]interface{}{"id": instanceID})
  721. if err != nil {
  722. return err
  723. }
  724. var result struct {
  725. WorkflowInstance *struct {
  726. ID string `json:"id"`
  727. Status string `json:"status"`
  728. Nodes []WorkflowNode `json:"nodes"`
  729. } `json:"workflowInstance"`
  730. }
  731. if err := json.Unmarshal(resp.Data, &result); err != nil {
  732. return err
  733. }
  734. if result.WorkflowInstance == nil {
  735. return fmt.Errorf("workflow instance not found")
  736. }
  737. if cmd.Bool("json") {
  738. enc := json.NewEncoder(os.Stdout)
  739. enc.SetIndent("", " ")
  740. return enc.Encode(result.WorkflowInstance.Nodes)
  741. }
  742. if len(result.WorkflowInstance.Nodes) == 0 {
  743. fmt.Println("No nodes found for this workflow instance.")
  744. return nil
  745. }
  746. table := tablewriter.NewWriter(os.Stdout)
  747. table.Header([]string{"ID", "Key", "Type", "Status", "Task", "Retries"})
  748. for _, n := range result.WorkflowInstance.Nodes {
  749. taskTitle := ""
  750. if n.Task != nil {
  751. taskTitle = n.Task.Title
  752. if len(taskTitle) > 30 {
  753. taskTitle = taskTitle[:27] + "..."
  754. }
  755. }
  756. table.Append([]string{n.ID, n.NodeKey, n.NodeType, n.Status, taskTitle, fmt.Sprintf("%d", n.RetryCount)})
  757. }
  758. table.Render()
  759. return nil
  760. }
  761. func workflowNodeGet(ctx context.Context, cmd *cli.Command) error {
  762. c, cfg, err := GetClient(ctx, cmd)
  763. if err != nil {
  764. return err
  765. }
  766. if err := RequireAuth(cfg); err != nil {
  767. return err
  768. }
  769. id := cmd.String("id")
  770. query := `query WorkflowNode($id: ID!) { workflowNode(id: $id) { id nodeKey nodeType status task { id title } inputData outputData retryCount createdAt updatedAt startedAt completedAt } }`
  771. // Note: This assumes a workflowNode query exists. If not, we need to fetch via instance
  772. // For now, let's use a workaround by fetching the instance and finding the node
  773. query = `query WorkflowInstances { workflowInstances { id nodes: workflowNodes { id nodeKey nodeType status task { id title content } inputData outputData retryCount createdAt updatedAt startedAt completedAt } } }`
  774. resp, err := c.Query(query, nil)
  775. if err != nil {
  776. return err
  777. }
  778. var result struct {
  779. WorkflowInstances []struct {
  780. ID string `json:"id"`
  781. Nodes []WorkflowNode `json:"nodes"`
  782. } `json:"workflowInstances"`
  783. }
  784. if err := json.Unmarshal(resp.Data, &result); err != nil {
  785. return err
  786. }
  787. // Find the node
  788. var node *WorkflowNode
  789. for _, instance := range result.WorkflowInstances {
  790. for _, n := range instance.Nodes {
  791. if n.ID == id {
  792. node = &n
  793. break
  794. }
  795. }
  796. if node != nil {
  797. break
  798. }
  799. }
  800. if node == nil {
  801. return fmt.Errorf("workflow node not found")
  802. }
  803. if cmd.Bool("json") {
  804. enc := json.NewEncoder(os.Stdout)
  805. enc.SetIndent("", " ")
  806. return enc.Encode(node)
  807. }
  808. fmt.Printf("ID: %s\n", node.ID)
  809. fmt.Printf("Key: %s\n", node.NodeKey)
  810. fmt.Printf("Type: %s\n", node.NodeType)
  811. fmt.Printf("Status: %s\n", node.Status)
  812. fmt.Printf("Retry Count: %d\n", node.RetryCount)
  813. if node.Task != nil {
  814. fmt.Printf("Task: %s (%s)\n", node.Task.Title, node.Task.ID)
  815. }
  816. fmt.Printf("Created At: %s\n", node.CreatedAt)
  817. fmt.Printf("Updated At: %s\n", node.UpdatedAt)
  818. if node.StartedAt != nil {
  819. fmt.Printf("Started At: %s\n", *node.StartedAt)
  820. }
  821. if node.CompletedAt != nil {
  822. fmt.Printf("Completed At: %s\n", *node.CompletedAt)
  823. }
  824. if node.InputData != "" {
  825. fmt.Printf("\nInput Data:\n%s\n", node.InputData)
  826. }
  827. if node.OutputData != "" {
  828. fmt.Printf("\nOutput Data:\n%s\n", node.OutputData)
  829. }
  830. return nil
  831. }
  832. func workflowNodeRetry(ctx context.Context, cmd *cli.Command) error {
  833. c, cfg, err := GetClient(ctx, cmd)
  834. if err != nil {
  835. return err
  836. }
  837. if err := RequireAuth(cfg); err != nil {
  838. return err
  839. }
  840. id := cmd.String("id")
  841. mutation := `mutation RetryWorkflowNode($nodeId: ID!) { retryWorkflowNode(nodeId: $nodeId) { id nodeKey status retryCount } }`
  842. resp, err := c.Mutation(mutation, map[string]interface{}{"nodeId": id})
  843. if err != nil {
  844. return err
  845. }
  846. var result struct {
  847. RetryWorkflowNode *WorkflowNode `json:"retryWorkflowNode"`
  848. }
  849. if err := json.Unmarshal(resp.Data, &result); err != nil {
  850. return err
  851. }
  852. if result.RetryWorkflowNode == nil {
  853. return fmt.Errorf("workflow node not found")
  854. }
  855. fmt.Printf("Workflow node retry initiated!\n")
  856. fmt.Printf("Node ID: %s\n", result.RetryWorkflowNode.ID)
  857. fmt.Printf("Key: %s\n", result.RetryWorkflowNode.NodeKey)
  858. fmt.Printf("Status: %s\n", result.RetryWorkflowNode.Status)
  859. fmt.Printf("Retry Count: %d\n", result.RetryWorkflowNode.RetryCount)
  860. return nil
  861. }
  862. // ReadFileOrString reads file content if input starts with @, otherwise returns as-is
  863. func ReadFileOrString(input string) string {
  864. if strings.HasPrefix(input, "@") {
  865. filename := input[1:]
  866. content, err := os.ReadFile(filename)
  867. if err != nil {
  868. return input // Return original if file can't be read
  869. }
  870. return string(content)
  871. }
  872. return input
  873. }