diff --git a/automation/service/service.go b/automation/service/service.go index c1f77213d..12682f2c4 100644 --- a/automation/service/service.go +++ b/automation/service/service.go @@ -2,6 +2,8 @@ package service import ( "context" + "time" + "github.com/cortezaproject/corteza-server/automation/automation" "github.com/cortezaproject/corteza-server/pkg/actionlog" "github.com/cortezaproject/corteza-server/pkg/corredor" @@ -13,7 +15,6 @@ import ( "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" "go.uber.org/zap" - "time" ) type ( @@ -99,6 +100,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, c Config) Registry().AddTypes( &expr.Any{}, + &expr.Array{}, &expr.Boolean{}, &expr.ID{}, &expr.Integer{}, diff --git a/compose/automation/records_handler.gen.go b/compose/automation/records_handler.gen.go index 3ad6a21e2..821cdb72c 100644 --- a/compose/automation/records_handler.gen.go +++ b/compose/automation/records_handler.gen.go @@ -29,6 +29,8 @@ func (h recordsHandler) register() { h.reg.AddFunctions( h.Lookup(), h.Search(), + h.First(), + h.Last(), h.Each(), h.New(), h.Validate(), @@ -436,6 +438,264 @@ func (h recordsHandler) Search() *atypes.Function { } } +type ( + recordsFirstArgs struct { + hasModule bool + Module interface{} + moduleID uint64 + moduleHandle string + moduleRes *types.Module + + hasNamespace bool + Namespace interface{} + namespaceID uint64 + namespaceHandle string + namespaceRes *types.Namespace + } + + recordsFirstResults struct { + Record *types.Record + } +) + +func (a recordsFirstArgs) GetModule() (bool, uint64, string, *types.Module) { + return a.hasModule, a.moduleID, a.moduleHandle, a.moduleRes +} + +func (a recordsFirstArgs) GetNamespace() (bool, uint64, string, *types.Namespace) { + return a.hasNamespace, a.namespaceID, a.namespaceHandle, a.namespaceRes +} + +// First function Returns the first created record +// +// expects implementation of first function: +// func (h recordsHandler) first(ctx context.Context, args *recordsFirstArgs) (results *recordsFirstResults, err error) { +// return +// } +func (h recordsHandler) First() *atypes.Function { + return &atypes.Function{ + Ref: "composeRecordsFirst", + Kind: "function", + Labels: map[string]string{"compose": "step,workflow", "record": "step,workflow"}, + Meta: &atypes.FunctionMeta{ + Short: "Returns the first created record", + }, + + Parameters: []*atypes.Param{ + { + Name: "module", + Types: []string{"ID", "Handle", "ComposeModule"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Module to set record type", + Description: "Even with unique record ID across all modules, module needs to be known\nbefore doing any record operations. Mainly because records of different\nmodules can be located in different stores.", + }, + }, + { + Name: "namespace", + Types: []string{"ID", "Handle", "ComposeNamespace"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "record", + Types: []string{"ComposeRecord"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &recordsFirstArgs{ + hasModule: in.Has("module"), + hasNamespace: in.Has("namespace"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Module argument + if args.hasModule { + aux := expr.Must(expr.Select(in, "module")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.moduleID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.moduleHandle = aux.Get().(string) + case h.reg.Type("ComposeModule").Type(): + args.moduleRes = aux.Get().(*types.Module) + } + } + + // Converting Namespace argument + if args.hasNamespace { + aux := expr.Must(expr.Select(in, "namespace")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.namespaceID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.namespaceHandle = aux.Get().(string) + case h.reg.Type("ComposeNamespace").Type(): + args.namespaceRes = aux.Get().(*types.Namespace) + } + } + + var results *recordsFirstResults + if results, err = h.first(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Record (*types.Record) to ComposeRecord + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("ComposeRecord").Cast(results.Record); err != nil { + return + } else if err = expr.Assign(out, "record", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + recordsLastArgs struct { + hasModule bool + Module interface{} + moduleID uint64 + moduleHandle string + moduleRes *types.Module + + hasNamespace bool + Namespace interface{} + namespaceID uint64 + namespaceHandle string + namespaceRes *types.Namespace + } + + recordsLastResults struct { + Record *types.Record + } +) + +func (a recordsLastArgs) GetModule() (bool, uint64, string, *types.Module) { + return a.hasModule, a.moduleID, a.moduleHandle, a.moduleRes +} + +func (a recordsLastArgs) GetNamespace() (bool, uint64, string, *types.Namespace) { + return a.hasNamespace, a.namespaceID, a.namespaceHandle, a.namespaceRes +} + +// Last function Returns the last created record +// +// expects implementation of last function: +// func (h recordsHandler) last(ctx context.Context, args *recordsLastArgs) (results *recordsLastResults, err error) { +// return +// } +func (h recordsHandler) Last() *atypes.Function { + return &atypes.Function{ + Ref: "composeRecordsLast", + Kind: "function", + Labels: map[string]string{"compose": "step,workflow", "record": "step,workflow"}, + Meta: &atypes.FunctionMeta{ + Short: "Returns the last created record", + }, + + Parameters: []*atypes.Param{ + { + Name: "module", + Types: []string{"ID", "Handle", "ComposeModule"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Module to set record type", + Description: "Even with unique record ID across all modules, module needs to be known\nbefore doing any record operations. Mainly because records of different\nmodules can be located in different stores.", + }, + }, + { + Name: "namespace", + Types: []string{"ID", "Handle", "ComposeNamespace"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "record", + Types: []string{"ComposeRecord"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &recordsLastArgs{ + hasModule: in.Has("module"), + hasNamespace: in.Has("namespace"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Module argument + if args.hasModule { + aux := expr.Must(expr.Select(in, "module")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.moduleID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.moduleHandle = aux.Get().(string) + case h.reg.Type("ComposeModule").Type(): + args.moduleRes = aux.Get().(*types.Module) + } + } + + // Converting Namespace argument + if args.hasNamespace { + aux := expr.Must(expr.Select(in, "namespace")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.namespaceID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.namespaceHandle = aux.Get().(string) + case h.reg.Type("ComposeNamespace").Type(): + args.namespaceRes = aux.Get().(*types.Namespace) + } + } + + var results *recordsLastResults + if results, err = h.last(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Record (*types.Record) to ComposeRecord + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("ComposeRecord").Cast(results.Record); err != nil { + return + } else if err = expr.Assign(out, "record", tval); err != nil { + return + } + } + + return + }, + } +} + type ( recordsEachArgs struct { hasModule bool diff --git a/compose/automation/records_handler.go b/compose/automation/records_handler.go index 5e0d101b5..391d64686 100644 --- a/compose/automation/records_handler.go +++ b/compose/automation/records_handler.go @@ -2,7 +2,9 @@ package automation import ( "context" + "errors" "fmt" + "github.com/cortezaproject/corteza-server/compose/types" . "github.com/cortezaproject/corteza-server/pkg/expr" "github.com/cortezaproject/corteza-server/pkg/filter" @@ -105,6 +107,28 @@ func (h recordsHandler) search(ctx context.Context, args *recordsSearchArgs) (re return } +func (h recordsHandler) first(ctx context.Context, args *recordsFirstArgs) (results *recordsFirstResults, err error) { + r, err := h.fetchEdge(ctx, args, true) + if err != nil { + return nil, err + } + + return &recordsFirstResults{ + Record: r, + }, nil +} + +func (h recordsHandler) last(ctx context.Context, args *recordsLastArgs) (results *recordsLastResults, err error) { + r, err := h.fetchEdge(ctx, args, false) + if err != nil { + return nil, err + } + + return &recordsLastResults{ + Record: r, + }, nil +} + func (h recordsHandler) each(ctx context.Context, args *recordsEachArgs) (out wfexec.IteratorHandler, err error) { var ( i = &recordSetIterator{} @@ -258,6 +282,34 @@ func (h recordsHandler) loadCombo(ctx context.Context, args interface{}) (namesp return } +func (h recordsHandler) fetchEdge(ctx context.Context, args interface{}, first bool) (*types.Record, error) { + f := types.RecordFilter{} + + if first { + f.Sort.Set("createdAt DESC") + } else { + f.Sort.Set("createdAt ASC") + } + + f.Limit = 1 + + if ns, mod, err := h.loadCombo(ctx, args); err != nil { + return nil, err + } else { + f.ModuleID = mod.ID + f.NamespaceID = ns.ID + } + + rr, _, err := h.rec.Find(ctx, f) + if err != nil { + return nil, err + } + if len(rr) == 0 { + return nil, errors.New("could not fetch records: no records found") + } + return rr[0], nil +} + func (i *recordSetIterator) More(context.Context, *Vars) (bool, error) { return i.ptr < len(i.set), nil } diff --git a/compose/automation/records_handler.yaml b/compose/automation/records_handler.yaml index f040fda3b..f2d0a7d8b 100644 --- a/compose/automation/records_handler.yaml +++ b/compose/automation/records_handler.yaml @@ -85,6 +85,10 @@ params: types: - { wf: String } + edgeParams: &edgeParams + module: *moduleLookup + namespace: *namespaceLookup + labels: &labels record: "step,workflow" compose: "step,workflow" @@ -115,6 +119,24 @@ functions: total: *rvTotal pageCursor: *rvPageCursor + first: + meta: + short: Returns the first created record + labels: + <<: *labels + params: *edgeParams + results: + record: *rvRecord + + last: + meta: + short: Returns the last created record + labels: + <<: *labels + params: *edgeParams + results: + record: *rvRecord + each: kind: iterator meta: