From a4689e646a0a257d974dac91af7d3c531a19bde9 Mon Sep 17 00:00:00 2001 From: Wouter Smeenk Date: Tue, 15 Sep 2020 15:20:53 +0200 Subject: [PATCH] Dashboard: Support configuring default timezone via config file (#27404) Add a default timezone that the administrator can set in the settings. This setting is be used as default for the users timezone preference. Can be used when creating Grafana instances without administrator intervention, in order to give user the correct default timezone. Fixes #25654 (cherry picked from commit 39eba5065bfdd86686bfe3422a2f84b49f0d32f9) --- conf/defaults.ini | 3 +- conf/sample.ini | 3 ++ docs/sources/administration/configuration.md | 3 ++ docs/sources/administration/preferences.md | 2 +- pkg/services/sqlstore/preferences.go | 8 ++--- pkg/services/sqlstore/preferences_test.go | 23 ++++++------ pkg/services/sqlstore/sqlstore.go | 1 + pkg/setting/date_formats.go | 30 ++++++++++++++++ pkg/setting/date_formats_test.go | 37 ++++++++++++++++++++ 9 files changed, 94 insertions(+), 16 deletions(-) create mode 100644 pkg/setting/date_formats_test.go diff --git a/conf/defaults.ini b/conf/defaults.ini index 81b32540198..b609005aba0 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -823,4 +823,5 @@ interval_year = YYYY # Experimental feature use_browser_locale = false - +# Default timezone for user preferences. Options are 'browser' for the browser local timezone or a timezone name from IANA Time Zone database, e.g. 'UTC' or 'Europe/Amsterdam' etc. +default_timezone = browser diff --git a/conf/sample.ini b/conf/sample.ini index f0254e24ed4..95d7dda6a72 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -811,3 +811,6 @@ # Experimental feature ;use_browser_locale = false + +# Default timezone for user preferences. Options are 'browser' for the browser local timezone or a timezone name from IANA Time Zone database, e.g. 'UTC' or 'Europe/Amsterdam' etc. +;default_timezone = browser \ No newline at end of file diff --git a/docs/sources/administration/configuration.md b/docs/sources/administration/configuration.md index f7b978726d7..49ff190313e 100644 --- a/docs/sources/administration/configuration.md +++ b/docs/sources/administration/configuration.md @@ -1426,3 +1426,6 @@ interval_year = YYYY Set this to `true` to have date formats be automatically be derived from browser locale. Defaults to `false`. This is an experimental feature right now with a few problems that remain unsolved. +### default_timezone + +Used as the default timezone for user preferences. Can be either `browser` for the browser local timezone or a timezone name from IANA Time Zone database, e.g. `UTC` or `Europe/Amsterdam` etc. \ No newline at end of file diff --git a/docs/sources/administration/preferences.md b/docs/sources/administration/preferences.md index 0bb5176d620..f71e7cca9f4 100644 --- a/docs/sources/administration/preferences.md +++ b/docs/sources/administration/preferences.md @@ -32,7 +32,7 @@ Your Grafana preferences include whether uses the dark or light theme, your home 1. In the Preferences section, you can edit any of the following: - **UI Theme -** Click to set the **Dark** or **Light** to select a theme. **Default** is either the dark theme or the theme selected by your Grafana administrator. - **Home Dashboard -** Refer to [Set your personal home dashboard]({{< relref "change-home-dashboard.md#set-your-personal-home-dashboard" >}}) for more information. - - **Timezone -** Click to select an option in the **Timezone** list. Refer to [Time range controls]({{< relref "../dashboards/time-range-controls.md" >}}) for more information about Grafana time settings. + - **Timezone -** Click to select an option in the **Timezone** list. **Default** is either the browser local timezone or the timezone selected by your Grafana administrator. Refer to [Time range controls]({{< relref "../dashboards/time-range-controls.md" >}}) for more information about Grafana time settings. 1. Click **Save**. ## View your assigned organizations diff --git a/pkg/services/sqlstore/preferences.go b/pkg/services/sqlstore/preferences.go index 4513a2b20f7..bb665ebcf3b 100644 --- a/pkg/services/sqlstore/preferences.go +++ b/pkg/services/sqlstore/preferences.go @@ -10,13 +10,13 @@ import ( "github.com/grafana/grafana/pkg/setting" ) -func init() { +func (ss *SqlStore) addPreferencesQueryAndCommandHandlers() { bus.AddHandler("sql", GetPreferences) - bus.AddHandler("sql", GetPreferencesWithDefaults) + bus.AddHandler("sql", ss.GetPreferencesWithDefaults) bus.AddHandler("sql", SavePreferences) } -func GetPreferencesWithDefaults(query *models.GetPreferencesWithDefaultsQuery) error { +func (ss *SqlStore) GetPreferencesWithDefaults(query *models.GetPreferencesWithDefaultsQuery) error { params := make([]interface{}, 0) filter := "" @@ -43,7 +43,7 @@ func GetPreferencesWithDefaults(query *models.GetPreferencesWithDefaultsQuery) e res := &models.Preferences{ Theme: setting.DefaultTheme, - Timezone: "browser", + Timezone: ss.Cfg.DateFormats.DefaultTimezone, HomeDashboardId: 0, } diff --git a/pkg/services/sqlstore/preferences_test.go b/pkg/services/sqlstore/preferences_test.go index 43e1e96d930..841a317a9a1 100644 --- a/pkg/services/sqlstore/preferences_test.go +++ b/pkg/services/sqlstore/preferences_test.go @@ -11,14 +11,17 @@ import ( func TestPreferencesDataAccess(t *testing.T) { Convey("Testing preferences data access", t, func() { - InitTestDB(t) + ss := InitTestDB(t) Convey("GetPreferencesWithDefaults with no saved preferences should return defaults", func() { + setting.DefaultTheme = "light" + ss.Cfg.DateFormats.DefaultTimezone = "UTC" + query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{}} - err := GetPreferencesWithDefaults(query) + err := ss.GetPreferencesWithDefaults(query) So(err, ShouldBeNil) - So(query.Result.Theme, ShouldEqual, setting.DefaultTheme) - So(query.Result.Timezone, ShouldEqual, "browser") + So(query.Result.Theme, ShouldEqual, "light") + So(query.Result.Timezone, ShouldEqual, "UTC") So(query.Result.HomeDashboardId, ShouldEqual, 0) }) @@ -29,7 +32,7 @@ func TestPreferencesDataAccess(t *testing.T) { So(err, ShouldBeNil) query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{OrgId: 1, UserId: 1}} - err = GetPreferencesWithDefaults(query) + err = ss.GetPreferencesWithDefaults(query) So(err, ShouldBeNil) So(query.Result.HomeDashboardId, ShouldEqual, 4) }) @@ -41,7 +44,7 @@ func TestPreferencesDataAccess(t *testing.T) { So(err, ShouldBeNil) query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{OrgId: 1, UserId: 2}} - err = GetPreferencesWithDefaults(query) + err = ss.GetPreferencesWithDefaults(query) So(err, ShouldBeNil) So(query.Result.HomeDashboardId, ShouldEqual, 1) }) @@ -57,7 +60,7 @@ func TestPreferencesDataAccess(t *testing.T) { query := &models.GetPreferencesWithDefaultsQuery{ User: &models.SignedInUser{OrgId: 1, Teams: []int64{2, 3}}, } - err = GetPreferencesWithDefaults(query) + err = ss.GetPreferencesWithDefaults(query) So(err, ShouldBeNil) So(query.Result.HomeDashboardId, ShouldEqual, 3) }) @@ -71,7 +74,7 @@ func TestPreferencesDataAccess(t *testing.T) { So(err, ShouldBeNil) query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{OrgId: 1}} - err = GetPreferencesWithDefaults(query) + err = ss.GetPreferencesWithDefaults(query) So(err, ShouldBeNil) So(query.Result.HomeDashboardId, ShouldEqual, 1) }) @@ -89,7 +92,7 @@ func TestPreferencesDataAccess(t *testing.T) { query := &models.GetPreferencesWithDefaultsQuery{ User: &models.SignedInUser{OrgId: 1, UserId: 1, Teams: []int64{2, 3}}, } - err = GetPreferencesWithDefaults(query) + err = ss.GetPreferencesWithDefaults(query) So(err, ShouldBeNil) So(query.Result.HomeDashboardId, ShouldEqual, 4) }) @@ -107,7 +110,7 @@ func TestPreferencesDataAccess(t *testing.T) { query := &models.GetPreferencesWithDefaultsQuery{ User: &models.SignedInUser{OrgId: 1, UserId: 2}, } - err = GetPreferencesWithDefaults(query) + err = ss.GetPreferencesWithDefaults(query) So(err, ShouldBeNil) So(query.Result.HomeDashboardId, ShouldEqual, 1) }) diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 406d2e194db..d77e4400670 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -102,6 +102,7 @@ func (ss *SqlStore) Init() error { // Register handlers ss.addUserQueryAndCommandHandlers() ss.addAlertNotificationUidByIdHandler() + ss.addPreferencesQueryAndCommandHandlers() err = ss.logOrgsNotice() if err != nil { diff --git a/pkg/setting/date_formats.go b/pkg/setting/date_formats.go index cc214759f6c..54aa02bb703 100644 --- a/pkg/setting/date_formats.go +++ b/pkg/setting/date_formats.go @@ -1,9 +1,16 @@ package setting +import ( + "time" + + "gopkg.in/ini.v1" +) + type DateFormats struct { FullDate string `json:"fullDate"` UseBrowserLocale bool `json:"useBrowserLocale"` Interval DateFormatIntervals `json:"interval"` + DefaultTimezone string `json:"defaultTimezone"` } type DateFormatIntervals struct { @@ -15,6 +22,23 @@ type DateFormatIntervals struct { Year string `json:"year"` } +const LocalBrowserTimezone = "browser" + +func valueAsTimezone(section *ini.Section, keyName string, defaultValue string) (string, error) { + timezone := section.Key(keyName).MustString(defaultValue) + + if timezone == LocalBrowserTimezone { + return LocalBrowserTimezone, nil + } + + location, err := time.LoadLocation(timezone) + if err != nil { + return LocalBrowserTimezone, err + } + + return location.String(), nil +} + func (cfg *Cfg) readDateFormats() { dateFormats := cfg.Raw.Section("date_formats") cfg.DateFormats.FullDate = valueAsString(dateFormats, "full_date", "YYYY-MM-DD HH:mm:ss") @@ -25,4 +49,10 @@ func (cfg *Cfg) readDateFormats() { cfg.DateFormats.Interval.Month = valueAsString(dateFormats, "interval_month", "YYYY-MM") cfg.DateFormats.Interval.Year = "YYYY" cfg.DateFormats.UseBrowserLocale = dateFormats.Key("date_format_use_browser_locale").MustBool(false) + + timezone, err := valueAsTimezone(dateFormats, "default_timezone", LocalBrowserTimezone) + if err != nil { + cfg.Logger.Warn("Unknown timezone as default_timezone", "err", err) + } + cfg.DateFormats.DefaultTimezone = timezone } diff --git a/pkg/setting/date_formats_test.go b/pkg/setting/date_formats_test.go new file mode 100644 index 00000000000..a0701207384 --- /dev/null +++ b/pkg/setting/date_formats_test.go @@ -0,0 +1,37 @@ +package setting + +import ( + "testing" + + "gopkg.in/ini.v1" + + "github.com/stretchr/testify/assert" +) + +func TestValueAsTimezone(t *testing.T) { + tests := map[string]struct { + output string + hasErr bool + }{ + "browser": {"browser", false}, + "UTC": {"UTC", false}, + "utc": {"browser", true}, + "Amsterdam": {"browser", true}, + "europe/amsterdam": {"browser", true}, + "Europe/Amsterdam": {"Europe/Amsterdam", false}, + } + + sec, err := ini.Empty().NewSection("test") + assert.NoError(t, err) + key, err := sec.NewKey("test", "") + assert.NoError(t, err) + + for input, expected := range tests { + key.SetValue(input) + + output, err := valueAsTimezone(sec, "test", "default") + + assert.Equal(t, expected.hasErr, err != nil, "Invalid has err for input: %s err: %v", input, err) + assert.Equal(t, expected.output, output, "Invalid output for input: %s", input) + } +}