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>
This commit is contained in:
idafurjes 2022-04-14 14:22:00 +02:00 committed by GitHub
parent 060ccacbf9
commit ecd6cd4a92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1054 additions and 1 deletions

View File

@ -65,13 +65,13 @@ import (
plugindashboardsservice "github.com/grafana/grafana/pkg/services/plugindashboards/service"
"github.com/grafana/grafana/pkg/services/pluginsettings"
pluginSettings "github.com/grafana/grafana/pkg/services/pluginsettings/service"
"github.com/grafana/grafana/pkg/services/preference/prefimpl"
"github.com/grafana/grafana/pkg/services/query"
"github.com/grafana/grafana/pkg/services/queryhistory"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/services/schemaloader"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/searchV2"
"github.com/grafana/grafana/pkg/services/secrets"
secretsDatabase "github.com/grafana/grafana/pkg/services/secrets/database"
@ -235,6 +235,7 @@ var wireBasicSet = wire.NewSet(
wire.Bind(new(alerting.DashAlertExtractor), new(*alerting.DashAlertExtractorService)),
comments.ProvideService,
guardian.ProvideService,
prefimpl.ProvideService,
avatar.ProvideAvatarCacheServer,
authproxy.ProvideAuthProxy,
statscollector.ProvideService,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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