refactor(preference): better readability

This commit is contained in:
grandwizard28 2025-06-04 00:59:02 +05:30
parent 6ed30318bd
commit a7d0ae21ac
No known key found for this signature in database
GPG Key ID: C7A26EDC5B7054B7
10 changed files with 487 additions and 540 deletions

View File

@ -11,6 +11,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/preference" "github.com/SigNoz/signoz/pkg/modules/preference"
"github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/preferencetypes" "github.com/SigNoz/signoz/pkg/types/preferencetypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
@ -22,7 +23,7 @@ func NewHandler(module preference.Module) preference.Handler {
return &handler{module: module} 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) ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel() defer cancel()
@ -32,65 +33,7 @@ func (handler *handler) GetOrg(rw http.ResponseWriter, r *http.Request) {
return return
} }
id, ok := mux.Vars(r)["preferenceId"] preferences, err := handler.module.ListByOrg(ctx, valuer.MustNewUUID(claims.OrgID))
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)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@ -99,7 +42,7 @@ func (handler *handler) GetAllOrg(rw http.ResponseWriter, r *http.Request) {
render.Success(rw, http.StatusOK, preferences) 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) ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel() defer cancel()
@ -109,13 +52,19 @@ func (handler *handler) GetUser(rw http.ResponseWriter, r *http.Request) {
return return
} }
id, ok := mux.Vars(r)["preferenceId"] nameString, ok := mux.Vars(r)["preferenceId"]
if !ok { if !ok {
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is required")) render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is required"))
return 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 { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@ -124,7 +73,7 @@ func (handler *handler) GetUser(rw http.ResponseWriter, r *http.Request) {
render.Success(rw, http.StatusOK, preference) 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) ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel() defer cancel()
@ -134,12 +83,18 @@ func (handler *handler) UpdateUser(rw http.ResponseWriter, r *http.Request) {
return return
} }
id, ok := mux.Vars(r)["preferenceId"] nameString, ok := mux.Vars(r)["preferenceId"]
if !ok { if !ok {
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is required")) render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is required"))
return return
} }
name, err := preferencetypes.NewName(nameString)
if err != nil {
render.Error(rw, err)
return
}
req := new(preferencetypes.UpdatablePreference) req := new(preferencetypes.UpdatablePreference)
err = json.NewDecoder(r.Body).Decode(req) err = json.NewDecoder(r.Body).Decode(req)
if err != nil { if err != nil {
@ -147,7 +102,7 @@ func (handler *handler) UpdateUser(rw http.ResponseWriter, r *http.Request) {
return 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 { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@ -156,7 +111,7 @@ func (handler *handler) UpdateUser(rw http.ResponseWriter, r *http.Request) {
render.Success(rw, http.StatusNoContent, nil) 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) ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel() defer cancel()
@ -166,7 +121,7 @@ func (handler *handler) GetAllUser(rw http.ResponseWriter, r *http.Request) {
return return
} }
preferences, err := handler.module.GetAllUser(ctx, claims.OrgID, claims.UserID) preferences, err := handler.module.ListByUser(ctx, valuer.MustNewUUID(claims.UserID))
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@ -174,3 +129,72 @@ func (handler *handler) GetAllUser(rw http.ResponseWriter, r *http.Request) {
render.Success(rw, http.StatusOK, preferences) 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)
}

View File

@ -2,8 +2,6 @@ package implpreference
import ( import (
"context" "context"
"database/sql"
"encoding/json"
"github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/modules/preference" "github.com/SigNoz/signoz/pkg/modules/preference"
@ -14,265 +12,155 @@ import (
// Do not take inspiration from this code, it is a work in progress. See Organization module for a better implementation. // Do not take inspiration from this code, it is a work in progress. See Organization module for a better implementation.
type module struct { type module struct {
store preferencetypes.Store store preferencetypes.Store
defaultMap map[string]preferencetypes.Preference available map[preferencetypes.Name]preferencetypes.Preference
} }
func NewModule(store preferencetypes.Store, defaultMap map[string]preferencetypes.Preference) preference.Module { func NewModule(store preferencetypes.Store, available map[preferencetypes.Name]preferencetypes.Preference) preference.Module {
return &module{store: store, defaultMap: defaultMap} return &module{store: store, available: available}
} }
func (module *module) GetOrg(ctx context.Context, preferenceID string, orgID string) (*preferencetypes.GettablePreference, error) { func (module *module) ListByOrg(ctx context.Context, orgID valuer.UUID) ([]*preferencetypes.GettablePreference, error) {
preference, seen := module.defaultMap[preferenceID] storableOrgPreferences, err := module.store.ListByOrg(ctx, orgID)
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)
if err != nil { if err != nil {
if err == sql.ErrNoRows { return nil, err
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 &preferencetypes.GettablePreference{ var gettablePreferences []*preferencetypes.GettablePreference
PreferenceID: preferenceID, for _, preference := range module.available {
PreferenceValue: preference.SanitizeValue(org.PreferenceValue), copyOfPreference, err := preferencetypes.NewPreferenceFromAvailable(preference.Name, module.available)
}, nil if err != nil {
continue
}
for _, storableOrgPreference := range storableOrgPreferences {
if storableOrgPreference.Name == preference.Name {
gettablePreferences = append(gettablePreferences, preferencetypes.NewGettablePreference(copyOfPreference, storableOrgPreference.Value))
continue
} }
func (module *module) UpdateOrg(ctx context.Context, preferenceID string, preferenceValue interface{}, orgID string) error { gettablePreferences = append(gettablePreferences, preferencetypes.NewGettablePreference(copyOfPreference, preference.DefaultValue))
preference, seen := module.defaultMap[preferenceID] }
if !seen {
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "cannot find preference with id: %s", preferenceID)
} }
isEnabled := preference.IsEnabledForScope(preferencetypes.OrgAllowedScope) return gettablePreferences, nil
if !isEnabled {
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "preference is not enabled at org scope: %s", preferenceID)
} }
err := preference.IsValidValue(preferenceValue) 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
}
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
}
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 { if err != nil {
return err return err
} }
storableValue, encodeErr := json.Marshal(preferenceValue) _, err = preferencetypes.NewPreferenceValueFromString(preference, preferenceValue)
if encodeErr != nil { if err != nil {
return errors.Wrapf(encodeErr, errors.TypeInvalidInput, errors.CodeInvalidInput, "error in encoding the preference value") return err
} }
org, dberr := module.store.GetOrg(ctx, orgID, preferenceID) storableOrgPreference, err := module.store.GetByOrg(ctx, orgID, name)
if dberr != nil && dberr != sql.ErrNoRows { if err != nil {
return errors.Wrapf(dberr, errors.TypeInternal, errors.CodeInternal, "error in getting the preference value") if !errors.As(err, errors.TypeNotFound) {
return err
}
} }
if dberr != nil { if storableOrgPreference == nil {
org.ID = valuer.GenerateUUID() storableOrgPreference = preferencetypes.NewStorableOrgPreference(preference, preferenceValue, orgID)
org.PreferenceID = preferenceID
org.PreferenceValue = string(storableValue)
org.OrgID = orgID
} else {
org.PreferenceValue = string(storableValue)
} }
dberr = module.store.UpsertOrg(ctx, org) err = module.store.UpsertByOrg(ctx, storableOrgPreference)
if dberr != nil { if err != nil {
return errors.Wrapf(dberr, errors.TypeInternal, errors.CodeInternal, "error in setting the preference value") return err
} }
return nil return nil
} }
func (module *module) GetAllOrg(ctx context.Context, orgID string) ([]*preferencetypes.PreferenceWithValue, error) { func (module *module) ListByUser(ctx context.Context, userID valuer.UUID) ([]*preferencetypes.GettablePreference, error) {
allOrgs := []*preferencetypes.PreferenceWithValue{} storableUserPreferences, err := module.store.ListByUser(ctx, userID)
orgs, err := module.store.GetAllOrg(ctx, orgID)
if err != nil { 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{}{} var gettablePreferences []*preferencetypes.GettablePreference
for _, preferenceValue := range orgs { for _, preference := range module.available {
preferenceValueMap[preferenceValue.PreferenceID] = preferenceValue.PreferenceValue 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
} }
for _, preference := range module.defaultMap { gettablePreferences = append(gettablePreferences, preferencetypes.NewGettablePreference(copyOfPreference, preference.DefaultValue))
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
}
preferenceWithValue.Value = preference.SanitizeValue(preferenceWithValue.Value)
allOrgs = append(allOrgs, preferenceWithValue)
}
}
return allOrgs, 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)
}
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
} }
} }
user, err := module.store.GetUser(ctx, userID, preferenceID) return gettablePreferences, nil
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 { func (module *module) GetByUser(ctx context.Context, userID valuer.UUID, name preferencetypes.Name) (*preferencetypes.GettablePreference, error) {
preferenceValue.PreferenceValue = user.PreferenceValue preference, err := preferencetypes.NewPreference(name, preferencetypes.ScopeUser, module.available)
if err != nil {
return nil, err
} }
return &preferencetypes.GettablePreference{ storableUserPreference, err := module.store.GetByUser(ctx, userID, name)
PreferenceID: preferenceValue.PreferenceID, if err != nil {
PreferenceValue: preference.SanitizeValue(preferenceValue.PreferenceValue), if errors.As(err, errors.TypeNotFound) {
}, nil return preferencetypes.NewGettablePreference(preference, preference.DefaultValue), nil
} }
func (module *module) UpdateUser(ctx context.Context, preferenceID string, preferenceValue interface{}, userID string) error { return nil, err
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) return preferencetypes.NewGettablePreference(preference, storableUserPreference.Value), nil
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 { if err != nil {
return err return err
} }
storableValue, encodeErr := json.Marshal(preferenceValue) _, err = preferencetypes.NewPreferenceValueFromString(preference, preferenceValue)
if encodeErr != nil { if err != nil {
return errors.Wrapf(encodeErr, errors.TypeInvalidInput, errors.CodeInvalidInput, "error in encoding the preference value") return err
} }
user, dberr := module.store.GetUser(ctx, userID, preferenceID) storableUserPreference, err := module.store.GetByUser(ctx, userID, name)
if dberr != nil && dberr != sql.ErrNoRows { if err != nil {
return errors.Wrapf(dberr, errors.TypeInternal, errors.CodeInternal, "error in getting the preference value") if !errors.As(err, errors.TypeNotFound) {
return err
}
} }
if dberr != nil { if storableUserPreference == nil {
user.ID = valuer.GenerateUUID() storableUserPreference = preferencetypes.NewStorableUserPreference(preference, preferenceValue, userID)
user.PreferenceID = preferenceID
user.PreferenceValue = string(storableValue)
user.UserID = userID
} else {
user.PreferenceValue = string(storableValue)
} }
dberr = module.store.UpsertUser(ctx, user) err = module.store.UpsertByUser(ctx, storableUserPreference)
if dberr != nil { if err != nil {
return errors.Wrapf(dberr, errors.TypeInternal, errors.CodeInternal, "error in setting the preference value") return err
} }
return nil 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
}

