mirror of https://github.com/grafana/grafana
Split preference store (#46843)
* Split preference store * Chore: Add tests to pref * Fix preference in wire * Rename and adjust * Add pref service test * Rename methods, add tests * Rename Preferences to Preference, names IDs correctly * Fix lint * Refactor Save * Refactor upsert Add new logic for QueryHistory Rename some fields according to go naming conventions Refactore tests * Roll back ID that breaks tests * Rename Id to ID in UpdatePreferenceQuery * Use preference as a model to modify store * Move pref store fakes to pref test file * Add integration tag for store tests * Adjust test Co-authored-by: yangkb09 <yangkb09@gmail.com>pull/47527/head
parent
060ccacbf9
commit
ecd6cd4a92
@ -0,0 +1,100 @@ |
||||
package pref |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"errors" |
||||
"time" |
||||
) |
||||
|
||||
var ErrPrefNotFound = errors.New("preference not found") |
||||
|
||||
type Preference struct { |
||||
ID int64 `xorm:"pk autoincr 'id'"` |
||||
OrgID int64 `xorm:"org_id"` |
||||
UserID int64 `xorm:"user_id"` |
||||
TeamID int64 `xorm:"team_id"` |
||||
Teams []int64 `xorm:"extends"` |
||||
Version int |
||||
HomeDashboardID int64 `xorm:"home_dashboard_id"` |
||||
Timezone string |
||||
WeekStart string |
||||
Theme string |
||||
Created time.Time |
||||
Updated time.Time |
||||
JSONData *PreferenceJSONData `xorm:"json_data"` |
||||
} |
||||
|
||||
type GetPreferenceWithDefaultsQuery struct { |
||||
Teams []int64 |
||||
OrgID int64 |
||||
UserID int64 |
||||
} |
||||
|
||||
type GetPreferenceQuery struct { |
||||
OrgID int64 |
||||
UserID int64 |
||||
TeamID int64 |
||||
} |
||||
|
||||
type SavePreferenceCommand struct { |
||||
UserID int64 |
||||
OrgID int64 |
||||
TeamID int64 |
||||
|
||||
HomeDashboardID int64 `json:"homeDashboardId,omitempty"` |
||||
Timezone string `json:"timezone,omitempty"` |
||||
WeekStart string `json:"weekStart,omitempty"` |
||||
Theme string `json:"theme,omitempty"` |
||||
Navbar *NavbarPreference `json:"navbar,omitempty"` |
||||
QueryHistory *QueryHistoryPreference `json:"queryHistory,omitempty"` |
||||
} |
||||
|
||||
type PatchPreferenceCommand struct { |
||||
UserID int64 |
||||
OrgID int64 |
||||
TeamID int64 |
||||
|
||||
HomeDashboardID *int64 `json:"homeDashboardId,omitempty"` |
||||
Timezone *string `json:"timezone,omitempty"` |
||||
WeekStart *string `json:"weekStart,omitempty"` |
||||
Theme *string `json:"theme,omitempty"` |
||||
Navbar *NavbarPreference `json:"navbar,omitempty"` |
||||
QueryHistory *QueryHistoryPreference `json:"queryHistory,omitempty"` |
||||
} |
||||
|
||||
type NavLink struct { |
||||
ID string `json:"id,omitempty"` |
||||
Text string `json:"text,omitempty"` |
||||
Url string `json:"url,omitempty"` |
||||
Target string `json:"target,omitempty"` |
||||
} |
||||
|
||||
type NavbarPreference struct { |
||||
SavedItems []NavLink `json:"savedItems"` |
||||
} |
||||
|
||||
type PreferenceJSONData struct { |
||||
Navbar NavbarPreference `json:"navbar"` |
||||
QueryHistory QueryHistoryPreference `json:"queryHistory"` |
||||
} |
||||
|
||||
type QueryHistoryPreference struct { |
||||
HomeTab string `json:"homeTab"` |
||||
} |
||||
|
||||
func (j *PreferenceJSONData) FromDB(data []byte) error { |
||||
dec := json.NewDecoder(bytes.NewBuffer(data)) |
||||
dec.UseNumber() |
||||
return dec.Decode(j) |
||||
} |
||||
|
||||
func (j *PreferenceJSONData) ToDB() ([]byte, error) { |
||||
if j == nil { |
||||
return nil, nil |
||||
} |
||||
|
||||
return json.Marshal(j) |
||||
} |
||||
|
||||
func (p Preference) TableName() string { return "preferences" } |
||||
@ -0,0 +1,13 @@ |
||||
package pref |
||||
|
||||
import ( |
||||
"context" |
||||
) |
||||
|
||||
type Service interface { |
||||
GetWithDefaults(context.Context, *GetPreferenceWithDefaultsQuery) (*Preference, error) |
||||
Get(context.Context, *GetPreferenceQuery) (*Preference, error) |
||||
Save(context.Context, *SavePreferenceCommand) error |
||||
Patch(ctx context.Context, cmd *PatchPreferenceCommand) error |
||||
GetDefaults() *Preference |
||||
} |
||||
@ -0,0 +1,203 @@ |
||||
package prefimpl |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"time" |
||||
|
||||
pref "github.com/grafana/grafana/pkg/services/preference" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore/db" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
) |
||||
|
||||
type Service struct { |
||||
store store |
||||
cfg *setting.Cfg |
||||
} |
||||
|
||||
func ProvideService(db db.DB, cfg *setting.Cfg) *Service { |
||||
return &Service{ |
||||
store: &sqlStore{ |
||||
db: db, |
||||
}, |
||||
cfg: cfg, |
||||
} |
||||
} |
||||
|
||||
func (s *Service) GetWithDefaults(ctx context.Context, query *pref.GetPreferenceWithDefaultsQuery) (*pref.Preference, error) { |
||||
listQuery := &pref.Preference{ |
||||
Teams: query.Teams, |
||||
OrgID: query.OrgID, |
||||
UserID: query.UserID, |
||||
} |
||||
prefs, err := s.store.List(ctx, listQuery) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
res := s.GetDefaults() |
||||
for _, p := range prefs { |
||||
if p.Theme != "" { |
||||
res.Theme = p.Theme |
||||
} |
||||
if p.Timezone != "" { |
||||
res.Timezone = p.Timezone |
||||
} |
||||
if p.WeekStart != "" { |
||||
res.WeekStart = p.WeekStart |
||||
} |
||||
if p.HomeDashboardID != 0 { |
||||
res.HomeDashboardID = p.HomeDashboardID |
||||
} |
||||
if p.JSONData != nil { |
||||
res.JSONData = p.JSONData |
||||
} |
||||
} |
||||
|
||||
return res, err |
||||
} |
||||
|
||||
func (s *Service) Get(ctx context.Context, query *pref.GetPreferenceQuery) (*pref.Preference, error) { |
||||
getPref := &pref.Preference{ |
||||
OrgID: query.OrgID, |
||||
UserID: query.UserID, |
||||
TeamID: query.TeamID, |
||||
} |
||||
prefs, err := s.store.Get(ctx, getPref) |
||||
if err != nil && !errors.Is(err, pref.ErrPrefNotFound) { |
||||
return nil, err |
||||
} |
||||
return prefs, nil |
||||
} |
||||
|
||||
func (s *Service) Save(ctx context.Context, cmd *pref.SavePreferenceCommand) error { |
||||
preference, err := s.store.Get(ctx, &pref.Preference{ |
||||
OrgID: cmd.OrgID, |
||||
UserID: cmd.UserID, |
||||
TeamID: cmd.TeamID, |
||||
}) |
||||
if err != nil { |
||||
if errors.Is(err, pref.ErrPrefNotFound) { |
||||
preference := &pref.Preference{ |
||||
UserID: cmd.UserID, |
||||
OrgID: cmd.OrgID, |
||||
TeamID: cmd.TeamID, |
||||
HomeDashboardID: cmd.HomeDashboardID, |
||||
Timezone: cmd.Timezone, |
||||
WeekStart: cmd.WeekStart, |
||||
Theme: cmd.Theme, |
||||
Created: time.Now(), |
||||
Updated: time.Now(), |
||||
} |
||||
_, err = s.store.Insert(ctx, preference) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return err |
||||
} |
||||
preference.Timezone = cmd.Timezone |
||||
preference.WeekStart = cmd.WeekStart |
||||
preference.Theme = cmd.Theme |
||||
preference.Updated = time.Now() |
||||
preference.Version += 1 |
||||
preference.JSONData = &pref.PreferenceJSONData{} |
||||
|
||||
if cmd.Navbar != nil { |
||||
preference.JSONData.Navbar = *cmd.Navbar |
||||
} |
||||
if cmd.QueryHistory != nil { |
||||
preference.JSONData.QueryHistory = *cmd.QueryHistory |
||||
} |
||||
return s.store.Update(ctx, preference) |
||||
} |
||||
|
||||
func (s *Service) Patch(ctx context.Context, cmd *pref.PatchPreferenceCommand) error { |
||||
var exists bool |
||||
preference, err := s.store.Get(ctx, &pref.Preference{ |
||||
OrgID: cmd.OrgID, |
||||
UserID: cmd.UserID, |
||||
TeamID: cmd.TeamID, |
||||
}) |
||||
if err != nil && !errors.Is(err, pref.ErrPrefNotFound) { |
||||
return err |
||||
} |
||||
|
||||
if errors.Is(err, pref.ErrPrefNotFound) { |
||||
preference = &pref.Preference{ |
||||
UserID: cmd.UserID, |
||||
OrgID: cmd.OrgID, |
||||
TeamID: cmd.TeamID, |
||||
Created: time.Now(), |
||||
JSONData: &pref.PreferenceJSONData{}, |
||||
} |
||||
} else { |
||||
exists = true |
||||
} |
||||
|
||||
if cmd.Navbar != nil { |
||||
if preference.JSONData == nil { |
||||
preference.JSONData = &pref.PreferenceJSONData{} |
||||
} |
||||
if cmd.Navbar.SavedItems != nil { |
||||
preference.JSONData.Navbar.SavedItems = cmd.Navbar.SavedItems |
||||
} |
||||
} |
||||
|
||||
if cmd.QueryHistory != nil { |
||||
if preference.JSONData == nil { |
||||
preference.JSONData = &pref.PreferenceJSONData{} |
||||
} |
||||
if cmd.QueryHistory.HomeTab != "" { |
||||
preference.JSONData.QueryHistory.HomeTab = cmd.QueryHistory.HomeTab |
||||
} |
||||
} |
||||
|
||||
if cmd.HomeDashboardID != nil { |
||||
preference.HomeDashboardID = *cmd.HomeDashboardID |
||||
} |
||||
|
||||
if cmd.Timezone != nil { |
||||
preference.Timezone = *cmd.Timezone |
||||
} |
||||
|
||||
if cmd.WeekStart != nil { |
||||
preference.WeekStart = *cmd.WeekStart |
||||
} |
||||
|
||||
if cmd.Theme != nil { |
||||
preference.Theme = *cmd.Theme |
||||
} |
||||
|
||||
preference.Updated = time.Now() |
||||
preference.Version += 1 |
||||
|
||||
// Wrap this in an if statement to maintain backwards compatibility
|
||||
if cmd.Navbar != nil { |
||||
if preference.JSONData == nil { |
||||
preference.JSONData = &pref.PreferenceJSONData{} |
||||
} |
||||
if cmd.Navbar.SavedItems != nil { |
||||
preference.JSONData.Navbar.SavedItems = cmd.Navbar.SavedItems |
||||
} |
||||
} |
||||
|
||||
if exists { |
||||
err = s.store.Update(ctx, preference) |
||||
} else { |
||||
_, err = s.store.Insert(ctx, preference) |
||||
} |
||||
return err |
||||
} |
||||
|
||||
func (s *Service) GetDefaults() *pref.Preference { |
||||
defaults := &pref.Preference{ |
||||
Theme: s.cfg.DefaultTheme, |
||||
Timezone: s.cfg.DateFormats.DefaultTimezone, |
||||
WeekStart: s.cfg.DateFormats.DefaultWeekStart, |
||||
HomeDashboardID: 0, |
||||
JSONData: &pref.PreferenceJSONData{}, |
||||
} |
||||
|
||||
return defaults |
||||
} |
||||
@ -0,0 +1,446 @@ |
||||
package prefimpl |
||||
|
||||
import ( |
||||
"context" |
||||
"testing" |
||||
|
||||
"github.com/google/go-cmp/cmp" |
||||
pref "github.com/grafana/grafana/pkg/services/preference" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestPreferencesService(t *testing.T) { |
||||
prefStoreFake := newPreferenceStoreFake() |
||||
prefService := &Service{ |
||||
store: prefStoreFake, |
||||
} |
||||
|
||||
emptyNavbarPreferences := pref.NavbarPreference{} |
||||
userNavbarPreferences := pref.NavbarPreference{ |
||||
SavedItems: []pref.NavLink{{ |
||||
ID: "explore", |
||||
Text: "Explore", |
||||
Url: "/explore", |
||||
}}, |
||||
} |
||||
orgNavbarPreferences := pref.NavbarPreference{ |
||||
SavedItems: []pref.NavLink{{ |
||||
ID: "alerting", |
||||
Text: "Alerting", |
||||
Url: "/alerting", |
||||
}}, |
||||
} |
||||
team1NavbarPreferences := pref.NavbarPreference{ |
||||
SavedItems: []pref.NavLink{{ |
||||
ID: "dashboards", |
||||
Text: "Dashboards", |
||||
Url: "/dashboards", |
||||
}}, |
||||
} |
||||
team2NavbarPreferences := pref.NavbarPreference{ |
||||
SavedItems: []pref.NavLink{{ |
||||
ID: "home", |
||||
Text: "Home", |
||||
Url: "/home", |
||||
}}, |
||||
} |
||||
|
||||
emptyQueryPreference := pref.QueryHistoryPreference{} |
||||
|
||||
queryPreference := pref.QueryHistoryPreference{ |
||||
HomeTab: "hometab", |
||||
} |
||||
|
||||
queryPreference2 := pref.QueryHistoryPreference{ |
||||
HomeTab: "hometab", |
||||
} |
||||
|
||||
emptyPreferencesJsonData := pref.PreferenceJSONData{ |
||||
Navbar: emptyNavbarPreferences, |
||||
} |
||||
userPreferencesJsonData := pref.PreferenceJSONData{ |
||||
Navbar: userNavbarPreferences, |
||||
QueryHistory: queryPreference, |
||||
} |
||||
orgPreferencesJsonData := pref.PreferenceJSONData{ |
||||
Navbar: orgNavbarPreferences, |
||||
} |
||||
team2PreferencesJsonData := pref.PreferenceJSONData{ |
||||
Navbar: team2NavbarPreferences, |
||||
} |
||||
team1PreferencesJsonData := pref.PreferenceJSONData{ |
||||
Navbar: team1NavbarPreferences, |
||||
} |
||||
|
||||
t.Run("GetDefaults should return defaults", func(t *testing.T) { |
||||
prefService.cfg = setting.NewCfg() |
||||
prefService.cfg.DefaultTheme = "light" |
||||
prefService.cfg.DateFormats.DefaultTimezone = "UTC" |
||||
|
||||
preferences := prefService.GetDefaults() |
||||
expected := &pref.Preference{ |
||||
Theme: "light", |
||||
Timezone: "UTC", |
||||
HomeDashboardID: 0, |
||||
JSONData: &pref.PreferenceJSONData{}, |
||||
} |
||||
if diff := cmp.Diff(expected, preferences); diff != "" { |
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff) |
||||
} |
||||
}) |
||||
|
||||
t.Run("GetDefaults with no saved preferences should return defaults", func(t *testing.T) { |
||||
prefStoreFake.ExpectedPreference = &pref.Preference{ |
||||
Theme: "light", |
||||
Timezone: "UTC", |
||||
} |
||||
query := &pref.GetPreferenceWithDefaultsQuery{} |
||||
preferences, err := prefService.GetWithDefaults(context.Background(), query) |
||||
require.NoError(t, err) |
||||
expected := &pref.Preference{ |
||||
Theme: "light", |
||||
Timezone: "UTC", |
||||
HomeDashboardID: 0, |
||||
JSONData: &emptyPreferencesJsonData, |
||||
} |
||||
if diff := cmp.Diff(expected, preferences); diff != "" { |
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff) |
||||
} |
||||
}) |
||||
|
||||
t.Run("GetWithDefaults with saved org and user home dashboard should return user home dashboard", func(t *testing.T) { |
||||
prefStoreFake.ExpectedPreference = &pref.Preference{} |
||||
prefStoreFake.ExpectedListPreferences = []*pref.Preference{ |
||||
{ |
||||
OrgID: 1, |
||||
HomeDashboardID: 1, |
||||
Theme: "dark", |
||||
Timezone: "UTC", |
||||
}, |
||||
{ |
||||
OrgID: 1, |
||||
UserID: 1, |
||||
HomeDashboardID: 4, |
||||
Theme: "light", |
||||
WeekStart: "1", |
||||
}, |
||||
} |
||||
query := &pref.GetPreferenceWithDefaultsQuery{OrgID: 1, UserID: 1} |
||||
preferences, err := prefService.GetWithDefaults(context.Background(), query) |
||||
require.NoError(t, err) |
||||
expected := &pref.Preference{ |
||||
Theme: "light", |
||||
Timezone: "UTC", |
||||
WeekStart: "1", |
||||
HomeDashboardID: 4, |
||||
JSONData: &pref.PreferenceJSONData{}, |
||||
} |
||||
if diff := cmp.Diff(expected, preferences); diff != "" { |
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff) |
||||
} |
||||
}) |
||||
|
||||
t.Run("GetWithDefaults with saved org and other user home dashboard should return org home dashboard", func(t *testing.T) { |
||||
prefStoreFake.ExpectedPreference = &pref.Preference{} |
||||
prefStoreFake.ExpectedListPreferences = []*pref.Preference{ |
||||
{ |
||||
OrgID: 1, |
||||
HomeDashboardID: 1, |
||||
Theme: "dark", |
||||
Timezone: "UTC", |
||||
WeekStart: "1", |
||||
}, |
||||
{ |
||||
OrgID: 1, |
||||
UserID: 1, |
||||
HomeDashboardID: 4, |
||||
Theme: "light", |
||||
Timezone: "browser", |
||||
WeekStart: "2", |
||||
}, |
||||
} |
||||
prefService.GetDefaults().HomeDashboardID = 1 |
||||
query := &pref.GetPreferenceWithDefaultsQuery{OrgID: 1, UserID: 2} |
||||
preferences, err := prefService.GetWithDefaults(context.Background(), query) |
||||
require.NoError(t, err) |
||||
expected := &pref.Preference{ |
||||
Theme: "light", |
||||
Timezone: "browser", |
||||
WeekStart: "2", |
||||
HomeDashboardID: 4, |
||||
JSONData: &pref.PreferenceJSONData{}, |
||||
} |
||||
if diff := cmp.Diff(expected, preferences); diff != "" { |
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff) |
||||
} |
||||
}) |
||||
|
||||
t.Run("GetPreferencesWithDefaults with saved org and user json data should return user json data", func(t *testing.T) { |
||||
prefStoreFake.ExpectedPreference = &pref.Preference{} |
||||
prefStoreFake.ExpectedListPreferences = []*pref.Preference{ |
||||
{ |
||||
OrgID: 1, |
||||
JSONData: &orgPreferencesJsonData, |
||||
}, |
||||
{ |
||||
OrgID: 1, |
||||
UserID: 1, |
||||
JSONData: &userPreferencesJsonData, |
||||
}, |
||||
} |
||||
query := &pref.GetPreferenceWithDefaultsQuery{OrgID: 1, UserID: 1} |
||||
preference, err := prefService.GetWithDefaults(context.Background(), query) |
||||
require.NoError(t, err) |
||||
require.Equal(t, &pref.Preference{ |
||||
Theme: "light", |
||||
JSONData: &userPreferencesJsonData, |
||||
Timezone: "UTC", |
||||
}, preference) |
||||
}) |
||||
|
||||
t.Run("GetWithDefaults with saved org and teams json data should return last team json data", func(t *testing.T) { |
||||
prefStoreFake.ExpectedPreference = &pref.Preference{} |
||||
prefStoreFake.ExpectedListPreferences = []*pref.Preference{ |
||||
{ |
||||
OrgID: 1, |
||||
JSONData: &orgPreferencesJsonData, |
||||
}, |
||||
{ |
||||
OrgID: 1, |
||||
TeamID: 2, |
||||
JSONData: &team1PreferencesJsonData, |
||||
}, |
||||
{ |
||||
OrgID: 1, |
||||
TeamID: 3, |
||||
JSONData: &team2PreferencesJsonData, |
||||
}, |
||||
} |
||||
query := &pref.GetPreferenceWithDefaultsQuery{ |
||||
OrgID: 1, Teams: []int64{2, 3}, |
||||
} |
||||
preference, err := prefService.GetWithDefaults(context.Background(), query) |
||||
require.NoError(t, err) |
||||
require.Equal(t, &pref.Preference{ |
||||
Timezone: "UTC", |
||||
Theme: "light", |
||||
JSONData: &team2PreferencesJsonData, |
||||
}, preference) |
||||
}) |
||||
|
||||
t.Run("GetWithDefaults with saved org and teams home dashboard should return last team home dashboard", func(t *testing.T) { |
||||
prefStoreFake.ExpectedPreference = &pref.Preference{ |
||||
Theme: "dark", |
||||
Timezone: "UTC", |
||||
} |
||||
prefStoreFake.ExpectedListPreferences = []*pref.Preference{ |
||||
{ |
||||
OrgID: 1, |
||||
HomeDashboardID: 1, |
||||
Theme: "light", |
||||
Timezone: "browser", |
||||
WeekStart: "1", |
||||
}, |
||||
{ |
||||
OrgID: 1, |
||||
UserID: 1, |
||||
HomeDashboardID: 4, |
||||
Theme: "light", |
||||
Timezone: "browser", |
||||
WeekStart: "2", |
||||
}, |
||||
} |
||||
|
||||
query := &pref.GetPreferenceWithDefaultsQuery{OrgID: 1, Teams: []int64{2, 3}} |
||||
preferences, err := prefService.GetWithDefaults(context.Background(), query) |
||||
require.NoError(t, err) |
||||
expected := &pref.Preference{ |
||||
Theme: "light", |
||||
Timezone: "browser", |
||||
WeekStart: "2", |
||||
HomeDashboardID: 4, |
||||
JSONData: &pref.PreferenceJSONData{}, |
||||
} |
||||
if diff := cmp.Diff(expected, preferences); diff != "" { |
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff) |
||||
} |
||||
}) |
||||
|
||||
t.Run("SavePreferences for a user should store correct values", func(t *testing.T) { |
||||
prefStoreFake.ExpectedPreference = &pref.Preference{ |
||||
ID: 1, |
||||
OrgID: 1, |
||||
UserID: 3, |
||||
TeamID: 6, |
||||
HomeDashboardID: 5, |
||||
Timezone: "browser", |
||||
WeekStart: "1", |
||||
Theme: "dark", |
||||
} |
||||
err := prefService.Save(context.Background(), |
||||
&pref.SavePreferenceCommand{ |
||||
Theme: "dark", |
||||
Timezone: "browser", |
||||
HomeDashboardID: 5, |
||||
WeekStart: "1"}, |
||||
) |
||||
require.NoError(t, err) |
||||
}) |
||||
|
||||
t.Run("SavePreferences for a user should store correct values, when preference not found", func(t *testing.T) { |
||||
prefStoreFake.ExpectedGetError = pref.ErrPrefNotFound |
||||
prefStoreFake.ExpectedPreference = &pref.Preference{ |
||||
ID: 1, |
||||
OrgID: 1, |
||||
UserID: 3, |
||||
TeamID: 6, |
||||
HomeDashboardID: 5, |
||||
Timezone: "browser", |
||||
WeekStart: "1", |
||||
Theme: "dark", |
||||
} |
||||
err := prefService.Save(context.Background(), |
||||
&pref.SavePreferenceCommand{ |
||||
Theme: "dark", |
||||
Timezone: "browser", |
||||
HomeDashboardID: 5, |
||||
WeekStart: "1", |
||||
}, |
||||
) |
||||
require.NoError(t, err) |
||||
prefStoreFake.ExpectedGetError = nil |
||||
}) |
||||
|
||||
t.Run("SavePreferences for a user should store correct values with nav and query history", func(t *testing.T) { |
||||
prefStoreFake.ExpectedPreference = &pref.Preference{ |
||||
ID: 1, |
||||
OrgID: 1, |
||||
UserID: 3, |
||||
TeamID: 6, |
||||
HomeDashboardID: 5, |
||||
Timezone: "browser", |
||||
WeekStart: "1", |
||||
Theme: "dark", |
||||
JSONData: &userPreferencesJsonData, |
||||
} |
||||
err := prefService.Save(context.Background(), |
||||
&pref.SavePreferenceCommand{ |
||||
Theme: "dark", |
||||
Timezone: "browser", |
||||
HomeDashboardID: 5, |
||||
WeekStart: "1", |
||||
Navbar: &userNavbarPreferences, |
||||
QueryHistory: &emptyQueryPreference, |
||||
}, |
||||
) |
||||
require.NoError(t, err) |
||||
}) |
||||
|
||||
t.Run("Get for a user should store correct values", func(t *testing.T) { |
||||
prefStoreFake.ExpectedPreference = &pref.Preference{ |
||||
HomeDashboardID: 5, |
||||
Timezone: "browser", |
||||
WeekStart: "1", |
||||
Theme: "dark", |
||||
} |
||||
preference, err := prefService.Get(context.Background(), &pref.GetPreferenceQuery{}) |
||||
require.NoError(t, err) |
||||
|
||||
expected := &pref.Preference{ |
||||
ID: preference.ID, |
||||
Version: preference.Version, |
||||
HomeDashboardID: 5, |
||||
Timezone: "browser", |
||||
WeekStart: "1", |
||||
Theme: "dark", |
||||
Created: preference.Created, |
||||
Updated: preference.Updated, |
||||
} |
||||
if diff := cmp.Diff(expected, preference); diff != "" { |
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff) |
||||
} |
||||
}) |
||||
|
||||
t.Run("Patch for a user should store correct values", func(t *testing.T) { |
||||
darkTheme := "dark" |
||||
prefStoreFake.ExpectedPreference = &pref.Preference{ |
||||
HomeDashboardID: 5, |
||||
Timezone: "browser", |
||||
WeekStart: "1", |
||||
Theme: "dark", |
||||
JSONData: &userPreferencesJsonData, |
||||
} |
||||
err := prefService.Patch(context.Background(), |
||||
&pref.PatchPreferenceCommand{ |
||||
Theme: &darkTheme, |
||||
Navbar: &userNavbarPreferences, |
||||
QueryHistory: &queryPreference2, |
||||
}) |
||||
require.NoError(t, err) |
||||
}) |
||||
|
||||
t.Run("Patch for a user should store correct values, without navbar and query history", func(t *testing.T) { |
||||
darkTheme := "dark" |
||||
prefStoreFake.ExpectedPreference = &pref.Preference{ |
||||
HomeDashboardID: 5, |
||||
Timezone: "browser", |
||||
WeekStart: "1", |
||||
Theme: "dark", |
||||
} |
||||
err := prefService.Patch(context.Background(), |
||||
&pref.PatchPreferenceCommand{ |
||||
Theme: &darkTheme, |
||||
Navbar: &userNavbarPreferences, |
||||
QueryHistory: &queryPreference2, |
||||
}) |
||||
require.NoError(t, err) |
||||
}) |
||||
|
||||
t.Run("Patch for a user should store correct values, when preference not found", func(t *testing.T) { |
||||
timezone := "browser" |
||||
weekStart := "1" |
||||
homeDashboardID := int64(5) |
||||
prefStoreFake.ExpectedGetError = pref.ErrPrefNotFound |
||||
prefStoreFake.ExpectedPreference = nil |
||||
|
||||
err := prefService.Patch(context.Background(), |
||||
&pref.PatchPreferenceCommand{ |
||||
HomeDashboardID: &homeDashboardID, |
||||
Timezone: &timezone, |
||||
WeekStart: &weekStart, |
||||
Navbar: &emptyNavbarPreferences, |
||||
QueryHistory: &emptyQueryPreference, |
||||
}) |
||||
require.NoError(t, err) |
||||
prefStoreFake.ExpectedGetError = nil |
||||
}) |
||||
} |
||||
|
||||
type FakePreferenceStore struct { |
||||
ExpectedPreference *pref.Preference |
||||
ExpectedListPreferences []*pref.Preference |
||||
ExpectedID int64 |
||||
ExpectedError error |
||||
ExpectedGetError error |
||||
} |
||||
|
||||
func newPreferenceStoreFake() *FakePreferenceStore { |
||||
return &FakePreferenceStore{} |
||||
} |
||||
|
||||
func (f *FakePreferenceStore) List(ctx context.Context, query *pref.Preference) ([]*pref.Preference, error) { |
||||
return f.ExpectedListPreferences, f.ExpectedError |
||||
} |
||||
|
||||
func (f *FakePreferenceStore) Get(ctx context.Context, query *pref.Preference) (*pref.Preference, error) { |
||||
return f.ExpectedPreference, f.ExpectedGetError |
||||
} |
||||
|
||||
func (f *FakePreferenceStore) Insert(ctx context.Context, cmd *pref.Preference) (int64, error) { |
||||
return f.ExpectedID, f.ExpectedError |
||||
} |
||||
|
||||
func (f *FakePreferenceStore) Update(ctx context.Context, cmd *pref.Preference) error { |
||||
return f.ExpectedError |
||||
} |
||||
@ -0,0 +1,88 @@ |
||||
package prefimpl |
||||
|
||||
import ( |
||||
"context" |
||||
"strings" |
||||
|
||||
pref "github.com/grafana/grafana/pkg/services/preference" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore/db" |
||||
) |
||||
|
||||
type store interface { |
||||
Get(context.Context, *pref.Preference) (*pref.Preference, error) |
||||
List(context.Context, *pref.Preference) ([]*pref.Preference, error) |
||||
Insert(context.Context, *pref.Preference) (int64, error) |
||||
Update(context.Context, *pref.Preference) error |
||||
} |
||||
|
||||
type sqlStore struct { |
||||
db db.DB |
||||
} |
||||
|
||||
func (s *sqlStore) Get(ctx context.Context, query *pref.Preference) (*pref.Preference, error) { |
||||
var prefs pref.Preference |
||||
err := s.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { |
||||
exist, err := sess.Where("org_id=? AND user_id=? AND team_id=?", query.OrgID, query.UserID, query.TeamID).Get(&prefs) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if !exist { |
||||
return pref.ErrPrefNotFound |
||||
} |
||||
return nil |
||||
}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &prefs, nil |
||||
} |
||||
|
||||
func (s *sqlStore) List(ctx context.Context, query *pref.Preference) ([]*pref.Preference, error) { |
||||
prefs := make([]*pref.Preference, 0) |
||||
params := make([]interface{}, 0) |
||||
filter := "" |
||||
|
||||
if len(query.Teams) > 0 { |
||||
filter = "(org_id=? AND team_id IN (?" + strings.Repeat(",?", len(query.Teams)-1) + ")) OR " |
||||
params = append(params, query.OrgID) |
||||
for _, v := range query.Teams { |
||||
params = append(params, v) |
||||
} |
||||
} |
||||
|
||||
filter += "(org_id=? AND user_id=? AND team_id=0) OR (org_id=? AND team_id=0 AND user_id=0)" |
||||
params = append(params, query.OrgID) |
||||
params = append(params, query.UserID) |
||||
params = append(params, query.OrgID) |
||||
|
||||
err := s.db.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error { |
||||
err := dbSession.Where(filter, params...). |
||||
OrderBy("user_id ASC, team_id ASC"). |
||||
Find(&prefs) |
||||
|
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
return nil |
||||
}) |
||||
return prefs, err |
||||
} |
||||
|
||||
func (s *sqlStore) Update(ctx context.Context, cmd *pref.Preference) error { |
||||
return s.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { |
||||
_, err := sess.ID(cmd.ID).AllCols().Update(cmd) |
||||
return err |
||||
}) |
||||
} |
||||
|
||||
func (s *sqlStore) Insert(ctx context.Context, cmd *pref.Preference) (int64, error) { |
||||
var ID int64 |
||||
var err error |
||||
err = s.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { |
||||
ID, err = sess.Insert(cmd) |
||||
return err |
||||
}) |
||||
return ID, err |
||||
} |
||||
@ -0,0 +1,170 @@ |
||||
//go:build integration
|
||||
// +build integration
|
||||
|
||||
package prefimpl |
||||
|
||||
import ( |
||||
"context" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/google/go-cmp/cmp" |
||||
"github.com/grafana/grafana/pkg/models" |
||||
pref "github.com/grafana/grafana/pkg/services/preference" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestPreferencesDataAccess(t *testing.T) { |
||||
ss := sqlstore.InitTestDB(t) |
||||
prefStore := sqlStore{db: ss} |
||||
orgNavbarPreferences := pref.NavbarPreference{ |
||||
SavedItems: []pref.NavLink{{ |
||||
ID: "alerting", |
||||
Text: "Alerting", |
||||
Url: "/alerting", |
||||
}}, |
||||
} |
||||
|
||||
t.Run("Get with saved org and user home dashboard returns not found", func(t *testing.T) { |
||||
query := &pref.Preference{OrgID: 1, UserID: 1, TeamID: 2} |
||||
prefs, err := prefStore.Get(context.Background(), query) |
||||
require.EqualError(t, err, pref.ErrPrefNotFound.Error()) |
||||
require.Nil(t, prefs) |
||||
}) |
||||
|
||||
t.Run("Get with saved org and user home dashboard should return user home dashboard", func(t *testing.T) { |
||||
_, err := prefStore.Insert(context.Background(), |
||||
&pref.Preference{ |
||||
OrgID: 1, |
||||
UserID: 1, |
||||
HomeDashboardID: 4, |
||||
TeamID: 2, |
||||
Created: time.Now(), |
||||
Updated: time.Now(), |
||||
}) |
||||
require.NoError(t, err) |
||||
|
||||
query := &pref.Preference{OrgID: 1, UserID: 1, TeamID: 2} |
||||
prefs, err := prefStore.Get(context.Background(), query) |
||||
require.NoError(t, err) |
||||
require.Equal(t, int64(4), prefs.HomeDashboardID) |
||||
}) |
||||
|
||||
t.Run("List with saved org and user home dashboard should return user home dashboard", func(t *testing.T) { |
||||
_, err := prefStore.Insert(context.Background(), |
||||
&pref.Preference{ |
||||
OrgID: 1, |
||||
UserID: 1, |
||||
TeamID: 3, |
||||
HomeDashboardID: 1, |
||||
Created: time.Now(), |
||||
Updated: time.Now(), |
||||
}) |
||||
require.NoError(t, err) |
||||
|
||||
query := &pref.Preference{OrgID: 1, UserID: 1, Teams: []int64{2}} |
||||
prefs, err := prefStore.List(context.Background(), query) |
||||
require.NoError(t, err) |
||||
require.Equal(t, int64(4), prefs[0].HomeDashboardID) |
||||
}) |
||||
|
||||
t.Run("List with saved org and other user home dashboard should return org home dashboard", func(t *testing.T) { |
||||
_, err := prefStore.Insert(context.Background(), |
||||
&pref.Preference{ |
||||
OrgID: 1, |
||||
UserID: 2, |
||||
TeamID: 3, |
||||
HomeDashboardID: 1, |
||||
Created: time.Now(), |
||||
Updated: time.Now(), |
||||
}) |
||||
require.NoError(t, err) |
||||
|
||||
query := &pref.Preference{OrgID: 1, UserID: 1, Teams: []int64{3}} |
||||
prefs, err := prefStore.List(context.Background(), query) |
||||
require.NoError(t, err) |
||||
require.Equal(t, int64(1), prefs[0].HomeDashboardID) |
||||
require.Equal(t, int64(1), prefs[1].HomeDashboardID) |
||||
}) |
||||
|
||||
t.Run("List with saved org and teams home dashboard should return last team home dashboard", func(t *testing.T) { |
||||
query := &pref.Preference{ |
||||
OrgID: 1, Teams: []int64{2, 3}, |
||||
} |
||||
prefs, err := prefStore.List(context.Background(), query) |
||||
require.NoError(t, err) |
||||
require.Equal(t, int64(4), prefs[0].HomeDashboardID) |
||||
require.Equal(t, int64(1), prefs[1].HomeDashboardID) |
||||
require.Equal(t, int64(1), prefs[2].HomeDashboardID) |
||||
}) |
||||
|
||||
t.Run("List with saved org and other teams home dashboard should return org home dashboard", func(t *testing.T) { |
||||
_, err := prefStore.Insert(context.Background(), &pref.Preference{OrgID: 1, HomeDashboardID: 1, Created: time.Now(), Updated: time.Now()}) |
||||
require.NoError(t, err) |
||||
_, err = prefStore.Insert(context.Background(), &pref.Preference{OrgID: 1, TeamID: 2, HomeDashboardID: 2, Created: time.Now(), Updated: time.Now()}) |
||||
require.NoError(t, err) |
||||
_, err = prefStore.Insert(context.Background(), &pref.Preference{OrgID: 1, TeamID: 3, HomeDashboardID: 3, Created: time.Now(), Updated: time.Now()}) |
||||
require.NoError(t, err) |
||||
|
||||
query := &pref.Preference{OrgID: 1} |
||||
prefs, err := prefStore.List(context.Background(), query) |
||||
require.NoError(t, err) |
||||
require.Equal(t, int64(1), prefs[0].HomeDashboardID) |
||||
}) |
||||
|
||||
t.Run("Update for a user should only modify a single value", func(t *testing.T) { |
||||
ss := sqlstore.InitTestDB(t) |
||||
prefStore := sqlStore{db: ss} |
||||
id, err := prefStore.Insert(context.Background(), &pref.Preference{ |
||||
UserID: models.SignedInUser{}.UserId, |
||||
Theme: "dark", |
||||
Timezone: "browser", |
||||
HomeDashboardID: 5, |
||||
WeekStart: "1", |
||||
JSONData: &pref.PreferenceJSONData{Navbar: orgNavbarPreferences}, |
||||
Created: time.Now(), |
||||
Updated: time.Now(), |
||||
}) |
||||
require.NoError(t, err) |
||||
|
||||
err = prefStore.Update(context.Background(), &pref.Preference{ |
||||
ID: id, |
||||
Theme: "dark", |
||||
HomeDashboardID: 5, |
||||
Timezone: "browser", |
||||
WeekStart: "1", |
||||
Created: time.Now(), |
||||
Updated: time.Now(), |
||||
JSONData: &pref.PreferenceJSONData{}, |
||||
}) |
||||
require.NoError(t, err) |
||||
query := &pref.Preference{} |
||||
prefs, err := prefStore.List(context.Background(), query) |
||||
require.NoError(t, err) |
||||
expected := &pref.Preference{ |
||||
ID: prefs[0].ID, |
||||
Version: prefs[0].Version, |
||||
HomeDashboardID: 5, |
||||
Timezone: "browser", |
||||
WeekStart: "1", |
||||
Theme: "dark", |
||||
JSONData: prefs[0].JSONData, |
||||
Created: prefs[0].Created, |
||||
Updated: prefs[0].Updated, |
||||
} |
||||
if diff := cmp.Diff(expected, prefs[0]); diff != "" { |
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff) |
||||
} |
||||
}) |
||||
t.Run("insert preference that does not exist", func(t *testing.T) { |
||||
_, err := prefStore.Insert(context.Background(), |
||||
&pref.Preference{ |
||||
UserID: models.SignedInUser{}.UserId, |
||||
Created: time.Now(), |
||||
Updated: time.Now(), |
||||
JSONData: &pref.PreferenceJSONData{}, |
||||
}) |
||||
require.NoError(t, err) |
||||
}) |
||||
} |
||||
@ -0,0 +1,32 @@ |
||||
package preftest |
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
pref "github.com/grafana/grafana/pkg/services/preference" |
||||
) |
||||
|
||||
type FakePreferenceService struct { |
||||
ExpectedPreference *pref.Preference |
||||
ExpectedError error |
||||
} |
||||
|
||||
func NewPreferenceServiceFake() *FakePreferenceService { |
||||
return &FakePreferenceService{} |
||||
} |
||||
|
||||
func (f *FakePreferenceService) GetWithDefaults(ctx context.Context, query *pref.GetPreferenceWithDefaultsQuery) (*pref.Preference, error) { |
||||
return f.ExpectedPreference, f.ExpectedError |
||||
} |
||||
|
||||
func (f *FakePreferenceService) Get(ctx context.Context, query *pref.GetPreferenceQuery) (*pref.Preference, error) { |
||||
return f.ExpectedPreference, f.ExpectedError |
||||
} |
||||
|
||||
func (f *FakePreferenceService) Save(ctx context.Context, cmd *pref.SavePreferenceCommand) error { |
||||
return f.ExpectedError |
||||
} |
||||
|
||||
func (f *FakePreferenceService) GetDefaults() *pref.Preference { |
||||
return f.ExpectedPreference |
||||
} |
||||
Loading…
Reference in new issue