From 769dfb085bd21a7f56badf8cbc26e2ffa6d59024 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Tue, 20 Sep 2022 14:45:11 +0200 Subject: [PATCH] Add support for marking sub workflows --- automation/rest.yaml | 19 +++++---- automation/rest/request/workflow.go | 35 ++++++++++++---- automation/rest/workflow.go | 11 ++--- automation/service/trigger.go | 7 ++++ automation/service/trigger_test.go | 52 ++++++++++++++++++++++++ automation/types/workflow.go | 7 ++++ store/adapters/rdbms/filter.go | 22 ++++++++++ store/tests/automation_workflows_test.go | 23 ++++++++--- 8 files changed, 148 insertions(+), 28 deletions(-) create mode 100644 automation/service/trigger_test.go diff --git a/automation/rest.yaml b/automation/rest.yaml index 03b52b631..4d011586a 100644 --- a/automation/rest.yaml +++ b/automation/rest.yaml @@ -21,15 +21,16 @@ endpoints: path: "/" parameters: get: - - { name: workflowID, type: "[]string", title: "Filter by workflow ID" } - - { name: query, type: "string", title: "Filter workflows" } - - { name: deleted, type: "uint", title: "Exclude (0, default), include (1) or return only (2) deleted workflows" } - - { name: disabled, type: "uint", title: "Exclude (0, default), include (1) or return only (2) disabled workflows" } - - { name: labels, type: "map[string]string", title: "Labels", parser: "label.ParseStrings" } - - { name: limit, type: "uint", title: "Limit" } - - { name: incTotal, type: "bool", title: "Include total rows counter" } - - { name: pageCursor, type: "string", title: "Page cursor" } - - { name: sort, type: "string", title: "Sort items" } + - { name: workflowID, type: "[]string", title: "Filter by workflow ID" } + - { name: query, type: "string", title: "Filter workflows" } + - { name: deleted, type: "uint", title: "Exclude (0, default), include (1) or return only (2) deleted workflows" } + - { name: disabled, type: "uint", title: "Exclude (0, default), include (1) or return only (2) disabled workflows" } + - { name: subWorkflow, type: "uint", title: "Exclude (0, default), include (1) or return only (2) sub workflows" } + - { name: labels, type: "map[string]string", title: "Labels", parser: "label.ParseStrings" } + - { name: limit, type: "uint", title: "Limit" } + - { name: incTotal, type: "bool", title: "Include total rows counter" } + - { name: pageCursor, type: "string", title: "Page cursor" } + - { name: sort, type: "string", title: "Sort items" } - name: create method: POST title: Create workflow diff --git a/automation/rest/request/workflow.go b/automation/rest/request/workflow.go index 16525e04e..42f3ee0db 100644 --- a/automation/rest/request/workflow.go +++ b/automation/rest/request/workflow.go @@ -57,6 +57,11 @@ type ( // Exclude (0, default), include (1) or return only (2) disabled workflows Disabled uint + // SubWorkflow GET parameter + // + // Exclude (0, default), include (1) or return only (2) sub workflows + SubWorkflow uint + // Labels GET parameter // // Labels @@ -281,15 +286,16 @@ func NewWorkflowList() *WorkflowList { // Auditable returns all auditable/loggable parameters func (r WorkflowList) Auditable() map[string]interface{} { return map[string]interface{}{ - "workflowID": r.WorkflowID, - "query": r.Query, - "deleted": r.Deleted, - "disabled": r.Disabled, - "labels": r.Labels, - "limit": r.Limit, - "incTotal": r.IncTotal, - "pageCursor": r.PageCursor, - "sort": r.Sort, + "workflowID": r.WorkflowID, + "query": r.Query, + "deleted": r.Deleted, + "disabled": r.Disabled, + "subWorkflow": r.SubWorkflow, + "labels": r.Labels, + "limit": r.Limit, + "incTotal": r.IncTotal, + "pageCursor": r.PageCursor, + "sort": r.Sort, } } @@ -313,6 +319,11 @@ func (r WorkflowList) GetDisabled() uint { return r.Disabled } +// Auditable returns all auditable/loggable parameters +func (r WorkflowList) GetSubWorkflow() uint { + return r.SubWorkflow +} + // Auditable returns all auditable/loggable parameters func (r WorkflowList) GetLabels() map[string]string { return r.Labels @@ -374,6 +385,12 @@ func (r *WorkflowList) Fill(req *http.Request) (err error) { return err } } + if val, ok := tmp["subWorkflow"]; ok && len(val) > 0 { + r.SubWorkflow, err = payload.ParseUint(val[0]), nil + if err != nil { + return err + } + } if val, ok := tmp["labels[]"]; ok { r.Labels, err = label.ParseStrings(val) if err != nil { diff --git a/automation/rest/workflow.go b/automation/rest/workflow.go index 7407dcd80..c965daf7b 100644 --- a/automation/rest/workflow.go +++ b/automation/rest/workflow.go @@ -83,11 +83,12 @@ func (ctrl Workflow) List(ctx context.Context, r *request.WorkflowList) (interfa var ( err error f = types.WorkflowFilter{ - WorkflowID: payload.ParseUint64s(r.WorkflowID), - Query: r.Query, - Labels: r.Labels, - Deleted: filter.State(r.Deleted), - Disabled: filter.State(r.Disabled), + WorkflowID: payload.ParseUint64s(r.WorkflowID), + Query: r.Query, + Labels: r.Labels, + Deleted: filter.State(r.Deleted), + Disabled: filter.State(r.Disabled), + SubWorkflow: filter.State(r.SubWorkflow), } ) diff --git a/automation/service/trigger.go b/automation/service/trigger.go index d853fff96..1c562da83 100644 --- a/automation/service/trigger.go +++ b/automation/service/trigger.go @@ -712,6 +712,13 @@ func validateWorkflowTriggers(wf *types.Workflow, tt ...*types.Trigger) (wis typ } } } + + if wf.Meta != nil && wf.Meta.SubWorkflow { + wis = wis.Append( + errors.InvalidData("workflow marked as sub-workflow cannot have triggers"), + map[string]int{"trigger": i}, + ) + } } return diff --git a/automation/service/trigger_test.go b/automation/service/trigger_test.go new file mode 100644 index 000000000..1a28747ab --- /dev/null +++ b/automation/service/trigger_test.go @@ -0,0 +1,52 @@ +package service + +import ( + "github.com/cortezaproject/corteza-server/automation/types" + sysEvent "github.com/cortezaproject/corteza-server/system/service/event" + "github.com/stretchr/testify/require" + "testing" +) + +func TestValidateWorkflowTriggersEmpty(t *testing.T) { + var ( + req = require.New(t) + + issues = validateWorkflowTriggers( + &types.Workflow{}, + ) + ) + + req.Empty(issues) +} +func TestValidateWorkflowTriggersRunAs(t *testing.T) { + var ( + req = require.New(t) + soi = sysEvent.SystemOnInterval() + + issues = validateWorkflowTriggers( + &types.Workflow{}, + &types.Trigger{ + Enabled: true, + ResourceType: soi.ResourceType(), + EventType: soi.EventType(), + }, + ) + ) + + req.Len(issues, 1) + req.Contains(issues[0].String(), "requires run-as to be set") +} + +func TestValidateWorkflowTriggersSubWorkflow(t *testing.T) { + var ( + req = require.New(t) + + issues = validateWorkflowTriggers( + &types.Workflow{Meta: &types.WorkflowMeta{SubWorkflow: true}}, + &types.Trigger{Enabled: true}, + ) + ) + + req.Len(issues, 1) + req.Contains(issues[0].String(), "marked as sub-workflow") +} diff --git a/automation/types/workflow.go b/automation/types/workflow.go index c2bee2346..b28d91dfd 100644 --- a/automation/types/workflow.go +++ b/automation/types/workflow.go @@ -53,6 +53,9 @@ type ( Deleted filter.State `json:"deleted"` Disabled filter.State `json:"disabled"` + // include sub-workflows + SubWorkflow filter.State `json:"subWorkflow"` + LabeledIDs []uint64 `json:"-"` Labels map[string]string `json:"labels,omitempty"` @@ -71,6 +74,10 @@ type ( Name string `json:"name"` Description string `json:"description"` Visual map[string]interface{} `json:"visual"` + + // list as one of the sub-workflows, when set to true + // there should be no enabled triggers on this workflow + SubWorkflow bool `json:"subWorkflow,omitempty"` } WorkflowIssue struct { diff --git a/store/adapters/rdbms/filter.go b/store/adapters/rdbms/filter.go index 7e3a1db6f..feb133340 100644 --- a/store/adapters/rdbms/filter.go +++ b/store/adapters/rdbms/filter.go @@ -2,6 +2,7 @@ package rdbms import ( "fmt" + "github.com/cortezaproject/corteza-server/pkg/dal" "strings" "time" @@ -304,6 +305,27 @@ func DefaultFilters() (f *extendedFilters) { return ee, f, err } + f.AutomationWorkflow = func(s *Store, f automationType.WorkflowFilter) (ee []goqu.Expression, _ automationType.WorkflowFilter, err error) { + if ee, f, err = AutomationWorkflowFilter(f); err != nil { + return + } + + if f.SubWorkflow != filter.StateInclusive { + vattr := &dal.Attribute{Type: &dal.TypeBoolean{}} + litexp, _ := s.Dialect.DeepIdentJSON(goqu.C("meta"), "subWorkflow") + litexp, _ = s.Dialect.AttributeCast(vattr, litexp) + + switch f.SubWorkflow { + case filter.StateExcluded: + ee = append(ee, goqu.Or(litexp.IsFalse(), litexp.IsNull())) + case filter.StateExclusive: + ee = append(ee, litexp.IsTrue()) + } + } + + return ee, f, err + } + return } diff --git a/store/tests/automation_workflows_test.go b/store/tests/automation_workflows_test.go index cb872aa2b..8832c6ab2 100644 --- a/store/tests/automation_workflows_test.go +++ b/store/tests/automation_workflows_test.go @@ -89,12 +89,17 @@ func testAutomationWorkflows(t *testing.T, s store.AutomationWorkflows) { makeNew("two-one"), makeNew("two-two"), makeNew("two-deleted"), + func() *types.Workflow { + wf := makeNew("subwf") + wf.Meta = &types.WorkflowMeta{SubWorkflow: true} + return wf + }(), } count := len(prefill) prefill[4].DeletedAt = &prefill[4].CreatedAt - valid := count - 1 + valid := count - 2 req.NoError(s.TruncateAutomationWorkflows(ctx)) req.NoError(s.CreateAutomationWorkflow(ctx, prefill...)) @@ -102,12 +107,15 @@ func testAutomationWorkflows(t *testing.T, s store.AutomationWorkflows) { // search for all valid set, f, err := s.SearchAutomationWorkflows(ctx, types.WorkflowFilter{}) req.NoError(err) - req.Len(set, valid) // we've deleted one + req.Len(set, valid) // all but deleted or workflows - // search for ALL - set, f, err = s.SearchAutomationWorkflows(ctx, types.WorkflowFilter{Deleted: filter.StateInclusive}) + // search for ALL, including deleted and sub-workflows + set, f, err = s.SearchAutomationWorkflows(ctx, types.WorkflowFilter{ + Deleted: filter.StateInclusive, + SubWorkflow: filter.StateInclusive, + }) req.NoError(err) - req.Len(set, count) // we've deleted one + req.Len(set, count) // search for deleted only set, f, err = s.SearchAutomationWorkflows(ctx, types.WorkflowFilter{Deleted: filter.StateExclusive}) @@ -119,6 +127,11 @@ func testAutomationWorkflows(t *testing.T, s store.AutomationWorkflows) { req.NoError(err) req.Len(set, 2) + // find just sub-workflows (one) + set, f, err = s.SearchAutomationWorkflows(ctx, types.WorkflowFilter{SubWorkflow: filter.StateExclusive}) + req.NoError(err) + req.Len(set, 1) + _ = f // dummy }) }