View File

@ -5,6 +5,7 @@ import (
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/preferencetypes" "github.com/SigNoz/signoz/pkg/types/preferencetypes"
"github.com/SigNoz/signoz/pkg/valuer"
) )
type store struct { type store struct {
@ -15,14 +16,14 @@ func NewStore(db sqlstore.SQLStore) preferencetypes.Store {
return &store{store: db} 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) orgPreference := new(preferencetypes.StorableOrgPreference)
err := store. err := store.
store. store.
BunDB(). BunDB().
NewSelect(). NewSelect().
Model(orgPreference). Model(orgPreference).
Where("preference_id = ?", preferenceID). Where("preference_id = ?", name).
Where("org_id = ?", orgID). Where("org_id = ?", orgID).
Scan(ctx) Scan(ctx)
@ -33,7 +34,7 @@ func (store *store) GetOrg(ctx context.Context, orgID string, preferenceID strin
return orgPreference, nil 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) orgPreferences := make([]*preferencetypes.StorableOrgPreference, 0)
err := store. err := store.
store. store.
@ -50,7 +51,7 @@ func (store *store) GetAllOrg(ctx context.Context, orgID string) ([]*preferencet
return orgPreferences, nil 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. _, err := store.
store. store.
BunDB(). BunDB().
@ -65,17 +66,16 @@ func (store *store) UpsertOrg(ctx context.Context, orgPreference *preferencetype
return nil 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) userPreference := new(preferencetypes.StorableUserPreference)
err := store. err := store.
store. store.
BunDB(). BunDB().
NewSelect(). NewSelect().
Model(userPreference). Model(userPreference).
Where("preference_id = ?", preferenceID).
Where("user_id = ?", userID). Where("user_id = ?", userID).
Scan(ctx) Scan(ctx)
if err != nil { if err != nil {
return userPreference, err return userPreference, err
} }
@ -83,7 +83,7 @@ func (store *store) GetUser(ctx context.Context, userID string, preferenceID str
return userPreference, nil 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) userPreferences := make([]*preferencetypes.StorableUserPreference, 0)
err := store. err := store.
store. store.
@ -100,7 +100,7 @@ func (store *store) GetAllUser(ctx context.Context, userID string) ([]*preferenc
return userPreferences, nil 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. _, err := store.
store. store.
BunDB(). BunDB().

View File

@ -5,44 +5,45 @@ import (
"net/http" "net/http"
"github.com/SigNoz/signoz/pkg/types/preferencetypes" "github.com/SigNoz/signoz/pkg/types/preferencetypes"
"github.com/SigNoz/signoz/pkg/valuer"
) )
type Module interface { 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 // 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 // Returns the preference for the given organization by name.
GetAllUser(ctx context.Context, orgId string, userId string) ([]*preferencetypes.PreferenceWithValue, error) GetByOrg(context.Context, valuer.UUID, preferencetypes.Name) (*preferencetypes.GettablePreference, error)
// Updates the preference for the given organization // 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 // 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 { type Handler interface {
// Returns the preference for the given organization // Returns the preference for the given organization
GetOrg(http.ResponseWriter, *http.Request) GetByOrg(http.ResponseWriter, *http.Request)
// Updates the preference for the given organization // Updates the preference for the given organization
UpdateOrg(http.ResponseWriter, *http.Request) UpdateByOrg(http.ResponseWriter, *http.Request)
// Returns all preferences for the given organization // Returns all preferences for the given organization
GetAllOrg(http.ResponseWriter, *http.Request) ListByOrg(http.ResponseWriter, *http.Request)
// Returns the preference for the given user // Returns the preference for the given user
GetUser(http.ResponseWriter, *http.Request) GetByUser(http.ResponseWriter, *http.Request)
// Updates the preference for the given user // Updates the preference for the given user
UpdateUser(http.ResponseWriter, *http.Request) UpdateByUser(http.ResponseWriter, *http.Request)
// Returns all preferences for the given user // Returns all preferences for the given user
GetAllUser(http.ResponseWriter, *http.Request) ListByUser(http.ResponseWriter, *http.Request)
} }

View File

@ -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/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", am.ViewAccess(aH.Signoz.Handlers.Preference.ListByUser)).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.GetByUser)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/user/preferences/{preferenceId}", am.ViewAccess(aH.Signoz.Handlers.Preference.UpdateUser)).Methods(http.MethodPut) 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.GetAllOrg)).Methods(http.MethodGet) 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.GetOrg)).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.UpdateOrg)).Methods(http.MethodPut) router.HandleFunc("/api/v1/org/preferences/{preferenceId}", am.AdminAccess(aH.Signoz.Handlers.Preference.UpdateByOrg)).Methods(http.MethodPut)
// Quick Filters // Quick Filters
router.HandleFunc("/api/v1/orgs/me/filters", am.ViewAccess(aH.Signoz.Handlers.QuickFilter.GetQuickFilters)).Methods(http.MethodGet) router.HandleFunc("/api/v1/orgs/me/filters", am.ViewAccess(aH.Signoz.Handlers.QuickFilter.GetQuickFilters)).Methods(http.MethodGet)

