diff --git a/compose/service/module.go b/compose/service/module.go index b5bd00394..b09a4f4bf 100644 --- a/compose/service/module.go +++ b/compose/service/module.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/cortezaproject/corteza-server/compose/service/event" + "github.com/cortezaproject/corteza-server/compose/service/values" "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/actionlog" "github.com/cortezaproject/corteza-server/pkg/errors" @@ -522,6 +523,12 @@ func updateModuleFields(ctx context.Context, s store.Storer, new, old *types.Mod } } + // Next preproc any default values + new.Fields, err = moduleFieldDefaultPreparer(ctx, s, new, new.Fields) + if err != nil { + return err + } + // Assure; create/update remaining fields idx := 0 ff = make(types.ModuleFieldSet, 0, len(old.Fields)) @@ -579,6 +586,48 @@ func updateModuleFields(ctx context.Context, s store.Storer, new, old *types.Mod return nil } +func moduleFieldDefaultPreparer(ctx context.Context, s store.Storer, m *types.Module, newFields types.ModuleFieldSet) (types.ModuleFieldSet, error) { + var err error + + for _, f := range newFields { + if f.DefaultValue == nil || len(f.DefaultValue) == 0 { + continue + } + vv := f.DefaultValue + vv.SetUpdatedFlag(true) + // Module field default values should not have a field name, so let's temporarily add it + vv.Walk(func(rv *types.RecordValue) error { + rv.Name = f.Name + return nil + }) + + if err = RecordValueSanitazion(m, vv); err != nil { + return nil, err + } + + vv = values.Sanitizer().Run(m, vv) + + r := &types.Record{ + Values: vv, + } + rve := defaultValidator().Run(ctx, s, m, r) + if !rve.IsValid() { + return nil, rve + } + + vv = values.Formatter().Run(m, vv) + + // Module field default values should not have a field name, so let's remove it + vv.Walk(func(rv *types.RecordValue) error { + rv.Name = "" + return nil + }) + + f.DefaultValue = vv + } + return newFields, nil +} + func loadModuleFields(ctx context.Context, s store.Storer, mm ...*types.Module) (err error) { if len(mm) == 0 { return nil diff --git a/compose/service/record.go b/compose/service/record.go index 05618f727..7741e8a14 100644 --- a/compose/service/record.go +++ b/compose/service/record.go @@ -141,6 +141,21 @@ type ( ) func Record() RecordService { + + return &record{ + actionlog: DefaultActionlog, + ac: DefaultAccessControl, + eventbus: eventbus.Service(), + optEmitEvents: true, + store: DefaultStore, + + formatter: values.Formatter(), + sanitizer: values.Sanitizer(), + validator: defaultValidator(), + } +} + +func defaultValidator() recordValuesValidator { // Initialize validator and setup all checkers it needs validator := values.Validator() @@ -175,17 +190,7 @@ func Record() RecordService { return r != nil, err }) - return &record{ - actionlog: DefaultActionlog, - ac: DefaultAccessControl, - eventbus: eventbus.Service(), - optEmitEvents: true, - store: DefaultStore, - - formatter: values.Formatter(), - sanitizer: values.Sanitizer(), - validator: validator, - } + return validator } func (svc *record) EventEmitting(enable bool) { diff --git a/tests/compose/module_test.go b/tests/compose/module_test.go index 3505979a6..3afa0be57 100644 --- a/tests/compose/module_test.go +++ b/tests/compose/module_test.go @@ -265,6 +265,121 @@ func TestModuleFieldsUpdate(t *testing.T) { h.a.Equal(m.Fields[1].Kind, "DateTime") } +func TestModuleFieldsDefaultValue(t *testing.T) { + var ns *types.Namespace + + h := newHelper(t) + h.allow(types.NamespaceRBACResource.AppendWildcard(), "read") + + prep := func() { + h.clearModules() + ns = h.makeNamespace("some-namespace") + } + + t.Run("boolean; true", func(t *testing.T) { + prep() + + m := h.makeModule(ns, "some-module", &types.ModuleField{ID: id.Next(), Kind: "Boolean", Name: "boolean"}) + h.allow(types.ModuleRBACResource.AppendWildcard(), "update") + + f := m.Fields[0] + fjs := fmt.Sprintf(`{ "name": "%s", "fields": [{ "fieldID": "%d", "name": "boolean", "kind": "Boolean", "defaultValue": [{"name": "boolean", "value": "1"}] }] }`, m.Name, f.ID) + h.apiInit(). + Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)). + JSON(fjs). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() + + m = h.lookupModuleByID(m.ID) + h.a.NotNil(m) + h.a.NotNil(m.Fields) + h.a.Len(m.Fields, 1) + + h.a.NotNil(m.Fields[0].DefaultValue) + h.a.Len(m.Fields[0].DefaultValue, 1) + h.a.Equal("1", m.Fields[0].DefaultValue[0].Value) + }) + + t.Run("boolean; false", func(t *testing.T) { + prep() + + m := h.makeModule(ns, "some-module", &types.ModuleField{ID: id.Next(), Kind: "Boolean", Name: "boolean"}) + h.allow(types.ModuleRBACResource.AppendWildcard(), "update") + + f := m.Fields[0] + fjs := fmt.Sprintf(`{ "name": "%s", "fields": [{ "fieldID": "%d", "name": "boolean", "kind": "Boolean", "defaultValue": [{"name": "boolean", "value": ""}] }] }`, m.Name, f.ID) + h.apiInit(). + Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)). + JSON(fjs). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() + + m = h.lookupModuleByID(m.ID) + h.a.NotNil(m) + h.a.NotNil(m.Fields) + h.a.Len(m.Fields, 1) + + h.a.NotNil(m.Fields[0].DefaultValue) + h.a.Len(m.Fields[0].DefaultValue, 1) + h.a.Equal("", m.Fields[0].DefaultValue[0].Value) + }) + + t.Run("boolean; undefined", func(t *testing.T) { + prep() + + m := h.makeModule(ns, "some-module", &types.ModuleField{ID: id.Next(), Kind: "Boolean", Name: "boolean"}) + h.allow(types.ModuleRBACResource.AppendWildcard(), "update") + + f := m.Fields[0] + fjs := fmt.Sprintf(`{ "name": "%s", "fields": [{ "fieldID": "%d", "name": "boolean", "kind": "Boolean" }] }`, m.Name, f.ID) + h.apiInit(). + Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)). + JSON(fjs). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() + + m = h.lookupModuleByID(m.ID) + h.a.NotNil(m) + h.a.NotNil(m.Fields) + h.a.Len(m.Fields, 1) + + h.a.Nil(m.Fields[0].DefaultValue) + h.a.Len(m.Fields[0].DefaultValue, 0) + }) + + t.Run("boolean; true; compact form", func(t *testing.T) { + prep() + + m := h.makeModule(ns, "some-module", &types.ModuleField{ID: id.Next(), Kind: "Boolean", Name: "boolean"}) + h.allow(types.ModuleRBACResource.AppendWildcard(), "update") + + f := m.Fields[0] + fjs := fmt.Sprintf(`{ "name": "%s", "fields": [{ "fieldID": "%d", "name": "boolean", "kind": "Boolean", "defaultValue": [{"value": "1"}] }] }`, m.Name, f.ID) + h.apiInit(). + Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)). + JSON(fjs). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() + + m = h.lookupModuleByID(m.ID) + h.a.NotNil(m) + h.a.NotNil(m.Fields) + h.a.Len(m.Fields, 1) + + h.a.NotNil(m.Fields[0].DefaultValue) + h.a.Len(m.Fields[0].DefaultValue, 1) + h.a.Equal("1", m.Fields[0].DefaultValue[0].Value) + }) +} + func TestModuleFieldsUpdate_removed(t *testing.T) { h := newHelper(t) h.clearModules()