From a7d0ae21ac04d9545046944517276258e74106ea Mon Sep 17 00:00:00 2001 From: grandwizard28 Date: Wed, 4 Jun 2025 00:59:02 +0530 Subject: [PATCH] refactor(preference): better readability --- .../preference/implpreference/handler.go | 160 ++++---- .../preference/implpreference/module.go | 302 +++++---------- .../preference/implpreference/store.go | 18 +- pkg/modules/preference/preference.go | 35 +- pkg/query-service/app/http_handler.go | 12 +- pkg/signoz/module.go | 6 +- pkg/types/preferencetypes/name.go | 44 +++ pkg/types/preferencetypes/preference.go | 344 +++++++----------- pkg/types/preferencetypes/scope.go | 10 + pkg/types/preferencetypes/value.go | 96 ++++- 10 files changed, 487 insertions(+), 540 deletions(-) create mode 100644 pkg/types/preferencetypes/name.go create mode 100644 pkg/types/preferencetypes/scope.go diff --git a/pkg/modules/preference/implpreference/handler.go b/pkg/modules/preference/implpreference/handler.go index 9d96219f42..e167f7760e 100644 --- a/pkg/modules/preference/implpreference/handler.go +++ b/pkg/modules/preference/implpreference/handler.go @@ -11,6 +11,7 @@ import ( "github.com/SigNoz/signoz/pkg/modules/preference" "github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/preferencetypes" + "github.com/SigNoz/signoz/pkg/valuer" "github.com/gorilla/mux" ) @@ -22,7 +23,7 @@ func NewHandler(module preference.Module) preference.Handler { return &handler{module: module} } -func (handler *handler) GetOrg(rw http.ResponseWriter, r *http.Request) { +func (handler *handler) ListByOrg(rw http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) defer cancel() @@ -32,65 +33,7 @@ func (handler *handler) GetOrg(rw http.ResponseWriter, r *http.Request) { return } - id, ok := mux.Vars(r)["preferenceId"] - if !ok { - render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is required")) - return - } - - preference, err := handler.module.GetOrg(ctx, id, claims.OrgID) - if err != nil { - render.Error(rw, err) - return - } - - render.Success(rw, http.StatusOK, preference) -} - -func (handler *handler) UpdateOrg(rw http.ResponseWriter, r *http.Request) { - ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) - defer cancel() - - claims, err := authtypes.ClaimsFromContext(ctx) - if err != nil { - render.Error(rw, err) - return - } - - id, ok := mux.Vars(r)["preferenceId"] - if !ok { - render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is required")) - return - } - - req := new(preferencetypes.UpdatablePreference) - - err = json.NewDecoder(r.Body).Decode(req) - if err != nil { - render.Error(rw, err) - return - } - - err = handler.module.UpdateOrg(ctx, id, req.PreferenceValue, claims.OrgID) - if err != nil { - render.Error(rw, err) - return - } - - render.Success(rw, http.StatusNoContent, nil) -} - -func (handler *handler) GetAllOrg(rw http.ResponseWriter, r *http.Request) { - ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) - defer cancel() - - claims, err := authtypes.ClaimsFromContext(ctx) - if err != nil { - render.Error(rw, err) - return - } - - preferences, err := handler.module.GetAllOrg(ctx, claims.OrgID) + preferences, err := handler.module.ListByOrg(ctx, valuer.MustNewUUID(claims.OrgID)) if err != nil { render.Error(rw, err) return @@ -99,7 +42,7 @@ func (handler *handler) GetAllOrg(rw http.ResponseWriter, r *http.Request) { render.Success(rw, http.StatusOK, preferences) } -func (handler *handler) GetUser(rw http.ResponseWriter, r *http.Request) { +func (handler *handler) GetByOrg(rw http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) defer cancel() @@ -109,13 +52,19 @@ func (handler *handler) GetUser(rw http.ResponseWriter, r *http.Request) { return } - id, ok := mux.Vars(r)["preferenceId"] + nameString, ok := mux.Vars(r)["preferenceId"] if !ok { render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is required")) return } - preference, err := handler.module.GetUser(ctx, id, claims.OrgID, claims.UserID) + name, err := preferencetypes.NewName(nameString) + if err != nil { + render.Error(rw, err) + return + } + + preference, err := handler.module.GetByOrg(ctx, valuer.MustNewUUID(claims.OrgID), name) if err != nil { render.Error(rw, err) return @@ -124,7 +73,7 @@ func (handler *handler) GetUser(rw http.ResponseWriter, r *http.Request) { render.Success(rw, http.StatusOK, preference) } -func (handler *handler) UpdateUser(rw http.ResponseWriter, r *http.Request) { +func (handler *handler) UpdateByOrg(rw http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) defer cancel() @@ -134,12 +83,18 @@ func (handler *handler) UpdateUser(rw http.ResponseWriter, r *http.Request) { return } - id, ok := mux.Vars(r)["preferenceId"] + nameString, ok := mux.Vars(r)["preferenceId"] if !ok { render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is required")) return } + name, err := preferencetypes.NewName(nameString) + if err != nil { + render.Error(rw, err) + return + } + req := new(preferencetypes.UpdatablePreference) err = json.NewDecoder(r.Body).Decode(req) if err != nil { @@ -147,7 +102,7 @@ func (handler *handler) UpdateUser(rw http.ResponseWriter, r *http.Request) { return } - err = handler.module.UpdateUser(ctx, id, req.PreferenceValue, claims.UserID) + err = handler.module.UpdateByOrg(ctx, valuer.MustNewUUID(claims.OrgID), name, req.Value) if err != nil { render.Error(rw, err) return @@ -156,7 +111,7 @@ func (handler *handler) UpdateUser(rw http.ResponseWriter, r *http.Request) { render.Success(rw, http.StatusNoContent, nil) } -func (handler *handler) GetAllUser(rw http.ResponseWriter, r *http.Request) { +func (handler *handler) ListByUser(rw http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) defer cancel() @@ -166,7 +121,7 @@ func (handler *handler) GetAllUser(rw http.ResponseWriter, r *http.Request) { return } - preferences, err := handler.module.GetAllUser(ctx, claims.OrgID, claims.UserID) + preferences, err := handler.module.ListByUser(ctx, valuer.MustNewUUID(claims.UserID)) if err != nil { render.Error(rw, err) return @@ -174,3 +129,72 @@ func (handler *handler) GetAllUser(rw http.ResponseWriter, r *http.Request) { render.Success(rw, http.StatusOK, preferences) } + +func (handler *handler) GetByUser(rw http.ResponseWriter, r *http.Request) { + ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) + defer cancel() + + claims, err := authtypes.ClaimsFromContext(ctx) + if err != nil { + render.Error(rw, err) + return + } + + nameString, ok := mux.Vars(r)["preferenceId"] + if !ok { + render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is required")) + return + } + + name, err := preferencetypes.NewName(nameString) + if err != nil { + render.Error(rw, err) + return + } + + preference, err := handler.module.GetByUser(ctx, valuer.MustNewUUID(claims.UserID), name) + if err != nil { + render.Error(rw, err) + return + } + + render.Success(rw, http.StatusOK, preference) +} + +func (handler *handler) UpdateByUser(rw http.ResponseWriter, r *http.Request) { + ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) + defer cancel() + + claims, err := authtypes.ClaimsFromContext(ctx) + if err != nil { + render.Error(rw, err) + return + } + + nameString, ok := mux.Vars(r)["preferenceId"] + if !ok { + render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is required")) + return + } + + name, err := preferencetypes.NewName(nameString) + if err != nil { + render.Error(rw, err) + return + } + + req := new(preferencetypes.UpdatablePreference) + err = json.NewDecoder(r.Body).Decode(req) + if err != nil { + render.Error(rw, err) + return + } + + err = handler.module.UpdateByUser(ctx, valuer.MustNewUUID(claims.UserID), name, req.Value) + if err != nil { + render.Error(rw, err) + return + } + + render.Success(rw, http.StatusNoContent, nil) +} diff --git a/pkg/modules/preference/implpreference/module.go b/pkg/modules/preference/implpreference/module.go index b00261e706..faa90653e8 100644 --- a/pkg/modules/preference/implpreference/module.go +++ b/pkg/modules/preference/implpreference/module.go @@ -2,8 +2,6 @@ package implpreference import ( "context" - "database/sql" - "encoding/json" "github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/modules/preference" @@ -13,266 +11,156 @@ import ( // Do not take inspiration from this code, it is a work in progress. See Organization module for a better implementation. type module struct { - store preferencetypes.Store - defaultMap map[string]preferencetypes.Preference + store preferencetypes.Store + available map[preferencetypes.Name]preferencetypes.Preference } -func NewModule(store preferencetypes.Store, defaultMap map[string]preferencetypes.Preference) preference.Module { - return &module{store: store, defaultMap: defaultMap} +func NewModule(store preferencetypes.Store, available map[preferencetypes.Name]preferencetypes.Preference) preference.Module { + return &module{store: store, available: available} } -func (module *module) GetOrg(ctx context.Context, preferenceID string, orgID string) (*preferencetypes.GettablePreference, error) { - preference, seen := module.defaultMap[preferenceID] - if !seen { - return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "cannot find preference with id: %s", preferenceID) - } - - isEnabled := preference.IsEnabledForScope(preferencetypes.OrgAllowedScope) - if !isEnabled { - return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "preference is not enabled at org scope: %s", preferenceID) - } - - org, err := module.store.GetOrg(ctx, orgID, preferenceID) +func (module *module) ListByOrg(ctx context.Context, orgID valuer.UUID) ([]*preferencetypes.GettablePreference, error) { + storableOrgPreferences, err := module.store.ListByOrg(ctx, orgID) if err != nil { - if err == sql.ErrNoRows { - return &preferencetypes.GettablePreference{ - PreferenceID: preferenceID, - PreferenceValue: preference.DefaultValue, - }, nil - } - return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "error in fetching the org preference: %s", preferenceID) + return nil, err } - return &preferencetypes.GettablePreference{ - PreferenceID: preferenceID, - PreferenceValue: preference.SanitizeValue(org.PreferenceValue), - }, nil + var gettablePreferences []*preferencetypes.GettablePreference + for _, preference := range module.available { + copyOfPreference, err := preferencetypes.NewPreferenceFromAvailable(preference.Name, module.available) + if err != nil { + continue + } + for _, storableOrgPreference := range storableOrgPreferences { + if storableOrgPreference.Name == preference.Name { + gettablePreferences = append(gettablePreferences, preferencetypes.NewGettablePreference(copyOfPreference, storableOrgPreference.Value)) + continue + } + + gettablePreferences = append(gettablePreferences, preferencetypes.NewGettablePreference(copyOfPreference, preference.DefaultValue)) + } + } + + return gettablePreferences, nil } -func (module *module) UpdateOrg(ctx context.Context, preferenceID string, preferenceValue interface{}, orgID string) error { - preference, seen := module.defaultMap[preferenceID] - if !seen { - return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "cannot find preference with id: %s", preferenceID) +func (module *module) GetByOrg(ctx context.Context, orgID valuer.UUID, name preferencetypes.Name) (*preferencetypes.GettablePreference, error) { + preference, err := preferencetypes.NewPreference(name, preferencetypes.ScopeOrg, module.available) + if err != nil { + return nil, err } - isEnabled := preference.IsEnabledForScope(preferencetypes.OrgAllowedScope) - if !isEnabled { - return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "preference is not enabled at org scope: %s", preferenceID) + org, err := module.store.GetByOrg(ctx, orgID, name) + if err != nil { + if errors.As(err, errors.TypeNotFound) { + return preferencetypes.NewGettablePreference(preference, preference.DefaultValue), nil + } + + return nil, err } - err := preference.IsValidValue(preferenceValue) + return preferencetypes.NewGettablePreference(preference, org.Value), nil +} + +func (module *module) UpdateByOrg(ctx context.Context, orgID valuer.UUID, name preferencetypes.Name, preferenceValue string) error { + preference, err := preferencetypes.NewPreference(name, preferencetypes.ScopeOrg, module.available) if err != nil { return err } - storableValue, encodeErr := json.Marshal(preferenceValue) - if encodeErr != nil { - return errors.Wrapf(encodeErr, errors.TypeInvalidInput, errors.CodeInvalidInput, "error in encoding the preference value") + _, err = preferencetypes.NewPreferenceValueFromString(preference, preferenceValue) + if err != nil { + return err } - org, dberr := module.store.GetOrg(ctx, orgID, preferenceID) - if dberr != nil && dberr != sql.ErrNoRows { - return errors.Wrapf(dberr, errors.TypeInternal, errors.CodeInternal, "error in getting the preference value") + storableOrgPreference, err := module.store.GetByOrg(ctx, orgID, name) + if err != nil { + if !errors.As(err, errors.TypeNotFound) { + return err + } } - if dberr != nil { - org.ID = valuer.GenerateUUID() - org.PreferenceID = preferenceID - org.PreferenceValue = string(storableValue) - org.OrgID = orgID - } else { - org.PreferenceValue = string(storableValue) + if storableOrgPreference == nil { + storableOrgPreference = preferencetypes.NewStorableOrgPreference(preference, preferenceValue, orgID) } - dberr = module.store.UpsertOrg(ctx, org) - if dberr != nil { - return errors.Wrapf(dberr, errors.TypeInternal, errors.CodeInternal, "error in setting the preference value") + err = module.store.UpsertByOrg(ctx, storableOrgPreference) + if err != nil { + return err } return nil } -func (module *module) GetAllOrg(ctx context.Context, orgID string) ([]*preferencetypes.PreferenceWithValue, error) { - allOrgs := []*preferencetypes.PreferenceWithValue{} - orgs, err := module.store.GetAllOrg(ctx, orgID) +func (module *module) ListByUser(ctx context.Context, userID valuer.UUID) ([]*preferencetypes.GettablePreference, error) { + storableUserPreferences, err := module.store.ListByUser(ctx, userID) if err != nil { - return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "error in setting all org preference values") + return nil, err } - preferenceValueMap := map[string]interface{}{} - for _, preferenceValue := range orgs { - preferenceValueMap[preferenceValue.PreferenceID] = preferenceValue.PreferenceValue - } - - for _, preference := range module.defaultMap { - isEnabledForOrgScope := preference.IsEnabledForScope(preferencetypes.OrgAllowedScope) - if isEnabledForOrgScope { - preferenceWithValue := &preferencetypes.PreferenceWithValue{} - preferenceWithValue.Key = preference.Key - preferenceWithValue.Name = preference.Name - preferenceWithValue.Description = preference.Description - preferenceWithValue.AllowedScopes = preference.AllowedScopes - preferenceWithValue.AllowedValues = preference.AllowedValues - preferenceWithValue.DefaultValue = preference.DefaultValue - preferenceWithValue.Range = preference.Range - preferenceWithValue.ValueType = preference.ValueType - preferenceWithValue.IsDiscreteValues = preference.IsDiscreteValues - value, seen := preferenceValueMap[preference.Key] - - if seen { - preferenceWithValue.Value = value - } else { - preferenceWithValue.Value = preference.DefaultValue + var gettablePreferences []*preferencetypes.GettablePreference + for _, preference := range module.available { + copyOfPreference, err := preferencetypes.NewPreferenceFromAvailable(preference.Name, module.available) + if err != nil { + continue + } + for _, storableUserPreference := range storableUserPreferences { + if storableUserPreference.Name == preference.Name { + gettablePreferences = append(gettablePreferences, preferencetypes.NewGettablePreference(copyOfPreference, storableUserPreference.Value)) + continue } - preferenceWithValue.Value = preference.SanitizeValue(preferenceWithValue.Value) - allOrgs = append(allOrgs, preferenceWithValue) + gettablePreferences = append(gettablePreferences, preferencetypes.NewGettablePreference(copyOfPreference, preference.DefaultValue)) } } - return allOrgs, nil + + return gettablePreferences, nil } -func (module *module) GetUser(ctx context.Context, preferenceID string, orgID string, userID string) (*preferencetypes.GettablePreference, error) { - preference, seen := module.defaultMap[preferenceID] - if !seen { - return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "cannot find preference with id: %s", preferenceID) +func (module *module) GetByUser(ctx context.Context, userID valuer.UUID, name preferencetypes.Name) (*preferencetypes.GettablePreference, error) { + preference, err := preferencetypes.NewPreference(name, preferencetypes.ScopeUser, module.available) + if err != nil { + return nil, err } - preferenceValue := preferencetypes.GettablePreference{ - PreferenceID: preferenceID, - PreferenceValue: preference.DefaultValue, - } - - isEnabledAtUserScope := preference.IsEnabledForScope(preferencetypes.UserAllowedScope) - if !isEnabledAtUserScope { - return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "preference is not enabled at user scope: %s", preferenceID) - } - - isEnabledAtOrgScope := preference.IsEnabledForScope(preferencetypes.OrgAllowedScope) - if isEnabledAtOrgScope { - org, err := module.store.GetOrg(ctx, orgID, preferenceID) - if err != nil && err != sql.ErrNoRows { - return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "error in fetching the org preference: %s", preferenceID) - } - if err == nil { - preferenceValue.PreferenceValue = org.PreferenceValue + storableUserPreference, err := module.store.GetByUser(ctx, userID, name) + if err != nil { + if errors.As(err, errors.TypeNotFound) { + return preferencetypes.NewGettablePreference(preference, preference.DefaultValue), nil } + + return nil, err } - user, err := module.store.GetUser(ctx, userID, preferenceID) - if err != nil && err != sql.ErrNoRows { - return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "error in fetching the user preference: %s", preferenceID) - } - - if err == nil { - preferenceValue.PreferenceValue = user.PreferenceValue - } - - return &preferencetypes.GettablePreference{ - PreferenceID: preferenceValue.PreferenceID, - PreferenceValue: preference.SanitizeValue(preferenceValue.PreferenceValue), - }, nil + return preferencetypes.NewGettablePreference(preference, storableUserPreference.Value), nil } -func (module *module) UpdateUser(ctx context.Context, preferenceID string, preferenceValue interface{}, userID string) error { - preference, seen := module.defaultMap[preferenceID] - if !seen { - return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "cannot find preference with id: %s", preferenceID) - } - - isEnabledAtUserScope := preference.IsEnabledForScope(preferencetypes.UserAllowedScope) - if !isEnabledAtUserScope { - return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "preference is not enabled at user scope: %s", preferenceID) - } - - err := preference.IsValidValue(preferenceValue) +func (module *module) UpdateByUser(ctx context.Context, userID valuer.UUID, name preferencetypes.Name, preferenceValue string) error { + preference, err := preferencetypes.NewPreference(name, preferencetypes.ScopeUser, module.available) if err != nil { return err } - storableValue, encodeErr := json.Marshal(preferenceValue) - if encodeErr != nil { - return errors.Wrapf(encodeErr, errors.TypeInvalidInput, errors.CodeInvalidInput, "error in encoding the preference value") + _, err = preferencetypes.NewPreferenceValueFromString(preference, preferenceValue) + if err != nil { + return err } - user, dberr := module.store.GetUser(ctx, userID, preferenceID) - if dberr != nil && dberr != sql.ErrNoRows { - return errors.Wrapf(dberr, errors.TypeInternal, errors.CodeInternal, "error in getting the preference value") + storableUserPreference, err := module.store.GetByUser(ctx, userID, name) + if err != nil { + if !errors.As(err, errors.TypeNotFound) { + return err + } } - if dberr != nil { - user.ID = valuer.GenerateUUID() - user.PreferenceID = preferenceID - user.PreferenceValue = string(storableValue) - user.UserID = userID - } else { - user.PreferenceValue = string(storableValue) + if storableUserPreference == nil { + storableUserPreference = preferencetypes.NewStorableUserPreference(preference, preferenceValue, userID) } - dberr = module.store.UpsertUser(ctx, user) - if dberr != nil { - return errors.Wrapf(dberr, errors.TypeInternal, errors.CodeInternal, "error in setting the preference value") + err = module.store.UpsertByUser(ctx, storableUserPreference) + if err != nil { + return err } return nil } - -func (module *module) GetAllUser(ctx context.Context, orgID string, userID string) ([]*preferencetypes.PreferenceWithValue, error) { - allUsers := []*preferencetypes.PreferenceWithValue{} - - orgs, err := module.store.GetAllOrg(ctx, orgID) - if err != nil { - return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "error in setting all org preference values") - } - - preferenceOrgValueMap := map[string]interface{}{} - for _, preferenceValue := range orgs { - preferenceOrgValueMap[preferenceValue.PreferenceID] = preferenceValue.PreferenceValue - } - - users, err := module.store.GetAllUser(ctx, userID) - if err != nil { - return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "error in setting all user preference values") - } - - preferenceUserValueMap := map[string]interface{}{} - for _, preferenceValue := range users { - preferenceUserValueMap[preferenceValue.PreferenceID] = preferenceValue.PreferenceValue - } - - for _, preference := range module.defaultMap { - isEnabledForUserScope := preference.IsEnabledForScope(preferencetypes.UserAllowedScope) - - if isEnabledForUserScope { - preferenceWithValue := &preferencetypes.PreferenceWithValue{} - preferenceWithValue.Key = preference.Key - preferenceWithValue.Name = preference.Name - preferenceWithValue.Description = preference.Description - preferenceWithValue.AllowedScopes = preference.AllowedScopes - preferenceWithValue.AllowedValues = preference.AllowedValues - preferenceWithValue.DefaultValue = preference.DefaultValue - preferenceWithValue.Range = preference.Range - preferenceWithValue.ValueType = preference.ValueType - preferenceWithValue.IsDiscreteValues = preference.IsDiscreteValues - preferenceWithValue.Value = preference.DefaultValue - - isEnabledForOrgScope := preference.IsEnabledForScope(preferencetypes.OrgAllowedScope) - if isEnabledForOrgScope { - value, seen := preferenceOrgValueMap[preference.Key] - if seen { - preferenceWithValue.Value = value - } - } - - value, seen := preferenceUserValueMap[preference.Key] - - if seen { - preferenceWithValue.Value = value - } - - preferenceWithValue.Value = preference.SanitizeValue(preferenceWithValue.Value) - allUsers = append(allUsers, preferenceWithValue) - } - } - return allUsers, nil -} diff --git a/pkg/modules/preference/implpreference/store.go b/pkg/modules/preference/implpreference/store.go index 9ac8734a22..f4344cc7c8 100644 --- a/pkg/modules/preference/implpreference/store.go +++ b/pkg/modules/preference/implpreference/store.go @@ -5,6 +5,7 @@ import ( "github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/types/preferencetypes" + "github.com/SigNoz/signoz/pkg/valuer" ) type store struct { @@ -15,14 +16,14 @@ func NewStore(db sqlstore.SQLStore) preferencetypes.Store { return &store{store: db} } -func (store *store) GetOrg(ctx context.Context, orgID string, preferenceID string) (*preferencetypes.StorableOrgPreference, error) { +func (store *store) GetByOrg(ctx context.Context, orgID valuer.UUID, name preferencetypes.Name) (*preferencetypes.StorableOrgPreference, error) { orgPreference := new(preferencetypes.StorableOrgPreference) err := store. store. BunDB(). NewSelect(). Model(orgPreference). - Where("preference_id = ?", preferenceID). + Where("preference_id = ?", name). Where("org_id = ?", orgID). Scan(ctx) @@ -33,7 +34,7 @@ func (store *store) GetOrg(ctx context.Context, orgID string, preferenceID strin return orgPreference, nil } -func (store *store) GetAllOrg(ctx context.Context, orgID string) ([]*preferencetypes.StorableOrgPreference, error) { +func (store *store) ListByOrg(ctx context.Context, orgID valuer.UUID) ([]*preferencetypes.StorableOrgPreference, error) { orgPreferences := make([]*preferencetypes.StorableOrgPreference, 0) err := store. store. @@ -50,7 +51,7 @@ func (store *store) GetAllOrg(ctx context.Context, orgID string) ([]*preferencet return orgPreferences, nil } -func (store *store) UpsertOrg(ctx context.Context, orgPreference *preferencetypes.StorableOrgPreference) error { +func (store *store) UpsertByOrg(ctx context.Context, orgPreference *preferencetypes.StorableOrgPreference) error { _, err := store. store. BunDB(). @@ -65,17 +66,16 @@ func (store *store) UpsertOrg(ctx context.Context, orgPreference *preferencetype return nil } -func (store *store) GetUser(ctx context.Context, userID string, preferenceID string) (*preferencetypes.StorableUserPreference, error) { +func (store *store) GetByUser(ctx context.Context, userID valuer.UUID, name preferencetypes.Name) (*preferencetypes.StorableUserPreference, error) { userPreference := new(preferencetypes.StorableUserPreference) + err := store. store. BunDB(). NewSelect(). Model(userPreference). - Where("preference_id = ?", preferenceID). Where("user_id = ?", userID). Scan(ctx) - if err != nil { return userPreference, err } @@ -83,7 +83,7 @@ func (store *store) GetUser(ctx context.Context, userID string, preferenceID str return userPreference, nil } -func (store *store) GetAllUser(ctx context.Context, userID string) ([]*preferencetypes.StorableUserPreference, error) { +func (store *store) ListByUser(ctx context.Context, userID valuer.UUID) ([]*preferencetypes.StorableUserPreference, error) { userPreferences := make([]*preferencetypes.StorableUserPreference, 0) err := store. store. @@ -100,7 +100,7 @@ func (store *store) GetAllUser(ctx context.Context, userID string) ([]*preferenc return userPreferences, nil } -func (store *store) UpsertUser(ctx context.Context, userPreference *preferencetypes.StorableUserPreference) error { +func (store *store) UpsertByUser(ctx context.Context, userPreference *preferencetypes.StorableUserPreference) error { _, err := store. store. BunDB(). diff --git a/pkg/modules/preference/preference.go b/pkg/modules/preference/preference.go index e740634466..50de821dea 100644 --- a/pkg/modules/preference/preference.go +++ b/pkg/modules/preference/preference.go @@ -5,44 +5,45 @@ import ( "net/http" "github.com/SigNoz/signoz/pkg/types/preferencetypes" + "github.com/SigNoz/signoz/pkg/valuer" ) type Module interface { - // Returns the preference for the given organization - GetOrg(ctx context.Context, preferenceId string, orgId string) (*preferencetypes.GettablePreference, error) - - // Returns the preference for the given user - GetUser(ctx context.Context, preferenceId string, orgId string, userId string) (*preferencetypes.GettablePreference, error) - // Returns all preferences for the given organization - GetAllOrg(ctx context.Context, orgId string) ([]*preferencetypes.PreferenceWithValue, error) + ListByOrg(context.Context, valuer.UUID) ([]*preferencetypes.GettablePreference, error) - // Returns all preferences for the given user - GetAllUser(ctx context.Context, orgId string, userId string) ([]*preferencetypes.PreferenceWithValue, error) + // Returns the preference for the given organization by name. + GetByOrg(context.Context, valuer.UUID, preferencetypes.Name) (*preferencetypes.GettablePreference, error) // Updates the preference for the given organization - UpdateOrg(ctx context.Context, preferenceId string, preferenceValue interface{}, orgId string) error + UpdateByOrg(context.Context, valuer.UUID, preferencetypes.Name, string) error + + // Returns all preferences for the given user + ListByUser(context.Context, valuer.UUID) ([]*preferencetypes.GettablePreference, error) + + // Returns the preference for the given user by name. + GetByUser(context.Context, valuer.UUID, preferencetypes.Name) (*preferencetypes.GettablePreference, error) // Updates the preference for the given user - UpdateUser(ctx context.Context, preferenceId string, preferenceValue interface{}, userId string) error + UpdateByUser(context.Context, valuer.UUID, preferencetypes.Name, string) error } type Handler interface { // Returns the preference for the given organization - GetOrg(http.ResponseWriter, *http.Request) + GetByOrg(http.ResponseWriter, *http.Request) // Updates the preference for the given organization - UpdateOrg(http.ResponseWriter, *http.Request) + UpdateByOrg(http.ResponseWriter, *http.Request) // Returns all preferences for the given organization - GetAllOrg(http.ResponseWriter, *http.Request) + ListByOrg(http.ResponseWriter, *http.Request) // Returns the preference for the given user - GetUser(http.ResponseWriter, *http.Request) + GetByUser(http.ResponseWriter, *http.Request) // Updates the preference for the given user - UpdateUser(http.ResponseWriter, *http.Request) + UpdateByUser(http.ResponseWriter, *http.Request) // Returns all preferences for the given user - GetAllUser(http.ResponseWriter, *http.Request) + ListByUser(http.ResponseWriter, *http.Request) } diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index 30cd18d274..2c2b03485f 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -561,12 +561,12 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) { router.HandleFunc("/api/v1/disks", am.ViewAccess(aH.getDisks)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/user/preferences", am.ViewAccess(aH.Signoz.Handlers.Preference.GetAllUser)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/user/preferences/{preferenceId}", am.ViewAccess(aH.Signoz.Handlers.Preference.GetUser)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/user/preferences/{preferenceId}", am.ViewAccess(aH.Signoz.Handlers.Preference.UpdateUser)).Methods(http.MethodPut) - router.HandleFunc("/api/v1/org/preferences", am.AdminAccess(aH.Signoz.Handlers.Preference.GetAllOrg)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/org/preferences/{preferenceId}", am.AdminAccess(aH.Signoz.Handlers.Preference.GetOrg)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/org/preferences/{preferenceId}", am.AdminAccess(aH.Signoz.Handlers.Preference.UpdateOrg)).Methods(http.MethodPut) + router.HandleFunc("/api/v1/user/preferences", am.ViewAccess(aH.Signoz.Handlers.Preference.ListByUser)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/user/preferences/{preferenceId}", am.ViewAccess(aH.Signoz.Handlers.Preference.GetByUser)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/user/preferences/{preferenceId}", am.ViewAccess(aH.Signoz.Handlers.Preference.UpdateByUser)).Methods(http.MethodPut) + router.HandleFunc("/api/v1/org/preferences", am.AdminAccess(aH.Signoz.Handlers.Preference.ListByOrg)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/org/preferences/{preferenceId}", am.AdminAccess(aH.Signoz.Handlers.Preference.GetByOrg)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/org/preferences/{preferenceId}", am.AdminAccess(aH.Signoz.Handlers.Preference.UpdateByOrg)).Methods(http.MethodPut) // Quick Filters router.HandleFunc("/api/v1/orgs/me/filters", am.ViewAccess(aH.Signoz.Handlers.QuickFilter.GetQuickFilters)).Methods(http.MethodGet) diff --git a/pkg/signoz/module.go b/pkg/signoz/module.go index bda2fcb702..25b10f5312 100644 --- a/pkg/signoz/module.go +++ b/pkg/signoz/module.go @@ -34,7 +34,7 @@ type Modules struct { Apdex apdex.Module Dashboard dashboard.Module QuickFilter quickfilter.Module - TraceFunnel tracefunnel.Module + TraceFunnel tracefunnel.Module } func NewModules( @@ -51,12 +51,12 @@ func NewModules( return Modules{ OrgGetter: orgGetter, OrgSetter: orgSetter, - Preference: implpreference.NewModule(implpreference.NewStore(sqlstore), preferencetypes.NewDefaultPreferenceMap()), + Preference: implpreference.NewModule(implpreference.NewStore(sqlstore), preferencetypes.NewAvailablePreference()), SavedView: implsavedview.NewModule(sqlstore), Apdex: implapdex.NewModule(sqlstore), Dashboard: impldashboard.NewModule(sqlstore, providerSettings), User: user, QuickFilter: quickfilter, - TraceFunnel: impltracefunnel.NewModule(impltracefunnel.NewStore(sqlstore)), + TraceFunnel: impltracefunnel.NewModule(impltracefunnel.NewStore(sqlstore)), } } diff --git a/pkg/types/preferencetypes/name.go b/pkg/types/preferencetypes/name.go new file mode 100644 index 0000000000..b5a96c3687 --- /dev/null +++ b/pkg/types/preferencetypes/name.go @@ -0,0 +1,44 @@ +package preferencetypes + +import ( + "slices" + + "github.com/SigNoz/signoz/pkg/errors" + "github.com/SigNoz/signoz/pkg/valuer" +) + +var ( + NameOrgOnboarding = Name{valuer.NewString("org_onboarding")} + NameWelcomeChecklistDoLater = Name{valuer.NewString("welcome_checklist_do_later")} + NameWelcomeChecklistSendLogsSkipped = Name{valuer.NewString("welcome_checklist_send_logs_skipped")} + NameWelcomeChecklistSendTracesSkipped = Name{valuer.NewString("welcome_checklist_send_traces_skipped")} + NameWelcomeChecklistSendInfraMetricsSkipped = Name{valuer.NewString("welcome_checklist_send_infra_metrics_skipped")} + NameWelcomeChecklistSetupDashboardsSkipped = Name{valuer.NewString("welcome_checklist_setup_dashboards_skipped")} + NameWelcomeChecklistSetupAlertsSkipped = Name{valuer.NewString("welcome_checklist_setup_alerts_skipped")} + NameWelcomeChecklistSetupSavedViewSkipped = Name{valuer.NewString("welcome_checklist_setup_saved_view_skipped")} + NameSidenavPinned = Name{valuer.NewString("sidenav_pinned")} +) + +type Name struct{ valuer.String } + +func NewName(name string) (Name, error) { + ok := slices.Contains( + []string{ + NameOrgOnboarding.StringValue(), + NameWelcomeChecklistDoLater.StringValue(), + NameWelcomeChecklistSendLogsSkipped.StringValue(), + NameWelcomeChecklistSendTracesSkipped.StringValue(), + NameWelcomeChecklistSendInfraMetricsSkipped.StringValue(), + NameWelcomeChecklistSetupDashboardsSkipped.StringValue(), + NameWelcomeChecklistSetupAlertsSkipped.StringValue(), + NameWelcomeChecklistSetupSavedViewSkipped.StringValue(), + NameSidenavPinned.StringValue(), + }, + name, + ) + if !ok { + return Name{}, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid name: %s", name) + } + + return Name{valuer.NewString(name)}, nil +} diff --git a/pkg/types/preferencetypes/preference.go b/pkg/types/preferencetypes/preference.go index 16d7365675..1f50f5e238 100644 --- a/pkg/types/preferencetypes/preference.go +++ b/pkg/types/preferencetypes/preference.go @@ -2,298 +2,204 @@ package preferencetypes import ( "context" - "strings" + "slices" "github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/types" + "github.com/SigNoz/signoz/pkg/valuer" "github.com/uptrace/bun" ) -type GettablePreference struct { - PreferenceID string `json:"preference_id" db:"preference_id"` - PreferenceValue interface{} `json:"preference_value" db:"preference_value"` -} - -type UpdatablePreference struct { - PreferenceValue interface{} `json:"preference_value" db:"preference_value"` +type Preference struct { + Name Name `json:"name"` + Description string `json:"description"` + ValueType ValueType `json:"valueType"` + DefaultValue any `json:"defaultValue"` + AllowedValues []any `json:"allowedValues"` + IsDiscreteValues bool `json:"isDiscreteValues"` + Range Range `json:"range"` + AllowedScopes []Scope `json:"allowedScopes"` } type StorableOrgPreference struct { bun.BaseModel `bun:"table:org_preference"` types.Identifiable - PreferenceID string `bun:"preference_id,type:text,notnull"` - PreferenceValue string `bun:"preference_value,type:text,notnull"` - OrgID string `bun:"org_id,type:text,notnull"` + Name Name `bun:"preference_id,type:text,notnull"` + Value string `bun:"preference_value,type:text,notnull"` + OrgID valuer.UUID `bun:"org_id,type:text,notnull"` } type StorableUserPreference struct { bun.BaseModel `bun:"table:user_preference"` types.Identifiable - PreferenceID string `bun:"preference_id,type:text,notnull"` - PreferenceValue string `bun:"preference_value,type:text,notnull"` - UserID string `bun:"user_id,type:text,notnull"` + Name Name `bun:"preference_id,type:text,notnull"` + Value string `bun:"preference_value,type:text,notnull"` + UserID valuer.UUID `bun:"user_id,type:text,notnull"` } -type Preference struct { - Key string `json:"key"` - Name string `json:"name"` - Description string `json:"description"` - ValueType string `json:"valueType"` - DefaultValue interface{} `json:"defaultValue"` - AllowedValues []interface{} `json:"allowedValues"` - IsDiscreteValues bool `json:"isDiscreteValues"` - Range Range `json:"range"` - AllowedScopes []string `json:"allowedScopes"` +type GettablePreference struct { + *Preference + Value any `json:"preference_value"` } -func NewDefaultPreferenceMap() map[string]Preference { - return map[string]Preference{ - "ORG_ONBOARDING": { - Key: "ORG_ONBOARDING", - Name: "Organisation Onboarding", +type UpdatablePreference struct { + Value string `json:"preference_value"` +} + +func NewAvailablePreference() map[Name]Preference { + return map[Name]Preference{ + NameOrgOnboarding: { + Name: NameOrgOnboarding, Description: "Organisation Onboarding", - ValueType: "boolean", + ValueType: ValueTypeBoolean, DefaultValue: false, - AllowedValues: []interface{}{true, false}, + AllowedValues: []any{true, false}, IsDiscreteValues: true, - AllowedScopes: []string{"org"}, + AllowedScopes: []Scope{ScopeOrg}, }, - "WELCOME_CHECKLIST_DO_LATER": { - Key: "WELCOME_CHECKLIST_DO_LATER", - Name: "Welcome Checklist Do Later", + NameWelcomeChecklistDoLater: { + Name: NameWelcomeChecklistDoLater, Description: "Welcome Checklist Do Later", - ValueType: "boolean", + ValueType: ValueTypeBoolean, DefaultValue: false, - AllowedValues: []interface{}{true, false}, + AllowedValues: []any{true, false}, IsDiscreteValues: true, - AllowedScopes: []string{"user"}, + AllowedScopes: []Scope{ScopeUser}, }, - "WELCOME_CHECKLIST_SEND_LOGS_SKIPPED": { - Key: "WELCOME_CHECKLIST_SEND_LOGS_SKIPPED", - Name: "Welcome Checklist Send Logs Skipped", + NameWelcomeChecklistSendLogsSkipped: { + Name: NameWelcomeChecklistSendLogsSkipped, Description: "Welcome Checklist Send Logs Skipped", - ValueType: "boolean", + ValueType: ValueTypeBoolean, DefaultValue: false, - AllowedValues: []interface{}{true, false}, + AllowedValues: []any{true, false}, IsDiscreteValues: true, - AllowedScopes: []string{"user"}, + AllowedScopes: []Scope{ScopeUser}, }, - "WELCOME_CHECKLIST_SEND_TRACES_SKIPPED": { - Key: "WELCOME_CHECKLIST_SEND_TRACES_SKIPPED", - Name: "Welcome Checklist Send Traces Skipped", + NameWelcomeChecklistSendTracesSkipped: { + Name: NameWelcomeChecklistSendTracesSkipped, Description: "Welcome Checklist Send Traces Skipped", - ValueType: "boolean", + ValueType: ValueTypeBoolean, DefaultValue: false, - AllowedValues: []interface{}{true, false}, + AllowedValues: []any{true, false}, IsDiscreteValues: true, - AllowedScopes: []string{"user"}, + AllowedScopes: []Scope{ScopeUser}, }, - "WELCOME_CHECKLIST_SEND_INFRA_METRICS_SKIPPED": { - Key: "WELCOME_CHECKLIST_SEND_INFRA_METRICS_SKIPPED", - Name: "Welcome Checklist Send Infra Metrics Skipped", + NameWelcomeChecklistSendInfraMetricsSkipped: { + Name: NameWelcomeChecklistSendInfraMetricsSkipped, Description: "Welcome Checklist Send Infra Metrics Skipped", - ValueType: "boolean", + ValueType: ValueTypeBoolean, DefaultValue: false, - AllowedValues: []interface{}{true, false}, + AllowedValues: []any{true, false}, IsDiscreteValues: true, - AllowedScopes: []string{"user"}, + AllowedScopes: []Scope{ScopeUser}, }, - "WELCOME_CHECKLIST_SETUP_DASHBOARDS_SKIPPED": { - Key: "WELCOME_CHECKLIST_SETUP_DASHBOARDS_SKIPPED", - Name: "Welcome Checklist Setup Dashboards Skipped", + NameWelcomeChecklistSetupDashboardsSkipped: { + Name: NameWelcomeChecklistSetupDashboardsSkipped, Description: "Welcome Checklist Setup Dashboards Skipped", - ValueType: "boolean", + ValueType: ValueTypeBoolean, DefaultValue: false, - AllowedValues: []interface{}{true, false}, + AllowedValues: []any{true, false}, IsDiscreteValues: true, - AllowedScopes: []string{"user"}, + AllowedScopes: []Scope{ScopeUser}, }, - "WELCOME_CHECKLIST_SETUP_ALERTS_SKIPPED": { - Key: "WELCOME_CHECKLIST_SETUP_ALERTS_SKIPPED", - Name: "Welcome Checklist Setup Alerts Skipped", + NameWelcomeChecklistSetupAlertsSkipped: { + Name: NameWelcomeChecklistSetupAlertsSkipped, Description: "Welcome Checklist Setup Alerts Skipped", - ValueType: "boolean", + ValueType: ValueTypeBoolean, DefaultValue: false, - AllowedValues: []interface{}{true, false}, + AllowedValues: []any{true, false}, IsDiscreteValues: true, - AllowedScopes: []string{"user"}, + AllowedScopes: []Scope{ScopeUser}, }, - "WELCOME_CHECKLIST_SETUP_SAVED_VIEW_SKIPPED": { - Key: "WELCOME_CHECKLIST_SETUP_SAVED_VIEW_SKIPPED", - Name: "Welcome Checklist Setup Saved View Skipped", + NameWelcomeChecklistSetupSavedViewSkipped: { + Name: NameWelcomeChecklistSetupSavedViewSkipped, Description: "Welcome Checklist Setup Saved View Skipped", - ValueType: "boolean", + ValueType: ValueTypeBoolean, DefaultValue: false, - AllowedValues: []interface{}{true, false}, + AllowedValues: []any{true, false}, IsDiscreteValues: true, - AllowedScopes: []string{"user"}, + AllowedScopes: []Scope{ScopeUser}, }, - "SIDENAV_PINNED": { - Key: "SIDENAV_PINNED", - Name: "Keep the primary sidenav always open", + NameSidenavPinned: { + Name: NameSidenavPinned, Description: "Controls whether the primary sidenav remains expanded or can be collapsed. When enabled, the sidenav will stay open and pinned to provide constant visibility of navigation options.", - ValueType: "boolean", + ValueType: ValueTypeBoolean, DefaultValue: false, - AllowedValues: []interface{}{true, false}, + AllowedValues: []any{true, false}, IsDiscreteValues: true, - AllowedScopes: []string{"user"}, + AllowedScopes: []Scope{ScopeUser}, }, } } -func (p *Preference) ErrorValueTypeMismatch() error { - return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "the preference value is not of expected type: %s", p.ValueType) +func NewPreference(name Name, scope Scope, available map[Name]Preference) (*Preference, error) { + preference, ok := available[name] + if !ok { + return nil, errors.Newf(errors.TypeNotFound, errors.CodeNotFound, "the preference does not exist: %s", name) + } + + if !slices.Contains(preference.AllowedScopes, scope) { + return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "the preference is not allowed for the given scope: %s", scope) + } + + return &Preference{ + Name: name, + Description: preference.Description, + ValueType: preference.ValueType, + DefaultValue: preference.DefaultValue, + AllowedValues: preference.AllowedValues, + IsDiscreteValues: preference.IsDiscreteValues, + Range: preference.Range, + AllowedScopes: preference.AllowedScopes, + }, nil } -func (p *Preference) checkIfInAllowedValues(preferenceValue interface{}) (bool, error) { - - switch p.ValueType { - case PreferenceValueTypeInteger: - _, ok := preferenceValue.(int64) - if !ok { - return false, p.ErrorValueTypeMismatch() - } - case PreferenceValueTypeFloat: - _, ok := preferenceValue.(float64) - if !ok { - return false, p.ErrorValueTypeMismatch() - } - case PreferenceValueTypeString: - _, ok := preferenceValue.(string) - if !ok { - return false, p.ErrorValueTypeMismatch() - } - case PreferenceValueTypeBoolean: - _, ok := preferenceValue.(bool) - if !ok { - return false, p.ErrorValueTypeMismatch() - } +func NewPreferenceFromAvailable(name Name, available map[Name]Preference) (*Preference, error) { + preference, ok := available[name] + if !ok { + return nil, errors.Newf(errors.TypeNotFound, errors.CodeNotFound, "the preference does not exist: %s", name) } - isInAllowedValues := false - for _, value := range p.AllowedValues { - switch p.ValueType { - case PreferenceValueTypeInteger: - allowedValue, ok := value.(int64) - if !ok { - return false, p.ErrorValueTypeMismatch() - } - if allowedValue == preferenceValue { - isInAllowedValues = true - } - case PreferenceValueTypeFloat: - allowedValue, ok := value.(float64) - if !ok { - return false, p.ErrorValueTypeMismatch() - } - - if allowedValue == preferenceValue { - isInAllowedValues = true - } - case PreferenceValueTypeString: - allowedValue, ok := value.(string) - if !ok { - return false, p.ErrorValueTypeMismatch() - } - - if allowedValue == preferenceValue { - isInAllowedValues = true - } - case PreferenceValueTypeBoolean: - allowedValue, ok := value.(bool) - if !ok { - return false, p.ErrorValueTypeMismatch() - } - - if allowedValue == preferenceValue { - isInAllowedValues = true - } - } - } - return isInAllowedValues, nil + return &Preference{ + Name: name, + Description: preference.Description, + ValueType: preference.ValueType, + DefaultValue: preference.DefaultValue, + AllowedValues: preference.AllowedValues, + IsDiscreteValues: preference.IsDiscreteValues, + Range: preference.Range, + AllowedScopes: preference.AllowedScopes, + }, nil } -func (p *Preference) IsValidValue(preferenceValue interface{}) error { - typeSafeValue := preferenceValue - switch p.ValueType { - case PreferenceValueTypeInteger: - val, ok := preferenceValue.(int64) - if !ok { - floatVal, ok := preferenceValue.(float64) - if !ok || floatVal != float64(int64(floatVal)) { - return p.ErrorValueTypeMismatch() - } - val = int64(floatVal) - typeSafeValue = val - } - if !p.IsDiscreteValues { - if val < p.Range.Min || val > p.Range.Max { - return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "the preference value is not in the range specified, min: %v , max: %v", p.Range.Min, p.Range.Max) - } - } - case PreferenceValueTypeString: - _, ok := preferenceValue.(string) - if !ok { - return p.ErrorValueTypeMismatch() - } - case PreferenceValueTypeFloat: - _, ok := preferenceValue.(float64) - if !ok { - return p.ErrorValueTypeMismatch() - } - case PreferenceValueTypeBoolean: - _, ok := preferenceValue.(bool) - if !ok { - return p.ErrorValueTypeMismatch() - } +func NewGettablePreference(preference *Preference, value any) *GettablePreference { + return &GettablePreference{ + Preference: preference, + Value: value, } - - // check the validity of the value being part of allowed values or the range specified if any - if p.IsDiscreteValues { - if p.AllowedValues != nil { - isInAllowedValues, valueMisMatchErr := p.checkIfInAllowedValues(typeSafeValue) - - if valueMisMatchErr != nil { - return valueMisMatchErr - } - if !isInAllowedValues { - return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "the preference value is not in the list of allowedValues: %v", p.AllowedValues) - } - } - } - return nil } -func (p *Preference) IsEnabledForScope(scope string) bool { - isPreferenceEnabledForGivenScope := false - if p.AllowedScopes != nil { - for _, allowedScope := range p.AllowedScopes { - if allowedScope == strings.ToLower(scope) { - isPreferenceEnabledForGivenScope = true - } - } +func NewStorableOrgPreference(preference *Preference, value string, orgID valuer.UUID) *StorableOrgPreference { + return &StorableOrgPreference{ + Name: preference.Name, + Value: value, + OrgID: orgID, } - return isPreferenceEnabledForGivenScope } -func (p *Preference) SanitizeValue(preferenceValue interface{}) interface{} { - switch p.ValueType { - case PreferenceValueTypeBoolean: - if preferenceValue == "1" || preferenceValue == true || preferenceValue == "true" { - return true - } else { - return false - } - default: - return preferenceValue +func NewStorableUserPreference(preference *Preference, value string, userID valuer.UUID) *StorableUserPreference { + return &StorableUserPreference{ + Name: preference.Name, + Value: value, + UserID: userID, } } type Store interface { - GetOrg(context.Context, string, string) (*StorableOrgPreference, error) - GetAllOrg(context.Context, string) ([]*StorableOrgPreference, error) - UpsertOrg(context.Context, *StorableOrgPreference) error - GetUser(context.Context, string, string) (*StorableUserPreference, error) - GetAllUser(context.Context, string) ([]*StorableUserPreference, error) - UpsertUser(context.Context, *StorableUserPreference) error + GetByOrg(context.Context, valuer.UUID, Name) (*StorableOrgPreference, error) + ListByOrg(context.Context, valuer.UUID) ([]*StorableOrgPreference, error) + UpsertByOrg(context.Context, *StorableOrgPreference) error + GetByUser(context.Context, valuer.UUID, Name) (*StorableUserPreference, error) + ListByUser(context.Context, valuer.UUID) ([]*StorableUserPreference, error) + UpsertByUser(context.Context, *StorableUserPreference) error } diff --git a/pkg/types/preferencetypes/scope.go b/pkg/types/preferencetypes/scope.go new file mode 100644 index 0000000000..ccd14d6318 --- /dev/null +++ b/pkg/types/preferencetypes/scope.go @@ -0,0 +1,10 @@ +package preferencetypes + +import "github.com/SigNoz/signoz/pkg/valuer" + +var ( + ScopeOrg = Scope{valuer.NewString("org")} + ScopeUser = Scope{valuer.NewString("user")} +) + +type Scope struct{ valuer.String } diff --git a/pkg/types/preferencetypes/value.go b/pkg/types/preferencetypes/value.go index c24b790a3c..b0eb0bc8a5 100644 --- a/pkg/types/preferencetypes/value.go +++ b/pkg/types/preferencetypes/value.go @@ -1,23 +1,97 @@ package preferencetypes -const ( - PreferenceValueTypeInteger string = "integer" - PreferenceValueTypeFloat string = "float" - PreferenceValueTypeString string = "string" - PreferenceValueTypeBoolean string = "boolean" +import ( + "encoding/json" + "strconv" + + "github.com/SigNoz/signoz/pkg/errors" + "github.com/SigNoz/signoz/pkg/valuer" ) -const ( - OrgAllowedScope string = "org" - UserAllowedScope string = "user" +var ( + ValueTypeInteger = ValueType{valuer.NewString("integer")} + ValueTypeFloat = ValueType{valuer.NewString("float")} + ValueTypeString = ValueType{valuer.NewString("string")} + ValueTypeBoolean = ValueType{valuer.NewString("boolean")} + ValueTypeArray = ValueType{valuer.NewString("array")} + ValueTypeObject = ValueType{valuer.NewString("object")} ) +type ValueType struct{ valuer.String } + type Range struct { Min int64 `json:"min"` Max int64 `json:"max"` } -type PreferenceWithValue struct { - Preference - Value interface{} `json:"value"` +func NewPreferenceValueFromString(preference *Preference, value string) (any, error) { + switch preference.ValueType { + case ValueTypeInteger: + val, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return nil, err + } + + if !preference.IsDiscreteValues { + if val < preference.Range.Min || val > preference.Range.Max { + return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "value is not in the range specified, min: %d , max: %d", preference.Range.Min, preference.Range.Max) + } + } + + if len(preference.AllowedValues) > 0 { + for _, allowedValue := range preference.AllowedValues { + if allowedValue == val { + return val, nil + } + } + return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "value is not one of the allowed values: %v", preference.AllowedValues) + } + + return val, nil + case ValueTypeFloat: + val, err := strconv.ParseFloat(value, 64) + if err != nil { + return nil, err + } + + if len(preference.AllowedValues) > 0 { + for _, allowedValue := range preference.AllowedValues { + if allowedValue == val { + return val, nil + } + } + return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "value is not one of the allowed values: %v", preference.AllowedValues) + } + + return val, nil + case ValueTypeString: + if len(preference.AllowedValues) > 0 { + for _, allowedValue := range preference.AllowedValues { + if allowedValue == value { + return value, nil + } + } + + return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "value is not in the list of allowedValues: %v", preference.AllowedValues) + } + return value, nil + case ValueTypeBoolean: + return strconv.ParseBool(value) + case ValueTypeArray: + var arr []any + err := json.Unmarshal([]byte(value), &arr) + if err != nil { + return nil, err + } + return arr, nil + case ValueTypeObject: + var obj map[string]any + err := json.Unmarshal([]byte(value), &obj) + if err != nil { + return nil, err + } + return obj, nil + default: + return nil, errors.Newf(errors.TypeUnsupported, errors.CodeUnsupported, "the preference value type is not supported: %s", preference.ValueType) + } }