View File

@ -51,7 +51,7 @@ func NewModules(
return Modules{ return Modules{
OrgGetter: orgGetter, OrgGetter: orgGetter,
OrgSetter: orgSetter, OrgSetter: orgSetter,
Preference: implpreference.NewModule(implpreference.NewStore(sqlstore), preferencetypes.NewDefaultPreferenceMap()), Preference: implpreference.NewModule(implpreference.NewStore(sqlstore), preferencetypes.NewAvailablePreference()),
SavedView: implsavedview.NewModule(sqlstore), SavedView: implsavedview.NewModule(sqlstore),
Apdex: implapdex.NewModule(sqlstore), Apdex: implapdex.NewModule(sqlstore),
Dashboard: impldashboard.NewModule(sqlstore, providerSettings), Dashboard: impldashboard.NewModule(sqlstore, providerSettings),

View File

@ -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
}

View File

@ -2,298 +2,204 @@ package preferencetypes
import ( import (
"context" "context"
"strings" "slices"
"github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types" "github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun" "github.com/uptrace/bun"
) )
type GettablePreference struct { type Preference struct {
PreferenceID string `json:"preference_id" db:"preference_id"` Name Name `json:"name"`
PreferenceValue interface{} `json:"preference_value" db:"preference_value"` Description string `json:"description"`
} ValueType ValueType `json:"valueType"`
DefaultValue any `json:"defaultValue"`
type UpdatablePreference struct { AllowedValues []any `json:"allowedValues"`
PreferenceValue interface{} `json:"preference_value" db:"preference_value"` IsDiscreteValues bool `json:"isDiscreteValues"`
Range Range `json:"range"`
AllowedScopes []Scope `json:"allowedScopes"`
} }
type StorableOrgPreference struct { type StorableOrgPreference struct {
bun.BaseModel `bun:"table:org_preference"` bun.BaseModel `bun:"table:org_preference"`
types.Identifiable types.Identifiable
PreferenceID string `bun:"preference_id,type:text,notnull"` Name Name `bun:"preference_id,type:text,notnull"`
PreferenceValue string `bun:"preference_value,type:text,notnull"` Value string `bun:"preference_value,type:text,notnull"`
OrgID string `bun:"org_id,type:text,notnull"` OrgID valuer.UUID `bun:"org_id,type:text,notnull"`
} }
type StorableUserPreference struct { type StorableUserPreference struct {
bun.BaseModel `bun:"table:user_preference"` bun.BaseModel `bun:"table:user_preference"`
types.Identifiable types.Identifiable
PreferenceID string `bun:"preference_id,type:text,notnull"` Name Name `bun:"preference_id,type:text,notnull"`
PreferenceValue string `bun:"preference_value,type:text,notnull"` Value string `bun:"preference_value,type:text,notnull"`
UserID string `bun:"user_id,type:text,notnull"` UserID valuer.UUID `bun:"user_id,type:text,notnull"`
} }
type Preference struct { type GettablePreference struct {
Key string `json:"key"` *Preference
Name string `json:"name"` Value any `json:"preference_value"`
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"`
} }
func NewDefaultPreferenceMap() map[string]Preference { type UpdatablePreference struct {
return map[string]Preference{ Value string `json:"preference_value"`
"ORG_ONBOARDING": { }
Key: "ORG_ONBOARDING",
Name: "Organisation Onboarding", func NewAvailablePreference() map[Name]Preference {
return map[Name]Preference{
NameOrgOnboarding: {
Name: NameOrgOnboarding,
Description: "Organisation Onboarding", Description: "Organisation Onboarding",
ValueType: "boolean", ValueType: ValueTypeBoolean,
DefaultValue: false, DefaultValue: false,
AllowedValues: []interface{}{true, false}, AllowedValues: []any{true, false},
IsDiscreteValues: true, IsDiscreteValues: true,
AllowedScopes: []string{"org"}, AllowedScopes: []Scope{ScopeOrg},
}, },
"WELCOME_CHECKLIST_DO_LATER": { NameWelcomeChecklistDoLater: {
Key: "WELCOME_CHECKLIST_DO_LATER", Name: NameWelcomeChecklistDoLater,
Name: "Welcome Checklist Do Later",
Description: "Welcome Checklist Do Later", Description: "Welcome Checklist Do Later",
ValueType: "boolean", ValueType: ValueTypeBoolean,
DefaultValue: false, DefaultValue: false,
AllowedValues: []interface{}{true, false}, AllowedValues: []any{true, false},
IsDiscreteValues: true, IsDiscreteValues: true,
AllowedScopes: []string{"user"}, AllowedScopes: []Scope{ScopeUser},
}, },
"WELCOME_CHECKLIST_SEND_LOGS_SKIPPED": { NameWelcomeChecklistSendLogsSkipped: {
Key: "WELCOME_CHECKLIST_SEND_LOGS_SKIPPED", Name: NameWelcomeChecklistSendLogsSkipped,
Name: "Welcome Checklist Send Logs Skipped",
Description: "Welcome Checklist Send Logs Skipped", Description: "Welcome Checklist Send Logs Skipped",
ValueType: "boolean", ValueType: ValueTypeBoolean,
DefaultValue: false, DefaultValue: false,
AllowedValues: []interface{}{true, false}, AllowedValues: []any{true, false},
IsDiscreteValues: true, IsDiscreteValues: true,
AllowedScopes: []string{"user"}, AllowedScopes: []Scope{ScopeUser},
}, },
"WELCOME_CHECKLIST_SEND_TRACES_SKIPPED": { NameWelcomeChecklistSendTracesSkipped: {
Key: "WELCOME_CHECKLIST_SEND_TRACES_SKIPPED", Name: NameWelcomeChecklistSendTracesSkipped,
Name: "Welcome Checklist Send Traces Skipped",
Description: "Welcome Checklist Send Traces Skipped", Description: "Welcome Checklist Send Traces Skipped",
ValueType: "boolean", ValueType: ValueTypeBoolean,
DefaultValue: false, DefaultValue: false,
AllowedValues: []interface{}{true, false}, AllowedValues: []any{true, false},
IsDiscreteValues: true, IsDiscreteValues: true,
AllowedScopes: []string{"user"}, AllowedScopes: []Scope{ScopeUser},
}, },
"WELCOME_CHECKLIST_SEND_INFRA_METRICS_SKIPPED": { NameWelcomeChecklistSendInfraMetricsSkipped: {
Key: "WELCOME_CHECKLIST_SEND_INFRA_METRICS_SKIPPED", Name: NameWelcomeChecklistSendInfraMetricsSkipped,
Name: "Welcome Checklist Send Infra Metrics Skipped",
Description: "Welcome Checklist Send Infra Metrics Skipped", Description: "Welcome Checklist Send Infra Metrics Skipped",
ValueType: "boolean", ValueType: ValueTypeBoolean,
DefaultValue: false, DefaultValue: false,
AllowedValues: []interface{}{true, false}, AllowedValues: []any{true, false},
IsDiscreteValues: true, IsDiscreteValues: true,
AllowedScopes: []string{"user"}, AllowedScopes: []Scope{ScopeUser},
}, },
"WELCOME_CHECKLIST_SETUP_DASHBOARDS_SKIPPED": { NameWelcomeChecklistSetupDashboardsSkipped: {
Key: "WELCOME_CHECKLIST_SETUP_DASHBOARDS_SKIPPED", Name: NameWelcomeChecklistSetupDashboardsSkipped,
Name: "Welcome Checklist Setup Dashboards Skipped",
Description: "Welcome Checklist Setup Dashboards Skipped", Description: "Welcome Checklist Setup Dashboards Skipped",
ValueType: "boolean", ValueType: ValueTypeBoolean,
DefaultValue: false, DefaultValue: false,
AllowedValues: []interface{}{true, false}, AllowedValues: []any{true, false},
IsDiscreteValues: true, IsDiscreteValues: true,
AllowedScopes: []string{"user"}, AllowedScopes: []Scope{ScopeUser},
}, },
"WELCOME_CHECKLIST_SETUP_ALERTS_SKIPPED": { NameWelcomeChecklistSetupAlertsSkipped: {
Key: "WELCOME_CHECKLIST_SETUP_ALERTS_SKIPPED", Name: NameWelcomeChecklistSetupAlertsSkipped,
Name: "Welcome Checklist Setup Alerts Skipped",
Description: "Welcome Checklist Setup Alerts Skipped", Description: "Welcome Checklist Setup Alerts Skipped",
ValueType: "boolean", ValueType: ValueTypeBoolean,
DefaultValue: false, DefaultValue: false,
AllowedValues: []interface{}{true, false}, AllowedValues: []any{true, false},
IsDiscreteValues: true, IsDiscreteValues: true,
AllowedScopes: []string{"user"}, AllowedScopes: []Scope{ScopeUser},
}, },
"WELCOME_CHECKLIST_SETUP_SAVED_VIEW_SKIPPED": { NameWelcomeChecklistSetupSavedViewSkipped: {
Key: "WELCOME_CHECKLIST_SETUP_SAVED_VIEW_SKIPPED", Name: NameWelcomeChecklistSetupSavedViewSkipped,
Name: "Welcome Checklist Setup Saved View Skipped",
Description: "Welcome Checklist Setup Saved View Skipped", Description: "Welcome Checklist Setup Saved View Skipped",
ValueType: "boolean", ValueType: ValueTypeBoolean,
DefaultValue: false, DefaultValue: false,
AllowedValues: []interface{}{true, false}, AllowedValues: []any{true, false},
IsDiscreteValues: true, IsDiscreteValues: true,
AllowedScopes: []string{"user"}, AllowedScopes: []Scope{ScopeUser},
}, },
"SIDENAV_PINNED": { NameSidenavPinned: {
Key: "SIDENAV_PINNED", Name: NameSidenavPinned,
Name: "Keep the primary sidenav always open",
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.", 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, DefaultValue: false,
AllowedValues: []interface{}{true, false}, AllowedValues: []any{true, false},
IsDiscreteValues: true, IsDiscreteValues: true,
AllowedScopes: []string{"user"}, AllowedScopes: []Scope{ScopeUser},
}, },
} }
} }
func (p *Preference) ErrorValueTypeMismatch() error { func NewPreference(name Name, scope Scope, available map[Name]Preference) (*Preference, error) {
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "the preference value is not of expected type: %s", p.ValueType) preference, ok := available[name]
if !ok {
return nil, errors.Newf(errors.TypeNotFound, errors.CodeNotFound, "the preference does not exist: %s", name)
} }
func (p *Preference) checkIfInAllowedValues(preferenceValue interface{}) (bool, error) { 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)
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()
}
}
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 { return &Preference{
isInAllowedValues = true Name: name,
} Description: preference.Description,
case PreferenceValueTypeFloat: ValueType: preference.ValueType,
allowedValue, ok := value.(float64) DefaultValue: preference.DefaultValue,
if !ok { AllowedValues: preference.AllowedValues,
return false, p.ErrorValueTypeMismatch() IsDiscreteValues: preference.IsDiscreteValues,
Range: preference.Range,
AllowedScopes: preference.AllowedScopes,
}, nil
} }
if allowedValue == preferenceValue { func NewPreferenceFromAvailable(name Name, available map[Name]Preference) (*Preference, error) {
isInAllowedValues = true preference, ok := available[name]
}
case PreferenceValueTypeString:
allowedValue, ok := value.(string)
if !ok { if !ok {
return false, p.ErrorValueTypeMismatch() return nil, errors.Newf(errors.TypeNotFound, errors.CodeNotFound, "the preference does not exist: %s", name)
} }
if allowedValue == preferenceValue { return &Preference{
isInAllowedValues = true Name: name,
} Description: preference.Description,
case PreferenceValueTypeBoolean: ValueType: preference.ValueType,
allowedValue, ok := value.(bool) DefaultValue: preference.DefaultValue,
if !ok { AllowedValues: preference.AllowedValues,
return false, p.ErrorValueTypeMismatch() IsDiscreteValues: preference.IsDiscreteValues,
Range: preference.Range,
AllowedScopes: preference.AllowedScopes,
}, nil
} }
if allowedValue == preferenceValue { func NewGettablePreference(preference *Preference, value any) *GettablePreference {
isInAllowedValues = true return &GettablePreference{
} Preference: preference,
} Value: value,
}
return isInAllowedValues, 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()
} }
} }
// check the validity of the value being part of allowed values or the range specified if any func NewStorableOrgPreference(preference *Preference, value string, orgID valuer.UUID) *StorableOrgPreference {
if p.IsDiscreteValues { return &StorableOrgPreference{
if p.AllowedValues != nil { Name: preference.Name,
isInAllowedValues, valueMisMatchErr := p.checkIfInAllowedValues(typeSafeValue) Value: value,
OrgID: orgID,
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 { func NewStorableUserPreference(preference *Preference, value string, userID valuer.UUID) *StorableUserPreference {
isPreferenceEnabledForGivenScope := false return &StorableUserPreference{
if p.AllowedScopes != nil { Name: preference.Name,
for _, allowedScope := range p.AllowedScopes { Value: value,
if allowedScope == strings.ToLower(scope) { UserID: userID,
isPreferenceEnabledForGivenScope = true
}
}
}
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
} }
} }
type Store interface { type Store interface {
GetOrg(context.Context, string, string) (*StorableOrgPreference, error) GetByOrg(context.Context, valuer.UUID, Name) (*StorableOrgPreference, error)
GetAllOrg(context.Context, string) ([]*StorableOrgPreference, error) ListByOrg(context.Context, valuer.UUID) ([]*StorableOrgPreference, error)
UpsertOrg(context.Context, *StorableOrgPreference) error UpsertByOrg(context.Context, *StorableOrgPreference) error
GetUser(context.Context, string, string) (*StorableUserPreference, error) GetByUser(context.Context, valuer.UUID, Name) (*StorableUserPreference, error)
GetAllUser(context.Context, string) ([]*StorableUserPreference, error) ListByUser(context.Context, valuer.UUID) ([]*StorableUserPreference, error)
UpsertUser(context.Context, *StorableUserPreference) error UpsertByUser(context.Context, *StorableUserPreference) error
} }

View File

@ -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 }

View File

@ -1,23 +1,97 @@
package preferencetypes package preferencetypes
const ( import (
PreferenceValueTypeInteger string = "integer" "encoding/json"
PreferenceValueTypeFloat string = "float" "strconv"
PreferenceValueTypeString string = "string"
PreferenceValueTypeBoolean string = "boolean" "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/valuer"
) )
const ( var (
OrgAllowedScope string = "org" ValueTypeInteger = ValueType{valuer.NewString("integer")}
UserAllowedScope string = "user" 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 { type Range struct {
Min int64 `json:"min"` Min int64 `json:"min"`
Max int64 `json:"max"` Max int64 `json:"max"`
} }
type PreferenceWithValue struct { func NewPreferenceValueFromString(preference *Preference, value string) (any, error) {
Preference switch preference.ValueType {
Value interface{} `json:"value"` 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)
}
} }