diff --git a/mdl/executor/cmd_microflows_format_action.go b/mdl/executor/cmd_microflows_format_action.go index ce277326..5a8e0cba 100644 --- a/mdl/executor/cmd_microflows_format_action.go +++ b/mdl/executor/cmd_microflows_format_action.go @@ -753,6 +753,24 @@ func (e *Executor) formatListOperation(op microflows.ListOperation, outputVar st return fmt.Sprintf("$%s = CONTAINS($%s, $%s);", outputVar, o.ListVariable, o.ObjectVariable) case *microflows.EqualsOperation: return fmt.Sprintf("$%s = EQUALS($%s, $%s);", outputVar, o.ListVariable1, o.ListVariable2) + case *microflows.FindByAttributeOperation: + ref := o.Attribute + if ref == "" { + ref = o.Association + } + parts := strings.Split(ref, ".") + fieldName := parts[len(parts)-1] + return fmt.Sprintf("$%s = FIND($%s, $currentObject/%s = %s);", outputVar, o.ListVariable, fieldName, o.Expression) + case *microflows.FilterByAttributeOperation: + ref := o.Attribute + if ref == "" { + ref = o.Association + } + parts := strings.Split(ref, ".") + fieldName := parts[len(parts)-1] + return fmt.Sprintf("$%s = FILTER($%s, $currentObject/%s = %s);", outputVar, o.ListVariable, fieldName, o.Expression) + case *microflows.RangeOperation: + return fmt.Sprintf("$%s = RANGE($%s, %s, %s);", outputVar, o.ListVariable, o.OffsetExpression, o.LimitExpression) default: return fmt.Sprintf("$%s = LIST OPERATION %T;", outputVar, op) } diff --git a/mdl/executor/cmd_microflows_format_listop_test.go b/mdl/executor/cmd_microflows_format_listop_test.go index ad2b2f84..3204b8b2 100644 --- a/mdl/executor/cmd_microflows_format_listop_test.go +++ b/mdl/executor/cmd_microflows_format_listop_test.go @@ -104,3 +104,51 @@ func TestFormatListOperation_Nil(t *testing.T) { t.Errorf("got %q", got) } } + +func TestFormatListOperation_FindByAttribute(t *testing.T) { + e := newTestExecutor() + got := e.formatListOperation(µflows.FindByAttributeOperation{ + ListVariable: "Orders", + Attribute: "MyModule.Order.Status", + Expression: "'Active'", + }, "Found") + if got != "$Found = FIND($Orders, $currentObject/Status = 'Active');" { + t.Errorf("got %q", got) + } +} + +func TestFormatListOperation_FindByAssociation(t *testing.T) { + e := newTestExecutor() + got := e.formatListOperation(µflows.FindByAttributeOperation{ + ListVariable: "Orders", + Association: "MyModule.Order_Customer", + Expression: "$Customer", + }, "Found") + if got != "$Found = FIND($Orders, $currentObject/Order_Customer = $Customer);" { + t.Errorf("got %q", got) + } +} + +func TestFormatListOperation_FilterByAttribute(t *testing.T) { + e := newTestExecutor() + got := e.formatListOperation(µflows.FilterByAttributeOperation{ + ListVariable: "Orders", + Attribute: "MyModule.Order.IsActive", + Expression: "true", + }, "Filtered") + if got != "$Filtered = FILTER($Orders, $currentObject/IsActive = true);" { + t.Errorf("got %q", got) + } +} + +func TestFormatListOperation_Range(t *testing.T) { + e := newTestExecutor() + got := e.formatListOperation(µflows.RangeOperation{ + ListVariable: "Orders", + OffsetExpression: "0", + LimitExpression: "10", + }, "Page") + if got != "$Page = RANGE($Orders, 0, 10);" { + t.Errorf("got %q", got) + } +} diff --git a/sdk/microflows/microflows_actions.go b/sdk/microflows/microflows_actions.go index 374d1216..00c06bd1 100644 --- a/sdk/microflows/microflows_actions.go +++ b/sdk/microflows/microflows_actions.go @@ -238,6 +238,38 @@ type FilterOperation struct { func (FilterOperation) isListOperation() {} +// FindByAttributeOperation finds an element by attribute or association value. +type FindByAttributeOperation struct { + model.BaseElement + ListVariable string `json:"listVariable"` + Attribute string `json:"attribute,omitempty"` + Association string `json:"association,omitempty"` + Expression string `json:"expression"` +} + +func (FindByAttributeOperation) isListOperation() {} + +// FilterByAttributeOperation filters a list by attribute or association value. +type FilterByAttributeOperation struct { + model.BaseElement + ListVariable string `json:"listVariable"` + Attribute string `json:"attribute,omitempty"` + Association string `json:"association,omitempty"` + Expression string `json:"expression"` +} + +func (FilterByAttributeOperation) isListOperation() {} + +// RangeOperation takes a range slice from a list. +type RangeOperation struct { + model.BaseElement + ListVariable string `json:"listVariable"` + OffsetExpression string `json:"offsetExpression"` + LimitExpression string `json:"limitExpression"` +} + +func (RangeOperation) isListOperation() {} + // SortOperation sorts a list. type SortOperation struct { model.BaseElement diff --git a/sdk/mpr/parser_listoperation_test.go b/sdk/mpr/parser_listoperation_test.go new file mode 100644 index 00000000..714ccebf --- /dev/null +++ b/sdk/mpr/parser_listoperation_test.go @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: Apache-2.0 + +package mpr + +import ( + "testing" + + "github.com/mendixlabs/mxcli/sdk/microflows" +) + +func TestParseListOperation_FindByAttribute(t *testing.T) { + raw := map[string]any{ + "$Type": "Microflows$Find", + "$ID": nil, + "ListName": "Orders", + "Attribute": "MyModule.Order.Status", + "Expression": "'Active'", + } + op := parseListOperation(raw) + got, ok := op.(*microflows.FindByAttributeOperation) + if !ok { + t.Fatalf("expected *FindByAttributeOperation, got %T", op) + } + if got.ListVariable != "Orders" { + t.Errorf("ListVariable: got %q, want %q", got.ListVariable, "Orders") + } + if got.Attribute != "MyModule.Order.Status" { + t.Errorf("Attribute: got %q, want %q", got.Attribute, "MyModule.Order.Status") + } + if got.Expression != "'Active'" { + t.Errorf("Expression: got %q, want %q", got.Expression, "'Active'") + } +} + +func TestParseListOperation_FindByAssociation(t *testing.T) { + raw := map[string]any{ + "$Type": "Microflows$Find", + "$ID": nil, + "ListName": "Orders", + "Association": "MyModule.Order_Customer", + "Expression": "$Customer", + } + op := parseListOperation(raw) + got, ok := op.(*microflows.FindByAttributeOperation) + if !ok { + t.Fatalf("expected *FindByAttributeOperation, got %T", op) + } + if got.Association != "MyModule.Order_Customer" { + t.Errorf("Association: got %q, want %q", got.Association, "MyModule.Order_Customer") + } +} + +func TestParseListOperation_FilterByAttribute(t *testing.T) { + raw := map[string]any{ + "$Type": "Microflows$Filter", + "$ID": nil, + "ListName": "Orders", + "Attribute": "MyModule.Order.IsActive", + "Expression": "true", + } + op := parseListOperation(raw) + got, ok := op.(*microflows.FilterByAttributeOperation) + if !ok { + t.Fatalf("expected *FilterByAttributeOperation, got %T", op) + } + if got.ListVariable != "Orders" { + t.Errorf("ListVariable: got %q, want %q", got.ListVariable, "Orders") + } + if got.Attribute != "MyModule.Order.IsActive" { + t.Errorf("Attribute: got %q, want %q", got.Attribute, "MyModule.Order.IsActive") + } +} + +func TestParseListOperation_Range(t *testing.T) { + raw := map[string]any{ + "$Type": "Microflows$ListRange", + "$ID": nil, + "ListName": "Orders", + "CustomRange": map[string]any{ + "$Type": "Microflows$CustomRange", + "OffsetExpression": "0", + "LimitExpression": "10", + }, + } + op := parseListOperation(raw) + got, ok := op.(*microflows.RangeOperation) + if !ok { + t.Fatalf("expected *RangeOperation, got %T", op) + } + if got.ListVariable != "Orders" { + t.Errorf("ListVariable: got %q, want %q", got.ListVariable, "Orders") + } + if got.OffsetExpression != "0" { + t.Errorf("OffsetExpression: got %q, want %q", got.OffsetExpression, "0") + } + if got.LimitExpression != "10" { + t.Errorf("LimitExpression: got %q, want %q", got.LimitExpression, "10") + } +} diff --git a/sdk/mpr/parser_microflow.go b/sdk/mpr/parser_microflow.go index 452e794f..f07d5369 100644 --- a/sdk/mpr/parser_microflow.go +++ b/sdk/mpr/parser_microflow.go @@ -993,6 +993,34 @@ func parseListOperation(raw map[string]any) microflows.ListOperation { ListVariable1: listVar, ListVariable2: extractString(raw["SecondListOrObjectName"]), } + case "Microflows$Find": + return µflows.FindByAttributeOperation{ + BaseElement: model.BaseElement{ID: id}, + ListVariable: listVar, + Attribute: extractString(raw["Attribute"]), + Association: extractString(raw["Association"]), + Expression: extractString(raw["Expression"]), + } + case "Microflows$Filter": + return µflows.FilterByAttributeOperation{ + BaseElement: model.BaseElement{ID: id}, + ListVariable: listVar, + Attribute: extractString(raw["Attribute"]), + Association: extractString(raw["Association"]), + Expression: extractString(raw["Expression"]), + } + case "Microflows$ListRange": + offset, limit := "", "" + if cr, ok := raw["CustomRange"].(map[string]any); ok { + offset = extractString(cr["OffsetExpression"]) + limit = extractString(cr["LimitExpression"]) + } + return µflows.RangeOperation{ + BaseElement: model.BaseElement{ID: id}, + ListVariable: listVar, + OffsetExpression: offset, + LimitExpression: limit, + } default: return nil }