-
Notifications
You must be signed in to change notification settings - Fork 0
feat(workflow): add replay, approve, templates, and task-definitions subcommands #107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+377
−0
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
c1ac7bb
feat(workflow): add replay, approve, templates, and task-definitions …
blue4209211 8ba1c2d
fix(workflow): validate template type flag and pre-allocate task-defi…
blue4209211 2e691d4
fix(workflow): make type flag optional in templates get
blue4209211 ac973e2
fix(workflow): use pointer for nullable template response and clarify…
blue4209211 d2cc2ef
fix(workflow): use non-pointer struct for WorkflowGetTemplate in temp…
blue4209211 1b34f6b
fix(workflow): normalize template type flag using strings.ToLower
blue4209211 ce8d5b9
refactor(workflow): use package-level client.Run and validate non-emp…
blue4209211 fb1d905
refactor(workflow): extract workflowTemplate struct and omit empty co…
blue4209211 a276597
fix(workflow): validate non-empty positional arguments across commands
blue4209211 4343371
fix(workflow): use pointer struct for WorkflowGetTemplate response
blue4209211 a3c87db
fix(workflow): enforce Args: cobra.NoArgs and trim flag values
blue4209211 b122ca6
fix(workflow): dereference template pointer for structured formatting
blue4209211 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| package cmd | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| "github.com/nudgebee/nbctl/pkg/client" | ||
| "github.com/nudgebee/nbctl/pkg/format" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| var workflowApproveCmd = &cobra.Command{ | ||
| Use: "approve <execution-id>", | ||
| Short: "Complete a human approval gate for a pending workflow execution", | ||
| Args: cobra.ExactArgs(1), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| executionID := strings.TrimSpace(args[0]) | ||
| if executionID == "" { | ||
| return fmt.Errorf("execution-id cannot be empty") | ||
| } | ||
| taskID, _ := cmd.Flags().GetString("task") | ||
| taskID = strings.TrimSpace(taskID) | ||
| if taskID == "" { | ||
| return fmt.Errorf("task flag is required and cannot be empty") | ||
| } | ||
|
|
||
| reject, _ := cmd.Flags().GetBool("reject") | ||
| comments, _ := cmd.Flags().GetString("comments") | ||
|
blue4209211 marked this conversation as resolved.
|
||
| comments = strings.TrimSpace(comments) | ||
|
|
||
| status := "approved" | ||
| if reject { | ||
| status = "rejected" | ||
| } | ||
|
|
||
| req := client.NewRequest(` | ||
| mutation CompleteWorkflowApproval($request: WorkflowCompleteApprovalRequest!) { | ||
| workflow_complete_approval(request: $request) { | ||
| status | ||
| message | ||
| } | ||
| } | ||
| `) | ||
| input := map[string]any{ | ||
| "execution_id": executionID, | ||
| "task_id": taskID, | ||
| "status": status, | ||
| } | ||
| if comments != "" { | ||
| input["comments"] = comments | ||
| } | ||
| req.Var("request", input) | ||
|
|
||
| var respData struct { | ||
| WorkflowCompleteApproval struct { | ||
| Status string `json:"status"` | ||
| Message string `json:"message"` | ||
| } `json:"workflow_complete_approval"` | ||
| } | ||
|
|
||
| if err := client.Run(cmd.Context(), req, &respData); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| format.GetFormat().Print(respData.WorkflowCompleteApproval) | ||
| return nil | ||
| }, | ||
| } | ||
|
|
||
| func init() { | ||
| workflowCmd.AddCommand(workflowApproveCmd) | ||
| workflowApproveCmd.Flags().String("task", "", "Task ID waiting for approval (required)") | ||
| workflowApproveCmd.Flags().Bool("reject", false, "Reject the approval step instead of approving") | ||
| workflowApproveCmd.Flags().String("comments", "", "Optional comments for the approval decision") | ||
| _ = workflowApproveCmd.MarkFlagRequired("task") | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package cmd | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| "github.com/nudgebee/nbctl/pkg/client" | ||
| "github.com/nudgebee/nbctl/pkg/format" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| var workflowReplayCmd = &cobra.Command{ | ||
| Use: "replay <execution-id>", | ||
| Short: "Replay a previous or failed workflow execution", | ||
| Args: cobra.ExactArgs(1), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| executionID := strings.TrimSpace(args[0]) | ||
| if executionID == "" { | ||
| return fmt.Errorf("execution-id cannot be empty") | ||
| } | ||
| req := client.NewRequest(` | ||
| mutation ReplayWorkflowExecution($request: WorkflowRetriggerRequest!) { | ||
| workflow_replay_execution(request: $request) { | ||
| execution_id | ||
| status | ||
| message | ||
| } | ||
| } | ||
| `) | ||
| req.Var("request", map[string]any{ | ||
| "execution_id": executionID, | ||
| }) | ||
|
|
||
| var respData struct { | ||
| WorkflowReplayExecution struct { | ||
| ExecutionID string `json:"execution_id"` | ||
| Status string `json:"status"` | ||
| Message string `json:"message"` | ||
| } `json:"workflow_replay_execution"` | ||
| } | ||
|
|
||
| if err := client.Run(cmd.Context(), req, &respData); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| format.GetFormat().Print(respData.WorkflowReplayExecution) | ||
| return nil | ||
| }, | ||
| } | ||
|
|
||
| func init() { | ||
| workflowCmd.AddCommand(workflowReplayCmd) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| package cmd | ||
|
|
||
| import ( | ||
| "strings" | ||
|
|
||
| "github.com/nudgebee/nbctl/pkg/client" | ||
| "github.com/nudgebee/nbctl/pkg/format" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
blue4209211 marked this conversation as resolved.
|
||
|
|
||
| var workflowTaskDefinitionsCmd = &cobra.Command{ | ||
| Use: "task-definitions", | ||
| Short: "List supported workflow task definitions and action schemas", | ||
| Args: cobra.NoArgs, | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
|
blue4209211 marked this conversation as resolved.
|
||
| nameFilter, _ := cmd.Flags().GetString("name") | ||
| nameFilter = strings.TrimSpace(nameFilter) | ||
| limit, _ := cmd.Flags().GetInt("limit") | ||
|
blue4209211 marked this conversation as resolved.
|
||
|
|
||
| req := client.NewRequest(` | ||
| query ListWorkflowTaskDefinitions($params: WorkflowTaskDefinitionListRequest!) { | ||
| workflow_list_taskdefinitions(params: $params) { | ||
| tasks { | ||
| name | ||
| description | ||
| aliases | ||
| } | ||
| } | ||
| } | ||
| `) | ||
|
|
||
| params := map[string]any{ | ||
| "limit": limit, | ||
| } | ||
| if nameFilter != "" { | ||
| params["name"] = nameFilter | ||
| } | ||
| req.Var("params", params) | ||
|
|
||
| var respData struct { | ||
| WorkflowListTaskdefinitions struct { | ||
| Tasks []struct { | ||
| Name string `json:"name"` | ||
| Description string `json:"description"` | ||
| Aliases []string `json:"aliases"` | ||
| } `json:"tasks"` | ||
| } `json:"workflow_list_taskdefinitions"` | ||
| } | ||
|
|
||
| if err := client.Run(cmd.Context(), req, &respData); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| type taskRow struct { | ||
| Name string `json:"name"` | ||
| Description string `json:"description"` | ||
| Aliases string `json:"aliases"` | ||
| } | ||
| rows := make([]taskRow, 0, len(respData.WorkflowListTaskdefinitions.Tasks)) | ||
| for _, t := range respData.WorkflowListTaskdefinitions.Tasks { | ||
| aliasesStr := "-" | ||
| if len(t.Aliases) > 0 { | ||
| aliasesStr = strings.Join(t.Aliases, ", ") | ||
| } | ||
| rows = append(rows, taskRow{ | ||
| Name: t.Name, | ||
| Description: t.Description, | ||
| Aliases: aliasesStr, | ||
| }) | ||
| } | ||
|
|
||
| table := format.TabularData{ | ||
| Data: rows, | ||
| Fields: []format.TableField{ | ||
| {Header: "Task Name", Field: "Name"}, | ||
| {Header: "Description", Field: "Description"}, | ||
| {Header: "Aliases", Field: "Aliases"}, | ||
| }, | ||
| } | ||
| format.GetFormat().Print(table) | ||
|
|
||
| return nil | ||
| }, | ||
| } | ||
|
|
||
| func init() { | ||
| workflowCmd.AddCommand(workflowTaskDefinitionsCmd) | ||
| workflowTaskDefinitionsCmd.Flags().String("name", "", "Filter task definitions by name") | ||
| workflowTaskDefinitionsCmd.Flags().Int("limit", 100, "Maximum number of task definitions to return") | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| package cmd | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| "github.com/nudgebee/nbctl/pkg/client" | ||
| "github.com/nudgebee/nbctl/pkg/format" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| type workflowTemplate struct { | ||
| ID string `json:"id"` | ||
| Name string `json:"name"` | ||
| Description string `json:"description"` | ||
| Category string `json:"category"` | ||
| IsSystem bool `json:"is_system"` | ||
| Status string `json:"status"` | ||
| } | ||
|
|
||
| var workflowTemplatesCmd = &cobra.Command{ | ||
| Use: "templates", | ||
| Short: "Browse and inspect pre-built workflow templates", | ||
| } | ||
|
|
||
| var workflowTemplatesListCmd = &cobra.Command{ | ||
| Use: "list", | ||
| Short: "List pre-built workflow templates", | ||
| Args: cobra.NoArgs, | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
|
blue4209211 marked this conversation as resolved.
|
||
| typeFlag, _ := cmd.Flags().GetString("type") | ||
|
blue4209211 marked this conversation as resolved.
|
||
| typeFlag = strings.ToLower(typeFlag) | ||
| category, _ := cmd.Flags().GetString("category") | ||
| category = strings.TrimSpace(category) | ||
| limit, _ := cmd.Flags().GetInt("limit") | ||
|
blue4209211 marked this conversation as resolved.
|
||
|
|
||
| if typeFlag != "system" && typeFlag != "custom" && typeFlag != "all" { | ||
| return fmt.Errorf("invalid template type '%s': must be 'system', 'custom', or 'all'", typeFlag) | ||
| } | ||
|
blue4209211 marked this conversation as resolved.
|
||
|
|
||
| req := client.NewRequest(` | ||
| query ListWorkflowTemplates($request: WorkflowListTemplateRequest!) { | ||
| workflow_list_template(request: $request) { | ||
| total_count | ||
| templates { | ||
| id | ||
| name | ||
| description | ||
| category | ||
| is_system | ||
| status | ||
| } | ||
| } | ||
| } | ||
| `) | ||
|
|
||
| input := map[string]any{ | ||
| "type": typeFlag, | ||
| "limit": limit, | ||
| } | ||
| if category != "" { | ||
| input["category"] = category | ||
| } | ||
| req.Var("request", input) | ||
|
blue4209211 marked this conversation as resolved.
|
||
|
|
||
| var respData struct { | ||
| WorkflowListTemplate struct { | ||
| TotalCount int `json:"total_count"` | ||
| Templates []workflowTemplate `json:"templates"` | ||
| } `json:"workflow_list_template"` | ||
| } | ||
|
|
||
| if err := client.Run(cmd.Context(), req, &respData); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| table := format.TabularData{ | ||
| Data: respData.WorkflowListTemplate.Templates, | ||
| Fields: []format.TableField{ | ||
| {Header: "Template ID", Field: "ID"}, | ||
| {Header: "Template Name", Field: "Name"}, | ||
| {Header: "Category", Field: "Category"}, | ||
| {Header: "System", Field: "IsSystem"}, | ||
| {Header: "Description", Field: "Description"}, | ||
| }, | ||
| } | ||
| format.GetFormat().Print(table) | ||
|
|
||
| return nil | ||
| }, | ||
| } | ||
|
|
||
| var workflowTemplatesGetCmd = &cobra.Command{ | ||
| Use: "get <template-id>", | ||
| Short: "Get details for a specific workflow template", | ||
| Args: cobra.ExactArgs(1), | ||
|
blue4209211 marked this conversation as resolved.
|
||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| templateID := strings.TrimSpace(args[0]) | ||
| if templateID == "" { | ||
| return fmt.Errorf("template-id cannot be empty") | ||
| } | ||
| typeFlag, _ := cmd.Flags().GetString("type") | ||
|
blue4209211 marked this conversation as resolved.
|
||
| typeFlag = strings.ToLower(typeFlag) | ||
|
|
||
| if typeFlag != "" && typeFlag != "system" && typeFlag != "custom" && typeFlag != "all" { | ||
| return fmt.Errorf("invalid template type '%s': must be 'system', 'custom', or 'all'", typeFlag) | ||
| } | ||
|
blue4209211 marked this conversation as resolved.
|
||
|
|
||
| typeVal := typeFlag | ||
| if typeVal == "" { | ||
| typeVal = "all" | ||
| } | ||
|
|
||
| req := client.NewRequest(` | ||
| query GetWorkflowTemplate($request: WorkflowGetTemplateRequest!) { | ||
| workflow_get_template(request: $request) { | ||
| id | ||
| name | ||
| description | ||
| category | ||
| is_system | ||
| status | ||
| } | ||
| } | ||
| `) | ||
| req.Var("request", map[string]any{ | ||
| "type": typeVal, | ||
| "id": templateID, | ||
| }) | ||
|
|
||
| var respData struct { | ||
| WorkflowGetTemplate *workflowTemplate `json:"workflow_get_template"` | ||
| } | ||
|
blue4209211 marked this conversation as resolved.
|
||
|
|
||
|
blue4209211 marked this conversation as resolved.
|
||
| if err := client.Run(cmd.Context(), req, &respData); err != nil { | ||
| return err | ||
| } | ||
|
blue4209211 marked this conversation as resolved.
|
||
|
|
||
| if respData.WorkflowGetTemplate == nil || respData.WorkflowGetTemplate.ID == "" { | ||
| return fmt.Errorf("workflow template '%s' not found", templateID) | ||
| } | ||
|
blue4209211 marked this conversation as resolved.
blue4209211 marked this conversation as resolved.
blue4209211 marked this conversation as resolved.
|
||
|
|
||
| format.GetFormat().Print(*respData.WorkflowGetTemplate) | ||
|
blue4209211 marked this conversation as resolved.
|
||
| return nil | ||
| }, | ||
| } | ||
|
|
||
| func init() { | ||
| workflowCmd.AddCommand(workflowTemplatesCmd) | ||
| workflowTemplatesCmd.AddCommand(workflowTemplatesListCmd) | ||
| workflowTemplatesCmd.AddCommand(workflowTemplatesGetCmd) | ||
|
|
||
| workflowTemplatesListCmd.Flags().String("type", "system", "Template type (system, custom, or all)") | ||
| workflowTemplatesListCmd.Flags().String("category", "", "Filter templates by category") | ||
| workflowTemplatesListCmd.Flags().Int("limit", 50, "Maximum number of templates to return") | ||
|
|
||
| workflowTemplatesGetCmd.Flags().String("type", "", "Optional template type filter (system, custom, or all)") | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.