snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
460
server/compose/automation/records_handler.go
Normal file
460
server/compose/automation/records_handler.go
Normal file
@@ -0,0 +1,460 @@
|
||||
package automation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
. "github.com/cortezaproject/corteza/server/pkg/expr"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
"github.com/cortezaproject/corteza/server/pkg/wfexec"
|
||||
)
|
||||
|
||||
type (
|
||||
recordService interface {
|
||||
FindByID(ctx context.Context, namespaceID, moduleID, recordID uint64) (*types.Record, *types.RecordValueErrorSet, error)
|
||||
Find(ctx context.Context, filter types.RecordFilter) (set types.RecordSet, f types.RecordFilter, err error)
|
||||
|
||||
Create(ctx context.Context, record *types.Record) (*types.Record, *types.RecordValueErrorSet, error)
|
||||
Update(ctx context.Context, record *types.Record) (*types.Record, *types.RecordValueErrorSet, error)
|
||||
Bulk(ctx context.Context, skipFailed bool, oo ...*types.RecordBulkOperation) ([]types.RecordBulkOperationResult, error)
|
||||
Report(ctx context.Context, namespaceID, moduleID uint64, metrics, dimensions, filter string) (out any, err error)
|
||||
|
||||
Validate(ctx context.Context, rec *types.Record) error
|
||||
|
||||
DeleteByID(ctx context.Context, namespaceID, moduleID uint64, recordID ...uint64) error
|
||||
}
|
||||
|
||||
recordsHandler struct {
|
||||
reg recordsHandlerRegistry
|
||||
ns namespaceService
|
||||
mod moduleService
|
||||
rec recordService
|
||||
}
|
||||
|
||||
recordSetIterator struct {
|
||||
// Item buffer, current item pointer, and total items traversed
|
||||
ptr uint
|
||||
buffer types.RecordSet
|
||||
total uint
|
||||
|
||||
// When filter limit is set, this constraints it
|
||||
iterLimit uint
|
||||
useIterLimit bool
|
||||
|
||||
// Item loader for additional chunks
|
||||
filter types.RecordFilter
|
||||
loader func() error
|
||||
}
|
||||
|
||||
recordLookup interface {
|
||||
GetRecord() (bool, uint64, *types.Record)
|
||||
}
|
||||
)
|
||||
|
||||
func RecordsHandler(reg recordsHandlerRegistry, ns namespaceService, mod moduleService, rec recordService) *recordsHandler {
|
||||
h := &recordsHandler{
|
||||
reg: reg,
|
||||
ns: ns,
|
||||
mod: mod,
|
||||
rec: rec,
|
||||
}
|
||||
|
||||
h.register()
|
||||
return h
|
||||
}
|
||||
|
||||
func (h recordsHandler) lookup(ctx context.Context, args *recordsLookupArgs) (results *recordsLookupResults, err error) {
|
||||
results = &recordsLookupResults{}
|
||||
results.Record, err = h.lookupRecord(ctx, args)
|
||||
return
|
||||
}
|
||||
|
||||
func (h recordsHandler) search(ctx context.Context, args *recordsSearchArgs) (results *recordsSearchResults, err error) {
|
||||
results = &recordsSearchResults{}
|
||||
|
||||
var (
|
||||
f = types.RecordFilter{
|
||||
Query: args.Query,
|
||||
Meta: args.Meta,
|
||||
Deleted: filter.State(args.Deleted),
|
||||
}
|
||||
)
|
||||
|
||||
if ns, mod, err := h.loadCombo(ctx, args); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
f.ModuleID = mod.ID
|
||||
f.NamespaceID = ns.ID
|
||||
}
|
||||
|
||||
if args.hasSort {
|
||||
if err = f.Sort.Set(args.Sort); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if args.hasPageCursor {
|
||||
if args.PageCursor != "" {
|
||||
f.PageCursor = &filter.PagingCursor{}
|
||||
if err = f.PageCursor.UnmarshalJSON([]byte(args.PageCursor)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f.IncTotal = args.IncTotal
|
||||
f.IncPageNavigation = args.IncPageNavigation
|
||||
|
||||
if args.hasMeta {
|
||||
f.Meta = args.Meta
|
||||
}
|
||||
|
||||
if args.hasLimit {
|
||||
f.Limit = uint(args.Limit)
|
||||
}
|
||||
|
||||
var auxf types.RecordFilter
|
||||
results.Records, auxf, err = h.rec.Find(ctx, f)
|
||||
results.Total = uint64(auxf.Total)
|
||||
if auxf.NextPage != nil {
|
||||
results.NextPage = auxf.NextPage.Encode()
|
||||
}
|
||||
if auxf.PrevPage != nil {
|
||||
results.PrevPage = auxf.PrevPage.Encode()
|
||||
}
|
||||
|
||||
// Always assure at least empty cursor when requesting page nav.
|
||||
if auxf.PageNavigation == nil && auxf.IncPageNavigation {
|
||||
results.PageNavigation = append(results.PageNavigation, nil)
|
||||
}
|
||||
|
||||
for _, pn := range auxf.PageNavigation {
|
||||
if pn == nil {
|
||||
continue
|
||||
}
|
||||
// first page is null -- no cursor
|
||||
if pn.Cursor == nil {
|
||||
results.PageNavigation = append(results.PageNavigation, nil)
|
||||
} else {
|
||||
results.PageNavigation = append(results.PageNavigation, Must(NewString(pn.Cursor.Encode())))
|
||||
}
|
||||
}
|
||||
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{}
|
||||
f = types.RecordFilter{
|
||||
Query: args.Query,
|
||||
Meta: args.Meta,
|
||||
Deleted: filter.State(args.Deleted),
|
||||
}
|
||||
)
|
||||
|
||||
if ns, mod, err := h.loadCombo(ctx, args); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
f.ModuleID = mod.ID
|
||||
f.NamespaceID = ns.ID
|
||||
}
|
||||
|
||||
if args.hasSort {
|
||||
if err = f.Sort.Set(args.Sort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if args.hasPageCursor {
|
||||
if args.PageCursor != "" {
|
||||
f.NextPage = &filter.PagingCursor{}
|
||||
if err = f.NextPage.UnmarshalJSON([]byte(args.PageCursor)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f.IncTotal = args.IncTotal
|
||||
f.IncPageNavigation = args.IncPageNavigation
|
||||
|
||||
if args.hasMeta {
|
||||
f.Meta = args.Meta
|
||||
}
|
||||
|
||||
if args.hasLimit {
|
||||
i.useIterLimit = true
|
||||
i.iterLimit = uint(args.Limit)
|
||||
|
||||
f.Limit = uint(args.Limit)
|
||||
|
||||
if args.Limit > uint64(wfexec.MaxIteratorBufferSize) {
|
||||
f.Limit = wfexec.MaxIteratorBufferSize
|
||||
}
|
||||
i.iterLimit = uint(args.Limit)
|
||||
} else {
|
||||
f.Limit = wfexec.MaxIteratorBufferSize
|
||||
}
|
||||
|
||||
i.filter = f
|
||||
i.loader = func() (err error) {
|
||||
// Edgecase
|
||||
if i.filter.PageCursor != nil && i.filter.NextPage == nil {
|
||||
return
|
||||
}
|
||||
|
||||
i.total += i.ptr
|
||||
i.ptr = 0
|
||||
|
||||
i.filter.PageCursor = i.filter.NextPage
|
||||
i.filter.NextPage = nil
|
||||
i.buffer, i.filter, err = h.rec.Find(ctx, i.filter)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Initial load
|
||||
return i, i.loader()
|
||||
}
|
||||
|
||||
func (h recordsHandler) validate(ctx context.Context, args *recordsValidateArgs) (*recordsValidateResults, error) {
|
||||
results := &recordsValidateResults{Valid: true}
|
||||
if err := h.rec.Validate(ctx, args.Record); err != nil {
|
||||
results.Valid = false
|
||||
|
||||
//if rves, is := err.(*types.RecordValueErrorSet); is {
|
||||
// results.Errors = rves
|
||||
//} else {
|
||||
// return nil, err
|
||||
//}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (h recordsHandler) new(ctx context.Context, args *recordsNewArgs) (*recordsNewResults, error) {
|
||||
results := &recordsNewResults{}
|
||||
namespace, module, err := h.loadCombo(ctx, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results.Record = &types.Record{
|
||||
ModuleID: module.ID,
|
||||
NamespaceID: namespace.ID,
|
||||
}
|
||||
|
||||
results.Record.SetModule(module)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (h recordsHandler) create(ctx context.Context, args *recordsCreateArgs) (results *recordsCreateResults, err error) {
|
||||
results = &recordsCreateResults{}
|
||||
results.Record, err = wrapRecordValueErrorSet(h.rec.Create(ctx, args.Record))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (h recordsHandler) update(ctx context.Context, args *recordsUpdateArgs) (results *recordsUpdateResults, err error) {
|
||||
results = &recordsUpdateResults{}
|
||||
results.Record, err = wrapRecordValueErrorSet(h.rec.Update(ctx, args.Record))
|
||||
return
|
||||
}
|
||||
|
||||
func (h recordsHandler) delete(ctx context.Context, args *recordsDeleteArgs) error {
|
||||
if rec, err := h.lookupRecord(ctx, args); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return h.rec.DeleteByID(ctx, rec.NamespaceID, rec.ModuleID, rec.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func (h recordsHandler) report(ctx context.Context, args *recordsReportArgs) (*recordsReportResults, error) {
|
||||
r := &recordsReportResults{}
|
||||
|
||||
ns, mod, err := h.loadCombo(ctx, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.Report, err = h.rec.Report(ctx, ns.ID, mod.ID, args.Metrics, args.Dimensons, args.Filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (h recordsHandler) lookupRecord(ctx context.Context, args recordLookup) (record *types.Record, err error) {
|
||||
var (
|
||||
namespace *types.Namespace
|
||||
module *types.Module
|
||||
recordID uint64
|
||||
)
|
||||
|
||||
if _, recordID, record = args.GetRecord(); record != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if namespace, module, err = h.loadCombo(ctx, args); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
record, _, err = h.rec.FindByID(ctx, namespace.ID, module.ID, recordID)
|
||||
return
|
||||
}
|
||||
|
||||
func (h recordsHandler) loadCombo(ctx context.Context, args interface{}) (namespace *types.Namespace, module *types.Module, err error) {
|
||||
if lkp, is := args.(namespaceLookup); is {
|
||||
if namespace, err = lookupNamespace(ctx, h.ns, lkp); err != nil {
|
||||
err = fmt.Errorf("could not load namespace: %w", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("could not extract namespace lookup arguments")
|
||||
return
|
||||
}
|
||||
|
||||
if lkp, is := args.(moduleLookup); is {
|
||||
if module, err = lookupModule(ctx, h.ns, h.mod, lkp); err != nil {
|
||||
err = fmt.Errorf("could not load module: %w", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("could not extract module lookup arguments")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (h recordsHandler) fetchEdge(ctx context.Context, args interface{}, first bool) (*types.Record, error) {
|
||||
f := types.RecordFilter{}
|
||||
|
||||
if first {
|
||||
f.Sort.Set("createdAt ASC")
|
||||
} else {
|
||||
f.Sort.Set("createdAt DESC")
|
||||
}
|
||||
|
||||
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, fmt.Errorf("could not fetch records: no records found")
|
||||
}
|
||||
return rr[0], nil
|
||||
}
|
||||
|
||||
func (i *recordSetIterator) More(context.Context, *Vars) (bool, error) {
|
||||
return wfexec.GenericResourceNextCheck(i.useIterLimit, i.ptr, uint(len(i.buffer)), i.total, i.iterLimit, i.filter.NextPage != nil), nil
|
||||
}
|
||||
|
||||
func (i *recordSetIterator) Start(context.Context, *Vars) error { i.ptr = 0; return nil }
|
||||
|
||||
func (i *recordSetIterator) Next(context.Context, *Vars) (out *Vars, err error) {
|
||||
if len(i.buffer)-int(i.ptr) <= 0 {
|
||||
if err = i.loader(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
out = &Vars{}
|
||||
out.Set("record", Must(NewComposeRecord(i.buffer[i.ptr])))
|
||||
out.Set("index", Must(NewInteger(i.total+i.ptr)))
|
||||
out.Set("total", Must(NewInteger(i.filter.Total)))
|
||||
|
||||
i.ptr++
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func wrapRecordValueErrorSet(rr *types.Record, res *types.RecordValueErrorSet, ee error) (r *types.Record, err error) {
|
||||
var (
|
||||
ss string
|
||||
j []byte
|
||||
e error
|
||||
)
|
||||
|
||||
r = rr
|
||||
err = ee
|
||||
|
||||
if res = types.IsRecordValueErrorSet(err); res == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !res.Safe() {
|
||||
return rr, err
|
||||
}
|
||||
|
||||
for _, vv := range res.Set {
|
||||
if j, e = json.Marshal(vv.Meta); e != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ss = fmt.Sprintf("%s, %s (%s)", ss, strings.ToLower(vv.Message), j)
|
||||
|
||||
}
|
||||
|
||||
re := regexp.MustCompile("^,\\s")
|
||||
ss = re.ReplaceAllString(ss, "")
|
||||
|
||||
return rr, errors.New(ss)
|
||||
}
|
||||
|
||||
func (h recordsHandler) clone(ctx context.Context, args *recordsCloneArgs) (*recordsCloneResults, error) {
|
||||
results := &recordsCloneResults{}
|
||||
|
||||
rec, err := h.lookupRecord(ctx, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results.Record = rec.Clone()
|
||||
|
||||
results.Record.ID = 0
|
||||
results.Record.CreatedAt = time.Now()
|
||||
results.Record.UpdatedAt = nil
|
||||
results.Record.DeletedAt = nil
|
||||
results.Record.CreatedBy = 0
|
||||
results.Record.UpdatedBy = 0
|
||||
results.Record.DeletedBy = 0
|
||||
|
||||
return results, nil
|
||||
}
|
||||
Reference in New Issue
Block a user