From c03d527d2511a9dd22451520d3bd44e53af398d7 Mon Sep 17 00:00:00 2001 From: Will Weber Date: Sun, 1 Jul 2018 08:56:16 -0400 Subject: [PATCH 01/25] pkg/social/github: Allow changing of userinfo data (#12471) Experienced a problem where a user whose email changed was no longer able to login. By using the ID as a primary identifier, we can avoid conflicts of this variety within the github OAuth provider. --- pkg/social/github_oauth.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/social/github_oauth.go b/pkg/social/github_oauth.go index 815c684cf03..b07f112b8d3 100644 --- a/pkg/social/github_oauth.go +++ b/pkg/social/github_oauth.go @@ -213,6 +213,7 @@ func (s *SocialGithub) UserInfo(client *http.Client, token *oauth2.Token) (*Basi userInfo := &BasicUserInfo{ Name: data.Login, Login: data.Login, + Id: fmt.Sprintf("%d", data.Id), Email: data.Email, } From c2c22c142bad47db4ba9f468eab8c0e63784faf5 Mon Sep 17 00:00:00 2001 From: Sven Klemm <31455525+svenklemm@users.noreply.github.com> Date: Sun, 1 Jul 2018 15:55:46 +0200 Subject: [PATCH 02/25] [postgres] fix timeGroup macro rounding (#12468) * fix timeGroup tests to check for correct grouping * do explicit floor rounding in $__timeGroup macro * fix typo in comments --- pkg/tsdb/postgres/macros.go | 2 +- pkg/tsdb/postgres/macros_test.go | 4 ++-- pkg/tsdb/postgres/postgres_test.go | 26 +++++++++++++++++--------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/pkg/tsdb/postgres/macros.go b/pkg/tsdb/postgres/macros.go index 05e39f2c762..61e88418ff4 100644 --- a/pkg/tsdb/postgres/macros.go +++ b/pkg/tsdb/postgres/macros.go @@ -109,7 +109,7 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string, m.Query.Model.Set("fillValue", floatVal) } } - return fmt.Sprintf("(extract(epoch from %s)/%v)::bigint*%v AS time", args[0], interval.Seconds(), interval.Seconds()), nil + return fmt.Sprintf("floor(extract(epoch from %s)/%v)*%v AS time", args[0], interval.Seconds(), interval.Seconds()), nil case "__unixEpochFilter": if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) diff --git a/pkg/tsdb/postgres/macros_test.go b/pkg/tsdb/postgres/macros_test.go index c3c15691e42..8c581850430 100644 --- a/pkg/tsdb/postgres/macros_test.go +++ b/pkg/tsdb/postgres/macros_test.go @@ -53,7 +53,7 @@ func TestMacroEngine(t *testing.T) { sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m')") So(err, ShouldBeNil) - So(sql, ShouldEqual, "GROUP BY (extract(epoch from time_column)/300)::bigint*300 AS time") + So(sql, ShouldEqual, "GROUP BY floor(extract(epoch from time_column)/300)*300 AS time") }) Convey("interpolate __timeGroup function with spaces between args", func() { @@ -61,7 +61,7 @@ func TestMacroEngine(t *testing.T) { sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column , '5m')") So(err, ShouldBeNil) - So(sql, ShouldEqual, "GROUP BY (extract(epoch from time_column)/300)::bigint*300 AS time") + So(sql, ShouldEqual, "GROUP BY floor(extract(epoch from time_column)/300)*300 AS time") }) Convey("interpolate __timeTo function", func() { diff --git a/pkg/tsdb/postgres/postgres_test.go b/pkg/tsdb/postgres/postgres_test.go index 7f24d5a2063..18ceecb1080 100644 --- a/pkg/tsdb/postgres/postgres_test.go +++ b/pkg/tsdb/postgres/postgres_test.go @@ -189,21 +189,23 @@ func TestPostgres(t *testing.T) { So(queryResult.Error, ShouldBeNil) points := queryResult.Series[0].Points - So(len(points), ShouldEqual, 6) + // without fill this should result in 4 buckets + So(len(points), ShouldEqual, 4) dt := fromStart - for i := 0; i < 3; i++ { + for i := 0; i < 2; i++ { aValue := points[i][0].Float64 aTime := time.Unix(int64(points[i][1].Float64)/1000, 0) So(aValue, ShouldEqual, 15) So(aTime, ShouldEqual, dt) + So(aTime.Unix()%300, ShouldEqual, 0) dt = dt.Add(5 * time.Minute) } - // adjust for 5 minute gap - dt = dt.Add(5 * time.Minute) - for i := 3; i < 6; i++ { + // adjust for 10 minute gap between first and second set of points + dt = dt.Add(10 * time.Minute) + for i := 2; i < 4; i++ { aValue := points[i][0].Float64 aTime := time.Unix(int64(points[i][1].Float64)/1000, 0) So(aValue, ShouldEqual, 20) @@ -239,7 +241,7 @@ func TestPostgres(t *testing.T) { dt := fromStart - for i := 0; i < 3; i++ { + for i := 0; i < 2; i++ { aValue := points[i][0].Float64 aTime := time.Unix(int64(points[i][1].Float64)/1000, 0) So(aValue, ShouldEqual, 15) @@ -247,17 +249,23 @@ func TestPostgres(t *testing.T) { dt = dt.Add(5 * time.Minute) } + // check for NULL values inserted by fill + So(points[2][0].Valid, ShouldBeFalse) So(points[3][0].Valid, ShouldBeFalse) - // adjust for 5 minute gap - dt = dt.Add(5 * time.Minute) - for i := 4; i < 7; i++ { + // adjust for 10 minute gap between first and second set of points + dt = dt.Add(10 * time.Minute) + for i := 4; i < 6; i++ { aValue := points[i][0].Float64 aTime := time.Unix(int64(points[i][1].Float64)/1000, 0) So(aValue, ShouldEqual, 20) So(aTime, ShouldEqual, dt) dt = dt.Add(5 * time.Minute) } + + // check for NULL values inserted by fill + So(points[6][0].Valid, ShouldBeFalse) + }) Convey("When doing a metric query using timeGroup with float fill enabled", func() { From f017c04a6537879ea40619dfad918e5b283e76db Mon Sep 17 00:00:00 2001 From: Sven Klemm <31455525+svenklemm@users.noreply.github.com> Date: Sun, 1 Jul 2018 15:57:02 +0200 Subject: [PATCH 03/25] [mssql] fix $__timeGroup rounding (#12470) * fix $__timeGroup rounding for mssql * revert enabling of mssql integration tests --- pkg/tsdb/mssql/macros.go | 2 +- pkg/tsdb/mssql/macros_test.go | 4 ++-- pkg/tsdb/mssql/mssql_test.go | 24 +++++++++++++++--------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/pkg/tsdb/mssql/macros.go b/pkg/tsdb/mssql/macros.go index bb9489cd654..fac25f58ff0 100644 --- a/pkg/tsdb/mssql/macros.go +++ b/pkg/tsdb/mssql/macros.go @@ -108,7 +108,7 @@ func (m *MsSqlMacroEngine) evaluateMacro(name string, args []string) (string, er m.Query.Model.Set("fillValue", floatVal) } } - return fmt.Sprintf("CAST(ROUND(DATEDIFF(second, '1970-01-01', %s)/%.1f, 0) as bigint)*%.0f", args[0], interval.Seconds(), interval.Seconds()), nil + return fmt.Sprintf("FLOOR(DATEDIFF(second, '1970-01-01', %s)/%.0f)*%.0f", args[0], interval.Seconds(), interval.Seconds()), nil case "__unixEpochFilter": if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) diff --git a/pkg/tsdb/mssql/macros_test.go b/pkg/tsdb/mssql/macros_test.go index ae0d4f67d2b..22d54aaa7da 100644 --- a/pkg/tsdb/mssql/macros_test.go +++ b/pkg/tsdb/mssql/macros_test.go @@ -56,14 +56,14 @@ func TestMacroEngine(t *testing.T) { sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m')") So(err, ShouldBeNil) - So(sql, ShouldEqual, "GROUP BY CAST(ROUND(DATEDIFF(second, '1970-01-01', time_column)/300.0, 0) as bigint)*300") + So(sql, ShouldEqual, "GROUP BY FLOOR(DATEDIFF(second, '1970-01-01', time_column)/300)*300") }) Convey("interpolate __timeGroup function with spaces around arguments", func() { sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column , '5m')") So(err, ShouldBeNil) - So(sql, ShouldEqual, "GROUP BY CAST(ROUND(DATEDIFF(second, '1970-01-01', time_column)/300.0, 0) as bigint)*300") + So(sql, ShouldEqual, "GROUP BY FLOOR(DATEDIFF(second, '1970-01-01', time_column)/300)*300") }) Convey("interpolate __timeGroup function with fill (value = NULL)", func() { diff --git a/pkg/tsdb/mssql/mssql_test.go b/pkg/tsdb/mssql/mssql_test.go index e62d30a6325..2ecd3cd9e96 100644 --- a/pkg/tsdb/mssql/mssql_test.go +++ b/pkg/tsdb/mssql/mssql_test.go @@ -210,11 +210,12 @@ func TestMSSQL(t *testing.T) { So(queryResult.Error, ShouldBeNil) points := queryResult.Series[0].Points - So(len(points), ShouldEqual, 6) + // without fill this should result in 4 buckets + So(len(points), ShouldEqual, 4) dt := fromStart - for i := 0; i < 3; i++ { + for i := 0; i < 2; i++ { aValue := points[i][0].Float64 aTime := time.Unix(int64(points[i][1].Float64)/1000, 0) So(aValue, ShouldEqual, 15) @@ -222,9 +223,9 @@ func TestMSSQL(t *testing.T) { dt = dt.Add(5 * time.Minute) } - // adjust for 5 minute gap - dt = dt.Add(5 * time.Minute) - for i := 3; i < 6; i++ { + // adjust for 10 minute gap between first and second set of points + dt = dt.Add(10 * time.Minute) + for i := 2; i < 4; i++ { aValue := points[i][0].Float64 aTime := time.Unix(int64(points[i][1].Float64)/1000, 0) So(aValue, ShouldEqual, 20) @@ -260,7 +261,7 @@ func TestMSSQL(t *testing.T) { dt := fromStart - for i := 0; i < 3; i++ { + for i := 0; i < 2; i++ { aValue := points[i][0].Float64 aTime := time.Unix(int64(points[i][1].Float64)/1000, 0) So(aValue, ShouldEqual, 15) @@ -268,17 +269,22 @@ func TestMSSQL(t *testing.T) { dt = dt.Add(5 * time.Minute) } + // check for NULL values inserted by fill + So(points[2][0].Valid, ShouldBeFalse) So(points[3][0].Valid, ShouldBeFalse) - // adjust for 5 minute gap - dt = dt.Add(5 * time.Minute) - for i := 4; i < 7; i++ { + // adjust for 10 minute gap between first and second set of points + dt = dt.Add(10 * time.Minute) + for i := 4; i < 6; i++ { aValue := points[i][0].Float64 aTime := time.Unix(int64(points[i][1].Float64)/1000, 0) So(aValue, ShouldEqual, 20) So(aTime, ShouldEqual, dt) dt = dt.Add(5 * time.Minute) } + + So(points[6][0].Valid, ShouldBeFalse) + }) Convey("When doing a metric query using timeGroup with float fill enabled", func() { From 1601f6d17c55d400200e5304ef751c4cb44d4c43 Mon Sep 17 00:00:00 2001 From: Sven Klemm <31455525+svenklemm@users.noreply.github.com> Date: Sun, 1 Jul 2018 15:59:05 +0200 Subject: [PATCH 04/25] [mysql] fix $__timeGroup rounding (#12469) * fix $__timeGroup rounding for mysql * revert accidentally commented out line --- pkg/tsdb/mysql/macros.go | 2 +- pkg/tsdb/mysql/macros_test.go | 4 ++-- pkg/tsdb/mysql/mysql_test.go | 25 ++++++++++++++++--------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/pkg/tsdb/mysql/macros.go b/pkg/tsdb/mysql/macros.go index fadcbe4edbc..bf815143b3c 100644 --- a/pkg/tsdb/mysql/macros.go +++ b/pkg/tsdb/mysql/macros.go @@ -103,7 +103,7 @@ func (m *MySqlMacroEngine) evaluateMacro(name string, args []string) (string, er m.Query.Model.Set("fillValue", floatVal) } } - return fmt.Sprintf("cast(cast(UNIX_TIMESTAMP(%s)/(%.0f) as signed)*%.0f as signed)", args[0], interval.Seconds(), interval.Seconds()), nil + return fmt.Sprintf("UNIX_TIMESTAMP(%s) DIV %.0f * %.0f", args[0], interval.Seconds(), interval.Seconds()), nil case "__unixEpochFilter": if len(args) == 0 { return "", fmt.Errorf("missing time column argument for macro %v", name) diff --git a/pkg/tsdb/mysql/macros_test.go b/pkg/tsdb/mysql/macros_test.go index 66ec143eac8..7cdd92b34b4 100644 --- a/pkg/tsdb/mysql/macros_test.go +++ b/pkg/tsdb/mysql/macros_test.go @@ -39,7 +39,7 @@ func TestMacroEngine(t *testing.T) { sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m')") So(err, ShouldBeNil) - So(sql, ShouldEqual, "GROUP BY cast(cast(UNIX_TIMESTAMP(time_column)/(300) as signed)*300 as signed)") + So(sql, ShouldEqual, "GROUP BY UNIX_TIMESTAMP(time_column) DIV 300 * 300") }) Convey("interpolate __timeGroup function with spaces around arguments", func() { @@ -47,7 +47,7 @@ func TestMacroEngine(t *testing.T) { sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column , '5m')") So(err, ShouldBeNil) - So(sql, ShouldEqual, "GROUP BY cast(cast(UNIX_TIMESTAMP(time_column)/(300) as signed)*300 as signed)") + So(sql, ShouldEqual, "GROUP BY UNIX_TIMESTAMP(time_column) DIV 300 * 300") }) Convey("interpolate __timeFilter function", func() { diff --git a/pkg/tsdb/mysql/mysql_test.go b/pkg/tsdb/mysql/mysql_test.go index 5650de237c5..22e98ac63ca 100644 --- a/pkg/tsdb/mysql/mysql_test.go +++ b/pkg/tsdb/mysql/mysql_test.go @@ -209,11 +209,12 @@ func TestMySQL(t *testing.T) { So(queryResult.Error, ShouldBeNil) points := queryResult.Series[0].Points - So(len(points), ShouldEqual, 6) + // without fill this should result in 4 buckets + So(len(points), ShouldEqual, 4) dt := fromStart - for i := 0; i < 3; i++ { + for i := 0; i < 2; i++ { aValue := points[i][0].Float64 aTime := time.Unix(int64(points[i][1].Float64)/1000, 0) So(aValue, ShouldEqual, 15) @@ -221,9 +222,9 @@ func TestMySQL(t *testing.T) { dt = dt.Add(5 * time.Minute) } - // adjust for 5 minute gap - dt = dt.Add(5 * time.Minute) - for i := 3; i < 6; i++ { + // adjust for 10 minute gap between first and second set of points + dt = dt.Add(10 * time.Minute) + for i := 2; i < 4; i++ { aValue := points[i][0].Float64 aTime := time.Unix(int64(points[i][1].Float64)/1000, 0) So(aValue, ShouldEqual, 20) @@ -259,7 +260,7 @@ func TestMySQL(t *testing.T) { dt := fromStart - for i := 0; i < 3; i++ { + for i := 0; i < 2; i++ { aValue := points[i][0].Float64 aTime := time.Unix(int64(points[i][1].Float64)/1000, 0) So(aValue, ShouldEqual, 15) @@ -267,17 +268,23 @@ func TestMySQL(t *testing.T) { dt = dt.Add(5 * time.Minute) } + // check for NULL values inserted by fill + So(points[2][0].Valid, ShouldBeFalse) So(points[3][0].Valid, ShouldBeFalse) - // adjust for 5 minute gap - dt = dt.Add(5 * time.Minute) - for i := 4; i < 7; i++ { + // adjust for 10 minute gap between first and second set of points + dt = dt.Add(10 * time.Minute) + for i := 4; i < 6; i++ { aValue := points[i][0].Float64 aTime := time.Unix(int64(points[i][1].Float64)/1000, 0) So(aValue, ShouldEqual, 20) So(aTime, ShouldEqual, dt) dt = dt.Add(5 * time.Minute) } + + // check for NULL values inserted by fill + So(points[6][0].Valid, ShouldBeFalse) + }) Convey("When doing a metric query using timeGroup with float fill enabled", func() { From 861af4cb9789a69c5d46f56f42c78f24385f702d Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Sun, 1 Jul 2018 16:01:43 +0200 Subject: [PATCH 05/25] Improve extensions build. (#12461) * enable ee build on pr/master * step1: of including group sync * disable commit pinning for now * fixes broken build * enable team to ldap group sync * avoid returning error for missing external handler * services: allow routes to be added before http server start * services: allows services to add their own migrations * moves db migrations to ee code base * build using master branch in ee * disable enterprise build in .bra.toml [skip ci] * removes team sync extensions * removes commented line --- .circleci/config.yml | 2 + pkg/api/api.go | 15 ++- pkg/api/frontendsettings.go | 1 + pkg/api/http_server.go | 15 ++- pkg/login/ext_user.go | 16 ++- pkg/login/ldap.go | 2 + pkg/login/ldap_test.go | 16 ++- pkg/models/team_member.go | 1 + pkg/models/user_auth.go | 6 + pkg/registry/registry.go | 12 +- pkg/services/sqlstore/migrations/team_mig.go | 1 + pkg/services/sqlstore/sqlstore.go | 7 + pkg/services/sqlstore/team.go | 10 +- public/app/core/config.ts | 1 + .../features/org/partials/team_details.html | 124 ++++++++++++------ public/app/features/org/team_details_ctrl.ts | 27 ++++ scripts/build/build_enterprise.sh | 4 +- 17 files changed, 197 insertions(+), 63 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3aedd49c935..9b729ecfa15 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -221,6 +221,8 @@ workflows: jobs: - build-all: filters: *filter-not-release + - build-enterprise: + filters: *filter-not-release - codespell: filters: *filter-not-release - gometalinter: diff --git a/pkg/api/api.go b/pkg/api/api.go index 39dae56eb69..78c7aaf3f39 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -9,7 +9,14 @@ import ( m "github.com/grafana/grafana/pkg/models" ) -// Register adds http routes +func (hs *HTTPServer) applyRoutes() { + hs.RouteRegister.Register(hs.macaron) + + InitAppPluginRoutes(hs.macaron) + + hs.macaron.NotFound(NotFoundHandler) +} + func (hs *HTTPServer) registerRoutes() { macaronR := hs.macaron reqSignedIn := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true}) @@ -393,10 +400,4 @@ func (hs *HTTPServer) registerRoutes() { // streams //r.Post("/api/streams/push", reqSignedIn, bind(dtos.StreamMessage{}), liveConn.PushToStream) - - r.Register(macaronR) - - InitAppPluginRoutes(macaronR) - - macaronR.NotFound(NotFoundHandler) } diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index e7272e68997..06e6405baaf 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -132,6 +132,7 @@ func getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) { } jsonObj := map[string]interface{}{ + "enterprise": setting.Enterprise, "defaultDatasource": defaultDatasource, "datasources": datasources, "panels": panels, diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index e1a10fb468f..627192bb69b 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -33,7 +33,11 @@ import ( ) func init() { - registry.RegisterService(&HTTPServer{}) + registry.Register(®istry.Descriptor{ + Name: "HTTPServer", + Instance: &HTTPServer{}, + InitPriority: registry.High, + }) } type HTTPServer struct { @@ -54,6 +58,10 @@ func (hs *HTTPServer) Init() error { hs.log = log.New("http.server") hs.cache = gocache.New(5*time.Minute, 10*time.Minute) + hs.streamManager = live.NewStreamManager() + hs.macaron = hs.newMacaron() + hs.registerRoutes() + return nil } @@ -61,10 +69,7 @@ func (hs *HTTPServer) Run(ctx context.Context) error { var err error hs.context = ctx - hs.streamManager = live.NewStreamManager() - hs.macaron = hs.newMacaron() - hs.registerRoutes() - + hs.applyRoutes() hs.streamManager.Run(ctx) listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort) diff --git a/pkg/login/ext_user.go b/pkg/login/ext_user.go index e1d5e3e3b48..d2f1aa1ff52 100644 --- a/pkg/login/ext_user.go +++ b/pkg/login/ext_user.go @@ -66,7 +66,21 @@ func UpsertUser(cmd *m.UpsertUserCommand) error { } } - return syncOrgRoles(cmd.Result, extUser) + err = syncOrgRoles(cmd.Result, extUser) + if err != nil { + return err + } + + err = bus.Dispatch(&m.SyncTeamsCommand{ + User: cmd.Result, + ExternalUser: extUser, + }) + + if err == bus.ErrHandlerNotFound { + return nil + } + + return err } func createUser(extUser *m.ExternalUserInfo) (*m.User, error) { diff --git a/pkg/login/ldap.go b/pkg/login/ldap.go index 026a94fa43e..bdf87b2db54 100644 --- a/pkg/login/ldap.go +++ b/pkg/login/ldap.go @@ -163,6 +163,7 @@ func (a *ldapAuther) GetGrafanaUserFor(ctx *m.ReqContext, ldapUser *LdapUserInfo Name: fmt.Sprintf("%s %s", ldapUser.FirstName, ldapUser.LastName), Login: ldapUser.Username, Email: ldapUser.Email, + Groups: ldapUser.MemberOf, OrgRoles: map[int64]m.RoleType{}, } @@ -194,6 +195,7 @@ func (a *ldapAuther) GetGrafanaUserFor(ctx *m.ReqContext, ldapUser *LdapUserInfo ExternalUser: extUser, SignupAllowed: setting.LdapAllowSignup, } + err := bus.Dispatch(userQuery) if err != nil { return nil, err diff --git a/pkg/login/ldap_test.go b/pkg/login/ldap_test.go index 34932926406..5080840704e 100644 --- a/pkg/login/ldap_test.go +++ b/pkg/login/ldap_test.go @@ -1,6 +1,7 @@ package login import ( + "context" "crypto/tls" "testing" @@ -14,6 +15,14 @@ func TestLdapAuther(t *testing.T) { Convey("When translating ldap user to grafana user", t, func() { + var user1 = &m.User{} + + bus.AddHandlerCtx("test", func(ctx context.Context, cmd *m.UpsertUserCommand) error { + cmd.Result = user1 + cmd.Result.Login = "torkelo" + return nil + }) + Convey("Given no ldap group map match", func() { ldapAuther := NewLdapAuthenticator(&LdapServerConf{ LdapGroups: []*LdapGroupToOrgRole{{}}, @@ -23,8 +32,6 @@ func TestLdapAuther(t *testing.T) { So(err, ShouldEqual, ErrInvalidCredentials) }) - var user1 = &m.User{} - ldapAutherScenario("Given wildcard group match", func(sc *scenarioContext) { ldapAuther := NewLdapAuthenticator(&LdapServerConf{ LdapGroups: []*LdapGroupToOrgRole{ @@ -96,7 +103,6 @@ func TestLdapAuther(t *testing.T) { }) Convey("When syncing ldap groups to grafana org roles", t, func() { - ldapAutherScenario("given no current user orgs", func(sc *scenarioContext) { ldapAuther := NewLdapAuthenticator(&LdapServerConf{ LdapGroups: []*LdapGroupToOrgRole{ @@ -322,6 +328,10 @@ func ldapAutherScenario(desc string, fn scenarioFunc) { bus.AddHandler("test", UpsertUser) + bus.AddHandlerCtx("test", func(ctx context.Context, cmd *m.SyncTeamsCommand) error { + return nil + }) + bus.AddHandler("test", func(cmd *m.GetUserByAuthInfoQuery) error { sc.getUserByAuthInfoQuery = cmd sc.getUserByAuthInfoQuery.Result = &m.User{Login: cmd.Login} diff --git a/pkg/models/team_member.go b/pkg/models/team_member.go index 19cf657292d..9434dad8ecd 100644 --- a/pkg/models/team_member.go +++ b/pkg/models/team_member.go @@ -42,6 +42,7 @@ type RemoveTeamMemberCommand struct { type GetTeamMembersQuery struct { OrgId int64 TeamId int64 + UserId int64 Result []*TeamMemberDTO } diff --git a/pkg/models/user_auth.go b/pkg/models/user_auth.go index 0ecd144d52c..162a4d867a9 100644 --- a/pkg/models/user_auth.go +++ b/pkg/models/user_auth.go @@ -19,6 +19,7 @@ type ExternalUserInfo struct { Email string Login string Name string + Groups []string OrgRoles map[int64]RoleType } @@ -70,3 +71,8 @@ type GetAuthInfoQuery struct { Result *UserAuth } + +type SyncTeamsCommand struct { + ExternalUser *ExternalUserInfo + User *User +} diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index 6bbc302c593..87fca27f6c1 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -4,6 +4,8 @@ import ( "context" "reflect" "sort" + + "github.com/grafana/grafana/pkg/services/sqlstore/migrator" ) type Descriptor struct { @@ -57,13 +59,21 @@ type CanBeDisabled interface { // BackgroundService should be implemented for services that have // long running tasks in the background. type BackgroundService interface { - // Run starts the background process of the service after `Init` have been called // on all services. The `context.Context` passed into the function should be used // to subscribe to ctx.Done() so the service can be notified when Grafana shuts down. Run(ctx context.Context) error } +// DatabaseMigrator allows the caller to add migrations to +// the migrator passed as argument +type DatabaseMigrator interface { + + // AddMigrations allows the service to add migrations to + // the database migrator. + AddMigration(mg *migrator.Migrator) +} + // IsDisabled takes an service and return true if its disabled func IsDisabled(srv Service) bool { canBeDisabled, ok := srv.(CanBeDisabled) diff --git a/pkg/services/sqlstore/migrations/team_mig.go b/pkg/services/sqlstore/migrations/team_mig.go index eb0641fbc32..9800d27f8ab 100644 --- a/pkg/services/sqlstore/migrations/team_mig.go +++ b/pkg/services/sqlstore/migrations/team_mig.go @@ -50,4 +50,5 @@ func addTeamMigrations(mg *Migrator) { mg.AddMigration("Add column email to team table", NewAddColumnMigration(teamV1, &Column{ Name: "email", Type: DB_NVarchar, Nullable: true, Length: 190, })) + } diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index b0edc1676e0..13d706b6198 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -132,6 +132,13 @@ func (ss *SqlStore) Init() error { migrator := migrator.NewMigrator(x) migrations.AddMigrations(migrator) + for _, descriptor := range registry.GetServices() { + sc, ok := descriptor.Instance.(registry.DatabaseMigrator) + if ok { + sc.AddMigration(migrator) + } + } + if err := migrator.Start(); err != nil { return fmt.Errorf("Migration failed err: %v", err) } diff --git a/pkg/services/sqlstore/team.go b/pkg/services/sqlstore/team.go index 7d53d114235..9378ca37f60 100644 --- a/pkg/services/sqlstore/team.go +++ b/pkg/services/sqlstore/team.go @@ -268,7 +268,15 @@ func GetTeamMembers(query *m.GetTeamMembersQuery) error { query.Result = make([]*m.TeamMemberDTO, 0) sess := x.Table("team_member") sess.Join("INNER", "user", fmt.Sprintf("team_member.user_id=%s.id", x.Dialect().Quote("user"))) - sess.Where("team_member.org_id=? and team_member.team_id=?", query.OrgId, query.TeamId) + if query.OrgId != 0 { + sess.Where("team_member.org_id=?", query.OrgId) + } + if query.TeamId != 0 { + sess.Where("team_member.team_id=?", query.TeamId) + } + if query.UserId != 0 { + sess.Where("team_member.user_id=?", query.UserId) + } sess.Cols("user.org_id", "team_member.team_id", "team_member.user_id", "user.email", "user.login") sess.Asc("user.login", "user.email") diff --git a/public/app/core/config.ts b/public/app/core/config.ts index e111d0d0e9f..83ef17662c8 100644 --- a/public/app/core/config.ts +++ b/public/app/core/config.ts @@ -22,6 +22,7 @@ class Settings { disableUserSignUp: boolean; loginHint: any; loginError: any; + enterprise: boolean; constructor(options) { var defaults = { diff --git a/public/app/features/org/partials/team_details.html b/public/app/features/org/partials/team_details.html index 3fce8b3c720..c5ac8bd37a3 100644 --- a/public/app/features/org/partials/team_details.html +++ b/public/app/features/org/partials/team_details.html @@ -1,22 +1,22 @@
-

Team Details

+

Team Details

Name -
-
- - Email - - This is optional and is primarily used for allowing custom team avatars. - - - -
+
+
+ + Email + + This is optional and is primarily used for allowing custom team avatars. + + + +
@@ -26,42 +26,80 @@

Team Members

- +
Add member - - + +
- - - - - - - - - - - - - - -
UsernameEmail
{{member.login}}{{member.email}} - - - -
-
- - This team has no members yet. - -
+ + + + Username + Email + + + + + + + + {{member.login}} + {{member.email}} + + + + + + + +
+ + This team has no members yet. + +
+
+ +
+ +

Team Group Mapping

+
+
+ Add group + +
+
+ +
+
+ + + + + + + + + + + + +
Group
{{group.groupId}} + + + +
+
+ + This team has no associated groups yet. + +
+ +
diff --git a/public/app/features/org/team_details_ctrl.ts b/public/app/features/org/team_details_ctrl.ts index 3d193880635..35fa04f55d3 100644 --- a/public/app/features/org/team_details_ctrl.ts +++ b/public/app/features/org/team_details_ctrl.ts @@ -1,15 +1,21 @@ import coreModule from 'app/core/core_module'; +import config from 'app/core/config'; export default class TeamDetailsCtrl { team: Team; teamMembers: User[] = []; navModel: any; + teamGroups: TeamGroup[] = []; + newGroupId: string; + enterprise: boolean; /** @ngInject **/ constructor(private $scope, private backendSrv, private $routeParams, navModelSrv) { this.navModel = navModelSrv.getNav('cfg', 'teams', 0); this.userPicked = this.userPicked.bind(this); this.get = this.get.bind(this); + this.newGroupId = ''; + this.enterprise = config.enterprise; this.get(); } @@ -18,9 +24,16 @@ export default class TeamDetailsCtrl { this.backendSrv.get(`/api/teams/${this.$routeParams.id}`).then(result => { this.team = result; }); + this.backendSrv.get(`/api/teams/${this.$routeParams.id}/members`).then(result => { this.teamMembers = result; }); + + if (config.enterprise) { + this.backendSrv.get(`/api/teams/${this.$routeParams.id}/groups`).then(result => { + this.teamGroups = result; + }); + } } } @@ -57,6 +70,20 @@ export default class TeamDetailsCtrl { this.get(); }); } + + addGroup() { + this.backendSrv.post(`/api/teams/${this.$routeParams.id}/groups`, { groupId: this.newGroupId }).then(() => { + this.get(); + }); + } + + removeGroup(group: TeamGroup) { + this.backendSrv.delete(`/api/teams/${this.$routeParams.id}/groups/${group.groupId}`).then(this.get); + } +} + +export interface TeamGroup { + groupId: string; } export interface Team { diff --git a/scripts/build/build_enterprise.sh b/scripts/build/build_enterprise.sh index 02d8c78c885..cda3952c36a 100755 --- a/scripts/build/build_enterprise.sh +++ b/scripts/build/build_enterprise.sh @@ -14,9 +14,9 @@ cd /go/src/github.com/grafana/grafana echo "current dir: $(pwd)" cd .. -git clone -b ee_build --single-branch git@github.com:grafana/grafana-enterprise.git --depth 10 +git clone -b master --single-branch git@github.com:grafana/grafana-enterprise.git --depth 10 cd grafana-enterprise -git checkout 7fbae9c1be3467c4a39cf6ad85278a6896ceb49f +#git checkout 7fbae9c1be3467c4a39cf6ad85278a6896ceb49f ./build.sh cd ../grafana From aa1b5959da01c2bb283c30ab49c89c230ba300c9 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Mon, 2 Jul 2018 14:56:24 +0900 Subject: [PATCH 06/25] omit extra template refresh (#12454) --- public/app/features/templating/variable_srv.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/public/app/features/templating/variable_srv.ts b/public/app/features/templating/variable_srv.ts index 8a096dd9ad2..8ad3c2845e2 100644 --- a/public/app/features/templating/variable_srv.ts +++ b/public/app/features/templating/variable_srv.ts @@ -38,7 +38,11 @@ export class VariableSrv { }); } - onDashboardRefresh() { + onDashboardRefresh(evt, payload) { + if (payload && payload.fromVariableValueUpdated) { + return Promise.resolve({}); + } + var promises = this.variables.filter(variable => variable.refresh === 2).map(variable => { var previousOptions = variable.options.slice(); @@ -130,7 +134,7 @@ export class VariableSrv { return this.$q.all(promises).then(() => { if (emitChangeEvents) { this.$rootScope.$emit('template-variable-value-updated'); - this.$rootScope.$broadcast('refresh'); + this.$rootScope.$broadcast('refresh', { fromVariableValueUpdated: true }); } }); } From 10e86eda690db3934a21ad95fe9df9b05c32d3fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 1 Jul 2018 23:35:50 -0700 Subject: [PATCH 07/25] fix: #12461 introduced issues with route registration ordering, adding plugin static routes before plugins package had been initiated (#12474) --- pkg/api/api.go | 12 ------------ pkg/api/app_routes.go | 2 +- pkg/api/http_server.go | 25 ++++++++++++++++++++++--- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index 78c7aaf3f39..829b7735343 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -9,16 +9,7 @@ import ( m "github.com/grafana/grafana/pkg/models" ) -func (hs *HTTPServer) applyRoutes() { - hs.RouteRegister.Register(hs.macaron) - - InitAppPluginRoutes(hs.macaron) - - hs.macaron.NotFound(NotFoundHandler) -} - func (hs *HTTPServer) registerRoutes() { - macaronR := hs.macaron reqSignedIn := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true}) reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true}) reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN) @@ -28,9 +19,6 @@ func (hs *HTTPServer) registerRoutes() { quota := middleware.Quota bind := binding.Bind - // automatically set HEAD for every GET - macaronR.SetAutoHead(true) - r := hs.RouteRegister // not logged in views diff --git a/pkg/api/app_routes.go b/pkg/api/app_routes.go index 0b7dcd32ce3..a2137089fc6 100644 --- a/pkg/api/app_routes.go +++ b/pkg/api/app_routes.go @@ -18,7 +18,7 @@ import ( var pluginProxyTransport *http.Transport -func InitAppPluginRoutes(r *macaron.Macaron) { +func (hs *HTTPServer) initAppPluginRoutes(r *macaron.Macaron) { pluginProxyTransport = &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: setting.PluginAppsSkipVerifyTLS, diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 627192bb69b..0de63ce5e08 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -69,6 +69,7 @@ func (hs *HTTPServer) Run(ctx context.Context) error { var err error hs.context = ctx + hs.applyRoutes() hs.streamManager.Run(ctx) @@ -169,6 +170,26 @@ func (hs *HTTPServer) newMacaron() *macaron.Macaron { macaron.Env = setting.Env m := macaron.New() + // automatically set HEAD for every GET + m.SetAutoHead(true) + + return m +} + +func (hs *HTTPServer) applyRoutes() { + // start with middlewares & static routes + hs.addMiddlewaresAndStaticRoutes() + // then add view routes & api routes + hs.RouteRegister.Register(hs.macaron) + // then custom app proxy routes + hs.initAppPluginRoutes(hs.macaron) + // lastly not found route + hs.macaron.NotFound(NotFoundHandler) +} + +func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() { + m := hs.macaron + m.Use(middleware.Logger()) if setting.EnableGzip { @@ -180,7 +201,7 @@ func (hs *HTTPServer) newMacaron() *macaron.Macaron { for _, route := range plugins.StaticRoutes { pluginRoute := path.Join("/public/plugins/", route.PluginId) hs.log.Debug("Plugins: Adding route", "route", pluginRoute, "dir", route.Directory) - hs.mapStatic(m, route.Directory, "", pluginRoute) + hs.mapStatic(hs.macaron, route.Directory, "", pluginRoute) } hs.mapStatic(m, setting.StaticRootPath, "build", "public/build") @@ -209,8 +230,6 @@ func (hs *HTTPServer) newMacaron() *macaron.Macaron { } m.Use(middleware.AddDefaultResponseHeaders()) - - return m } func (hs *HTTPServer) metricsEndpoint(ctx *macaron.Context) { From 2941dff42887c750c75d3cf2e26fd825de2cce89 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed Date: Mon, 2 Jul 2018 13:18:23 +0200 Subject: [PATCH 08/25] Karma to Jest: datasource_srv (#12456) * Karma to Jest: datasource_srv * Sort function differs between Karma and Jest * Fix error based on .sort() implementation * Remove Karma test * alerting: only log when screenshot been uploaded * Remove comments * Karma to Jest: datasource_srv * Sort function differs between Karma and Jest * Fix error based on .sort() implementation * Remove Karma test * Remove comments * Remove console.log * Remove console.log * Change sorting and add test for default data source --- public/app/features/plugins/datasource_srv.ts | 25 ++++---- .../plugins/specs/datasource_srv.jest.ts | 59 +++++++++++++++++ .../plugins/specs/datasource_srv_specs.ts | 64 ------------------- 3 files changed, 73 insertions(+), 75 deletions(-) create mode 100644 public/app/features/plugins/specs/datasource_srv.jest.ts delete mode 100644 public/app/features/plugins/specs/datasource_srv_specs.ts diff --git a/public/app/features/plugins/datasource_srv.ts b/public/app/features/plugins/datasource_srv.ts index 19bdf599ce1..b5e0316163c 100644 --- a/public/app/features/plugins/datasource_srv.ts +++ b/public/app/features/plugins/datasource_srv.ts @@ -91,10 +91,20 @@ export class DatasourceSrv { _.each(config.datasources, function(value, key) { if (value.meta && value.meta.metrics) { - metricSources.push({ value: key, name: key, meta: value.meta }); + let metricSource = { value: key, name: key, meta: value.meta, sort: key }; + + //Make sure grafana and mixed are sorted at the bottom + if (value.meta.id === 'grafana') { + metricSource.sort = String.fromCharCode(253); + } else if (value.meta.id === 'mixed') { + metricSource.sort = String.fromCharCode(254); + } + + metricSources.push(metricSource); if (key === config.defaultDatasource) { - metricSources.push({ value: null, name: 'default', meta: value.meta }); + metricSource = { value: null, name: 'default', meta: value.meta, sort: key }; + metricSources.push(metricSource); } } }); @@ -104,17 +114,10 @@ export class DatasourceSrv { } metricSources.sort(function(a, b) { - // these two should always be at the bottom - if (a.meta.id === 'mixed' || a.meta.id === 'grafana') { + if (a.sort.toLowerCase() > b.sort.toLowerCase()) { return 1; } - if (b.meta.id === 'mixed' || b.meta.id === 'grafana') { - return -1; - } - if (a.name.toLowerCase() > b.name.toLowerCase()) { - return 1; - } - if (a.name.toLowerCase() < b.name.toLowerCase()) { + if (a.sort.toLowerCase() < b.sort.toLowerCase()) { return -1; } return 0; diff --git a/public/app/features/plugins/specs/datasource_srv.jest.ts b/public/app/features/plugins/specs/datasource_srv.jest.ts new file mode 100644 index 00000000000..f261c4e2249 --- /dev/null +++ b/public/app/features/plugins/specs/datasource_srv.jest.ts @@ -0,0 +1,59 @@ +import config from 'app/core/config'; +import 'app/features/plugins/datasource_srv'; +import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; + +describe('datasource_srv', function() { + let _datasourceSrv = new DatasourceSrv({}, {}, {}, {}); + let metricSources; + + describe('when loading metric sources', () => { + let unsortedDatasources = { + mmm: { + type: 'test-db', + meta: { metrics: { m: 1 } }, + }, + '--Grafana--': { + type: 'grafana', + meta: { builtIn: true, metrics: { m: 1 }, id: 'grafana' }, + }, + '--Mixed--': { + type: 'test-db', + meta: { builtIn: true, metrics: { m: 1 }, id: 'mixed' }, + }, + ZZZ: { + type: 'test-db', + meta: { metrics: { m: 1 } }, + }, + aaa: { + type: 'test-db', + meta: { metrics: { m: 1 } }, + }, + BBB: { + type: 'test-db', + meta: { metrics: { m: 1 } }, + }, + }; + beforeEach(() => { + config.datasources = unsortedDatasources; + metricSources = _datasourceSrv.getMetricSources({ skipVariables: true }); + }); + + it('should return a list of sources sorted case insensitively with builtin sources last', () => { + expect(metricSources[0].name).toBe('aaa'); + expect(metricSources[1].name).toBe('BBB'); + expect(metricSources[2].name).toBe('mmm'); + expect(metricSources[3].name).toBe('ZZZ'); + expect(metricSources[4].name).toBe('--Grafana--'); + expect(metricSources[5].name).toBe('--Mixed--'); + }); + + beforeEach(() => { + config.defaultDatasource = 'BBB'; + }); + + it('should set default data source', () => { + expect(metricSources[2].name).toBe('default'); + expect(metricSources[2].sort).toBe('BBB'); + }); + }); +}); diff --git a/public/app/features/plugins/specs/datasource_srv_specs.ts b/public/app/features/plugins/specs/datasource_srv_specs.ts deleted file mode 100644 index 85a66b59ee7..00000000000 --- a/public/app/features/plugins/specs/datasource_srv_specs.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { describe, beforeEach, it, expect, angularMocks } from 'test/lib/common'; -import config from 'app/core/config'; -import 'app/features/plugins/datasource_srv'; - -describe('datasource_srv', function() { - var _datasourceSrv; - var metricSources; - var templateSrv = {}; - - beforeEach(angularMocks.module('grafana.core')); - beforeEach( - angularMocks.module(function($provide) { - $provide.value('templateSrv', templateSrv); - }) - ); - beforeEach(angularMocks.module('grafana.services')); - beforeEach( - angularMocks.inject(function(datasourceSrv) { - _datasourceSrv = datasourceSrv; - }) - ); - - describe('when loading metric sources', function() { - var unsortedDatasources = { - mmm: { - type: 'test-db', - meta: { metrics: { m: 1 } }, - }, - '--Grafana--': { - type: 'grafana', - meta: { builtIn: true, metrics: { m: 1 }, id: 'grafana' }, - }, - '--Mixed--': { - type: 'test-db', - meta: { builtIn: true, metrics: { m: 1 }, id: 'mixed' }, - }, - ZZZ: { - type: 'test-db', - meta: { metrics: { m: 1 } }, - }, - aaa: { - type: 'test-db', - meta: { metrics: { m: 1 } }, - }, - BBB: { - type: 'test-db', - meta: { metrics: { m: 1 } }, - }, - }; - beforeEach(function() { - config.datasources = unsortedDatasources; - metricSources = _datasourceSrv.getMetricSources({ skipVariables: true }); - }); - - it('should return a list of sources sorted case insensitively with builtin sources last', function() { - expect(metricSources[0].name).to.be('aaa'); - expect(metricSources[1].name).to.be('BBB'); - expect(metricSources[2].name).to.be('mmm'); - expect(metricSources[3].name).to.be('ZZZ'); - expect(metricSources[4].name).to.be('--Grafana--'); - expect(metricSources[5].name).to.be('--Mixed--'); - }); - }); -}); From b8724ae0c400a9d5f62d2390bce1a5a601540339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 2 Jul 2018 04:33:39 -0700 Subject: [PATCH 09/25] refactoring: enterprise build/hooks refactorings (#12478) --- pkg/api/dtos/index.go | 1 + pkg/api/frontendsettings.go | 2 +- pkg/api/index.go | 1 + pkg/cmd/grafana-server/main.go | 5 +- pkg/extensions/main.go | 2 +- pkg/metrics/metrics.go | 9 +++ pkg/setting/setting.go | 7 +- public/app/core/config.ts | 19 +++++- public/app/core/services/context_srv.ts | 4 -- .../features/dashboard/specs/exporter.jest.ts | 4 +- .../features/org/partials/team_details.html | 66 +++++++++---------- public/app/features/org/team_details_ctrl.ts | 6 +- public/views/index.template.html | 2 +- 13 files changed, 73 insertions(+), 55 deletions(-) diff --git a/pkg/api/dtos/index.go b/pkg/api/dtos/index.go index 8c7f505277d..77004899fc3 100644 --- a/pkg/api/dtos/index.go +++ b/pkg/api/dtos/index.go @@ -13,6 +13,7 @@ type IndexViewData struct { Theme string NewGrafanaVersionExists bool NewGrafanaVersion string + AppName string } type PluginCss struct { diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 06e6405baaf..da3c88566c1 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -132,7 +132,6 @@ func getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) { } jsonObj := map[string]interface{}{ - "enterprise": setting.Enterprise, "defaultDatasource": defaultDatasource, "datasources": datasources, "panels": panels, @@ -154,6 +153,7 @@ func getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) { "latestVersion": plugins.GrafanaLatestVersion, "hasUpdate": plugins.GrafanaHasUpdate, "env": setting.Env, + "isEnterprise": setting.IsEnterprise, }, } diff --git a/pkg/api/index.go b/pkg/api/index.go index a52bd3e77b0..ea10940d3ba 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -76,6 +76,7 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) { BuildCommit: setting.BuildCommit, NewGrafanaVersion: plugins.GrafanaLatestVersion, NewGrafanaVersionExists: plugins.GrafanaHasUpdate, + AppName: setting.ApplicationName, } if setting.DisableGravatar { diff --git a/pkg/cmd/grafana-server/main.go b/pkg/cmd/grafana-server/main.go index 7b90cf8b1e8..f00e6bba0fd 100644 --- a/pkg/cmd/grafana-server/main.go +++ b/pkg/cmd/grafana-server/main.go @@ -18,7 +18,7 @@ import ( "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/setting" - _ "github.com/grafana/grafana/pkg/extensions" + extensions "github.com/grafana/grafana/pkg/extensions" _ "github.com/grafana/grafana/pkg/services/alerting/conditions" _ "github.com/grafana/grafana/pkg/services/alerting/notifiers" _ "github.com/grafana/grafana/pkg/tsdb/cloudwatch" @@ -35,7 +35,6 @@ import ( var version = "5.0.0" var commit = "NA" var buildstamp string -var enterprise string var configFile = flag.String("config", "", "path to config file") var homePath = flag.String("homepath", "", "path to grafana install/home path, defaults to working directory") @@ -78,7 +77,7 @@ func main() { setting.BuildVersion = version setting.BuildCommit = commit setting.BuildStamp = buildstampInt64 - setting.Enterprise, _ = strconv.ParseBool(enterprise) + setting.IsEnterprise = extensions.IsEnterprise metrics.M_Grafana_Version.WithLabelValues(version).Set(1) diff --git a/pkg/extensions/main.go b/pkg/extensions/main.go index 34ac9da7e86..6e3461da8a8 100644 --- a/pkg/extensions/main.go +++ b/pkg/extensions/main.go @@ -1,3 +1,3 @@ package extensions -import _ "github.com/pkg/errors" +var IsEnterprise bool = false diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index 3d3cfc2e1b6..4dd84c12151 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -334,6 +334,14 @@ func updateTotalStats() { var usageStatsURL = "https://stats.grafana.org/grafana-usage-report" +func getEdition() string { + if setting.IsEnterprise { + return "enterprise" + } else { + return "oss" + } +} + func sendUsageStats() { if !setting.ReportingEnabled { return @@ -349,6 +357,7 @@ func sendUsageStats() { "metrics": metrics, "os": runtime.GOOS, "arch": runtime.GOARCH, + "edition": getEdition(), } statsQuery := models.GetSystemStatsQuery{} diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index e71a3619aa5..d8c8e6431c0 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -18,9 +18,10 @@ import ( "github.com/go-macaron/session" + "time" + "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/util" - "time" ) type Scheme string @@ -49,7 +50,7 @@ var ( BuildVersion string BuildCommit string BuildStamp int64 - Enterprise bool + IsEnterprise bool ApplicationName string // Paths @@ -517,7 +518,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { Raw = cfg.Raw ApplicationName = "Grafana" - if Enterprise { + if IsEnterprise { ApplicationName += " Enterprise" } diff --git a/public/app/core/config.ts b/public/app/core/config.ts index 83ef17662c8..e065ddb22fb 100644 --- a/public/app/core/config.ts +++ b/public/app/core/config.ts @@ -1,11 +1,18 @@ import _ from 'lodash'; -class Settings { +export interface BuildInfo { + version: string; + commit: string; + isEnterprise: boolean; + env: string; +} + +export class Settings { datasources: any; panels: any; appSubUrl: string; window_title_prefix: string; - buildInfo: any; + buildInfo: BuildInfo; new_panel_title: string; bootData: any; externalUserMngLinkUrl: string; @@ -22,7 +29,6 @@ class Settings { disableUserSignUp: boolean; loginHint: any; loginError: any; - enterprise: boolean; constructor(options) { var defaults = { @@ -33,7 +39,14 @@ class Settings { playlist_timespan: '1m', unsaved_changes_warning: true, appSubUrl: '', + buildInfo: { + version: 'v1.0', + commit: '1', + env: 'production', + isEnterprise: false, + }, }; + _.extend(this, defaults, options); } } diff --git a/public/app/core/services/context_srv.ts b/public/app/core/services/context_srv.ts index be8a0af7b7b..8959573e731 100644 --- a/public/app/core/services/context_srv.ts +++ b/public/app/core/services/context_srv.ts @@ -34,14 +34,10 @@ export class ContextSrv { constructor() { this.sidemenu = store.getBool('grafana.sidemenu', true); - if (!config.buildInfo) { - config.buildInfo = {}; - } if (!config.bootData) { config.bootData = { user: {}, settings: {} }; } - this.version = config.buildInfo.version; this.user = new User(); this.isSignedIn = this.user.isSignedIn; this.isGrafanaAdmin = this.user.isGrafanaAdmin; diff --git a/public/app/features/dashboard/specs/exporter.jest.ts b/public/app/features/dashboard/specs/exporter.jest.ts index aa574a4b85a..c7727a4af4d 100644 --- a/public/app/features/dashboard/specs/exporter.jest.ts +++ b/public/app/features/dashboard/specs/exporter.jest.ts @@ -86,9 +86,7 @@ describe('given dashboard with repeated panels', () => { ], }; - config.buildInfo = { - version: '3.0.2', - }; + config.buildInfo.version = '3.0.2'; //Stubs test function calls var datasourceSrvStub = { get: jest.fn(arg => getStub(arg)) }; diff --git a/public/app/features/org/partials/team_details.html b/public/app/features/org/partials/team_details.html index c5ac8bd37a3..3ce851d5546 100644 --- a/public/app/features/org/partials/team_details.html +++ b/public/app/features/org/partials/team_details.html @@ -67,39 +67,39 @@
-
+
-

Team Group Mapping

-
-
- Add group - -
-
- -
-
+

Mappings to external groups

+
+
+ Add group + +
+
+ +
+
- - - - - - - - - - - -
Group
{{group.groupId}} - - - -
-
- - This team has no associated groups yet. - -
+ + + + + + + + + + + +
Group
{{group.groupId}} + + + +
+
+ + This team has no associated groups yet. + +
-
+
diff --git a/public/app/features/org/team_details_ctrl.ts b/public/app/features/org/team_details_ctrl.ts index 35fa04f55d3..6e0fddafa9d 100644 --- a/public/app/features/org/team_details_ctrl.ts +++ b/public/app/features/org/team_details_ctrl.ts @@ -7,7 +7,7 @@ export default class TeamDetailsCtrl { navModel: any; teamGroups: TeamGroup[] = []; newGroupId: string; - enterprise: boolean; + isMappingsEnabled: boolean; /** @ngInject **/ constructor(private $scope, private backendSrv, private $routeParams, navModelSrv) { @@ -15,7 +15,7 @@ export default class TeamDetailsCtrl { this.userPicked = this.userPicked.bind(this); this.get = this.get.bind(this); this.newGroupId = ''; - this.enterprise = config.enterprise; + this.isMappingsEnabled = config.buildInfo.isEnterprise; this.get(); } @@ -29,7 +29,7 @@ export default class TeamDetailsCtrl { this.teamMembers = result; }); - if (config.enterprise) { + if (this.isMappingsEnabled) { this.backendSrv.get(`/api/teams/${this.$routeParams.id}/groups`).then(result => { this.teamGroups = result; }); diff --git a/public/views/index.template.html b/public/views/index.template.html index 79da1d7179c..4140321d633 100644 --- a/public/views/index.template.html +++ b/public/views/index.template.html @@ -65,7 +65,7 @@
  • - Grafana + [[.AppName]] v[[.BuildVersion]] (commit: [[.BuildCommit]])
  • [[if .NewGrafanaVersionExists]] From 6286c31d4b217eb3526475bfd7e560c6c4a0199a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 2 Jul 2018 14:10:39 +0200 Subject: [PATCH 10/25] refactoring: making api wrap public --- build.go | 1 - pkg/api/api.go | 272 +++++++++++++++++++++++----------------------- pkg/api/common.go | 2 +- 3 files changed, 137 insertions(+), 138 deletions(-) diff --git a/build.go b/build.go index 3f92f8833a2..77cbde50c41 100644 --- a/build.go +++ b/build.go @@ -465,7 +465,6 @@ func ldflags() string { b.WriteString(fmt.Sprintf(" -X main.version=%s", version)) b.WriteString(fmt.Sprintf(" -X main.commit=%s", getGitSha())) b.WriteString(fmt.Sprintf(" -X main.buildstamp=%d", buildStamp())) - b.WriteString(fmt.Sprintf(" -X main.enterprise=%t", enterprise)) return b.String() } diff --git a/pkg/api/api.go b/pkg/api/api.go index 829b7735343..8870b9b095e 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -24,7 +24,7 @@ func (hs *HTTPServer) registerRoutes() { // not logged in views r.Get("/", reqSignedIn, Index) r.Get("/logout", Logout) - r.Post("/login", quota("session"), bind(dtos.LoginCommand{}), wrap(LoginPost)) + r.Post("/login", quota("session"), bind(dtos.LoginCommand{}), Wrap(LoginPost)) r.Get("/login/:name", quota("session"), OAuthLogin) r.Get("/login", LoginView) r.Get("/invite/:code", Index) @@ -83,20 +83,20 @@ func (hs *HTTPServer) registerRoutes() { // sign up r.Get("/signup", Index) - r.Get("/api/user/signup/options", wrap(GetSignUpOptions)) - r.Post("/api/user/signup", quota("user"), bind(dtos.SignUpForm{}), wrap(SignUp)) - r.Post("/api/user/signup/step2", bind(dtos.SignUpStep2Form{}), wrap(SignUpStep2)) + r.Get("/api/user/signup/options", Wrap(GetSignUpOptions)) + r.Post("/api/user/signup", quota("user"), bind(dtos.SignUpForm{}), Wrap(SignUp)) + r.Post("/api/user/signup/step2", bind(dtos.SignUpStep2Form{}), Wrap(SignUpStep2)) // invited - r.Get("/api/user/invite/:code", wrap(GetInviteInfoByCode)) - r.Post("/api/user/invite/complete", bind(dtos.CompleteInviteForm{}), wrap(CompleteInvite)) + r.Get("/api/user/invite/:code", Wrap(GetInviteInfoByCode)) + r.Post("/api/user/invite/complete", bind(dtos.CompleteInviteForm{}), Wrap(CompleteInvite)) // reset password r.Get("/user/password/send-reset-email", Index) r.Get("/user/password/reset", Index) - r.Post("/api/user/password/send-reset-email", bind(dtos.SendResetPasswordEmailForm{}), wrap(SendResetPasswordEmail)) - r.Post("/api/user/password/reset", bind(dtos.ResetUserPasswordForm{}), wrap(ResetPassword)) + r.Post("/api/user/password/send-reset-email", bind(dtos.SendResetPasswordEmailForm{}), Wrap(SendResetPasswordEmail)) + r.Post("/api/user/password/reset", bind(dtos.ResetUserPasswordForm{}), Wrap(ResetPassword)) // dashboard snapshots r.Get("/dashboard/snapshot/*", Index) @@ -106,8 +106,8 @@ func (hs *HTTPServer) registerRoutes() { r.Post("/api/snapshots/", bind(m.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot) r.Get("/api/snapshot/shared-options/", GetSharingOptions) r.Get("/api/snapshots/:key", GetDashboardSnapshot) - r.Get("/api/snapshots-delete/:deleteKey", wrap(DeleteDashboardSnapshotByDeleteKey)) - r.Delete("/api/snapshots/:key", reqEditorRole, wrap(DeleteDashboardSnapshot)) + r.Get("/api/snapshots-delete/:deleteKey", Wrap(DeleteDashboardSnapshotByDeleteKey)) + r.Delete("/api/snapshots/:key", reqEditorRole, Wrap(DeleteDashboardSnapshot)) // api renew session based on remember cookie r.Get("/api/login/ping", quota("session"), LoginAPIPing) @@ -117,138 +117,138 @@ func (hs *HTTPServer) registerRoutes() { // user (signed in) apiRoute.Group("/user", func(userRoute routing.RouteRegister) { - userRoute.Get("/", wrap(GetSignedInUser)) - userRoute.Put("/", bind(m.UpdateUserCommand{}), wrap(UpdateSignedInUser)) - userRoute.Post("/using/:id", wrap(UserSetUsingOrg)) - userRoute.Get("/orgs", wrap(GetSignedInUserOrgList)) + userRoute.Get("/", Wrap(GetSignedInUser)) + userRoute.Put("/", bind(m.UpdateUserCommand{}), Wrap(UpdateSignedInUser)) + userRoute.Post("/using/:id", Wrap(UserSetUsingOrg)) + userRoute.Get("/orgs", Wrap(GetSignedInUserOrgList)) - userRoute.Post("/stars/dashboard/:id", wrap(StarDashboard)) - userRoute.Delete("/stars/dashboard/:id", wrap(UnstarDashboard)) + userRoute.Post("/stars/dashboard/:id", Wrap(StarDashboard)) + userRoute.Delete("/stars/dashboard/:id", Wrap(UnstarDashboard)) - userRoute.Put("/password", bind(m.ChangeUserPasswordCommand{}), wrap(ChangeUserPassword)) - userRoute.Get("/quotas", wrap(GetUserQuotas)) - userRoute.Put("/helpflags/:id", wrap(SetHelpFlag)) + userRoute.Put("/password", bind(m.ChangeUserPasswordCommand{}), Wrap(ChangeUserPassword)) + userRoute.Get("/quotas", Wrap(GetUserQuotas)) + userRoute.Put("/helpflags/:id", Wrap(SetHelpFlag)) // For dev purpose - userRoute.Get("/helpflags/clear", wrap(ClearHelpFlags)) + userRoute.Get("/helpflags/clear", Wrap(ClearHelpFlags)) - userRoute.Get("/preferences", wrap(GetUserPreferences)) - userRoute.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateUserPreferences)) + userRoute.Get("/preferences", Wrap(GetUserPreferences)) + userRoute.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), Wrap(UpdateUserPreferences)) }) // users (admin permission required) apiRoute.Group("/users", func(usersRoute routing.RouteRegister) { - usersRoute.Get("/", wrap(SearchUsers)) - usersRoute.Get("/search", wrap(SearchUsersWithPaging)) - usersRoute.Get("/:id", wrap(GetUserByID)) - usersRoute.Get("/:id/orgs", wrap(GetUserOrgList)) + usersRoute.Get("/", Wrap(SearchUsers)) + usersRoute.Get("/search", Wrap(SearchUsersWithPaging)) + usersRoute.Get("/:id", Wrap(GetUserByID)) + usersRoute.Get("/:id/orgs", Wrap(GetUserOrgList)) // query parameters /users/lookup?loginOrEmail=admin@example.com - usersRoute.Get("/lookup", wrap(GetUserByLoginOrEmail)) - usersRoute.Put("/:id", bind(m.UpdateUserCommand{}), wrap(UpdateUser)) - usersRoute.Post("/:id/using/:orgId", wrap(UpdateUserActiveOrg)) + usersRoute.Get("/lookup", Wrap(GetUserByLoginOrEmail)) + usersRoute.Put("/:id", bind(m.UpdateUserCommand{}), Wrap(UpdateUser)) + usersRoute.Post("/:id/using/:orgId", Wrap(UpdateUserActiveOrg)) }, reqGrafanaAdmin) // team (admin permission required) apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) { - teamsRoute.Post("/", bind(m.CreateTeamCommand{}), wrap(CreateTeam)) - teamsRoute.Put("/:teamId", bind(m.UpdateTeamCommand{}), wrap(UpdateTeam)) - teamsRoute.Delete("/:teamId", wrap(DeleteTeamByID)) - teamsRoute.Get("/:teamId/members", wrap(GetTeamMembers)) - teamsRoute.Post("/:teamId/members", bind(m.AddTeamMemberCommand{}), wrap(AddTeamMember)) - teamsRoute.Delete("/:teamId/members/:userId", wrap(RemoveTeamMember)) + teamsRoute.Post("/", bind(m.CreateTeamCommand{}), Wrap(CreateTeam)) + teamsRoute.Put("/:teamId", bind(m.UpdateTeamCommand{}), Wrap(UpdateTeam)) + teamsRoute.Delete("/:teamId", Wrap(DeleteTeamByID)) + teamsRoute.Get("/:teamId/members", Wrap(GetTeamMembers)) + teamsRoute.Post("/:teamId/members", bind(m.AddTeamMemberCommand{}), Wrap(AddTeamMember)) + teamsRoute.Delete("/:teamId/members/:userId", Wrap(RemoveTeamMember)) }, reqOrgAdmin) // team without requirement of user to be org admin apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) { - teamsRoute.Get("/:teamId", wrap(GetTeamByID)) - teamsRoute.Get("/search", wrap(SearchTeams)) + teamsRoute.Get("/:teamId", Wrap(GetTeamByID)) + teamsRoute.Get("/search", Wrap(SearchTeams)) }) // org information available to all users. apiRoute.Group("/org", func(orgRoute routing.RouteRegister) { - orgRoute.Get("/", wrap(GetOrgCurrent)) - orgRoute.Get("/quotas", wrap(GetOrgQuotas)) + orgRoute.Get("/", Wrap(GetOrgCurrent)) + orgRoute.Get("/quotas", Wrap(GetOrgQuotas)) }) // current org apiRoute.Group("/org", func(orgRoute routing.RouteRegister) { - orgRoute.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrgCurrent)) - orgRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddressCurrent)) - orgRoute.Post("/users", quota("user"), bind(m.AddOrgUserCommand{}), wrap(AddOrgUserToCurrentOrg)) - orgRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUserForCurrentOrg)) - orgRoute.Delete("/users/:userId", wrap(RemoveOrgUserForCurrentOrg)) + orgRoute.Put("/", bind(dtos.UpdateOrgForm{}), Wrap(UpdateOrgCurrent)) + orgRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), Wrap(UpdateOrgAddressCurrent)) + orgRoute.Post("/users", quota("user"), bind(m.AddOrgUserCommand{}), Wrap(AddOrgUserToCurrentOrg)) + orgRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), Wrap(UpdateOrgUserForCurrentOrg)) + orgRoute.Delete("/users/:userId", Wrap(RemoveOrgUserForCurrentOrg)) // invites - orgRoute.Get("/invites", wrap(GetPendingOrgInvites)) - orgRoute.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), wrap(AddOrgInvite)) - orgRoute.Patch("/invites/:code/revoke", wrap(RevokeInvite)) + orgRoute.Get("/invites", Wrap(GetPendingOrgInvites)) + orgRoute.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), Wrap(AddOrgInvite)) + orgRoute.Patch("/invites/:code/revoke", Wrap(RevokeInvite)) // prefs - orgRoute.Get("/preferences", wrap(GetOrgPreferences)) - orgRoute.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateOrgPreferences)) + orgRoute.Get("/preferences", Wrap(GetOrgPreferences)) + orgRoute.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), Wrap(UpdateOrgPreferences)) }, reqOrgAdmin) // current org without requirement of user to be org admin apiRoute.Group("/org", func(orgRoute routing.RouteRegister) { - orgRoute.Get("/users", wrap(GetOrgUsersForCurrentOrg)) + orgRoute.Get("/users", Wrap(GetOrgUsersForCurrentOrg)) }) // create new org - apiRoute.Post("/orgs", quota("org"), bind(m.CreateOrgCommand{}), wrap(CreateOrg)) + apiRoute.Post("/orgs", quota("org"), bind(m.CreateOrgCommand{}), Wrap(CreateOrg)) // search all orgs - apiRoute.Get("/orgs", reqGrafanaAdmin, wrap(SearchOrgs)) + apiRoute.Get("/orgs", reqGrafanaAdmin, Wrap(SearchOrgs)) // orgs (admin routes) apiRoute.Group("/orgs/:orgId", func(orgsRoute routing.RouteRegister) { - orgsRoute.Get("/", wrap(GetOrgByID)) - orgsRoute.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrg)) - orgsRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddress)) - orgsRoute.Delete("/", wrap(DeleteOrgByID)) - orgsRoute.Get("/users", wrap(GetOrgUsers)) - orgsRoute.Post("/users", bind(m.AddOrgUserCommand{}), wrap(AddOrgUser)) - orgsRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUser)) - orgsRoute.Delete("/users/:userId", wrap(RemoveOrgUser)) - orgsRoute.Get("/quotas", wrap(GetOrgQuotas)) - orgsRoute.Put("/quotas/:target", bind(m.UpdateOrgQuotaCmd{}), wrap(UpdateOrgQuota)) + orgsRoute.Get("/", Wrap(GetOrgByID)) + orgsRoute.Put("/", bind(dtos.UpdateOrgForm{}), Wrap(UpdateOrg)) + orgsRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), Wrap(UpdateOrgAddress)) + orgsRoute.Delete("/", Wrap(DeleteOrgByID)) + orgsRoute.Get("/users", Wrap(GetOrgUsers)) + orgsRoute.Post("/users", bind(m.AddOrgUserCommand{}), Wrap(AddOrgUser)) + orgsRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), Wrap(UpdateOrgUser)) + orgsRoute.Delete("/users/:userId", Wrap(RemoveOrgUser)) + orgsRoute.Get("/quotas", Wrap(GetOrgQuotas)) + orgsRoute.Put("/quotas/:target", bind(m.UpdateOrgQuotaCmd{}), Wrap(UpdateOrgQuota)) }, reqGrafanaAdmin) // orgs (admin routes) apiRoute.Group("/orgs/name/:name", func(orgsRoute routing.RouteRegister) { - orgsRoute.Get("/", wrap(GetOrgByName)) + orgsRoute.Get("/", Wrap(GetOrgByName)) }, reqGrafanaAdmin) // auth api keys apiRoute.Group("/auth/keys", func(keysRoute routing.RouteRegister) { - keysRoute.Get("/", wrap(GetAPIKeys)) - keysRoute.Post("/", quota("api_key"), bind(m.AddApiKeyCommand{}), wrap(AddAPIKey)) - keysRoute.Delete("/:id", wrap(DeleteAPIKey)) + keysRoute.Get("/", Wrap(GetAPIKeys)) + keysRoute.Post("/", quota("api_key"), bind(m.AddApiKeyCommand{}), Wrap(AddAPIKey)) + keysRoute.Delete("/:id", Wrap(DeleteAPIKey)) }, reqOrgAdmin) // Preferences apiRoute.Group("/preferences", func(prefRoute routing.RouteRegister) { - prefRoute.Post("/set-home-dash", bind(m.SavePreferencesCommand{}), wrap(SetHomeDashboard)) + prefRoute.Post("/set-home-dash", bind(m.SavePreferencesCommand{}), Wrap(SetHomeDashboard)) }) // Data sources apiRoute.Group("/datasources", func(datasourceRoute routing.RouteRegister) { - datasourceRoute.Get("/", wrap(GetDataSources)) - datasourceRoute.Post("/", quota("data_source"), bind(m.AddDataSourceCommand{}), wrap(AddDataSource)) - datasourceRoute.Put("/:id", bind(m.UpdateDataSourceCommand{}), wrap(UpdateDataSource)) - datasourceRoute.Delete("/:id", wrap(DeleteDataSourceByID)) - datasourceRoute.Delete("/name/:name", wrap(DeleteDataSourceByName)) - datasourceRoute.Get("/:id", wrap(GetDataSourceByID)) - datasourceRoute.Get("/name/:name", wrap(GetDataSourceByName)) + datasourceRoute.Get("/", Wrap(GetDataSources)) + datasourceRoute.Post("/", quota("data_source"), bind(m.AddDataSourceCommand{}), Wrap(AddDataSource)) + datasourceRoute.Put("/:id", bind(m.UpdateDataSourceCommand{}), Wrap(UpdateDataSource)) + datasourceRoute.Delete("/:id", Wrap(DeleteDataSourceByID)) + datasourceRoute.Delete("/name/:name", Wrap(DeleteDataSourceByName)) + datasourceRoute.Get("/:id", Wrap(GetDataSourceByID)) + datasourceRoute.Get("/name/:name", Wrap(GetDataSourceByName)) }, reqOrgAdmin) - apiRoute.Get("/datasources/id/:name", wrap(GetDataSourceIDByName), reqSignedIn) + apiRoute.Get("/datasources/id/:name", Wrap(GetDataSourceIDByName), reqSignedIn) - apiRoute.Get("/plugins", wrap(GetPluginList)) - apiRoute.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingByID)) - apiRoute.Get("/plugins/:pluginId/markdown/:name", wrap(GetPluginMarkdown)) + apiRoute.Get("/plugins", Wrap(GetPluginList)) + apiRoute.Get("/plugins/:pluginId/settings", Wrap(GetPluginSettingByID)) + apiRoute.Get("/plugins/:pluginId/markdown/:name", Wrap(GetPluginMarkdown)) apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) { - pluginRoute.Get("/:pluginId/dashboards/", wrap(GetPluginDashboards)) - pluginRoute.Post("/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting)) + pluginRoute.Get("/:pluginId/dashboards/", Wrap(GetPluginDashboards)) + pluginRoute.Post("/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), Wrap(UpdatePluginSetting)) }, reqOrgAdmin) apiRoute.Get("/frontend/settings/", GetFrontendSettings) @@ -257,106 +257,106 @@ func (hs *HTTPServer) registerRoutes() { // Folders apiRoute.Group("/folders", func(folderRoute routing.RouteRegister) { - folderRoute.Get("/", wrap(GetFolders)) - folderRoute.Get("/id/:id", wrap(GetFolderByID)) - folderRoute.Post("/", bind(m.CreateFolderCommand{}), wrap(CreateFolder)) + folderRoute.Get("/", Wrap(GetFolders)) + folderRoute.Get("/id/:id", Wrap(GetFolderByID)) + folderRoute.Post("/", bind(m.CreateFolderCommand{}), Wrap(CreateFolder)) folderRoute.Group("/:uid", func(folderUidRoute routing.RouteRegister) { - folderUidRoute.Get("/", wrap(GetFolderByUID)) - folderUidRoute.Put("/", bind(m.UpdateFolderCommand{}), wrap(UpdateFolder)) - folderUidRoute.Delete("/", wrap(DeleteFolder)) + folderUidRoute.Get("/", Wrap(GetFolderByUID)) + folderUidRoute.Put("/", bind(m.UpdateFolderCommand{}), Wrap(UpdateFolder)) + folderUidRoute.Delete("/", Wrap(DeleteFolder)) folderUidRoute.Group("/permissions", func(folderPermissionRoute routing.RouteRegister) { - folderPermissionRoute.Get("/", wrap(GetFolderPermissionList)) - folderPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), wrap(UpdateFolderPermissions)) + folderPermissionRoute.Get("/", Wrap(GetFolderPermissionList)) + folderPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), Wrap(UpdateFolderPermissions)) }) }) }) // Dashboard apiRoute.Group("/dashboards", func(dashboardRoute routing.RouteRegister) { - dashboardRoute.Get("/uid/:uid", wrap(GetDashboard)) - dashboardRoute.Delete("/uid/:uid", wrap(DeleteDashboardByUID)) + dashboardRoute.Get("/uid/:uid", Wrap(GetDashboard)) + dashboardRoute.Delete("/uid/:uid", Wrap(DeleteDashboardByUID)) - dashboardRoute.Get("/db/:slug", wrap(GetDashboard)) - dashboardRoute.Delete("/db/:slug", wrap(DeleteDashboard)) + dashboardRoute.Get("/db/:slug", Wrap(GetDashboard)) + dashboardRoute.Delete("/db/:slug", Wrap(DeleteDashboard)) - dashboardRoute.Post("/calculate-diff", bind(dtos.CalculateDiffOptions{}), wrap(CalculateDashboardDiff)) + dashboardRoute.Post("/calculate-diff", bind(dtos.CalculateDiffOptions{}), Wrap(CalculateDashboardDiff)) - dashboardRoute.Post("/db", bind(m.SaveDashboardCommand{}), wrap(PostDashboard)) - dashboardRoute.Get("/home", wrap(GetHomeDashboard)) + dashboardRoute.Post("/db", bind(m.SaveDashboardCommand{}), Wrap(PostDashboard)) + dashboardRoute.Get("/home", Wrap(GetHomeDashboard)) dashboardRoute.Get("/tags", GetDashboardTags) - dashboardRoute.Post("/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard)) + dashboardRoute.Post("/import", bind(dtos.ImportDashboardCommand{}), Wrap(ImportDashboard)) dashboardRoute.Group("/id/:dashboardId", func(dashIdRoute routing.RouteRegister) { - dashIdRoute.Get("/versions", wrap(GetDashboardVersions)) - dashIdRoute.Get("/versions/:id", wrap(GetDashboardVersion)) - dashIdRoute.Post("/restore", bind(dtos.RestoreDashboardVersionCommand{}), wrap(RestoreDashboardVersion)) + dashIdRoute.Get("/versions", Wrap(GetDashboardVersions)) + dashIdRoute.Get("/versions/:id", Wrap(GetDashboardVersion)) + dashIdRoute.Post("/restore", bind(dtos.RestoreDashboardVersionCommand{}), Wrap(RestoreDashboardVersion)) dashIdRoute.Group("/permissions", func(dashboardPermissionRoute routing.RouteRegister) { - dashboardPermissionRoute.Get("/", wrap(GetDashboardPermissionList)) - dashboardPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), wrap(UpdateDashboardPermissions)) + dashboardPermissionRoute.Get("/", Wrap(GetDashboardPermissionList)) + dashboardPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), Wrap(UpdateDashboardPermissions)) }) }) }) // Dashboard snapshots apiRoute.Group("/dashboard/snapshots", func(dashboardRoute routing.RouteRegister) { - dashboardRoute.Get("/", wrap(SearchDashboardSnapshots)) + dashboardRoute.Get("/", Wrap(SearchDashboardSnapshots)) }) // Playlist apiRoute.Group("/playlists", func(playlistRoute routing.RouteRegister) { - playlistRoute.Get("/", wrap(SearchPlaylists)) - playlistRoute.Get("/:id", ValidateOrgPlaylist, wrap(GetPlaylist)) - playlistRoute.Get("/:id/items", ValidateOrgPlaylist, wrap(GetPlaylistItems)) - playlistRoute.Get("/:id/dashboards", ValidateOrgPlaylist, wrap(GetPlaylistDashboards)) - playlistRoute.Delete("/:id", reqEditorRole, ValidateOrgPlaylist, wrap(DeletePlaylist)) - playlistRoute.Put("/:id", reqEditorRole, bind(m.UpdatePlaylistCommand{}), ValidateOrgPlaylist, wrap(UpdatePlaylist)) - playlistRoute.Post("/", reqEditorRole, bind(m.CreatePlaylistCommand{}), wrap(CreatePlaylist)) + playlistRoute.Get("/", Wrap(SearchPlaylists)) + playlistRoute.Get("/:id", ValidateOrgPlaylist, Wrap(GetPlaylist)) + playlistRoute.Get("/:id/items", ValidateOrgPlaylist, Wrap(GetPlaylistItems)) + playlistRoute.Get("/:id/dashboards", ValidateOrgPlaylist, Wrap(GetPlaylistDashboards)) + playlistRoute.Delete("/:id", reqEditorRole, ValidateOrgPlaylist, Wrap(DeletePlaylist)) + playlistRoute.Put("/:id", reqEditorRole, bind(m.UpdatePlaylistCommand{}), ValidateOrgPlaylist, Wrap(UpdatePlaylist)) + playlistRoute.Post("/", reqEditorRole, bind(m.CreatePlaylistCommand{}), Wrap(CreatePlaylist)) }) // Search apiRoute.Get("/search/", Search) // metrics - apiRoute.Post("/tsdb/query", bind(dtos.MetricRequest{}), wrap(QueryMetrics)) - apiRoute.Get("/tsdb/testdata/scenarios", wrap(GetTestDataScenarios)) - apiRoute.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, wrap(GenerateSQLTestData)) - apiRoute.Get("/tsdb/testdata/random-walk", wrap(GetTestDataRandomWalk)) + apiRoute.Post("/tsdb/query", bind(dtos.MetricRequest{}), Wrap(QueryMetrics)) + apiRoute.Get("/tsdb/testdata/scenarios", Wrap(GetTestDataScenarios)) + apiRoute.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, Wrap(GenerateSQLTestData)) + apiRoute.Get("/tsdb/testdata/random-walk", Wrap(GetTestDataRandomWalk)) apiRoute.Group("/alerts", func(alertsRoute routing.RouteRegister) { - alertsRoute.Post("/test", bind(dtos.AlertTestCommand{}), wrap(AlertTest)) - alertsRoute.Post("/:alertId/pause", reqEditorRole, bind(dtos.PauseAlertCommand{}), wrap(PauseAlert)) - alertsRoute.Get("/:alertId", ValidateOrgAlert, wrap(GetAlert)) - alertsRoute.Get("/", wrap(GetAlerts)) - alertsRoute.Get("/states-for-dashboard", wrap(GetAlertStatesForDashboard)) + alertsRoute.Post("/test", bind(dtos.AlertTestCommand{}), Wrap(AlertTest)) + alertsRoute.Post("/:alertId/pause", reqEditorRole, bind(dtos.PauseAlertCommand{}), Wrap(PauseAlert)) + alertsRoute.Get("/:alertId", ValidateOrgAlert, Wrap(GetAlert)) + alertsRoute.Get("/", Wrap(GetAlerts)) + alertsRoute.Get("/states-for-dashboard", Wrap(GetAlertStatesForDashboard)) }) - apiRoute.Get("/alert-notifications", wrap(GetAlertNotifications)) - apiRoute.Get("/alert-notifiers", wrap(GetAlertNotifiers)) + apiRoute.Get("/alert-notifications", Wrap(GetAlertNotifications)) + apiRoute.Get("/alert-notifiers", Wrap(GetAlertNotifiers)) apiRoute.Group("/alert-notifications", func(alertNotifications routing.RouteRegister) { - alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), wrap(NotificationTest)) - alertNotifications.Post("/", bind(m.CreateAlertNotificationCommand{}), wrap(CreateAlertNotification)) - alertNotifications.Put("/:notificationId", bind(m.UpdateAlertNotificationCommand{}), wrap(UpdateAlertNotification)) - alertNotifications.Get("/:notificationId", wrap(GetAlertNotificationByID)) - alertNotifications.Delete("/:notificationId", wrap(DeleteAlertNotification)) + alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), Wrap(NotificationTest)) + alertNotifications.Post("/", bind(m.CreateAlertNotificationCommand{}), Wrap(CreateAlertNotification)) + alertNotifications.Put("/:notificationId", bind(m.UpdateAlertNotificationCommand{}), Wrap(UpdateAlertNotification)) + alertNotifications.Get("/:notificationId", Wrap(GetAlertNotificationByID)) + alertNotifications.Delete("/:notificationId", Wrap(DeleteAlertNotification)) }, reqEditorRole) - apiRoute.Get("/annotations", wrap(GetAnnotations)) - apiRoute.Post("/annotations/mass-delete", reqOrgAdmin, bind(dtos.DeleteAnnotationsCmd{}), wrap(DeleteAnnotations)) + apiRoute.Get("/annotations", Wrap(GetAnnotations)) + apiRoute.Post("/annotations/mass-delete", reqOrgAdmin, bind(dtos.DeleteAnnotationsCmd{}), Wrap(DeleteAnnotations)) apiRoute.Group("/annotations", func(annotationsRoute routing.RouteRegister) { - annotationsRoute.Post("/", bind(dtos.PostAnnotationsCmd{}), wrap(PostAnnotation)) - annotationsRoute.Delete("/:annotationId", wrap(DeleteAnnotationByID)) - annotationsRoute.Put("/:annotationId", bind(dtos.UpdateAnnotationsCmd{}), wrap(UpdateAnnotation)) - annotationsRoute.Delete("/region/:regionId", wrap(DeleteAnnotationRegion)) - annotationsRoute.Post("/graphite", reqEditorRole, bind(dtos.PostGraphiteAnnotationsCmd{}), wrap(PostGraphiteAnnotation)) + annotationsRoute.Post("/", bind(dtos.PostAnnotationsCmd{}), Wrap(PostAnnotation)) + annotationsRoute.Delete("/:annotationId", Wrap(DeleteAnnotationByID)) + annotationsRoute.Put("/:annotationId", bind(dtos.UpdateAnnotationsCmd{}), Wrap(UpdateAnnotation)) + annotationsRoute.Delete("/region/:regionId", Wrap(DeleteAnnotationRegion)) + annotationsRoute.Post("/graphite", reqEditorRole, bind(dtos.PostGraphiteAnnotationsCmd{}), Wrap(PostGraphiteAnnotation)) }) // error test - r.Get("/metrics/error", wrap(GenerateError)) + r.Get("/metrics/error", Wrap(GenerateError)) }, reqSignedIn) @@ -367,10 +367,10 @@ func (hs *HTTPServer) registerRoutes() { adminRoute.Put("/users/:id/password", bind(dtos.AdminUpdateUserPasswordForm{}), AdminUpdateUserPassword) adminRoute.Put("/users/:id/permissions", bind(dtos.AdminUpdateUserPermissionsForm{}), AdminUpdateUserPermissions) adminRoute.Delete("/users/:id", AdminDeleteUser) - adminRoute.Get("/users/:id/quotas", wrap(GetUserQuotas)) - adminRoute.Put("/users/:id/quotas/:target", bind(m.UpdateUserQuotaCmd{}), wrap(UpdateUserQuota)) + adminRoute.Get("/users/:id/quotas", Wrap(GetUserQuotas)) + adminRoute.Put("/users/:id/quotas/:target", bind(m.UpdateUserQuotaCmd{}), Wrap(UpdateUserQuota)) adminRoute.Get("/stats", AdminGetStats) - adminRoute.Post("/pause-all-alerts", bind(dtos.PauseAllAlertsCommand{}), wrap(PauseAllAlerts)) + adminRoute.Post("/pause-all-alerts", bind(dtos.PauseAllAlertsCommand{}), Wrap(PauseAllAlerts)) }, reqGrafanaAdmin) // rendering diff --git a/pkg/api/common.go b/pkg/api/common.go index 97f41ff7c72..7973c72c8fa 100644 --- a/pkg/api/common.go +++ b/pkg/api/common.go @@ -30,7 +30,7 @@ type NormalResponse struct { err error } -func wrap(action interface{}) macaron.Handler { +func Wrap(action interface{}) macaron.Handler { return func(c *m.ReqContext) { var res Response From 8d6797c367bbb45228cf7e198808981fa2c0bffe Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 2 Jul 2018 16:05:23 +0200 Subject: [PATCH 11/25] ci: publishes grafana enterprise to s3. --- .circleci/config.yml | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9b729ecfa15..7c8fad40905 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -160,7 +160,7 @@ jobs: steps: - checkout - run: - name: build and package grafana + name: build, test and package grafana enterprise command: './scripts/build/build_enterprise.sh' - run: name: sign packages @@ -168,6 +168,26 @@ jobs: - run: name: sha-sum packages command: 'go run build.go sha-dist' + -run: + name: move enterprise packages into their own folder + command: 'mv dist enterprise-dist' + - persist_to_workspace: + root: . + paths: + - enterprise-dist/grafana* + + deploy-enterprise-master: + docker: + - image: circleci/python:2.7-stretch + steps: + - attach_workspace: + at: . + - run: + name: install awscli + command: 'sudo pip install awscli' + - run: + name: deploy to s3 + command: 'aws s3 sync ./enterprise-dist s3://$ENTERPRISE_BUCKET_NAME/master' deploy-master: docker: @@ -247,6 +267,20 @@ workflows: filters: branches: only: master + - deploy-enterprise-master: + requires: + - build-all + - test-backend + - test-frontend + - codespell + - gometalinter + - mysql-integration-test + - postgres-integration-test + - build-enterprise + filters: + branches: + only: master + release: jobs: - build-all: From 4245f0ce119f8f016243844bd58de01c9adc1b51 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 2 Jul 2018 16:10:20 +0200 Subject: [PATCH 12/25] ci: typo --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7c8fad40905..0217c129a62 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -168,7 +168,7 @@ jobs: - run: name: sha-sum packages command: 'go run build.go sha-dist' - -run: + - run: name: move enterprise packages into their own folder command: 'mv dist enterprise-dist' - persist_to_workspace: From 5e08bf5130e5f118afb1c43b36e62e116f369ec2 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 2 Jul 2018 17:13:59 +0200 Subject: [PATCH 13/25] test: fixed usage of wrap in tests. --- pkg/api/alerting_test.go | 2 +- pkg/api/annotations_test.go | 6 +++--- pkg/api/common_test.go | 4 ++-- pkg/api/dashboard_permission_test.go | 2 +- pkg/api/dashboard_test.go | 4 ++-- pkg/api/folder_permission_test.go | 2 +- pkg/api/folder_test.go | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/api/alerting_test.go b/pkg/api/alerting_test.go index abfdfb66322..9eba0e0d5b6 100644 --- a/pkg/api/alerting_test.go +++ b/pkg/api/alerting_test.go @@ -135,7 +135,7 @@ func postAlertScenario(desc string, url string, routePattern string, role m.Role defer bus.ClearBusHandlers() sc := setupScenarioContext(url) - sc.defaultHandler = wrap(func(c *m.ReqContext) Response { + sc.defaultHandler = Wrap(func(c *m.ReqContext) Response { sc.context = c sc.context.UserId = TestUserID sc.context.OrgId = TestOrgID diff --git a/pkg/api/annotations_test.go b/pkg/api/annotations_test.go index e5f63ce022b..6590eb19ff2 100644 --- a/pkg/api/annotations_test.go +++ b/pkg/api/annotations_test.go @@ -223,7 +223,7 @@ func postAnnotationScenario(desc string, url string, routePattern string, role m defer bus.ClearBusHandlers() sc := setupScenarioContext(url) - sc.defaultHandler = wrap(func(c *m.ReqContext) Response { + sc.defaultHandler = Wrap(func(c *m.ReqContext) Response { sc.context = c sc.context.UserId = TestUserID sc.context.OrgId = TestOrgID @@ -246,7 +246,7 @@ func putAnnotationScenario(desc string, url string, routePattern string, role m. defer bus.ClearBusHandlers() sc := setupScenarioContext(url) - sc.defaultHandler = wrap(func(c *m.ReqContext) Response { + sc.defaultHandler = Wrap(func(c *m.ReqContext) Response { sc.context = c sc.context.UserId = TestUserID sc.context.OrgId = TestOrgID @@ -269,7 +269,7 @@ func deleteAnnotationsScenario(desc string, url string, routePattern string, rol defer bus.ClearBusHandlers() sc := setupScenarioContext(url) - sc.defaultHandler = wrap(func(c *m.ReqContext) Response { + sc.defaultHandler = Wrap(func(c *m.ReqContext) Response { sc.context = c sc.context.UserId = TestUserID sc.context.OrgId = TestOrgID diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index 40c438b607a..8b66a7a468b 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -23,7 +23,7 @@ func loggedInUserScenarioWithRole(desc string, method string, url string, routeP defer bus.ClearBusHandlers() sc := setupScenarioContext(url) - sc.defaultHandler = wrap(func(c *m.ReqContext) Response { + sc.defaultHandler = Wrap(func(c *m.ReqContext) Response { sc.context = c sc.context.UserId = TestUserID sc.context.OrgId = TestOrgID @@ -51,7 +51,7 @@ func anonymousUserScenario(desc string, method string, url string, routePattern defer bus.ClearBusHandlers() sc := setupScenarioContext(url) - sc.defaultHandler = wrap(func(c *m.ReqContext) Response { + sc.defaultHandler = Wrap(func(c *m.ReqContext) Response { sc.context = c if sc.handlerFunc != nil { return sc.handlerFunc(sc.context) diff --git a/pkg/api/dashboard_permission_test.go b/pkg/api/dashboard_permission_test.go index 24f0bdca365..f65c5f1f5fa 100644 --- a/pkg/api/dashboard_permission_test.go +++ b/pkg/api/dashboard_permission_test.go @@ -194,7 +194,7 @@ func updateDashboardPermissionScenario(desc string, url string, routePattern str sc := setupScenarioContext(url) - sc.defaultHandler = wrap(func(c *m.ReqContext) Response { + sc.defaultHandler = Wrap(func(c *m.ReqContext) Response { sc.context = c sc.context.OrgId = TestOrgID sc.context.UserId = TestUserID diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index ccde2382787..50a2e314f5c 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -882,7 +882,7 @@ func postDashboardScenario(desc string, url string, routePattern string, mock *d defer bus.ClearBusHandlers() sc := setupScenarioContext(url) - sc.defaultHandler = wrap(func(c *m.ReqContext) Response { + sc.defaultHandler = Wrap(func(c *m.ReqContext) Response { sc.context = c sc.context.SignedInUser = &m.SignedInUser{OrgId: cmd.OrgId, UserId: cmd.UserId} @@ -907,7 +907,7 @@ func postDiffScenario(desc string, url string, routePattern string, cmd dtos.Cal defer bus.ClearBusHandlers() sc := setupScenarioContext(url) - sc.defaultHandler = wrap(func(c *m.ReqContext) Response { + sc.defaultHandler = Wrap(func(c *m.ReqContext) Response { sc.context = c sc.context.SignedInUser = &m.SignedInUser{ OrgId: TestOrgID, diff --git a/pkg/api/folder_permission_test.go b/pkg/api/folder_permission_test.go index f7458af6dce..64a746ca937 100644 --- a/pkg/api/folder_permission_test.go +++ b/pkg/api/folder_permission_test.go @@ -226,7 +226,7 @@ func updateFolderPermissionScenario(desc string, url string, routePattern string sc := setupScenarioContext(url) - sc.defaultHandler = wrap(func(c *m.ReqContext) Response { + sc.defaultHandler = Wrap(func(c *m.ReqContext) Response { sc.context = c sc.context.OrgId = TestOrgID sc.context.UserId = TestUserID diff --git a/pkg/api/folder_test.go b/pkg/api/folder_test.go index 0d9b9495686..6e24e432535 100644 --- a/pkg/api/folder_test.go +++ b/pkg/api/folder_test.go @@ -152,7 +152,7 @@ func createFolderScenario(desc string, url string, routePattern string, mock *fa defer bus.ClearBusHandlers() sc := setupScenarioContext(url) - sc.defaultHandler = wrap(func(c *m.ReqContext) Response { + sc.defaultHandler = Wrap(func(c *m.ReqContext) Response { sc.context = c sc.context.SignedInUser = &m.SignedInUser{OrgId: TestOrgID, UserId: TestUserID} @@ -181,7 +181,7 @@ func updateFolderScenario(desc string, url string, routePattern string, mock *fa defer bus.ClearBusHandlers() sc := setupScenarioContext(url) - sc.defaultHandler = wrap(func(c *m.ReqContext) Response { + sc.defaultHandler = Wrap(func(c *m.ReqContext) Response { sc.context = c sc.context.SignedInUser = &m.SignedInUser{OrgId: TestOrgID, UserId: TestUserID} From 37efa934f16de14630c28c190162105a45040dda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 2 Jul 2018 17:25:44 +0200 Subject: [PATCH 14/25] ldap: improved ldap test env with more structured users and groups --- .gitignore | 1 + docker/blocks/openldap/entrypoint.sh | 10 +++++++++- docker/blocks/openldap/prepopulate/admin.ldif | 10 ---------- docker/blocks/openldap/prepopulate/adminsgroup.ldif | 5 ----- docker/blocks/openldap/prepopulate/editor.ldif | 10 ---------- docker/blocks/openldap/prepopulate/groups/admins.ldif | 5 +++++ .../blocks/openldap/prepopulate/groups/backend.ldif | 5 +++++ docker/blocks/openldap/prepopulate/groups/editor.ldif | 5 +++++ .../blocks/openldap/prepopulate/groups/frontend.ldif | 5 +++++ docker/blocks/openldap/prepopulate/units/groups.ldif | 3 +++ docker/blocks/openldap/prepopulate/units/users.ldif | 3 +++ .../blocks/openldap/prepopulate/users/ldap-admin.ldif | 11 +++++++++++ .../openldap/prepopulate/users/ldap-editor.ldif | 10 ++++++++++ .../openldap/prepopulate/users/ldap-frontend-1.ldif | 10 ++++++++++ .../{viewer.ldif => users/ldap-viewer.ldif} | 8 ++++---- docker/blocks/openldap/prepopulate/usersgroup.ldif | 5 ----- 16 files changed, 71 insertions(+), 35 deletions(-) delete mode 100644 docker/blocks/openldap/prepopulate/admin.ldif delete mode 100644 docker/blocks/openldap/prepopulate/adminsgroup.ldif delete mode 100644 docker/blocks/openldap/prepopulate/editor.ldif create mode 100644 docker/blocks/openldap/prepopulate/groups/admins.ldif create mode 100644 docker/blocks/openldap/prepopulate/groups/backend.ldif create mode 100644 docker/blocks/openldap/prepopulate/groups/editor.ldif create mode 100644 docker/blocks/openldap/prepopulate/groups/frontend.ldif create mode 100644 docker/blocks/openldap/prepopulate/units/groups.ldif create mode 100644 docker/blocks/openldap/prepopulate/units/users.ldif create mode 100644 docker/blocks/openldap/prepopulate/users/ldap-admin.ldif create mode 100644 docker/blocks/openldap/prepopulate/users/ldap-editor.ldif create mode 100644 docker/blocks/openldap/prepopulate/users/ldap-frontend-1.ldif rename docker/blocks/openldap/prepopulate/{viewer.ldif => users/ldap-viewer.ldif} (52%) delete mode 100644 docker/blocks/openldap/prepopulate/usersgroup.ldif diff --git a/.gitignore b/.gitignore index 25325b37890..accc24d84cd 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ fig.yml docker-compose.yml docker-compose.yaml /conf/provisioning/**/custom.yaml +/conf/ldap_dev.toml profile.cov /grafana /local diff --git a/docker/blocks/openldap/entrypoint.sh b/docker/blocks/openldap/entrypoint.sh index d560b78d388..2f383355907 100755 --- a/docker/blocks/openldap/entrypoint.sh +++ b/docker/blocks/openldap/entrypoint.sh @@ -80,7 +80,15 @@ EOF done fi - for file in `ls /etc/ldap/prepopulate/*.ldif`; do + for file in `ls /etc/ldap/prepopulate/units/*.ldif`; do + slapadd -F /etc/ldap/slapd.d -l "$file" + done + + for file in `ls /etc/ldap/prepopulate/groups/*.ldif`; do + slapadd -F /etc/ldap/slapd.d -l "$file" + done + + for file in `ls /etc/ldap/prepopulate/users/*.ldif`; do slapadd -F /etc/ldap/slapd.d -l "$file" done diff --git a/docker/blocks/openldap/prepopulate/admin.ldif b/docker/blocks/openldap/prepopulate/admin.ldif deleted file mode 100644 index 3f4406d5810..00000000000 --- a/docker/blocks/openldap/prepopulate/admin.ldif +++ /dev/null @@ -1,10 +0,0 @@ -dn: cn=ldapadmin,dc=grafana,dc=org -mail: ldapadmin@grafana.com -userPassword: grafana -objectClass: person -objectClass: top -objectClass: inetOrgPerson -objectClass: organizationalPerson -sn: ldapadmin -cn: ldapadmin -memberOf: cn=admins,dc=grafana,dc=org diff --git a/docker/blocks/openldap/prepopulate/adminsgroup.ldif b/docker/blocks/openldap/prepopulate/adminsgroup.ldif deleted file mode 100644 index d8dece4e458..00000000000 --- a/docker/blocks/openldap/prepopulate/adminsgroup.ldif +++ /dev/null @@ -1,5 +0,0 @@ -dn: cn=admins,dc=grafana,dc=org -cn: admins -member: cn=ldapadmin,dc=grafana,dc=org -objectClass: groupOfNames -objectClass: top diff --git a/docker/blocks/openldap/prepopulate/editor.ldif b/docker/blocks/openldap/prepopulate/editor.ldif deleted file mode 100644 index eba3adc4352..00000000000 --- a/docker/blocks/openldap/prepopulate/editor.ldif +++ /dev/null @@ -1,10 +0,0 @@ -dn: cn=ldapeditor,dc=grafana,dc=org -mail: ldapeditor@grafana.com -userPassword: grafana -objectClass: person -objectClass: top -objectClass: inetOrgPerson -objectClass: organizationalPerson -sn: ldapeditor -cn: ldapeditor -memberOf: cn=users,dc=grafana,dc=org diff --git a/docker/blocks/openldap/prepopulate/groups/admins.ldif b/docker/blocks/openldap/prepopulate/groups/admins.ldif new file mode 100644 index 00000000000..50d3a0ea4a8 --- /dev/null +++ b/docker/blocks/openldap/prepopulate/groups/admins.ldif @@ -0,0 +1,5 @@ +dn: cn=admins,ou=groups,dc=grafana,dc=org +cn: admins +objectClass: groupOfNames +objectClass: top +member: cn=ldap-admin,ou=users,dc=grafana,dc=org diff --git a/docker/blocks/openldap/prepopulate/groups/backend.ldif b/docker/blocks/openldap/prepopulate/groups/backend.ldif new file mode 100644 index 00000000000..09a661adea0 --- /dev/null +++ b/docker/blocks/openldap/prepopulate/groups/backend.ldif @@ -0,0 +1,5 @@ +dn: cn=backend,ou=groups,dc=grafana,dc=org +cn: backend +objectClass: groupOfNames +objectClass: top +member: cn=ldap-editor,dc=grafana,dc=org diff --git a/docker/blocks/openldap/prepopulate/groups/editor.ldif b/docker/blocks/openldap/prepopulate/groups/editor.ldif new file mode 100644 index 00000000000..331ecc94141 --- /dev/null +++ b/docker/blocks/openldap/prepopulate/groups/editor.ldif @@ -0,0 +1,5 @@ +dn: cn=editors,ou=groups,dc=grafana,dc=org +cn: editors +objectClass: groupOfNames +objectClass: top +member: cn=ldap-editor,ou=users,dc=grafana,dc=org diff --git a/docker/blocks/openldap/prepopulate/groups/frontend.ldif b/docker/blocks/openldap/prepopulate/groups/frontend.ldif new file mode 100644 index 00000000000..c410b96c7ad --- /dev/null +++ b/docker/blocks/openldap/prepopulate/groups/frontend.ldif @@ -0,0 +1,5 @@ +dn: cn=frontend,ou=groups,dc=grafana,dc=org +cn: frontend +objectClass: groupOfNames +objectClass: top +member: cn=ldap-frontend-1,ou=users,dc=grafana,dc=org diff --git a/docker/blocks/openldap/prepopulate/units/groups.ldif b/docker/blocks/openldap/prepopulate/units/groups.ldif new file mode 100644 index 00000000000..64e21ad744f --- /dev/null +++ b/docker/blocks/openldap/prepopulate/units/groups.ldif @@ -0,0 +1,3 @@ +dn: ou=groups,dc=grafana,dc=org +objectclass: top +objectclass: organizationalUnit diff --git a/docker/blocks/openldap/prepopulate/units/users.ldif b/docker/blocks/openldap/prepopulate/units/users.ldif new file mode 100644 index 00000000000..76fc50dd299 --- /dev/null +++ b/docker/blocks/openldap/prepopulate/units/users.ldif @@ -0,0 +1,3 @@ +dn: ou=users,dc=grafana,dc=org +objectclass: top +objectclass: organizationalUnit diff --git a/docker/blocks/openldap/prepopulate/users/ldap-admin.ldif b/docker/blocks/openldap/prepopulate/users/ldap-admin.ldif new file mode 100644 index 00000000000..1704a15c3db --- /dev/null +++ b/docker/blocks/openldap/prepopulate/users/ldap-admin.ldif @@ -0,0 +1,11 @@ +dn: cn=ldap-admin,ou=users,dc=grafana,dc=org +mail: ldap-admin@grafana.com +userPassword: grafana +objectClass: person +objectClass: top +objectClass: inetOrgPerson +objectClass: organizationalPerson +sn: ldap-admin +cn: ldap-admin +memberOf: cn=admins,ou=groups,dc=grafana,dc=org +memberOf: cn=editors,ou=groups,dc=grafana,dc=org diff --git a/docker/blocks/openldap/prepopulate/users/ldap-editor.ldif b/docker/blocks/openldap/prepopulate/users/ldap-editor.ldif new file mode 100644 index 00000000000..d0de99f8c16 --- /dev/null +++ b/docker/blocks/openldap/prepopulate/users/ldap-editor.ldif @@ -0,0 +1,10 @@ +dn: cn=ldap-editor,ou=users,dc=grafana,dc=org +mail: ldap-editor@grafana.com +userPassword: grafana +objectClass: person +objectClass: top +objectClass: inetOrgPerson +objectClass: organizationalPerson +sn: ldap-editor +cn: ldap-editor +memberOf: cn=editors,ou=groups,dc=grafana,dc=org diff --git a/docker/blocks/openldap/prepopulate/users/ldap-frontend-1.ldif b/docker/blocks/openldap/prepopulate/users/ldap-frontend-1.ldif new file mode 100644 index 00000000000..f5ebe0b41c4 --- /dev/null +++ b/docker/blocks/openldap/prepopulate/users/ldap-frontend-1.ldif @@ -0,0 +1,10 @@ +dn: cn=ldap-frontend-1,ou=users,dc=grafana,dc=org +mail: ldap-frontend-1@grafana.com +userPassword: grafana +objectClass: person +objectClass: top +objectClass: inetOrgPerson +objectClass: organizationalPerson +sn: ldap-frontend-1 +cn: ldap-frontend-1 +memberOf: cn=frontend,ou=groups,dc=grafana,dc=org diff --git a/docker/blocks/openldap/prepopulate/viewer.ldif b/docker/blocks/openldap/prepopulate/users/ldap-viewer.ldif similarity index 52% rename from docker/blocks/openldap/prepopulate/viewer.ldif rename to docker/blocks/openldap/prepopulate/users/ldap-viewer.ldif index f699a7df57b..07066355a13 100644 --- a/docker/blocks/openldap/prepopulate/viewer.ldif +++ b/docker/blocks/openldap/prepopulate/users/ldap-viewer.ldif @@ -1,9 +1,9 @@ -dn: cn=ldapviewer,dc=grafana,dc=org -mail: ldapviewer@grafana.com +dn: cn=ldap-viewer,ou=users,dc=grafana,dc=org +mail: ldap-viewer@grafana.com userPassword: grafana objectClass: person objectClass: top objectClass: inetOrgPerson objectClass: organizationalPerson -sn: ldapviewer -cn: ldapviewer +sn: ldap-viewer +cn: ldap-viewer diff --git a/docker/blocks/openldap/prepopulate/usersgroup.ldif b/docker/blocks/openldap/prepopulate/usersgroup.ldif deleted file mode 100644 index a1de3a50d38..00000000000 --- a/docker/blocks/openldap/prepopulate/usersgroup.ldif +++ /dev/null @@ -1,5 +0,0 @@ -dn: cn=users,dc=grafana,dc=org -cn: users -member: cn=ldapeditor,dc=grafana,dc=org -objectClass: groupOfNames -objectClass: top From a89351e8e0c8a594de31065a9410675d9bdd9c8d Mon Sep 17 00:00:00 2001 From: zicklam Date: Mon, 2 Jul 2018 18:01:42 +0200 Subject: [PATCH 15/25] correct example (#12481) foo.bar.com in description, but foo.bar in configuration. Updated description, makes more sense then changing the configuration :) --- docs/sources/installation/behind_proxy.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/behind_proxy.md b/docs/sources/installation/behind_proxy.md index 89711aecb46..6e3884456ac 100644 --- a/docs/sources/installation/behind_proxy.md +++ b/docs/sources/installation/behind_proxy.md @@ -26,7 +26,7 @@ Otherwise Grafana will not behave correctly. See example below. ## Examples Here are some example configurations for running Grafana behind a reverse proxy. -### Grafana configuration (ex http://foo.bar.com) +### Grafana configuration (ex http://foo.bar) ```bash [server] @@ -47,7 +47,7 @@ server { } ``` -### Examples with **sub path** (ex http://foo.bar.com/grafana) +### Examples with **sub path** (ex http://foo.bar/grafana) #### Grafana configuration with sub path ```bash From 0d1f7c8782f3cc7ad9cdd20c5257fdc087362f61 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 2 Jul 2018 20:04:36 +0200 Subject: [PATCH 16/25] Fix bar width issue in aligned prometheus queries (#12483) * Fix bar width issue in aligned prometheus queries This was broken because null values were filled in with unaligned times. * use aligned times for result transformation * add tests An earlier version of this fix aligned the times again in the transformer, but I think it's safe to only deal with aligned times in the response. * Fixed prometheus heatmap tranformer test The interval needs to be 1 to prevent step alignment. --- public/app/core/specs/time_series.jest.ts | 14 ++++ .../datasource/prometheus/datasource.ts | 4 +- .../prometheus/specs/datasource.jest.ts | 2 +- .../specs/result_transformer.jest.ts | 78 +++++++++++++++++++ 4 files changed, 95 insertions(+), 3 deletions(-) diff --git a/public/app/core/specs/time_series.jest.ts b/public/app/core/specs/time_series.jest.ts index f5245476218..bf50d807e03 100644 --- a/public/app/core/specs/time_series.jest.ts +++ b/public/app/core/specs/time_series.jest.ts @@ -119,6 +119,20 @@ describe('TimeSeries', function() { series.getFlotPairs('null'); expect(series.stats.avg).toBe(null); }); + + it('calculates timeStep', function() { + series = new TimeSeries({ + datapoints: [[null, 1], [null, 2], [null, 3]], + }); + series.getFlotPairs('null'); + expect(series.stats.timeStep).toBe(1); + + series = new TimeSeries({ + datapoints: [[0, 1530529290], [0, 1530529305], [0, 1530529320]], + }); + series.getFlotPairs('null'); + expect(series.stats.timeStep).toBe(15); + }); }); describe('When checking if ms resolution is needed', function() { diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 46431a08ab1..d7d33264c99 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -162,8 +162,8 @@ export class PrometheusDatasource { format: activeTargets[index].format, step: queries[index].step, legendFormat: activeTargets[index].legendFormat, - start: start, - end: end, + start: queries[index].start, + end: queries[index].end, query: queries[index].expr, responseListLength: responseList.length, responseIndex: index, diff --git a/public/app/plugins/datasource/prometheus/specs/datasource.jest.ts b/public/app/plugins/datasource/prometheus/specs/datasource.jest.ts index 0157322da58..219b990e5dd 100644 --- a/public/app/plugins/datasource/prometheus/specs/datasource.jest.ts +++ b/public/app/plugins/datasource/prometheus/specs/datasource.jest.ts @@ -68,7 +68,7 @@ describe('PrometheusDatasource', () => { ctx.query = { range: { from: moment(1443454528000), to: moment(1443454528000) }, targets: [{ expr: 'test{job="testjob"}', format: 'heatmap', legendFormat: '{{le}}' }], - interval: '60s', + interval: '1s', }; }); diff --git a/public/app/plugins/datasource/prometheus/specs/result_transformer.jest.ts b/public/app/plugins/datasource/prometheus/specs/result_transformer.jest.ts index 56a05d5aedb..b94cca79059 100644 --- a/public/app/plugins/datasource/prometheus/specs/result_transformer.jest.ts +++ b/public/app/plugins/datasource/prometheus/specs/result_transformer.jest.ts @@ -127,4 +127,82 @@ describe('Prometheus Result Transformer', () => { ]); }); }); + + describe('When resultFormat is time series', () => { + it('should transform matrix into timeseries', () => { + const response = { + status: 'success', + data: { + resultType: 'matrix', + result: [ + { + metric: { __name__: 'test', job: 'testjob' }, + values: [[0, '10'], [1, '10'], [2, '0']], + }, + ], + }, + }; + let result = []; + let options = { + format: 'timeseries', + start: 0, + end: 2, + }; + + ctx.resultTransformer.transform(result, { data: response }, options); + expect(result).toEqual([{ target: 'test{job="testjob"}', datapoints: [[10, 0], [10, 1000], [0, 2000]] }]); + }); + + it('should fill timeseries with null values', () => { + const response = { + status: 'success', + data: { + resultType: 'matrix', + result: [ + { + metric: { __name__: 'test', job: 'testjob' }, + values: [[1, '10'], [2, '0']], + }, + ], + }, + }; + let result = []; + let options = { + format: 'timeseries', + step: 1, + start: 0, + end: 2, + }; + + ctx.resultTransformer.transform(result, { data: response }, options); + expect(result).toEqual([{ target: 'test{job="testjob"}', datapoints: [[null, 0], [10, 1000], [0, 2000]] }]); + }); + + it('should align null values with step', () => { + const response = { + status: 'success', + data: { + resultType: 'matrix', + result: [ + { + metric: { __name__: 'test', job: 'testjob' }, + values: [[4, '10'], [8, '10']], + }, + ], + }, + }; + let result = []; + let options = { + format: 'timeseries', + step: 2, + start: 0, + end: 8, + }; + + ctx.resultTransformer.transform(result, { data: response }, options); + expect(result).toEqual([ + { target: 'test{job="testjob"}', datapoints: [[null, 0], [null, 2000], [10, 4000], [null, 6000], [10, 8000]] }, + ]); + }); + }); }); From 6046c8b4ca528bd4b2bb914f6d398914b6b29299 Mon Sep 17 00:00:00 2001 From: Martin Packman Date: Mon, 2 Jul 2018 20:14:41 +0200 Subject: [PATCH 17/25] Make table sorting stable when null values exist (#12362) Currently if a null appears in a table column, for instance in data returned by postgres, sorting on that gives an arbitrary order. This is due to null being neither greater or less than any string, which makes the sort unstable. Change the table sort function to compare on nullness first. Note this is a slight behaviour change for numbers, which would otherwise treat null and 0 as equivalent. Signed-off-by: Martin Packman --- public/app/core/specs/table_model.jest.ts | 35 +++++++++++++++++++++++ public/app/core/table_model.ts | 17 ++++------- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/public/app/core/specs/table_model.jest.ts b/public/app/core/specs/table_model.jest.ts index a2c1eb5e1af..3d4c526cfea 100644 --- a/public/app/core/specs/table_model.jest.ts +++ b/public/app/core/specs/table_model.jest.ts @@ -44,3 +44,38 @@ describe('when sorting table asc', () => { expect(table.rows[2][1]).toBe(15); }); }); + +describe('when sorting with nulls', () => { + var table; + var values; + + beforeEach(() => { + table = new TableModel(); + table.columns = [{}, {}]; + table.rows = [[42, ''], [19, 'a'], [null, 'b'], [0, 'd'], [null, null], [2, 'c'], [0, null], [-8, '']]; + }); + + it('numbers with nulls at end with asc sort', () => { + table.sort({ col: 0, desc: false }); + values = table.rows.map(row => row[0]); + expect(values).toEqual([-8, 0, 0, 2, 19, 42, null, null]); + }); + + it('numbers with nulls at start with desc sort', () => { + table.sort({ col: 0, desc: true }); + values = table.rows.map(row => row[0]); + expect(values).toEqual([null, null, 42, 19, 2, 0, 0, -8]); + }); + + it('strings with nulls at end with asc sort', () => { + table.sort({ col: 1, desc: false }); + values = table.rows.map(row => row[1]); + expect(values).toEqual(['', '', 'a', 'b', 'c', 'd', null, null]); + }); + + it('strings with nulls at start with desc sort', () => { + table.sort({ col: 1, desc: true }); + values = table.rows.map(row => row[1]); + expect(values).toEqual([null, null, 'd', 'c', 'b', 'a', '', '']); + }); +}); diff --git a/public/app/core/table_model.ts b/public/app/core/table_model.ts index 5716aac2be6..04857eb806d 100644 --- a/public/app/core/table_model.ts +++ b/public/app/core/table_model.ts @@ -19,23 +19,16 @@ export default class TableModel { this.rows.sort(function(a, b) { a = a[options.col]; b = b[options.col]; - if (a < b) { - return -1; - } - if (a > b) { - return 1; - } - return 0; + // Sort null or undefined seperately from comparable values + return +(a == null) - +(b == null) || +(a > b) || -(a < b); }); - this.columns[options.col].sort = true; - if (options.desc) { this.rows.reverse(); - this.columns[options.col].desc = true; - } else { - this.columns[options.col].desc = false; } + + this.columns[options.col].sort = true; + this.columns[options.col].desc = options.desc; } addColumn(col) { From 1586a42a719f0c0724843e54ed9c4a015d8d00dc Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Tue, 3 Jul 2018 09:25:15 +0200 Subject: [PATCH 18/25] ci: Only publish grafana enterprise packages tagged with enterprise. --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0217c129a62..9b2f436adee 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -174,7 +174,7 @@ jobs: - persist_to_workspace: root: . paths: - - enterprise-dist/grafana* + - enterprise-dist/grafana-enterprise* deploy-enterprise-master: docker: From 1f97df46c136939e31b6eb3a72661190817a2fce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 3 Jul 2018 12:57:48 +0200 Subject: [PATCH 19/25] devenv: open ldap docker block now prepopulating data with correct member groups --- docker/blocks/openldap/Dockerfile | 4 +- docker/blocks/openldap/entrypoint.sh | 17 ++-- docker/blocks/openldap/notes.md | 26 +++++- docker/blocks/openldap/prepopulate.sh | 14 ++++ .../blocks/openldap/prepopulate/1_units.ldif | 9 +++ .../blocks/openldap/prepopulate/2_users.ldif | 80 +++++++++++++++++++ .../blocks/openldap/prepopulate/3_groups.ldif | 25 ++++++ .../openldap/prepopulate/groups/admins.ldif | 5 -- .../openldap/prepopulate/groups/backend.ldif | 5 -- .../openldap/prepopulate/groups/editor.ldif | 5 -- .../openldap/prepopulate/groups/frontend.ldif | 5 -- .../openldap/prepopulate/units/groups.ldif | 3 - .../openldap/prepopulate/units/users.ldif | 3 - .../prepopulate/users/ldap-admin.ldif | 11 --- .../prepopulate/users/ldap-editor.ldif | 10 --- .../prepopulate/users/ldap-frontend-1.ldif | 10 --- .../prepopulate/users/ldap-viewer.ldif | 9 --- pkg/login/ext_user.go | 2 + 18 files changed, 163 insertions(+), 80 deletions(-) create mode 100755 docker/blocks/openldap/prepopulate.sh create mode 100644 docker/blocks/openldap/prepopulate/1_units.ldif create mode 100644 docker/blocks/openldap/prepopulate/2_users.ldif create mode 100644 docker/blocks/openldap/prepopulate/3_groups.ldif delete mode 100644 docker/blocks/openldap/prepopulate/groups/admins.ldif delete mode 100644 docker/blocks/openldap/prepopulate/groups/backend.ldif delete mode 100644 docker/blocks/openldap/prepopulate/groups/editor.ldif delete mode 100644 docker/blocks/openldap/prepopulate/groups/frontend.ldif delete mode 100644 docker/blocks/openldap/prepopulate/units/groups.ldif delete mode 100644 docker/blocks/openldap/prepopulate/units/users.ldif delete mode 100644 docker/blocks/openldap/prepopulate/users/ldap-admin.ldif delete mode 100644 docker/blocks/openldap/prepopulate/users/ldap-editor.ldif delete mode 100644 docker/blocks/openldap/prepopulate/users/ldap-frontend-1.ldif delete mode 100644 docker/blocks/openldap/prepopulate/users/ldap-viewer.ldif diff --git a/docker/blocks/openldap/Dockerfile b/docker/blocks/openldap/Dockerfile index c9b928ad56a..76172e133a4 100644 --- a/docker/blocks/openldap/Dockerfile +++ b/docker/blocks/openldap/Dockerfile @@ -8,7 +8,8 @@ ENV OPENLDAP_VERSION 2.4.40 RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ - slapd=${OPENLDAP_VERSION}* && \ + slapd=${OPENLDAP_VERSION}* \ + ldap-utils && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* @@ -22,6 +23,7 @@ COPY modules/ /etc/ldap.dist/modules COPY prepopulate/ /etc/ldap.dist/prepopulate COPY entrypoint.sh /entrypoint.sh +COPY prepopulate.sh /prepopulate.sh ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/blocks/openldap/entrypoint.sh b/docker/blocks/openldap/entrypoint.sh index 2f383355907..d202ed14b31 100755 --- a/docker/blocks/openldap/entrypoint.sh +++ b/docker/blocks/openldap/entrypoint.sh @@ -76,21 +76,14 @@ EOF IFS=","; declare -a modules=($SLAPD_ADDITIONAL_MODULES); unset IFS for module in "${modules[@]}"; do - slapadd -n0 -F /etc/ldap/slapd.d -l "/etc/ldap/modules/${module}.ldif" >/dev/null 2>&1 + echo "Adding module ${module}" + slapadd -n0 -F /etc/ldap/slapd.d -l "/etc/ldap/modules/${module}.ldif" >/dev/null 2>&1 done fi - for file in `ls /etc/ldap/prepopulate/units/*.ldif`; do - slapadd -F /etc/ldap/slapd.d -l "$file" - done - - for file in `ls /etc/ldap/prepopulate/groups/*.ldif`; do - slapadd -F /etc/ldap/slapd.d -l "$file" - done - - for file in `ls /etc/ldap/prepopulate/users/*.ldif`; do - slapadd -F /etc/ldap/slapd.d -l "$file" - done + # This needs to run in background + # Will prepopulate entries after ldap daemon has started + ./prepopulate.sh & chown -R openldap:openldap /etc/ldap/slapd.d/ /var/lib/ldap/ /var/run/slapd/ else diff --git a/docker/blocks/openldap/notes.md b/docker/blocks/openldap/notes.md index 483266f0d88..8de23d5ccf2 100644 --- a/docker/blocks/openldap/notes.md +++ b/docker/blocks/openldap/notes.md @@ -1,6 +1,6 @@ # Notes on OpenLdap Docker Block -Any ldif files added to the prepopulate subdirectory will be automatically imported into the OpenLdap database. +Any ldif files added to the prepopulate subdirectory will be automatically imported into the OpenLdap database. The ldif files add three users, `ldapviewer`, `ldapeditor` and `ldapadmin`. Two groups, `admins` and `users`, are added that correspond with the group mappings in the default conf/ldap.toml. `ldapadmin` is a member of `admins` and `ldapeditor` is a member of `users`. @@ -22,3 +22,27 @@ enabled = true config_file = conf/ldap.toml ; allow_sign_up = true ``` + +Test groups & users + +admins + ldap-admin + ldap-torkel + ldap-daniel +backend + ldap-carl + ldap-torkel + ldap-leo +frontend + ldap-torkel + ldap-tobias + ldap-daniel +editors + ldap-editors + + +no groups + ldap-viewer + + + diff --git a/docker/blocks/openldap/prepopulate.sh b/docker/blocks/openldap/prepopulate.sh new file mode 100755 index 00000000000..aa11f8aba4f --- /dev/null +++ b/docker/blocks/openldap/prepopulate.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +echo "Pre-populating ldap entries, first waiting for ldap to start" + +sleep 3 + +adminUserDn="cn=admin,dc=grafana,dc=org" +adminPassword="grafana" + +for file in `ls /etc/ldap/prepopulate/*.ldif`; do + ldapadd -x -D $adminUserDn -w $adminPassword -f "$file" +done + + diff --git a/docker/blocks/openldap/prepopulate/1_units.ldif b/docker/blocks/openldap/prepopulate/1_units.ldif new file mode 100644 index 00000000000..22e06303688 --- /dev/null +++ b/docker/blocks/openldap/prepopulate/1_units.ldif @@ -0,0 +1,9 @@ +dn: ou=groups,dc=grafana,dc=org +ou: Groups +objectclass: top +objectclass: organizationalUnit + +dn: ou=users,dc=grafana,dc=org +ou: Users +objectclass: top +objectclass: organizationalUnit diff --git a/docker/blocks/openldap/prepopulate/2_users.ldif b/docker/blocks/openldap/prepopulate/2_users.ldif new file mode 100644 index 00000000000..52e74b1e4b1 --- /dev/null +++ b/docker/blocks/openldap/prepopulate/2_users.ldif @@ -0,0 +1,80 @@ +# ldap-admin +dn: cn=ldap-admin,ou=users,dc=grafana,dc=org +mail: ldap-admin@grafana.com +userPassword: grafana +objectClass: person +objectClass: top +objectClass: inetOrgPerson +objectClass: organizationalPerson +sn: ldap-admin +cn: ldap-admin + +dn: cn=ldap-editor,ou=users,dc=grafana,dc=org +mail: ldap-editor@grafana.com +userPassword: grafana +objectClass: person +objectClass: top +objectClass: inetOrgPerson +objectClass: organizationalPerson +sn: ldap-editor +cn: ldap-editor + +dn: cn=ldap-viewer,ou=users,dc=grafana,dc=org +mail: ldap-viewer@grafana.com +userPassword: grafana +objectClass: person +objectClass: top +objectClass: inetOrgPerson +objectClass: organizationalPerson +sn: ldap-viewer +cn: ldap-viewer + +dn: cn=ldap-carl,ou=users,dc=grafana,dc=org +mail: ldap-carl@grafana.com +userPassword: grafana +objectClass: person +objectClass: top +objectClass: inetOrgPerson +objectClass: organizationalPerson +sn: ldap-carl +cn: ldap-carl + +dn: cn=ldap-daniel,ou=users,dc=grafana,dc=org +mail: ldap-daniel@grafana.com +userPassword: grafana +objectClass: person +objectClass: top +objectClass: inetOrgPerson +objectClass: organizationalPerson +sn: ldap-daniel +cn: ldap-daniel + +dn: cn=ldap-leo,ou=users,dc=grafana,dc=org +mail: ldap-leo@grafana.com +userPassword: grafana +objectClass: person +objectClass: top +objectClass: inetOrgPerson +objectClass: organizationalPerson +sn: ldap-leo +cn: ldap-leo + +dn: cn=ldap-tobias,ou=users,dc=grafana,dc=org +mail: ldap-tobias@grafana.com +userPassword: grafana +objectClass: person +objectClass: top +objectClass: inetOrgPerson +objectClass: organizationalPerson +sn: ldap-tobias +cn: ldap-tobias + +dn: cn=ldap-torkel,ou=users,dc=grafana,dc=org +mail: ldap-torkel@grafana.com +userPassword: grafana +objectClass: person +objectClass: top +objectClass: inetOrgPerson +objectClass: organizationalPerson +sn: ldap-torkel +cn: ldap-torkel diff --git a/docker/blocks/openldap/prepopulate/3_groups.ldif b/docker/blocks/openldap/prepopulate/3_groups.ldif new file mode 100644 index 00000000000..8638a089cc8 --- /dev/null +++ b/docker/blocks/openldap/prepopulate/3_groups.ldif @@ -0,0 +1,25 @@ +dn: cn=admins,ou=groups,dc=grafana,dc=org +cn: admins +objectClass: groupOfNames +objectClass: top +member: cn=ldap-admin,ou=users,dc=grafana,dc=org +member: cn=ldap-torkel,ou=users,dc=grafana,dc=org + +dn: cn=editors,ou=groups,dc=grafana,dc=org +cn: editors +objectClass: groupOfNames +member: cn=ldap-editor,ou=users,dc=grafana,dc=org + +dn: cn=backend,ou=groups,dc=grafana,dc=org +cn: backend +objectClass: groupOfNames +member: cn=ldap-carl,ou=users,dc=grafana,dc=org +member: cn=ldap-leo,ou=users,dc=grafana,dc=org +member: cn=ldap-torkel,ou=users,dc=grafana,dc=org + +dn: cn=frontend,ou=groups,dc=grafana,dc=org +cn: frontend +objectClass: groupOfNames +member: cn=ldap-torkel,ou=users,dc=grafana,dc=org +member: cn=ldap-daniel,ou=users,dc=grafana,dc=org +member: cn=ldap-leo,ou=users,dc=grafana,dc=org diff --git a/docker/blocks/openldap/prepopulate/groups/admins.ldif b/docker/blocks/openldap/prepopulate/groups/admins.ldif deleted file mode 100644 index 50d3a0ea4a8..00000000000 --- a/docker/blocks/openldap/prepopulate/groups/admins.ldif +++ /dev/null @@ -1,5 +0,0 @@ -dn: cn=admins,ou=groups,dc=grafana,dc=org -cn: admins -objectClass: groupOfNames -objectClass: top -member: cn=ldap-admin,ou=users,dc=grafana,dc=org diff --git a/docker/blocks/openldap/prepopulate/groups/backend.ldif b/docker/blocks/openldap/prepopulate/groups/backend.ldif deleted file mode 100644 index 09a661adea0..00000000000 --- a/docker/blocks/openldap/prepopulate/groups/backend.ldif +++ /dev/null @@ -1,5 +0,0 @@ -dn: cn=backend,ou=groups,dc=grafana,dc=org -cn: backend -objectClass: groupOfNames -objectClass: top -member: cn=ldap-editor,dc=grafana,dc=org diff --git a/docker/blocks/openldap/prepopulate/groups/editor.ldif b/docker/blocks/openldap/prepopulate/groups/editor.ldif deleted file mode 100644 index 331ecc94141..00000000000 --- a/docker/blocks/openldap/prepopulate/groups/editor.ldif +++ /dev/null @@ -1,5 +0,0 @@ -dn: cn=editors,ou=groups,dc=grafana,dc=org -cn: editors -objectClass: groupOfNames -objectClass: top -member: cn=ldap-editor,ou=users,dc=grafana,dc=org diff --git a/docker/blocks/openldap/prepopulate/groups/frontend.ldif b/docker/blocks/openldap/prepopulate/groups/frontend.ldif deleted file mode 100644 index c410b96c7ad..00000000000 --- a/docker/blocks/openldap/prepopulate/groups/frontend.ldif +++ /dev/null @@ -1,5 +0,0 @@ -dn: cn=frontend,ou=groups,dc=grafana,dc=org -cn: frontend -objectClass: groupOfNames -objectClass: top -member: cn=ldap-frontend-1,ou=users,dc=grafana,dc=org diff --git a/docker/blocks/openldap/prepopulate/units/groups.ldif b/docker/blocks/openldap/prepopulate/units/groups.ldif deleted file mode 100644 index 64e21ad744f..00000000000 --- a/docker/blocks/openldap/prepopulate/units/groups.ldif +++ /dev/null @@ -1,3 +0,0 @@ -dn: ou=groups,dc=grafana,dc=org -objectclass: top -objectclass: organizationalUnit diff --git a/docker/blocks/openldap/prepopulate/units/users.ldif b/docker/blocks/openldap/prepopulate/units/users.ldif deleted file mode 100644 index 76fc50dd299..00000000000 --- a/docker/blocks/openldap/prepopulate/units/users.ldif +++ /dev/null @@ -1,3 +0,0 @@ -dn: ou=users,dc=grafana,dc=org -objectclass: top -objectclass: organizationalUnit diff --git a/docker/blocks/openldap/prepopulate/users/ldap-admin.ldif b/docker/blocks/openldap/prepopulate/users/ldap-admin.ldif deleted file mode 100644 index 1704a15c3db..00000000000 --- a/docker/blocks/openldap/prepopulate/users/ldap-admin.ldif +++ /dev/null @@ -1,11 +0,0 @@ -dn: cn=ldap-admin,ou=users,dc=grafana,dc=org -mail: ldap-admin@grafana.com -userPassword: grafana -objectClass: person -objectClass: top -objectClass: inetOrgPerson -objectClass: organizationalPerson -sn: ldap-admin -cn: ldap-admin -memberOf: cn=admins,ou=groups,dc=grafana,dc=org -memberOf: cn=editors,ou=groups,dc=grafana,dc=org diff --git a/docker/blocks/openldap/prepopulate/users/ldap-editor.ldif b/docker/blocks/openldap/prepopulate/users/ldap-editor.ldif deleted file mode 100644 index d0de99f8c16..00000000000 --- a/docker/blocks/openldap/prepopulate/users/ldap-editor.ldif +++ /dev/null @@ -1,10 +0,0 @@ -dn: cn=ldap-editor,ou=users,dc=grafana,dc=org -mail: ldap-editor@grafana.com -userPassword: grafana -objectClass: person -objectClass: top -objectClass: inetOrgPerson -objectClass: organizationalPerson -sn: ldap-editor -cn: ldap-editor -memberOf: cn=editors,ou=groups,dc=grafana,dc=org diff --git a/docker/blocks/openldap/prepopulate/users/ldap-frontend-1.ldif b/docker/blocks/openldap/prepopulate/users/ldap-frontend-1.ldif deleted file mode 100644 index f5ebe0b41c4..00000000000 --- a/docker/blocks/openldap/prepopulate/users/ldap-frontend-1.ldif +++ /dev/null @@ -1,10 +0,0 @@ -dn: cn=ldap-frontend-1,ou=users,dc=grafana,dc=org -mail: ldap-frontend-1@grafana.com -userPassword: grafana -objectClass: person -objectClass: top -objectClass: inetOrgPerson -objectClass: organizationalPerson -sn: ldap-frontend-1 -cn: ldap-frontend-1 -memberOf: cn=frontend,ou=groups,dc=grafana,dc=org diff --git a/docker/blocks/openldap/prepopulate/users/ldap-viewer.ldif b/docker/blocks/openldap/prepopulate/users/ldap-viewer.ldif deleted file mode 100644 index 07066355a13..00000000000 --- a/docker/blocks/openldap/prepopulate/users/ldap-viewer.ldif +++ /dev/null @@ -1,9 +0,0 @@ -dn: cn=ldap-viewer,ou=users,dc=grafana,dc=org -mail: ldap-viewer@grafana.com -userPassword: grafana -objectClass: person -objectClass: top -objectClass: inetOrgPerson -objectClass: organizationalPerson -sn: ldap-viewer -cn: ldap-viewer diff --git a/pkg/login/ext_user.go b/pkg/login/ext_user.go index d2f1aa1ff52..d6eaf9a975e 100644 --- a/pkg/login/ext_user.go +++ b/pkg/login/ext_user.go @@ -21,6 +21,7 @@ func UpsertUser(cmd *m.UpsertUserCommand) error { Email: extUser.Email, Login: extUser.Login, } + err := bus.Dispatch(userQuery) if err != m.ErrUserNotFound && err != nil { return err @@ -90,6 +91,7 @@ func createUser(extUser *m.ExternalUserInfo) (*m.User, error) { Name: extUser.Name, SkipOrgSetup: len(extUser.OrgRoles) > 0, } + if err := bus.Dispatch(cmd); err != nil { return nil, err } From 27aa1cd568c8ffdb748fcfdd311fa836f93bc9f2 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 3 Jul 2018 19:41:34 +0200 Subject: [PATCH 20/25] changelog: add notes about closing #12362 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3ba9076774..3dd520f8d76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * **Api**: Delete nonexistent datasource should return 404 [#12313](https://github.com/grafana/grafana/issues/12313), thx [@AustinWinstanley](https://github.com/AustinWinstanley) * **Dashboard**: Fix selecting current dashboard from search should not reload dashboard [#12248](https://github.com/grafana/grafana/issues/12248) * **Singlestat**: Make colorization of prefix and postfix optional in singlestat [#11892](https://github.com/grafana/grafana/pull/11892), thx [@ApsOps](https://github.com/ApsOps) +* **Table**: Make table sorting stable when null values exist [#12362](https://github.com/grafana/grafana/pull/12362), thx [@bz2](https://github.com/bz2) # 5.2.1 (2018-06-29) From 60792d57ddb256b266a7c89f37b390ba4c6fceeb Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 3 Jul 2018 19:44:21 +0200 Subject: [PATCH 21/25] changelog: add notes about closing #12379 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dd520f8d76..d9150128289 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * **Dashboard**: Fix selecting current dashboard from search should not reload dashboard [#12248](https://github.com/grafana/grafana/issues/12248) * **Singlestat**: Make colorization of prefix and postfix optional in singlestat [#11892](https://github.com/grafana/grafana/pull/11892), thx [@ApsOps](https://github.com/ApsOps) * **Table**: Make table sorting stable when null values exist [#12362](https://github.com/grafana/grafana/pull/12362), thx [@bz2](https://github.com/bz2) +* **Prometheus**: Fix graph panel bar width issue in aligned prometheus queries [#12379](https://github.com/grafana/grafana/issues/12379) # 5.2.1 (2018-06-29) From 512e7f8567e0ce07df5882b26c3daf697bb458e0 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 3 Jul 2018 19:50:21 +0200 Subject: [PATCH 22/25] changelog: add notes about closing #8186 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9150128289..717d9fcaa5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * **Singlestat**: Make colorization of prefix and postfix optional in singlestat [#11892](https://github.com/grafana/grafana/pull/11892), thx [@ApsOps](https://github.com/ApsOps) * **Table**: Make table sorting stable when null values exist [#12362](https://github.com/grafana/grafana/pull/12362), thx [@bz2](https://github.com/bz2) * **Prometheus**: Fix graph panel bar width issue in aligned prometheus queries [#12379](https://github.com/grafana/grafana/issues/12379) +* **Variables**: Skip unneeded extra query request when de-selecting variable values used for repeated panels [#8186](https://github.com/grafana/grafana/issues/8186), thx [@mtanda](https://github.com/mtanda) # 5.2.1 (2018-06-29) From faf2ab4249ee8859c52fb83db48268c30e05bac9 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 3 Jul 2018 19:54:09 +0200 Subject: [PATCH 23/25] changelog: add notes about closing #12460 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 717d9fcaa5f..a7a4f6ca3e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * **Table**: Make table sorting stable when null values exist [#12362](https://github.com/grafana/grafana/pull/12362), thx [@bz2](https://github.com/bz2) * **Prometheus**: Fix graph panel bar width issue in aligned prometheus queries [#12379](https://github.com/grafana/grafana/issues/12379) * **Variables**: Skip unneeded extra query request when de-selecting variable values used for repeated panels [#8186](https://github.com/grafana/grafana/issues/8186), thx [@mtanda](https://github.com/mtanda) +* **Postgres/MySQL/MSSQL**: Use floor rounding in $__timeGroup macro function [#12460](https://github.com/grafana/grafana/issues/12460), thx [@svenklemm](https://github.com/svenklemm) # 5.2.1 (2018-06-29) From 634c77469d975fe4ef0536b2fc043de9d21342b5 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 3 Jul 2018 19:58:19 +0200 Subject: [PATCH 24/25] changelog: add notes about closing #11818 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7a4f6ca3e7..879dc23eaed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * **Prometheus**: Fix graph panel bar width issue in aligned prometheus queries [#12379](https://github.com/grafana/grafana/issues/12379) * **Variables**: Skip unneeded extra query request when de-selecting variable values used for repeated panels [#8186](https://github.com/grafana/grafana/issues/8186), thx [@mtanda](https://github.com/mtanda) * **Postgres/MySQL/MSSQL**: Use floor rounding in $__timeGroup macro function [#12460](https://github.com/grafana/grafana/issues/12460), thx [@svenklemm](https://github.com/svenklemm) +- **Github OAuth** Allow changes of user info at Github to be synched to Grafana when signing in [#11818](https://github.com/grafana/grafana/issues/11818), thx [@rwaweber](https://github.com/rwaweber) # 5.2.1 (2018-06-29) From 723a894fc5d077b2a11d8685d777e3812bdf77bb Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 3 Jul 2018 19:59:34 +0200 Subject: [PATCH 25/25] changelog: update [skip ci] --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 879dc23eaed..6ddc9497dbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ * **Prometheus**: Fix graph panel bar width issue in aligned prometheus queries [#12379](https://github.com/grafana/grafana/issues/12379) * **Variables**: Skip unneeded extra query request when de-selecting variable values used for repeated panels [#8186](https://github.com/grafana/grafana/issues/8186), thx [@mtanda](https://github.com/mtanda) * **Postgres/MySQL/MSSQL**: Use floor rounding in $__timeGroup macro function [#12460](https://github.com/grafana/grafana/issues/12460), thx [@svenklemm](https://github.com/svenklemm) -- **Github OAuth** Allow changes of user info at Github to be synched to Grafana when signing in [#11818](https://github.com/grafana/grafana/issues/11818), thx [@rwaweber](https://github.com/rwaweber) +* **Github OAuth**: Allow changes of user info at Github to be synched to Grafana when signing in [#11818](https://github.com/grafana/grafana/issues/11818), thx [@rwaweber](https://github.com/rwaweber) # 5.2.1 (2018-06-29)