|
@@ -0,0 +1,1034 @@
|
|
|
|
|
+package cmd
|
|
|
|
|
+
|
|
|
|
|
+import (
|
|
|
|
|
+ "context"
|
|
|
|
|
+ "encoding/json"
|
|
|
|
|
+ "fmt"
|
|
|
|
|
+ "os"
|
|
|
|
|
+ "strings"
|
|
|
|
|
+
|
|
|
|
|
+ "gogs.dmsc.dev/arp/arp_cli/client"
|
|
|
|
|
+ "gogs.dmsc.dev/arp/arp_cli/config"
|
|
|
|
|
+
|
|
|
|
|
+ "github.com/AlecAivazis/survey/v2"
|
|
|
|
|
+ "github.com/olekukonko/tablewriter"
|
|
|
|
|
+ "github.com/urfave/cli/v3"
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+// WorkflowCommand returns the workflow command
|
|
|
|
|
+func WorkflowCommand() *cli.Command {
|
|
|
|
|
+ return &cli.Command{
|
|
|
|
|
+ Name: "workflow",
|
|
|
|
|
+ Usage: "Manage workflows",
|
|
|
|
|
+ Description: `Manage ARP workflows. Workflows are automated processes defined by templates.
|
|
|
|
|
+
|
|
|
|
|
+Workflow templates define the structure of automated workflows as DAGs (Directed Acyclic Graphs).
|
|
|
|
|
+Workflow instances are running executions of templates.
|
|
|
|
|
+Workflow nodes represent individual steps in a running workflow.`,
|
|
|
|
|
+ Commands: []*cli.Command{
|
|
|
|
|
+ // WorkflowTemplate commands
|
|
|
|
|
+ {
|
|
|
|
|
+ Name: "template",
|
|
|
|
|
+ Usage: "Manage workflow templates",
|
|
|
|
|
+ Commands: []*cli.Command{
|
|
|
|
|
+ {
|
|
|
|
|
+ Name: "list",
|
|
|
|
|
+ Aliases: []string{"ls"},
|
|
|
|
|
+ Usage: "List all workflow templates",
|
|
|
|
|
+ Flags: []cli.Flag{
|
|
|
|
|
+ &cli.BoolFlag{
|
|
|
|
|
+ Name: "json",
|
|
|
|
|
+ Aliases: []string{"j"},
|
|
|
|
|
+ Usage: "Output as JSON",
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ Action: workflowTemplateList,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ Name: "get",
|
|
|
|
|
+ Usage: "Get a workflow template by ID",
|
|
|
|
|
+ Flags: []cli.Flag{
|
|
|
|
|
+ &cli.StringFlag{
|
|
|
|
|
+ Name: "id",
|
|
|
|
|
+ Aliases: []string{"i"},
|
|
|
|
|
+ Usage: "Template ID",
|
|
|
|
|
+ Required: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ &cli.BoolFlag{
|
|
|
|
|
+ Name: "json",
|
|
|
|
|
+ Aliases: []string{"j"},
|
|
|
|
|
+ Usage: "Output as JSON",
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ Action: workflowTemplateGet,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ Name: "create",
|
|
|
|
|
+ Usage: "Create a new workflow template",
|
|
|
|
|
+ Action: workflowTemplateCreate,
|
|
|
|
|
+ Flags: []cli.Flag{
|
|
|
|
|
+ &cli.StringFlag{
|
|
|
|
|
+ Name: "name",
|
|
|
|
|
+ Aliases: []string{"n"},
|
|
|
|
|
+ Usage: "Template name",
|
|
|
|
|
+ },
|
|
|
|
|
+ &cli.StringFlag{
|
|
|
|
|
+ Name: "description",
|
|
|
|
|
+ Aliases: []string{"d"},
|
|
|
|
|
+ Usage: "Template description",
|
|
|
|
|
+ },
|
|
|
|
|
+ &cli.StringFlag{
|
|
|
|
|
+ Name: "definition",
|
|
|
|
|
+ Aliases: []string{"f"},
|
|
|
|
|
+ Usage: "Workflow definition (JSON string or @filename)",
|
|
|
|
|
+ },
|
|
|
|
|
+ &cli.BoolFlag{
|
|
|
|
|
+ Name: "active",
|
|
|
|
|
+ Usage: "Set template as active",
|
|
|
|
|
+ Value: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ Name: "update",
|
|
|
|
|
+ Usage: "Update a workflow template",
|
|
|
|
|
+ Action: workflowTemplateUpdate,
|
|
|
|
|
+ Flags: []cli.Flag{
|
|
|
|
|
+ &cli.StringFlag{
|
|
|
|
|
+ Name: "id",
|
|
|
|
|
+ Aliases: []string{"i"},
|
|
|
|
|
+ Usage: "Template ID",
|
|
|
|
|
+ Required: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ &cli.StringFlag{
|
|
|
|
|
+ Name: "name",
|
|
|
|
|
+ Aliases: []string{"n"},
|
|
|
|
|
+ Usage: "Template name",
|
|
|
|
|
+ },
|
|
|
|
|
+ &cli.StringFlag{
|
|
|
|
|
+ Name: "description",
|
|
|
|
|
+ Aliases: []string{"d"},
|
|
|
|
|
+ Usage: "Template description",
|
|
|
|
|
+ },
|
|
|
|
|
+ &cli.StringFlag{
|
|
|
|
|
+ Name: "definition",
|
|
|
|
|
+ Aliases: []string{"f"},
|
|
|
|
|
+ Usage: "Workflow definition (JSON string or @filename)",
|
|
|
|
|
+ },
|
|
|
|
|
+ &cli.BoolFlag{
|
|
|
|
|
+ Name: "active",
|
|
|
|
|
+ Usage: "Set template as active",
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ Name: "delete",
|
|
|
|
|
+ Usage: "Delete a workflow template",
|
|
|
|
|
+ Action: workflowTemplateDelete,
|
|
|
|
|
+ Flags: []cli.Flag{
|
|
|
|
|
+ &cli.StringFlag{
|
|
|
|
|
+ Name: "id",
|
|
|
|
|
+ Aliases: []string{"i"},
|
|
|
|
|
+ Usage: "Template ID",
|
|
|
|
|
+ Required: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ &cli.BoolFlag{
|
|
|
|
|
+ Name: "yes",
|
|
|
|
|
+ Aliases: []string{"y"},
|
|
|
|
|
+ Usage: "Skip confirmation",
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // WorkflowInstance commands
|
|
|
|
|
+ {
|
|
|
|
|
+ Name: "instance",
|
|
|
|
|
+ Usage: "Manage workflow instances",
|
|
|
|
|
+ Commands: []*cli.Command{
|
|
|
|
|
+ {
|
|
|
|
|
+ Name: "list",
|
|
|
|
|
+ Aliases: []string{"ls"},
|
|
|
|
|
+ Usage: "List all workflow instances",
|
|
|
|
|
+ Flags: []cli.Flag{
|
|
|
|
|
+ &cli.BoolFlag{
|
|
|
|
|
+ Name: "json",
|
|
|
|
|
+ Aliases: []string{"j"},
|
|
|
|
|
+ Usage: "Output as JSON",
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ Action: workflowInstanceList,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ Name: "get",
|
|
|
|
|
+ Usage: "Get a workflow instance by ID",
|
|
|
|
|
+ Flags: []cli.Flag{
|
|
|
|
|
+ &cli.StringFlag{
|
|
|
|
|
+ Name: "id",
|
|
|
|
|
+ Aliases: []string{"i"},
|
|
|
|
|
+ Usage: "Instance ID",
|
|
|
|
|
+ Required: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ &cli.BoolFlag{
|
|
|
|
|
+ Name: "json",
|
|
|
|
|
+ Aliases: []string{"j"},
|
|
|
|
|
+ Usage: "Output as JSON",
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ Action: workflowInstanceGet,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ Name: "start",
|
|
|
|
|
+ Usage: "Start a new workflow instance from a template",
|
|
|
|
|
+ Action: workflowInstanceStart,
|
|
|
|
|
+ Flags: []cli.Flag{
|
|
|
|
|
+ &cli.StringFlag{
|
|
|
|
|
+ Name: "template",
|
|
|
|
|
+ Aliases: []string{"t"},
|
|
|
|
|
+ Usage: "Template ID",
|
|
|
|
|
+ Required: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ &cli.StringFlag{
|
|
|
|
|
+ Name: "service",
|
|
|
|
|
+ Aliases: []string{"s"},
|
|
|
|
|
+ Usage: "Service ID to associate with the workflow",
|
|
|
|
|
+ },
|
|
|
|
|
+ &cli.StringFlag{
|
|
|
|
|
+ Name: "context",
|
|
|
|
|
+ Aliases: []string{"c"},
|
|
|
|
|
+ Usage: "Initial workflow context (JSON string)",
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ Name: "cancel",
|
|
|
|
|
+ Usage: "Cancel a running workflow instance",
|
|
|
|
|
+ Action: workflowInstanceCancel,
|
|
|
|
|
+ Flags: []cli.Flag{
|
|
|
|
|
+ &cli.StringFlag{
|
|
|
|
|
+ Name: "id",
|
|
|
|
|
+ Aliases: []string{"i"},
|
|
|
|
|
+ Usage: "Instance ID",
|
|
|
|
|
+ Required: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // WorkflowNode commands
|
|
|
|
|
+ {
|
|
|
|
|
+ Name: "node",
|
|
|
|
|
+ Usage: "Manage workflow nodes",
|
|
|
|
|
+ Commands: []*cli.Command{
|
|
|
|
|
+ {
|
|
|
|
|
+ Name: "list",
|
|
|
|
|
+ Aliases: []string{"ls"},
|
|
|
|
|
+ Usage: "List all nodes for a workflow instance",
|
|
|
|
|
+ Flags: []cli.Flag{
|
|
|
|
|
+ &cli.StringFlag{
|
|
|
|
|
+ Name: "instance",
|
|
|
|
|
+ Aliases: []string{"i"},
|
|
|
|
|
+ Usage: "Instance ID",
|
|
|
|
|
+ Required: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ &cli.BoolFlag{
|
|
|
|
|
+ Name: "json",
|
|
|
|
|
+ Aliases: []string{"j"},
|
|
|
|
|
+ Usage: "Output as JSON",
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ Action: workflowNodeList,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ Name: "get",
|
|
|
|
|
+ Usage: "Get a workflow node by ID",
|
|
|
|
|
+ Flags: []cli.Flag{
|
|
|
|
|
+ &cli.StringFlag{
|
|
|
|
|
+ Name: "id",
|
|
|
|
|
+ Aliases: []string{"i"},
|
|
|
|
|
+ Usage: "Node ID",
|
|
|
|
|
+ Required: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ &cli.BoolFlag{
|
|
|
|
|
+ Name: "json",
|
|
|
|
|
+ Aliases: []string{"j"},
|
|
|
|
|
+ Usage: "Output as JSON",
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ Action: workflowNodeGet,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ Name: "retry",
|
|
|
|
|
+ Usage: "Retry a failed workflow node",
|
|
|
|
|
+ Action: workflowNodeRetry,
|
|
|
|
|
+ Flags: []cli.Flag{
|
|
|
|
|
+ &cli.StringFlag{
|
|
|
|
|
+ Name: "id",
|
|
|
|
|
+ Aliases: []string{"i"},
|
|
|
|
|
+ Usage: "Node ID",
|
|
|
|
|
+ Required: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// WorkflowTemplate represents a workflow template
|
|
|
|
|
+type WorkflowTemplate struct {
|
|
|
|
|
+ ID string `json:"id"`
|
|
|
|
|
+ Name string `json:"name"`
|
|
|
|
|
+ Description string `json:"description"`
|
|
|
|
|
+ Definition string `json:"definition"`
|
|
|
|
|
+ IsActive bool `json:"isActive"`
|
|
|
|
|
+ CreatedBy *User `json:"createdBy"`
|
|
|
|
|
+ CreatedAt string `json:"createdAt"`
|
|
|
|
|
+ UpdatedAt string `json:"updatedAt"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// WorkflowInstance represents a running workflow instance
|
|
|
|
|
+type WorkflowInstance struct {
|
|
|
|
|
+ ID string `json:"id"`
|
|
|
|
|
+ Template *WorkflowTemplate `json:"template"`
|
|
|
|
|
+ Status string `json:"status"`
|
|
|
|
|
+ Context string `json:"context"`
|
|
|
|
|
+ Service *Service `json:"service"`
|
|
|
|
|
+ CreatedAt string `json:"createdAt"`
|
|
|
|
|
+ UpdatedAt string `json:"updatedAt"`
|
|
|
|
|
+ CompletedAt *string `json:"completedAt"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// WorkflowNode represents a node in a workflow instance
|
|
|
|
|
+type WorkflowNode struct {
|
|
|
|
|
+ ID string `json:"id"`
|
|
|
|
|
+ NodeKey string `json:"nodeKey"`
|
|
|
|
|
+ NodeType string `json:"nodeType"`
|
|
|
|
|
+ Status string `json:"status"`
|
|
|
|
|
+ Task *Task `json:"task"`
|
|
|
|
|
+ InputData string `json:"inputData"`
|
|
|
|
|
+ OutputData string `json:"outputData"`
|
|
|
|
|
+ RetryCount int `json:"retryCount"`
|
|
|
|
|
+ CreatedAt string `json:"createdAt"`
|
|
|
|
|
+ UpdatedAt string `json:"updatedAt"`
|
|
|
|
|
+ StartedAt *string `json:"startedAt"`
|
|
|
|
|
+ CompletedAt *string `json:"completedAt"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// WorkflowTemplate CRUD operations
|
|
|
|
|
+
|
|
|
|
|
+func workflowTemplateList(ctx context.Context, cmd *cli.Command) error {
|
|
|
|
|
+ cfg, err := config.Load()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := RequireAuth(cfg); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ c := client.New(cfg.ServerURL)
|
|
|
|
|
+ c.SetToken(cfg.Token)
|
|
|
|
|
+
|
|
|
|
|
+ query := `query WorkflowTemplates { workflowTemplates { id name description definition isActive createdBy { id email } createdAt updatedAt } }`
|
|
|
|
|
+
|
|
|
|
|
+ resp, err := c.Query(query, nil)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var result struct {
|
|
|
|
|
+ WorkflowTemplates []WorkflowTemplate `json:"workflowTemplates"`
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := json.Unmarshal(resp.Data, &result); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if cmd.Bool("json") {
|
|
|
|
|
+ enc := json.NewEncoder(os.Stdout)
|
|
|
|
|
+ enc.SetIndent("", " ")
|
|
|
|
|
+ return enc.Encode(result.WorkflowTemplates)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if len(result.WorkflowTemplates) == 0 {
|
|
|
|
|
+ fmt.Println("No workflow templates found.")
|
|
|
|
|
+ return nil
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ table := tablewriter.NewWriter(os.Stdout)
|
|
|
|
|
+ table.Header([]string{"ID", "Name", "Description", "Active", "Created By", "Created At"})
|
|
|
|
|
+
|
|
|
|
|
+ for _, t := range result.WorkflowTemplates {
|
|
|
|
|
+ desc := t.Description
|
|
|
|
|
+ if len(desc) > 30 {
|
|
|
|
|
+ desc = desc[:27] + "..."
|
|
|
|
|
+ }
|
|
|
|
|
+ createdBy := ""
|
|
|
|
|
+ if t.CreatedBy != nil {
|
|
|
|
|
+ createdBy = t.CreatedBy.Email
|
|
|
|
|
+ }
|
|
|
|
|
+ active := "yes"
|
|
|
|
|
+ if !t.IsActive {
|
|
|
|
|
+ active = "no"
|
|
|
|
|
+ }
|
|
|
|
|
+ table.Append([]string{t.ID, t.Name, desc, active, createdBy, t.CreatedAt})
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ table.Render()
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func workflowTemplateGet(ctx context.Context, cmd *cli.Command) error {
|
|
|
|
|
+ cfg, err := config.Load()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := RequireAuth(cfg); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ c := client.New(cfg.ServerURL)
|
|
|
|
|
+ c.SetToken(cfg.Token)
|
|
|
|
|
+
|
|
|
|
|
+ id := cmd.String("id")
|
|
|
|
|
+ query := `query WorkflowTemplate($id: ID!) { workflowTemplate(id: $id) { id name description definition isActive createdBy { id email } createdAt updatedAt } }`
|
|
|
|
|
+
|
|
|
|
|
+ resp, err := c.Query(query, map[string]interface{}{"id": id})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var result struct {
|
|
|
|
|
+ WorkflowTemplate *WorkflowTemplate `json:"workflowTemplate"`
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := json.Unmarshal(resp.Data, &result); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if result.WorkflowTemplate == nil {
|
|
|
|
|
+ return fmt.Errorf("workflow template not found")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if cmd.Bool("json") {
|
|
|
|
|
+ enc := json.NewEncoder(os.Stdout)
|
|
|
|
|
+ enc.SetIndent("", " ")
|
|
|
|
|
+ return enc.Encode(result.WorkflowTemplate)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ t := result.WorkflowTemplate
|
|
|
|
|
+ fmt.Printf("ID: %s\n", t.ID)
|
|
|
|
|
+ fmt.Printf("Name: %s\n", t.Name)
|
|
|
|
|
+ fmt.Printf("Description: %s\n", t.Description)
|
|
|
|
|
+ fmt.Printf("Active: %v\n", t.IsActive)
|
|
|
|
|
+ if t.CreatedBy != nil {
|
|
|
|
|
+ fmt.Printf("Created By: %s\n", t.CreatedBy.Email)
|
|
|
|
|
+ }
|
|
|
|
|
+ fmt.Printf("Created At: %s\n", t.CreatedAt)
|
|
|
|
|
+ fmt.Printf("Updated At: %s\n", t.UpdatedAt)
|
|
|
|
|
+ fmt.Printf("\nDefinition:\n%s\n", t.Definition)
|
|
|
|
|
+
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func workflowTemplateCreate(ctx context.Context, cmd *cli.Command) error {
|
|
|
|
|
+ cfg, err := config.Load()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := RequireAuth(cfg); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ name := cmd.String("name")
|
|
|
|
|
+ description := cmd.String("description")
|
|
|
|
|
+ definition := cmd.String("definition")
|
|
|
|
|
+ isActive := cmd.Bool("active")
|
|
|
|
|
+
|
|
|
|
|
+ if name == "" {
|
|
|
|
|
+ prompt := &survey.Input{Message: "Template name:"}
|
|
|
|
|
+ if err := survey.AskOne(prompt, &name, survey.WithValidator(survey.Required)); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if description == "" {
|
|
|
|
|
+ prompt := &survey.Input{Message: "Description (optional):"}
|
|
|
|
|
+ survey.AskOne(prompt, &description)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if definition == "" {
|
|
|
|
|
+ prompt := &survey.Multiline{Message: "Workflow definition (JSON):"}
|
|
|
|
|
+ if err := survey.AskOne(prompt, &definition, survey.WithValidator(survey.Required)); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Handle file input for definition
|
|
|
|
|
+ definition = ReadFileOrString(definition)
|
|
|
|
|
+
|
|
|
|
|
+ c := client.New(cfg.ServerURL)
|
|
|
|
|
+ c.SetToken(cfg.Token)
|
|
|
|
|
+
|
|
|
|
|
+ mutation := `mutation CreateWorkflowTemplate($input: NewWorkflowTemplate!) { createWorkflowTemplate(input: $input) { id name description definition isActive createdBy { id email } createdAt updatedAt } }`
|
|
|
|
|
+
|
|
|
|
|
+ input := map[string]interface{}{
|
|
|
|
|
+ "name": name,
|
|
|
|
|
+ "description": description,
|
|
|
|
|
+ "definition": definition,
|
|
|
|
|
+ "isActive": isActive,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ resp, err := c.Mutation(mutation, map[string]interface{}{"input": input})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var result struct {
|
|
|
|
|
+ CreateWorkflowTemplate *WorkflowTemplate `json:"createWorkflowTemplate"`
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := json.Unmarshal(resp.Data, &result); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if result.CreateWorkflowTemplate == nil {
|
|
|
|
|
+ return fmt.Errorf("failed to create workflow template")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fmt.Printf("Workflow template created successfully!\n")
|
|
|
|
|
+ fmt.Printf("ID: %s\n", result.CreateWorkflowTemplate.ID)
|
|
|
|
|
+ fmt.Printf("Name: %s\n", result.CreateWorkflowTemplate.Name)
|
|
|
|
|
+
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func workflowTemplateUpdate(ctx context.Context, cmd *cli.Command) error {
|
|
|
|
|
+ cfg, err := config.Load()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := RequireAuth(cfg); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ id := cmd.String("id")
|
|
|
|
|
+ name := cmd.String("name")
|
|
|
|
|
+ description := cmd.String("description")
|
|
|
|
|
+ definition := cmd.String("definition")
|
|
|
|
|
+
|
|
|
|
|
+ // Check if active flag was explicitly set
|
|
|
|
|
+ var isActive *bool
|
|
|
|
|
+ if cmd.IsSet("active") {
|
|
|
|
|
+ val := cmd.Bool("active")
|
|
|
|
|
+ isActive = &val
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if name == "" && description == "" && definition == "" && isActive == nil {
|
|
|
|
|
+ fmt.Println("No updates provided. Use flags to specify what to update.")
|
|
|
|
|
+ return nil
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ c := client.New(cfg.ServerURL)
|
|
|
|
|
+ c.SetToken(cfg.Token)
|
|
|
|
|
+
|
|
|
|
|
+ input := make(map[string]interface{})
|
|
|
|
|
+ if name != "" {
|
|
|
|
|
+ input["name"] = name
|
|
|
|
|
+ }
|
|
|
|
|
+ if description != "" {
|
|
|
|
|
+ input["description"] = description
|
|
|
|
|
+ }
|
|
|
|
|
+ if definition != "" {
|
|
|
|
|
+ input["definition"] = ReadFileOrString(definition)
|
|
|
|
|
+ }
|
|
|
|
|
+ if isActive != nil {
|
|
|
|
|
+ input["isActive"] = *isActive
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ mutation := `mutation UpdateWorkflowTemplate($id: ID!, $input: UpdateWorkflowTemplateInput!) { updateWorkflowTemplate(id: $id, input: $input) { id name description definition isActive createdAt updatedAt } }`
|
|
|
|
|
+
|
|
|
|
|
+ resp, err := c.Mutation(mutation, map[string]interface{}{"id": id, "input": input})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var result struct {
|
|
|
|
|
+ UpdateWorkflowTemplate *WorkflowTemplate `json:"updateWorkflowTemplate"`
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := json.Unmarshal(resp.Data, &result); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if result.UpdateWorkflowTemplate == nil {
|
|
|
|
|
+ return fmt.Errorf("workflow template not found")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fmt.Printf("Workflow template updated successfully!\n")
|
|
|
|
|
+ fmt.Printf("ID: %s\n", result.UpdateWorkflowTemplate.ID)
|
|
|
|
|
+ fmt.Printf("Name: %s\n", result.UpdateWorkflowTemplate.Name)
|
|
|
|
|
+
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func workflowTemplateDelete(ctx context.Context, cmd *cli.Command) error {
|
|
|
|
|
+ cfg, err := config.Load()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := RequireAuth(cfg); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ id := cmd.String("id")
|
|
|
|
|
+ skipConfirm := cmd.Bool("yes")
|
|
|
|
|
+
|
|
|
|
|
+ if !skipConfirm {
|
|
|
|
|
+ confirm := false
|
|
|
|
|
+ prompt := &survey.Confirm{
|
|
|
|
|
+ Message: fmt.Sprintf("Are you sure you want to delete workflow template %s?", id),
|
|
|
|
|
+ Default: false,
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := survey.AskOne(prompt, &confirm); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ if !confirm {
|
|
|
|
|
+ fmt.Println("Deletion cancelled.")
|
|
|
|
|
+ return nil
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ c := client.New(cfg.ServerURL)
|
|
|
|
|
+ c.SetToken(cfg.Token)
|
|
|
|
|
+
|
|
|
|
|
+ mutation := `mutation DeleteWorkflowTemplate($id: ID!) { deleteWorkflowTemplate(id: $id) }`
|
|
|
|
|
+
|
|
|
|
|
+ resp, err := c.Mutation(mutation, map[string]interface{}{"id": id})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var result struct {
|
|
|
|
|
+ DeleteWorkflowTemplate bool `json:"deleteWorkflowTemplate"`
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := json.Unmarshal(resp.Data, &result); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if result.DeleteWorkflowTemplate {
|
|
|
|
|
+ fmt.Printf("Workflow template %s deleted successfully.\n", id)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ fmt.Printf("Failed to delete workflow template %s.\n", id)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// WorkflowInstance operations
|
|
|
|
|
+
|
|
|
|
|
+func workflowInstanceList(ctx context.Context, cmd *cli.Command) error {
|
|
|
|
|
+ cfg, err := config.Load()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := RequireAuth(cfg); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ c := client.New(cfg.ServerURL)
|
|
|
|
|
+ c.SetToken(cfg.Token)
|
|
|
|
|
+
|
|
|
|
|
+ query := `query WorkflowInstances { workflowInstances { id template { id name } status context service { id name } createdAt updatedAt completedAt } }`
|
|
|
|
|
+
|
|
|
|
|
+ resp, err := c.Query(query, nil)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var result struct {
|
|
|
|
|
+ WorkflowInstances []WorkflowInstance `json:"workflowInstances"`
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := json.Unmarshal(resp.Data, &result); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if cmd.Bool("json") {
|
|
|
|
|
+ enc := json.NewEncoder(os.Stdout)
|
|
|
|
|
+ enc.SetIndent("", " ")
|
|
|
|
|
+ return enc.Encode(result.WorkflowInstances)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if len(result.WorkflowInstances) == 0 {
|
|
|
|
|
+ fmt.Println("No workflow instances found.")
|
|
|
|
|
+ return nil
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ table := tablewriter.NewWriter(os.Stdout)
|
|
|
|
|
+ table.Header([]string{"ID", "Template", "Status", "Service", "Created At"})
|
|
|
|
|
+
|
|
|
|
|
+ for _, i := range result.WorkflowInstances {
|
|
|
|
|
+ templateName := ""
|
|
|
|
|
+ if i.Template != nil {
|
|
|
|
|
+ templateName = i.Template.Name
|
|
|
|
|
+ }
|
|
|
|
|
+ serviceName := ""
|
|
|
|
|
+ if i.Service != nil {
|
|
|
|
|
+ serviceName = i.Service.Name
|
|
|
|
|
+ }
|
|
|
|
|
+ table.Append([]string{i.ID, templateName, i.Status, serviceName, i.CreatedAt})
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ table.Render()
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func workflowInstanceGet(ctx context.Context, cmd *cli.Command) error {
|
|
|
|
|
+ cfg, err := config.Load()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := RequireAuth(cfg); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ c := client.New(cfg.ServerURL)
|
|
|
|
|
+ c.SetToken(cfg.Token)
|
|
|
|
|
+
|
|
|
|
|
+ id := cmd.String("id")
|
|
|
|
|
+ query := `query WorkflowInstance($id: ID!) { workflowInstance(id: $id) { id template { id name description } status context service { id name } createdAt updatedAt completedAt } }`
|
|
|
|
|
+
|
|
|
|
|
+ resp, err := c.Query(query, map[string]interface{}{"id": id})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var result struct {
|
|
|
|
|
+ WorkflowInstance *WorkflowInstance `json:"workflowInstance"`
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := json.Unmarshal(resp.Data, &result); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if result.WorkflowInstance == nil {
|
|
|
|
|
+ return fmt.Errorf("workflow instance not found")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if cmd.Bool("json") {
|
|
|
|
|
+ enc := json.NewEncoder(os.Stdout)
|
|
|
|
|
+ enc.SetIndent("", " ")
|
|
|
|
|
+ return enc.Encode(result.WorkflowInstance)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ i := result.WorkflowInstance
|
|
|
|
|
+ fmt.Printf("ID: %s\n", i.ID)
|
|
|
|
|
+ if i.Template != nil {
|
|
|
|
|
+ fmt.Printf("Template: %s (%s)\n", i.Template.Name, i.Template.ID)
|
|
|
|
|
+ }
|
|
|
|
|
+ fmt.Printf("Status: %s\n", i.Status)
|
|
|
|
|
+ if i.Service != nil {
|
|
|
|
|
+ fmt.Printf("Service: %s (%s)\n", i.Service.Name, i.Service.ID)
|
|
|
|
|
+ }
|
|
|
|
|
+ fmt.Printf("Created At: %s\n", i.CreatedAt)
|
|
|
|
|
+ fmt.Printf("Updated At: %s\n", i.UpdatedAt)
|
|
|
|
|
+ if i.CompletedAt != nil {
|
|
|
|
|
+ fmt.Printf("Completed At: %s\n", *i.CompletedAt)
|
|
|
|
|
+ }
|
|
|
|
|
+ if i.Context != "" {
|
|
|
|
|
+ fmt.Printf("\nContext:\n%s\n", i.Context)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func workflowInstanceStart(ctx context.Context, cmd *cli.Command) error {
|
|
|
|
|
+ cfg, err := config.Load()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := RequireAuth(cfg); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ templateID := cmd.String("template")
|
|
|
|
|
+ serviceID := cmd.String("service")
|
|
|
|
|
+ contextJSON := cmd.String("context")
|
|
|
|
|
+
|
|
|
|
|
+ c := client.New(cfg.ServerURL)
|
|
|
|
|
+ c.SetToken(cfg.Token)
|
|
|
|
|
+
|
|
|
|
|
+ mutation := `mutation StartWorkflow($templateId: ID!, $input: StartWorkflowInput!) { startWorkflow(templateId: $templateId, input: $input) { id template { id name } status context service { id name } createdAt } }`
|
|
|
|
|
+
|
|
|
|
|
+ input := make(map[string]interface{})
|
|
|
|
|
+ if serviceID != "" {
|
|
|
|
|
+ input["serviceId"] = serviceID
|
|
|
|
|
+ }
|
|
|
|
|
+ if contextJSON != "" {
|
|
|
|
|
+ input["context"] = contextJSON
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ resp, err := c.Mutation(mutation, map[string]interface{}{"templateId": templateID, "input": input})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var result struct {
|
|
|
|
|
+ StartWorkflow *WorkflowInstance `json:"startWorkflow"`
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := json.Unmarshal(resp.Data, &result); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if result.StartWorkflow == nil {
|
|
|
|
|
+ return fmt.Errorf("failed to start workflow")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fmt.Printf("Workflow started successfully!\n")
|
|
|
|
|
+ fmt.Printf("Instance ID: %s\n", result.StartWorkflow.ID)
|
|
|
|
|
+ if result.StartWorkflow.Template != nil {
|
|
|
|
|
+ fmt.Printf("Template: %s\n", result.StartWorkflow.Template.Name)
|
|
|
|
|
+ }
|
|
|
|
|
+ fmt.Printf("Status: %s\n", result.StartWorkflow.Status)
|
|
|
|
|
+
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func workflowInstanceCancel(ctx context.Context, cmd *cli.Command) error {
|
|
|
|
|
+ cfg, err := config.Load()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := RequireAuth(cfg); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ id := cmd.String("id")
|
|
|
|
|
+
|
|
|
|
|
+ c := client.New(cfg.ServerURL)
|
|
|
|
|
+ c.SetToken(cfg.Token)
|
|
|
|
|
+
|
|
|
|
|
+ mutation := `mutation CancelWorkflow($id: ID!) { cancelWorkflow(id: $id) { id status completedAt } }`
|
|
|
|
|
+
|
|
|
|
|
+ resp, err := c.Mutation(mutation, map[string]interface{}{"id": id})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var result struct {
|
|
|
|
|
+ CancelWorkflow *WorkflowInstance `json:"cancelWorkflow"`
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := json.Unmarshal(resp.Data, &result); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if result.CancelWorkflow == nil {
|
|
|
|
|
+ return fmt.Errorf("workflow instance not found")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fmt.Printf("Workflow cancelled successfully!\n")
|
|
|
|
|
+ fmt.Printf("Instance ID: %s\n", result.CancelWorkflow.ID)
|
|
|
|
|
+ fmt.Printf("Status: %s\n", result.CancelWorkflow.Status)
|
|
|
|
|
+
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// WorkflowNode operations
|
|
|
|
|
+
|
|
|
|
|
+func workflowNodeList(ctx context.Context, cmd *cli.Command) error {
|
|
|
|
|
+ cfg, err := config.Load()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := RequireAuth(cfg); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ c := client.New(cfg.ServerURL)
|
|
|
|
|
+ c.SetToken(cfg.Token)
|
|
|
|
|
+
|
|
|
|
|
+ instanceID := cmd.String("instance")
|
|
|
|
|
+ query := `query WorkflowInstance($id: ID!) { workflowInstance(id: $id) { id status nodes: workflowNodes { id nodeKey nodeType status task { id title } retryCount createdAt startedAt completedAt } } }`
|
|
|
|
|
+
|
|
|
|
|
+ resp, err := c.Query(query, map[string]interface{}{"id": instanceID})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var result struct {
|
|
|
|
|
+ WorkflowInstance *struct {
|
|
|
|
|
+ ID string `json:"id"`
|
|
|
|
|
+ Status string `json:"status"`
|
|
|
|
|
+ Nodes []WorkflowNode `json:"nodes"`
|
|
|
|
|
+ } `json:"workflowInstance"`
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := json.Unmarshal(resp.Data, &result); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if result.WorkflowInstance == nil {
|
|
|
|
|
+ return fmt.Errorf("workflow instance not found")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if cmd.Bool("json") {
|
|
|
|
|
+ enc := json.NewEncoder(os.Stdout)
|
|
|
|
|
+ enc.SetIndent("", " ")
|
|
|
|
|
+ return enc.Encode(result.WorkflowInstance.Nodes)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if len(result.WorkflowInstance.Nodes) == 0 {
|
|
|
|
|
+ fmt.Println("No nodes found for this workflow instance.")
|
|
|
|
|
+ return nil
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ table := tablewriter.NewWriter(os.Stdout)
|
|
|
|
|
+ table.Header([]string{"ID", "Key", "Type", "Status", "Task", "Retries"})
|
|
|
|
|
+
|
|
|
|
|
+ for _, n := range result.WorkflowInstance.Nodes {
|
|
|
|
|
+ taskTitle := ""
|
|
|
|
|
+ if n.Task != nil {
|
|
|
|
|
+ taskTitle = n.Task.Title
|
|
|
|
|
+ if len(taskTitle) > 30 {
|
|
|
|
|
+ taskTitle = taskTitle[:27] + "..."
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ table.Append([]string{n.ID, n.NodeKey, n.NodeType, n.Status, taskTitle, fmt.Sprintf("%d", n.RetryCount)})
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ table.Render()
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func workflowNodeGet(ctx context.Context, cmd *cli.Command) error {
|
|
|
|
|
+ cfg, err := config.Load()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := RequireAuth(cfg); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ c := client.New(cfg.ServerURL)
|
|
|
|
|
+ c.SetToken(cfg.Token)
|
|
|
|
|
+
|
|
|
|
|
+ id := cmd.String("id")
|
|
|
|
|
+ query := `query WorkflowNode($id: ID!) { workflowNode(id: $id) { id nodeKey nodeType status task { id title } inputData outputData retryCount createdAt updatedAt startedAt completedAt } }`
|
|
|
|
|
+
|
|
|
|
|
+ // Note: This assumes a workflowNode query exists. If not, we need to fetch via instance
|
|
|
|
|
+ // For now, let's use a workaround by fetching the instance and finding the node
|
|
|
|
|
+ query = `query WorkflowInstances { workflowInstances { id nodes: workflowNodes { id nodeKey nodeType status task { id title content } inputData outputData retryCount createdAt updatedAt startedAt completedAt } } }`
|
|
|
|
|
+
|
|
|
|
|
+ resp, err := c.Query(query, nil)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var result struct {
|
|
|
|
|
+ WorkflowInstances []struct {
|
|
|
|
|
+ ID string `json:"id"`
|
|
|
|
|
+ Nodes []WorkflowNode `json:"nodes"`
|
|
|
|
|
+ } `json:"workflowInstances"`
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := json.Unmarshal(resp.Data, &result); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Find the node
|
|
|
|
|
+ var node *WorkflowNode
|
|
|
|
|
+ for _, instance := range result.WorkflowInstances {
|
|
|
|
|
+ for _, n := range instance.Nodes {
|
|
|
|
|
+ if n.ID == id {
|
|
|
|
|
+ node = &n
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if node != nil {
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if node == nil {
|
|
|
|
|
+ return fmt.Errorf("workflow node not found")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if cmd.Bool("json") {
|
|
|
|
|
+ enc := json.NewEncoder(os.Stdout)
|
|
|
|
|
+ enc.SetIndent("", " ")
|
|
|
|
|
+ return enc.Encode(node)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fmt.Printf("ID: %s\n", node.ID)
|
|
|
|
|
+ fmt.Printf("Key: %s\n", node.NodeKey)
|
|
|
|
|
+ fmt.Printf("Type: %s\n", node.NodeType)
|
|
|
|
|
+ fmt.Printf("Status: %s\n", node.Status)
|
|
|
|
|
+ fmt.Printf("Retry Count: %d\n", node.RetryCount)
|
|
|
|
|
+ if node.Task != nil {
|
|
|
|
|
+ fmt.Printf("Task: %s (%s)\n", node.Task.Title, node.Task.ID)
|
|
|
|
|
+ }
|
|
|
|
|
+ fmt.Printf("Created At: %s\n", node.CreatedAt)
|
|
|
|
|
+ fmt.Printf("Updated At: %s\n", node.UpdatedAt)
|
|
|
|
|
+ if node.StartedAt != nil {
|
|
|
|
|
+ fmt.Printf("Started At: %s\n", *node.StartedAt)
|
|
|
|
|
+ }
|
|
|
|
|
+ if node.CompletedAt != nil {
|
|
|
|
|
+ fmt.Printf("Completed At: %s\n", *node.CompletedAt)
|
|
|
|
|
+ }
|
|
|
|
|
+ if node.InputData != "" {
|
|
|
|
|
+ fmt.Printf("\nInput Data:\n%s\n", node.InputData)
|
|
|
|
|
+ }
|
|
|
|
|
+ if node.OutputData != "" {
|
|
|
|
|
+ fmt.Printf("\nOutput Data:\n%s\n", node.OutputData)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func workflowNodeRetry(ctx context.Context, cmd *cli.Command) error {
|
|
|
|
|
+ cfg, err := config.Load()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := RequireAuth(cfg); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ id := cmd.String("id")
|
|
|
|
|
+
|
|
|
|
|
+ c := client.New(cfg.ServerURL)
|
|
|
|
|
+ c.SetToken(cfg.Token)
|
|
|
|
|
+
|
|
|
|
|
+ mutation := `mutation RetryWorkflowNode($nodeId: ID!) { retryWorkflowNode(nodeId: $nodeId) { id nodeKey status retryCount } }`
|
|
|
|
|
+
|
|
|
|
|
+ resp, err := c.Mutation(mutation, map[string]interface{}{"nodeId": id})
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var result struct {
|
|
|
|
|
+ RetryWorkflowNode *WorkflowNode `json:"retryWorkflowNode"`
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := json.Unmarshal(resp.Data, &result); err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if result.RetryWorkflowNode == nil {
|
|
|
|
|
+ return fmt.Errorf("workflow node not found")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fmt.Printf("Workflow node retry initiated!\n")
|
|
|
|
|
+ fmt.Printf("Node ID: %s\n", result.RetryWorkflowNode.ID)
|
|
|
|
|
+ fmt.Printf("Key: %s\n", result.RetryWorkflowNode.NodeKey)
|
|
|
|
|
+ fmt.Printf("Status: %s\n", result.RetryWorkflowNode.Status)
|
|
|
|
|
+ fmt.Printf("Retry Count: %d\n", result.RetryWorkflowNode.RetryCount)
|
|
|
|
|
+
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ReadFileOrString reads file content if input starts with @, otherwise returns as-is
|
|
|
|
|
+func ReadFileOrString(input string) string {
|
|
|
|
|
+ if strings.HasPrefix(input, "@") {
|
|
|
|
|
+ filename := input[1:]
|
|
|
|
|
+ content, err := os.ReadFile(filename)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return input // Return original if file can't be read
|
|
|
|
|
+ }
|
|
|
|
|
+ return string(content)
|
|
|
|
|
+ }
|
|
|
|
|
+ return input
|
|
|
|
|
+}
|