From 6092fa4dc3cc4d00c6b48626f8ac1ec1ac1fe5c7 Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 29 Oct 2018 23:13:07 +0300 Subject: [PATCH 01/55] Fix bug with background color in table cell with link --- public/app/plugins/panel/table/renderer.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/public/app/plugins/panel/table/renderer.ts b/public/app/plugins/panel/table/renderer.ts index c25e37357cb..39a2f98c165 100644 --- a/public/app/plugins/panel/table/renderer.ts +++ b/public/app/plugins/panel/table/renderer.ts @@ -211,16 +211,17 @@ export class TableRenderer { value = this.formatColumnValue(columnIndex, value); const column = this.table.columns[columnIndex]; - let style = ''; + let cellStyle = ''; + let textStyle = ''; const cellClasses = []; let cellClass = ''; if (this.colorState.cell) { - style = ' style="background-color:' + this.colorState.cell + '"'; + cellStyle = ' style="background-color:' + this.colorState.cell + '"'; cellClasses.push('table-panel-color-cell'); this.colorState.cell = null; } else if (this.colorState.value) { - style = ' style="color:' + this.colorState.value + '"'; + textStyle = ' style="color:' + this.colorState.value + '"'; this.colorState.value = null; } // because of the fixed table headers css only solution @@ -232,7 +233,7 @@ export class TableRenderer { } if (value === undefined) { - style = ' style="display:none;"'; + cellStyle = ' style="display:none;"'; column.hidden = true; } else { column.hidden = false; @@ -258,7 +259,7 @@ export class TableRenderer { cellClasses.push('table-panel-cell-link'); columnHtml += ` - + ${value} `; @@ -283,7 +284,7 @@ export class TableRenderer { cellClass = ' class="' + cellClasses.join(' ') + '"'; } - columnHtml = '' + columnHtml + ''; + columnHtml = '' + columnHtml + ''; return columnHtml; } From 355e76a48ea9956ac0126527fb9960c849af489d Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 29 Oct 2018 23:26:29 +0300 Subject: [PATCH 02/55] Fix cell coloring --- public/app/plugins/panel/table/renderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/panel/table/renderer.ts b/public/app/plugins/panel/table/renderer.ts index 39a2f98c165..524aa06343b 100644 --- a/public/app/plugins/panel/table/renderer.ts +++ b/public/app/plugins/panel/table/renderer.ts @@ -284,7 +284,7 @@ export class TableRenderer { cellClass = ' class="' + cellClasses.join(' ') + '"'; } - columnHtml = '' + columnHtml + ''; + columnHtml = '' + columnHtml + ''; return columnHtml; } From cfb061ddaba0d8bf38199b74eb0f25eaa33b27ad Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 26 Oct 2018 10:40:33 +0200 Subject: [PATCH 03/55] refactor datasource caching --- pkg/api/dataproxy.go | 50 ++++--------------------------- pkg/api/http_server.go | 18 ++++++----- pkg/api/metrics.go | 5 +++- pkg/cmd/grafana-server/server.go | 9 +++++- pkg/middleware/headers.go | 14 +++++++++ pkg/middleware/middleware.go | 1 + pkg/models/context.go | 1 + pkg/services/cache/cache.go | 17 +++++++++++ pkg/services/datasources/cache.go | 49 ++++++++++++++++++++++++++++++ 9 files changed, 109 insertions(+), 55 deletions(-) create mode 100644 pkg/middleware/headers.go create mode 100644 pkg/services/cache/cache.go create mode 100644 pkg/services/datasources/cache.go diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index 3bb2f236129..1bc97eb42ed 100644 --- a/pkg/api/dataproxy.go +++ b/pkg/api/dataproxy.go @@ -1,62 +1,22 @@ package api import ( - "fmt" - "github.com/pkg/errors" - "time" - "github.com/grafana/grafana/pkg/api/pluginproxy" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/metrics" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" ) -const HeaderNameNoBackendCache = "X-Grafana-NoCache" - -func (hs *HTTPServer) getDatasourceFromCache(id int64, c *m.ReqContext) (*m.DataSource, error) { - userPermissionsQuery := m.GetDataSourcePermissionsForUserQuery{ - User: c.SignedInUser, - } - if err := bus.Dispatch(&userPermissionsQuery); err != nil { - if err != bus.ErrHandlerNotFound { - return nil, err - } - } else { - permissionType, exists := userPermissionsQuery.Result[id] - if exists && permissionType != m.DsPermissionQuery { - return nil, errors.New("User not allowed to access datasource") - } - } - - nocache := c.Req.Header.Get(HeaderNameNoBackendCache) == "true" - cacheKey := fmt.Sprintf("ds-%d", id) - - if !nocache { - if cached, found := hs.cache.Get(cacheKey); found { - ds := cached.(*m.DataSource) - if ds.OrgId == c.OrgId { - return ds, nil - } - } - } - - query := m.GetDataSourceByIdQuery{Id: id, OrgId: c.OrgId} - if err := bus.Dispatch(&query); err != nil { - return nil, err - } - - hs.cache.Set(cacheKey, query.Result, time.Second*5) - return query.Result, nil -} - func (hs *HTTPServer) ProxyDataSourceRequest(c *m.ReqContext) { c.TimeRequest(metrics.M_DataSource_ProxyReq_Timer) dsId := c.ParamsInt64(":id") - ds, err := hs.getDatasourceFromCache(dsId, c) - + ds, err := hs.DatasourceCache.GetDatasource(dsId, c.SignedInUser, c.SkipCache) if err != nil { + if err == m.ErrDataSourceAccessDenied { + c.JsonApiErr(403, "Access denied to datasource", nil) + return + } c.JsonApiErr(500, "Unable to load datasource meta data", err) return } diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 858b3c5a8c5..ce28e4716ee 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -16,7 +16,6 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" - gocache "github.com/patrickmn/go-cache" macaron "gopkg.in/macaron.v1" "github.com/grafana/grafana/pkg/api/live" @@ -28,6 +27,8 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/services/cache" + "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/hooks" "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/setting" @@ -46,19 +47,19 @@ type HTTPServer struct { macaron *macaron.Macaron context context.Context streamManager *live.StreamManager - cache *gocache.Cache httpSrv *http.Server - RouteRegister routing.RouteRegister `inject:""` - Bus bus.Bus `inject:""` - RenderService rendering.Service `inject:""` - Cfg *setting.Cfg `inject:""` - HooksService *hooks.HooksService `inject:""` + RouteRegister routing.RouteRegister `inject:""` + Bus bus.Bus `inject:""` + RenderService rendering.Service `inject:""` + Cfg *setting.Cfg `inject:""` + HooksService *hooks.HooksService `inject:""` + CacheService *cache.CacheService `inject:""` + DatasourceCache datasources.CacheService `inject:""` } 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() @@ -231,6 +232,7 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() { m.Use(middleware.ValidateHostHeader(setting.Domain)) } + m.Use(middleware.HandleNoCacheHeader()) m.Use(middleware.AddDefaultResponseHeaders()) } diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index cb80bd346b8..a6cfe4d09de 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -25,8 +25,11 @@ func (hs *HTTPServer) QueryMetrics(c *m.ReqContext, reqDto dtos.MetricRequest) R return Error(400, "Query missing datasourceId", nil) } - ds, err := hs.getDatasourceFromCache(datasourceId, c) + ds, err := hs.DatasourceCache.GetDatasource(datasourceId, c.SignedInUser, c.SkipCache) if err != nil { + if err == m.ErrDataSourceAccessDenied { + return Error(403, "Access denied to datasource", nil) + } return Error(500, "Unable to load datasource meta data", err) } diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index 765b8ddf993..a07cb692a68 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -15,9 +15,15 @@ import ( "github.com/grafana/grafana/pkg/api" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/bus" - _ "github.com/grafana/grafana/pkg/extensions" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/login" + "github.com/grafana/grafana/pkg/services/cache" + "github.com/grafana/grafana/pkg/setting" + + "github.com/grafana/grafana/pkg/social" + + // self registering services + _ "github.com/grafana/grafana/pkg/extensions" _ "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/middleware" _ "github.com/grafana/grafana/pkg/plugins" @@ -72,6 +78,7 @@ func (g *GrafanaServerImpl) Run() error { serviceGraph.Provide(&inject.Object{Value: bus.GetBus()}) serviceGraph.Provide(&inject.Object{Value: g.cfg}) serviceGraph.Provide(&inject.Object{Value: routing.NewRouteRegister(middleware.RequestMetrics, middleware.RequestTracing)}) + serviceGraph.Provide(&inject.Object{Value: cache.New(5*time.Minute, 10*time.Minute)}) // self registered services services := registry.GetServices() diff --git a/pkg/middleware/headers.go b/pkg/middleware/headers.go new file mode 100644 index 00000000000..28c623d74b0 --- /dev/null +++ b/pkg/middleware/headers.go @@ -0,0 +1,14 @@ +package middleware + +import ( + m "github.com/grafana/grafana/pkg/models" + macaron "gopkg.in/macaron.v1" +) + +const HeaderNameNoBackendCache = "X-Grafana-NoCache" + +func HandleNoCacheHeader() macaron.Handler { + return func(ctx *m.ReqContext) { + ctx.SkipCache = ctx.Req.Header.Get(HeaderNameNoBackendCache) == "true" + } +} diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index 7b29901c1a3..ace72d998eb 100644 --- a/pkg/middleware/middleware.go +++ b/pkg/middleware/middleware.go @@ -29,6 +29,7 @@ func GetContextHandler() macaron.Handler { Session: session.GetSession(), IsSignedIn: false, AllowAnonymous: false, + SkipCache: false, Logger: log.New("context"), } diff --git a/pkg/models/context.go b/pkg/models/context.go index 262f6550954..8ed7fa61a7d 100644 --- a/pkg/models/context.go +++ b/pkg/models/context.go @@ -20,6 +20,7 @@ type ReqContext struct { IsSignedIn bool IsRenderCall bool AllowAnonymous bool + SkipCache bool Logger log.Logger } diff --git a/pkg/services/cache/cache.go b/pkg/services/cache/cache.go new file mode 100644 index 00000000000..93b2cf76e26 --- /dev/null +++ b/pkg/services/cache/cache.go @@ -0,0 +1,17 @@ +package cache + +import ( + "time" + + gocache "github.com/patrickmn/go-cache" +) + +type CacheService struct { + *gocache.Cache +} + +func New(defaultExpiration, cleanupInterval time.Duration) *CacheService { + return &CacheService{ + Cache: gocache.New(defaultExpiration, cleanupInterval), + } +} diff --git a/pkg/services/datasources/cache.go b/pkg/services/datasources/cache.go new file mode 100644 index 00000000000..c984b4f8743 --- /dev/null +++ b/pkg/services/datasources/cache.go @@ -0,0 +1,49 @@ +package datasources + +import ( + "fmt" + "time" + + "github.com/grafana/grafana/pkg/bus" + m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/services/cache" +) + +type CacheService interface { + GetDatasource(datasourceID int64, user *m.SignedInUser, skipCache bool) (*m.DataSource, error) +} + +type CacheServiceImpl struct { + Bus bus.Bus `inject:""` + CacheService *cache.CacheService `inject:""` +} + +func init() { + registry.RegisterService(&CacheServiceImpl{}) +} + +func (dc *CacheServiceImpl) Init() error { + return nil +} + +func (dc *CacheServiceImpl) GetDatasource(datasourceID int64, user *m.SignedInUser, skipCache bool) (*m.DataSource, error) { + cacheKey := fmt.Sprintf("ds-%d", datasourceID) + + if !skipCache { + if cached, found := dc.CacheService.Get(cacheKey); found { + ds := cached.(*m.DataSource) + if ds.OrgId == user.OrgId { + return ds, nil + } + } + } + + query := m.GetDataSourceByIdQuery{Id: datasourceID, OrgId: user.OrgId} + if err := dc.Bus.Dispatch(&query); err != nil { + return nil, err + } + + dc.CacheService.Set(cacheKey, query.Result, time.Second*5) + return query.Result, nil +} From 9edaa3fa8c45f312bab0bb809b0b586318a647b8 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 26 Oct 2018 13:57:31 +0200 Subject: [PATCH 04/55] application lifecycle event support --- pkg/cmd/grafana-server/server.go | 18 ++++++++-------- pkg/lifecycle/lifecycle.go | 22 ++++++++++++++++++++ pkg/lifecycle/lifecycle_test.go | 35 ++++++++++++++++++++++++++++++++ pkg/login/auth.go | 10 ++++++--- pkg/social/social.go | 9 +++++++- 5 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 pkg/lifecycle/lifecycle.go create mode 100644 pkg/lifecycle/lifecycle_test.go diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index a07cb692a68..d8d021d502b 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -15,13 +15,17 @@ import ( "github.com/grafana/grafana/pkg/api" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/lifecycle" + "github.com/grafana/grafana/pkg/middleware" + "github.com/grafana/grafana/pkg/registry" + + "golang.org/x/sync/errgroup" + + "github.com/grafana/grafana/pkg/api" "github.com/grafana/grafana/pkg/log" - "github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/services/cache" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/social" - // self registering services _ "github.com/grafana/grafana/pkg/extensions" _ "github.com/grafana/grafana/pkg/metrics" @@ -35,8 +39,7 @@ import ( _ "github.com/grafana/grafana/pkg/services/rendering" _ "github.com/grafana/grafana/pkg/services/search" _ "github.com/grafana/grafana/pkg/services/sqlstore" - "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/social" // self registering services + "github.com/grafana/grafana/pkg/setting" // self registering services _ "github.com/grafana/grafana/pkg/tracing" "golang.org/x/sync/errgroup" ) @@ -71,8 +74,7 @@ func (g *GrafanaServerImpl) Run() error { g.loadConfiguration() g.writePIDFile() - login.Init() - social.NewOAuthService() + lifecycle.Notify(lifecycle.ApplicationStarting) serviceGraph := inject.Graph{} serviceGraph.Provide(&inject.Object{Value: bus.GetBus()}) @@ -145,7 +147,7 @@ func (g *GrafanaServerImpl) Run() error { } sendSystemdNotification("READY=1") - + lifecycle.Notify(lifecycle.ApplicationStarted) return g.childRoutines.Wait() } diff --git a/pkg/lifecycle/lifecycle.go b/pkg/lifecycle/lifecycle.go new file mode 100644 index 00000000000..eea733a7da8 --- /dev/null +++ b/pkg/lifecycle/lifecycle.go @@ -0,0 +1,22 @@ +package lifecycle + +type Event int + +const ( + ApplicationStarting Event = iota + ApplicationStarted +) + +type EventHandlerFunc func() + +var listeners = map[int][]EventHandlerFunc{} + +func AddListener(evt Event, fn EventHandlerFunc) { + listeners[int(evt)] = append(listeners[int(evt)], fn) +} + +func Notify(evt Event) { + for _, handler := range listeners[int(evt)] { + handler() + } +} diff --git a/pkg/lifecycle/lifecycle_test.go b/pkg/lifecycle/lifecycle_test.go new file mode 100644 index 00000000000..e946cc2f4a8 --- /dev/null +++ b/pkg/lifecycle/lifecycle_test.go @@ -0,0 +1,35 @@ +package lifecycle + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestLifecycle(t *testing.T) { + Convey("TestLifecycle", t, func() { + Convey("Given listeners", func() { + applicationStartingCounter := 0 + AddListener(ApplicationStarting, func() { + applicationStartingCounter++ + }) + + applicationStartedCounter := 0 + AddListener(ApplicationStarted, func() { + applicationStartedCounter++ + }) + + Convey("When notify application starting should call listener", func() { + Notify(ApplicationStarting) + So(applicationStartingCounter, ShouldEqual, 1) + So(applicationStartedCounter, ShouldEqual, 0) + }) + + Convey("When notify application started should call listener", func() { + Notify(ApplicationStarted) + So(applicationStartingCounter, ShouldEqual, 0) + So(applicationStartedCounter, ShouldEqual, 1) + }) + }) + }) +} diff --git a/pkg/login/auth.go b/pkg/login/auth.go index 991fa72fd54..b195b8dd2e4 100644 --- a/pkg/login/auth.go +++ b/pkg/login/auth.go @@ -2,7 +2,9 @@ package login import ( "errors" + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/lifecycle" m "github.com/grafana/grafana/pkg/models" ) @@ -18,9 +20,11 @@ var ( ErrGettingUserQuota = errors.New("Error getting user quota") ) -func Init() { - bus.AddHandler("auth", AuthenticateUser) - loadLdapConfig() +func init() { + lifecycle.AddListener(lifecycle.ApplicationStarting, func() { + bus.AddHandler("auth", AuthenticateUser) + loadLdapConfig() + }) } func AuthenticateUser(query *m.LoginUserQuery) error { diff --git a/pkg/social/social.go b/pkg/social/social.go index 8918507f3b9..054420fd994 100644 --- a/pkg/social/social.go +++ b/pkg/social/social.go @@ -8,11 +8,18 @@ import ( "golang.org/x/oauth2" + "github.com/grafana/grafana/pkg/lifecycle" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) +func init() { + lifecycle.AddListener(lifecycle.ApplicationStarting, func() { + initOAuthService() + }) +} + type BasicUserInfo struct { Id string Name string @@ -56,7 +63,7 @@ var ( allOauthes = []string{"github", "gitlab", "google", "generic_oauth", "grafananet", grafanaCom} ) -func NewOAuthService() { +func initOAuthService() { setting.OAuthService = &setting.OAuther{} setting.OAuthService.OAuthInfos = make(map[string]*setting.OAuthInfo) From 70ddf936887869d3991f3926f1864a83a967d12c Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 25 Oct 2018 15:20:01 +0200 Subject: [PATCH 05/55] include teams on signed in user --- pkg/models/user.go | 1 + pkg/services/sqlstore/user.go | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/pkg/models/user.go b/pkg/models/user.go index d5b912e0a9c..e3c7b556d35 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -165,6 +165,7 @@ type SignedInUser struct { IsAnonymous bool HelpFlags1 HelpFlags1 LastSeenAt time.Time + Teams []int64 } func (u *SignedInUser) ShouldUpdateLastSeenAt() bool { diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index 72d5654a777..3744a04d88a 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -387,6 +387,17 @@ func GetSignedInUser(query *m.GetSignedInUserQuery) error { if user.OrgRole == "" { user.OrgId = -1 user.OrgName = "Org missing" + } else { + getTeamsByUserQuery := &m.GetTeamsByUserQuery{OrgId: user.OrgId, UserId: user.UserId} + err = GetTeamsByUser(getTeamsByUserQuery) + if err != nil { + return err + } + + user.Teams = make([]int64, len(getTeamsByUserQuery.Result)) + for i, t := range getTeamsByUserQuery.Result { + user.Teams[i] = t.Id + } } query.Result = &user From 52d825f5351ed6271bde1e2aee27ceb4888a6689 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 26 Oct 2018 18:34:10 +0200 Subject: [PATCH 06/55] log error on datasource access denied --- pkg/api/dataproxy.go | 2 +- pkg/api/metrics.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index 1bc97eb42ed..5cde0efd0b4 100644 --- a/pkg/api/dataproxy.go +++ b/pkg/api/dataproxy.go @@ -14,7 +14,7 @@ func (hs *HTTPServer) ProxyDataSourceRequest(c *m.ReqContext) { ds, err := hs.DatasourceCache.GetDatasource(dsId, c.SignedInUser, c.SkipCache) if err != nil { if err == m.ErrDataSourceAccessDenied { - c.JsonApiErr(403, "Access denied to datasource", nil) + c.JsonApiErr(403, "Access denied to datasource", err) return } c.JsonApiErr(500, "Unable to load datasource meta data", err) diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index a6cfe4d09de..6e5ae0f8761 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -28,7 +28,7 @@ func (hs *HTTPServer) QueryMetrics(c *m.ReqContext, reqDto dtos.MetricRequest) R ds, err := hs.DatasourceCache.GetDatasource(datasourceId, c.SignedInUser, c.SkipCache) if err != nil { if err == m.ErrDataSourceAccessDenied { - return Error(403, "Access denied to datasource", nil) + return Error(403, "Access denied to datasource", err) } return Error(500, "Unable to load datasource meta data", err) } From 2332b3e20579de39c7bbc227f69c11d184d6722b Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 26 Oct 2018 18:55:16 +0200 Subject: [PATCH 07/55] remove unused code --- pkg/cmd/grafana-server/server.go | 5 ----- pkg/models/datasource.go | 5 ----- 2 files changed, 10 deletions(-) diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index d8d021d502b..d4223df19ed 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -21,7 +21,6 @@ import ( "golang.org/x/sync/errgroup" - "github.com/grafana/grafana/pkg/api" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/services/cache" "github.com/grafana/grafana/pkg/setting" @@ -29,9 +28,7 @@ import ( // self registering services _ "github.com/grafana/grafana/pkg/extensions" _ "github.com/grafana/grafana/pkg/metrics" - "github.com/grafana/grafana/pkg/middleware" _ "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/registry" _ "github.com/grafana/grafana/pkg/services/alerting" _ "github.com/grafana/grafana/pkg/services/cleanup" _ "github.com/grafana/grafana/pkg/services/notifications" @@ -39,9 +36,7 @@ import ( _ "github.com/grafana/grafana/pkg/services/rendering" _ "github.com/grafana/grafana/pkg/services/search" _ "github.com/grafana/grafana/pkg/services/sqlstore" - "github.com/grafana/grafana/pkg/setting" // self registering services _ "github.com/grafana/grafana/pkg/tracing" - "golang.org/x/sync/errgroup" ) func NewGrafanaServer() *GrafanaServerImpl { diff --git a/pkg/models/datasource.go b/pkg/models/datasource.go index b71d17ec0d1..89439420d7a 100644 --- a/pkg/models/datasource.go +++ b/pkg/models/datasource.go @@ -207,11 +207,6 @@ func (p DsPermissionType) String() string { return names[int(p)] } -type GetDataSourcePermissionsForUserQuery struct { - User *SignedInUser - Result map[int64]DsPermissionType -} - type DatasourcesPermissionFilterQuery struct { User *SignedInUser Datasources []*DataSource From 5d4dc18bbc7586de466feb0a935a12f575907e73 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 30 Oct 2018 12:31:28 +0100 Subject: [PATCH 08/55] revert application lifecycle event support --- pkg/cmd/grafana-server/server.go | 7 ++++--- pkg/lifecycle/lifecycle.go | 22 -------------------- pkg/lifecycle/lifecycle_test.go | 35 -------------------------------- pkg/login/auth.go | 9 +++----- pkg/social/social.go | 9 +------- 5 files changed, 8 insertions(+), 74 deletions(-) delete mode 100644 pkg/lifecycle/lifecycle.go delete mode 100644 pkg/lifecycle/lifecycle_test.go diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index d4223df19ed..2c67a06a843 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -15,9 +15,10 @@ import ( "github.com/grafana/grafana/pkg/api" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/bus" - "github.com/grafana/grafana/pkg/lifecycle" + "github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/social" "golang.org/x/sync/errgroup" @@ -69,7 +70,8 @@ func (g *GrafanaServerImpl) Run() error { g.loadConfiguration() g.writePIDFile() - lifecycle.Notify(lifecycle.ApplicationStarting) + login.Init() + social.NewOAuthService() serviceGraph := inject.Graph{} serviceGraph.Provide(&inject.Object{Value: bus.GetBus()}) @@ -142,7 +144,6 @@ func (g *GrafanaServerImpl) Run() error { } sendSystemdNotification("READY=1") - lifecycle.Notify(lifecycle.ApplicationStarted) return g.childRoutines.Wait() } diff --git a/pkg/lifecycle/lifecycle.go b/pkg/lifecycle/lifecycle.go deleted file mode 100644 index eea733a7da8..00000000000 --- a/pkg/lifecycle/lifecycle.go +++ /dev/null @@ -1,22 +0,0 @@ -package lifecycle - -type Event int - -const ( - ApplicationStarting Event = iota - ApplicationStarted -) - -type EventHandlerFunc func() - -var listeners = map[int][]EventHandlerFunc{} - -func AddListener(evt Event, fn EventHandlerFunc) { - listeners[int(evt)] = append(listeners[int(evt)], fn) -} - -func Notify(evt Event) { - for _, handler := range listeners[int(evt)] { - handler() - } -} diff --git a/pkg/lifecycle/lifecycle_test.go b/pkg/lifecycle/lifecycle_test.go deleted file mode 100644 index e946cc2f4a8..00000000000 --- a/pkg/lifecycle/lifecycle_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package lifecycle - -import ( - "testing" - - . "github.com/smartystreets/goconvey/convey" -) - -func TestLifecycle(t *testing.T) { - Convey("TestLifecycle", t, func() { - Convey("Given listeners", func() { - applicationStartingCounter := 0 - AddListener(ApplicationStarting, func() { - applicationStartingCounter++ - }) - - applicationStartedCounter := 0 - AddListener(ApplicationStarted, func() { - applicationStartedCounter++ - }) - - Convey("When notify application starting should call listener", func() { - Notify(ApplicationStarting) - So(applicationStartingCounter, ShouldEqual, 1) - So(applicationStartedCounter, ShouldEqual, 0) - }) - - Convey("When notify application started should call listener", func() { - Notify(ApplicationStarted) - So(applicationStartingCounter, ShouldEqual, 0) - So(applicationStartedCounter, ShouldEqual, 1) - }) - }) - }) -} diff --git a/pkg/login/auth.go b/pkg/login/auth.go index b195b8dd2e4..b766d963328 100644 --- a/pkg/login/auth.go +++ b/pkg/login/auth.go @@ -4,7 +4,6 @@ import ( "errors" "github.com/grafana/grafana/pkg/bus" - "github.com/grafana/grafana/pkg/lifecycle" m "github.com/grafana/grafana/pkg/models" ) @@ -20,11 +19,9 @@ var ( ErrGettingUserQuota = errors.New("Error getting user quota") ) -func init() { - lifecycle.AddListener(lifecycle.ApplicationStarting, func() { - bus.AddHandler("auth", AuthenticateUser) - loadLdapConfig() - }) +func Init() { + bus.AddHandler("auth", AuthenticateUser) + loadLdapConfig() } func AuthenticateUser(query *m.LoginUserQuery) error { diff --git a/pkg/social/social.go b/pkg/social/social.go index 054420fd994..8918507f3b9 100644 --- a/pkg/social/social.go +++ b/pkg/social/social.go @@ -8,18 +8,11 @@ import ( "golang.org/x/oauth2" - "github.com/grafana/grafana/pkg/lifecycle" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) -func init() { - lifecycle.AddListener(lifecycle.ApplicationStarting, func() { - initOAuthService() - }) -} - type BasicUserInfo struct { Id string Name string @@ -63,7 +56,7 @@ var ( allOauthes = []string{"github", "gitlab", "google", "generic_oauth", "grafananet", grafanaCom} ) -func initOAuthService() { +func NewOAuthService() { setting.OAuthService = &setting.OAuther{} setting.OAuthService.OAuthInfos = make(map[string]*setting.OAuthInfo) From 6f9c0241afce94323f65e050a01d53357c4a1419 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 30 Oct 2018 12:32:14 +0100 Subject: [PATCH 09/55] register datasource cache service with proper name --- pkg/services/datasources/cache.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/services/datasources/cache.go b/pkg/services/datasources/cache.go index c984b4f8743..0cd2bae63b5 100644 --- a/pkg/services/datasources/cache.go +++ b/pkg/services/datasources/cache.go @@ -20,7 +20,11 @@ type CacheServiceImpl struct { } func init() { - registry.RegisterService(&CacheServiceImpl{}) + registry.Register(®istry.Descriptor{ + Name: "DatasourceCacheService", + Instance: &CacheServiceImpl{}, + InitPriority: registry.Low, + }) } func (dc *CacheServiceImpl) Init() error { From d0c00388e6193cd438257935dba25b88eb56216a Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 30 Oct 2018 13:37:30 +0100 Subject: [PATCH 10/55] add functionality to override service in registry --- pkg/registry/registry.go | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index 87fca27f6c1..487a6db7927 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -29,11 +29,42 @@ func Register(descriptor *Descriptor) { } func GetServices() []*Descriptor { - sort.Slice(services, func(i, j int) bool { - return services[i].InitPriority > services[j].InitPriority + slice := getServicesWithOverrides() + + sort.Slice(slice, func(i, j int) bool { + return slice[i].InitPriority > slice[j].InitPriority }) - return services + return slice +} + +type OverrideServiceFunc func(descriptor Descriptor) (*Descriptor, bool) + +var overrides []OverrideServiceFunc + +func RegisterOverride(fn OverrideServiceFunc) { + overrides = append(overrides, fn) +} + +func getServicesWithOverrides() []*Descriptor { + slice := []*Descriptor{} + for _, s := range services { + var descriptor *Descriptor + for _, fn := range overrides { + if newDescriptor, override := fn(*s); override { + descriptor = newDescriptor + break + } + } + + if descriptor != nil { + slice = append(slice, descriptor) + } else { + slice = append(slice, s) + } + } + + return slice } // Service interface is the lowest common shape that services From 93453c2d94856cec8861e1b627ba0c47c65061d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 31 Oct 2018 06:47:14 -0700 Subject: [PATCH 11/55] added caching of signed in user DB calls --- pkg/services/sqlstore/sqlstore.go | 10 +++++++--- pkg/services/sqlstore/user.go | 15 +++++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index f904b44c3c8..9f2d83d3284 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -16,6 +16,7 @@ import ( m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/annotations" + "github.com/grafana/grafana/pkg/services/cache" "github.com/grafana/grafana/pkg/services/sqlstore/migrations" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/sqlstore/sqlutil" @@ -47,8 +48,9 @@ func init() { } type SqlStore struct { - Cfg *setting.Cfg `inject:""` - Bus bus.Bus `inject:""` + Cfg *setting.Cfg `inject:""` + Bus bus.Bus `inject:""` + CacheService *cache.CacheService `inject:""` dbCfg DatabaseConfig engine *xorm.Engine @@ -148,9 +150,11 @@ func (ss *SqlStore) Init() error { // Init repo instances annotations.SetRepository(&SqlAnnotationRepo{}) - ss.Bus.SetTransactionManager(ss) + // Register handlers + ss.addUserQueryAndCommandHandlers() + // ensure admin user if ss.skipEnsureAdmin { return nil diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index 3744a04d88a..8e5d61579b9 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -15,8 +15,9 @@ import ( "github.com/grafana/grafana/pkg/util" ) -func init() { - //bus.AddHandler("sql", CreateUser) +func (ss *SqlStore) addUserQueryAndCommandHandlers() { + ss.Bus.AddHandler(ss.GetSignedInUser) + bus.AddHandler("sql", GetUserById) bus.AddHandler("sql", UpdateUser) bus.AddHandler("sql", ChangeUserPassword) @@ -25,7 +26,6 @@ func init() { bus.AddHandler("sql", SetUsingOrg) bus.AddHandler("sql", UpdateUserLastSeenAt) bus.AddHandler("sql", GetUserProfile) - bus.AddHandler("sql", GetSignedInUser) bus.AddHandler("sql", SearchUsers) bus.AddHandler("sql", GetUserOrgList) bus.AddHandler("sql", DeleteUser) @@ -345,12 +345,18 @@ func GetUserOrgList(query *m.GetUserOrgListQuery) error { return err } -func GetSignedInUser(query *m.GetSignedInUserQuery) error { +func (ss *SqlStore) GetSignedInUser(query *m.GetSignedInUserQuery) error { orgId := "u.org_id" if query.OrgId > 0 { orgId = strconv.FormatInt(query.OrgId, 10) } + cacheKey := fmt.Sprintf("signed-in-user-%d-%s", query.UserId, query.OrgId) + if cached, found := ss.CacheService.Get(cacheKey); found { + query.Result = cached.(*m.SignedInUser) + return nil + } + var rawSql = `SELECT u.id as user_id, u.is_admin as is_grafana_admin, @@ -401,6 +407,7 @@ func GetSignedInUser(query *m.GetSignedInUserQuery) error { } query.Result = &user + ss.CacheService.Set(cacheKey, &user, time.Second*5) return err } From edd575b552374eeb81e9007aedf0ce1123e20420 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Thu, 1 Nov 2018 09:36:09 +0100 Subject: [PATCH 12/55] Explore: fix metric selector for additional rows - race condition in language provider leads to only one row getting selector options - fixed by always returning the start task promise --- .../logging/components/LoggingQueryField.tsx | 8 ++++---- .../plugins/datasource/logging/language_provider.ts | 8 +++----- .../prometheus/components/PromQueryField.tsx | 6 +++--- .../plugins/datasource/prometheus/language_provider.ts | 10 ++++------ public/app/types/explore.ts | 3 ++- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/public/app/plugins/datasource/logging/components/LoggingQueryField.tsx b/public/app/plugins/datasource/logging/components/LoggingQueryField.tsx index 82ebc444c62..ce79d38f9a8 100644 --- a/public/app/plugins/datasource/logging/components/LoggingQueryField.tsx +++ b/public/app/plugins/datasource/logging/components/LoggingQueryField.tsx @@ -95,9 +95,9 @@ class LoggingQueryField extends React.PureComponent { - remaining.map(task => task.then(this.onReceiveMetrics).catch(() => {})); + remaining.map(task => task.then(this.onUpdateLanguage).catch(() => {})); }) - .then(() => this.onReceiveMetrics()); + .then(() => this.onUpdateLanguage()); } } @@ -119,7 +119,7 @@ class LoggingQueryField extends React.PureComponent {}); }; @@ -147,7 +147,7 @@ class LoggingQueryField extends React.PureComponent { + onUpdateLanguage = () => { Prism.languages[PRISM_SYNTAX] = this.languageProvider.getSyntax(); const { logLabelOptions } = this.languageProvider; this.setState({ diff --git a/public/app/plugins/datasource/logging/language_provider.ts b/public/app/plugins/datasource/logging/language_provider.ts index 32f4f8a0cd7..0896168ca56 100644 --- a/public/app/plugins/datasource/logging/language_provider.ts +++ b/public/app/plugins/datasource/logging/language_provider.ts @@ -47,7 +47,6 @@ export default class LoggingLanguageProvider extends LanguageProvider { this.datasource = datasource; this.labelKeys = {}; this.labelValues = {}; - this.started = false; Object.assign(this, initialValues); } @@ -63,11 +62,10 @@ export default class LoggingLanguageProvider extends LanguageProvider { }; start = () => { - if (!this.started) { - this.started = true; - return this.fetchLogLabels(); + if (!this.startTask) { + this.startTask = this.fetchLogLabels(); } - return Promise.resolve([]); + return this.startTask; }; // Keep this DOM-free for testing diff --git a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx index 649b17ad8cf..a7787096d85 100644 --- a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx @@ -134,9 +134,9 @@ class PromQueryField extends React.PureComponent { - remaining.map(task => task.then(this.onReceiveMetrics).catch(() => {})); + remaining.map(task => task.then(this.onUpdateLanguage).catch(() => {})); }) - .then(() => this.onReceiveMetrics()); + .then(() => this.onUpdateLanguage()); } } @@ -176,7 +176,7 @@ class PromQueryField extends React.PureComponent { + onUpdateLanguage = () => { const { histogramMetrics, metrics } = this.languageProvider; if (!metrics) { return; diff --git a/public/app/plugins/datasource/prometheus/language_provider.ts b/public/app/plugins/datasource/prometheus/language_provider.ts index 5f97ebfa7b5..23c25885041 100644 --- a/public/app/plugins/datasource/prometheus/language_provider.ts +++ b/public/app/plugins/datasource/prometheus/language_provider.ts @@ -46,7 +46,7 @@ export default class PromQlLanguageProvider extends LanguageProvider { labelKeys?: { [index: string]: string[] }; // metric -> [labelKey,...] labelValues?: { [index: string]: { [index: string]: string[] } }; // metric -> labelKey -> [labelValue,...] metrics?: string[]; - started: boolean; + startTask: Promise; constructor(datasource: any, initialValues?: any) { super(); @@ -56,7 +56,6 @@ export default class PromQlLanguageProvider extends LanguageProvider { this.labelKeys = {}; this.labelValues = {}; this.metrics = []; - this.started = false; Object.assign(this, initialValues); } @@ -72,11 +71,10 @@ export default class PromQlLanguageProvider extends LanguageProvider { }; start = () => { - if (!this.started) { - this.started = true; - return this.fetchMetricNames().then(() => [this.fetchHistogramMetrics()]); + if (!this.startTask) { + this.startTask = this.fetchMetricNames().then(() => [this.fetchHistogramMetrics()]); } - return Promise.resolve([]); + return this.startTask; }; // Keep this DOM-free for testing diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index a96d6c084fb..5a9db7e9b53 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -86,10 +86,11 @@ export abstract class LanguageProvider { datasource: any; request: (url) => Promise; /** - * Returns a promise that resolves with a task list when main syntax is loaded. + * Returns startTask that resolves with a task list when main syntax is loaded. * Task list consists of secondary promises that load more detailed language features. */ start: () => Promise; + startTask?: Promise; } export interface TypeaheadInput { From 7e13aa2cfba9de964016473ed67ff132152f037d Mon Sep 17 00:00:00 2001 From: Matthew Miner Date: Thu, 1 Nov 2018 09:57:19 -0700 Subject: [PATCH 13/55] Fix minor JSON typo in HTTP API docs --- docs/sources/http_api/alerting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/http_api/alerting.md b/docs/sources/http_api/alerting.md index 103de190793..2d70a6d2017 100644 --- a/docs/sources/http_api/alerting.md +++ b/docs/sources/http_api/alerting.md @@ -290,7 +290,7 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk "sendReminder": true, "frequency": "15m", "settings": { - "addresses: "carl@grafana.com;dev@grafana.com" + "addresses": "carl@grafana.com;dev@grafana.com" } } ``` From 4d4eb354b72860bea3d376f5c95a839aad19f37c Mon Sep 17 00:00:00 2001 From: Tarek Becker Date: Thu, 1 Nov 2018 21:43:07 +0100 Subject: [PATCH 14/55] Add [hash] to filename of grafana.{light,dark}.css --- public/views/index.template.html | 2 +- scripts/webpack/webpack.dev.js | 2 +- scripts/webpack/webpack.prod.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/views/index.template.html b/public/views/index.template.html index 1f6e511784d..096684d3e37 100644 --- a/public/views/index.template.html +++ b/public/views/index.template.html @@ -15,7 +15,7 @@ - + diff --git a/scripts/webpack/webpack.dev.js b/scripts/webpack/webpack.dev.js index 7eecceeb1bf..5cef1b6afd4 100644 --- a/scripts/webpack/webpack.dev.js +++ b/scripts/webpack/webpack.dev.js @@ -80,7 +80,7 @@ module.exports = merge(common, { plugins: [ new CleanWebpackPlugin('../../public/build', { allowExternal: true }), new MiniCssExtractPlugin({ - filename: "grafana.[name].css" + filename: "grafana.[name].[hash].css" }), new HtmlWebpackPlugin({ filename: path.resolve(__dirname, '../../public/views/index.html'), diff --git a/scripts/webpack/webpack.prod.js b/scripts/webpack/webpack.prod.js index 9e1e4cfb0b5..761d22892ea 100644 --- a/scripts/webpack/webpack.prod.js +++ b/scripts/webpack/webpack.prod.js @@ -71,7 +71,7 @@ module.exports = merge(common, { plugins: [ new MiniCssExtractPlugin({ - filename: "grafana.[name].css" + filename: "grafana.[name].[hash].css" }), new ngAnnotatePlugin(), new HtmlWebpackPlugin({ From 70bb81c6eb6db2648d59385df9f5902a72489423 Mon Sep 17 00:00:00 2001 From: Tarek Becker Date: Thu, 1 Nov 2018 23:31:17 +0100 Subject: [PATCH 15/55] Load hash based styles in error.html, too --- .gitignore | 1 + public/views/{error.html => error.template.html} | 2 +- scripts/webpack/webpack.common.js | 2 +- scripts/webpack/webpack.dev.js | 5 +++++ scripts/webpack/webpack.prod.js | 5 +++++ 5 files changed, 13 insertions(+), 2 deletions(-) rename public/views/{error.html => error.template.html} (98%) diff --git a/.gitignore b/.gitignore index 21083741e14..05ae4907e89 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ awsconfig /dist /public/build /public/views/index.html +/public/views/error.html /emails/dist /public_gen /public/vendor/npm diff --git a/public/views/error.html b/public/views/error.template.html similarity index 98% rename from public/views/error.html rename to public/views/error.template.html index 5c51e28eff4..af430fc6c3d 100644 --- a/public/views/error.html +++ b/public/views/error.template.html @@ -10,7 +10,7 @@ - + diff --git a/scripts/webpack/webpack.common.js b/scripts/webpack/webpack.common.js index dc4a7f363a2..7da6c3559bf 100644 --- a/scripts/webpack/webpack.common.js +++ b/scripts/webpack/webpack.common.js @@ -47,7 +47,7 @@ module.exports = { }, { test: /\.html$/, - exclude: /index\.template.html/, + exclude: /(index|error)\.template\.html/, use: [ { loader: 'ngtemplate-loader?relativeTo=' + (path.resolve(__dirname, '../../public')) + '&prefix=public' }, { diff --git a/scripts/webpack/webpack.dev.js b/scripts/webpack/webpack.dev.js index 5cef1b6afd4..456cc277f2f 100644 --- a/scripts/webpack/webpack.dev.js +++ b/scripts/webpack/webpack.dev.js @@ -82,6 +82,11 @@ module.exports = merge(common, { new MiniCssExtractPlugin({ filename: "grafana.[name].[hash].css" }), + new HtmlWebpackPlugin({ + filename: path.resolve(__dirname, '../../public/views/error.html'), + template: path.resolve(__dirname, '../../public/views/error.template.html'), + inject: 'false', + }), new HtmlWebpackPlugin({ filename: path.resolve(__dirname, '../../public/views/index.html'), template: path.resolve(__dirname, '../../public/views/index.template.html'), diff --git a/scripts/webpack/webpack.prod.js b/scripts/webpack/webpack.prod.js index 761d22892ea..c4b4b27245e 100644 --- a/scripts/webpack/webpack.prod.js +++ b/scripts/webpack/webpack.prod.js @@ -80,6 +80,11 @@ module.exports = merge(common, { inject: 'body', chunks: ['vendor', 'app'], }), + new HtmlWebpackPlugin({ + filename: path.resolve(__dirname, '../../public/views/error.html'), + template: path.resolve(__dirname, '../../public/views/error.template.html'), + inject: false, + }), function () { this.hooks.done.tap('Done', function (stats) { if (stats.compilation.errors && stats.compilation.errors.length) { From bc37e3caa283d9e079a0ccb0a86c8995cc5061d9 Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 2 Nov 2018 10:43:05 +0100 Subject: [PATCH 16/55] alerting: increase default duration for queries we should promote using longer queries since this should increase the quality of the alerts. only using a 5min range means that we will only have 4 datapoints in data is written every min which is not good enough for the generic alert rule --- public/app/features/alerting/AlertTabCtrl.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/features/alerting/AlertTabCtrl.ts b/public/app/features/alerting/AlertTabCtrl.ts index c91ff5cd6c3..146b7026353 100644 --- a/public/app/features/alerting/AlertTabCtrl.ts +++ b/public/app/features/alerting/AlertTabCtrl.ts @@ -166,7 +166,7 @@ export class AlertTabCtrl { alert.noDataState = alert.noDataState || config.alertingNoDataOrNullValues; alert.executionErrorState = alert.executionErrorState || config.alertingErrorOrTimeout; - alert.frequency = alert.frequency || '60s'; + alert.frequency = alert.frequency || '1m'; alert.handler = alert.handler || 1; alert.notifications = alert.notifications || []; @@ -217,7 +217,7 @@ export class AlertTabCtrl { buildDefaultCondition() { return { type: 'query', - query: { params: ['A', '5m', 'now'] }, + query: { params: ['A', '15m', 'now'] }, reducer: { type: 'avg', params: [] }, evaluator: { type: 'gt', params: [null] }, operator: { type: 'and' }, From b415d826116c567ac89313055d792873a7ff39b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 2 Nov 2018 10:49:46 +0100 Subject: [PATCH 17/55] fixed to template PR issues, #13938 --- pkg/middleware/middleware_test.go | 1 + pkg/middleware/recovery.go | 2 +- pkg/middleware/recovery_test.go | 4 ++ pkg/models/context.go | 2 +- pkg/setting/setting.go | 4 ++ .../dashboard/specs/panel_model.test.ts | 72 +++++++++++++++++++ ...rror.template.html => error-template.html} | 0 ...ndex.template.html => index-template.html} | 0 scripts/webpack/webpack.common.js | 2 +- scripts/webpack/webpack.dev.js | 4 +- scripts/webpack/webpack.hot.js | 2 +- scripts/webpack/webpack.prod.js | 4 +- 12 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 public/app/features/dashboard/specs/panel_model.test.ts rename public/views/{error.template.html => error-template.html} (100%) rename public/views/{index.template.html => index-template.html} (100%) diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go index 1830b3eb161..e9a3c8059f8 100644 --- a/pkg/middleware/middleware_test.go +++ b/pkg/middleware/middleware_test.go @@ -18,6 +18,7 @@ import ( ) func TestMiddlewareContext(t *testing.T) { + setting.ERR_TEMPLATE_NAME = "error-template" Convey("Given the grafana middleware", t, func() { middlewareScenario("middleware should add context to injector", func(sc *scenarioContext) { diff --git a/pkg/middleware/recovery.go b/pkg/middleware/recovery.go index 456bc91354e..eef07c8c24a 100644 --- a/pkg/middleware/recovery.go +++ b/pkg/middleware/recovery.go @@ -138,7 +138,7 @@ func Recovery() macaron.Handler { c.JSON(500, resp) } else { - c.HTML(500, "error") + c.HTML(500, setting.ERR_TEMPLATE_NAME) } } }() diff --git a/pkg/middleware/recovery_test.go b/pkg/middleware/recovery_test.go index 4bbedbc3b21..c92150f3b7d 100644 --- a/pkg/middleware/recovery_test.go +++ b/pkg/middleware/recovery_test.go @@ -8,11 +8,14 @@ import ( "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/session" + "github.com/grafana/grafana/pkg/setting" . "github.com/smartystreets/goconvey/convey" "gopkg.in/macaron.v1" ) func TestRecoveryMiddleware(t *testing.T) { + setting.ERR_TEMPLATE_NAME = "error-template" + Convey("Given an api route that panics", t, func() { apiURL := "/api/whatever" recoveryScenario("recovery middleware should return json", apiURL, func(sc *scenarioContext) { @@ -50,6 +53,7 @@ func recoveryScenario(desc string, url string, fn scenarioFunc) { sc := &scenarioContext{ url: url, } + viewsPath, _ := filepath.Abs("../../public/views") sc.m = macaron.New() diff --git a/pkg/models/context.go b/pkg/models/context.go index 262f6550954..c78028665a6 100644 --- a/pkg/models/context.go +++ b/pkg/models/context.go @@ -36,7 +36,7 @@ func (ctx *ReqContext) Handle(status int, title string, err error) { ctx.Data["AppSubUrl"] = setting.AppSubUrl ctx.Data["Theme"] = "dark" - ctx.HTML(status, "error") + ctx.HTML(status, setting.ERR_TEMPLATE_NAME) } func (ctx *ReqContext) JsonOK(message string) { diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 9e8f6fec9a8..afae642f5b3 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -38,6 +38,10 @@ const ( APP_NAME_ENTERPRISE = "Grafana Enterprise" ) +var ( + ERR_TEMPLATE_NAME = "error" +) + var ( // App settings. Env = DEV diff --git a/public/app/features/dashboard/specs/panel_model.test.ts b/public/app/features/dashboard/specs/panel_model.test.ts new file mode 100644 index 00000000000..b1e31ce3f91 --- /dev/null +++ b/public/app/features/dashboard/specs/panel_model.test.ts @@ -0,0 +1,72 @@ +import _ from 'lodash'; +import { PanelModel } from '../panel_model'; + +describe('PanelModel', () => { + describe('when creating new panel model', () => { + let model; + + beforeEach(() => { + model = new PanelModel({}); + }); + + it('should apply defaults', () => { + expect(model.gridPos.h).toBe(3); + }); + + it('getSaveModel should remove defaults', () => { + const saveModel = model.getSaveModel(); + expect(saveModel.gridPos).toBe(undefined); + }); + + it('getSaveModel should remove nonPersistedProperties', () => { + const saveModel = model.getSaveModel(); + expect(saveModel.events).toBe(undefined); + }); + + describe('when calling applyDefaults', () => { + beforeEach(() => { + const defaults = { + myName: 'My name', + myBool1: true, + myBool2: false, + myNumber: 0, + nestedObj: { + myName: 'nested name', + myBool1: true, + myBool2: false, + myNumber: 0, + }, + }; + model.applyDefaults(defaults); + }); + + it('Should apply defaults', () => { + expect(model.myName).toBe('My name'); + expect(model.myBool1).toBe(true); + expect(model.myBool2).toBe(false); + expect(model.myNumber).toBe(0); + expect(model.nestedObj.myName).toBe('nested name'); + expect(model.nestedObj.myBool1).toBe(true); + expect(model.nestedObj.myBool2).toBe(false); + expect(model.nestedObj.myNumber).toBe(0); + }); + + it('getSaveModel should remove them', () => { + const saveModel = model.getSaveModel(); + expect(saveModel.myName).toBe(undefined); + expect(saveModel.nestedObj).toBe(undefined); + }); + + it('getSaveModel should remove only unchanged defaults', () => { + model.myName = 'changed'; + model.nestedObj.myBool2 = true; + + const saveModel = model.getSaveModel(); + + expect(saveModel.myName).toBe('changed'); + expect(saveModel.nestedObj.myBool2).toBe(true); + expect(saveModel.nestedObj.myBool1).toBe(undefined); + }); + }); + }); +}); diff --git a/public/views/error.template.html b/public/views/error-template.html similarity index 100% rename from public/views/error.template.html rename to public/views/error-template.html diff --git a/public/views/index.template.html b/public/views/index-template.html similarity index 100% rename from public/views/index.template.html rename to public/views/index-template.html diff --git a/scripts/webpack/webpack.common.js b/scripts/webpack/webpack.common.js index 7da6c3559bf..ae7222e8374 100644 --- a/scripts/webpack/webpack.common.js +++ b/scripts/webpack/webpack.common.js @@ -47,7 +47,7 @@ module.exports = { }, { test: /\.html$/, - exclude: /(index|error)\.template\.html/, + exclude: /(index|error)\-template\.html/, use: [ { loader: 'ngtemplate-loader?relativeTo=' + (path.resolve(__dirname, '../../public')) + '&prefix=public' }, { diff --git a/scripts/webpack/webpack.dev.js b/scripts/webpack/webpack.dev.js index 456cc277f2f..228df79b3f8 100644 --- a/scripts/webpack/webpack.dev.js +++ b/scripts/webpack/webpack.dev.js @@ -84,12 +84,12 @@ module.exports = merge(common, { }), new HtmlWebpackPlugin({ filename: path.resolve(__dirname, '../../public/views/error.html'), - template: path.resolve(__dirname, '../../public/views/error.template.html'), + template: path.resolve(__dirname, '../../public/views/error-template.html'), inject: 'false', }), new HtmlWebpackPlugin({ filename: path.resolve(__dirname, '../../public/views/index.html'), - template: path.resolve(__dirname, '../../public/views/index.template.html'), + template: path.resolve(__dirname, '../../public/views/index-template.html'), inject: 'body', chunks: ['manifest', 'vendor', 'app'], }), diff --git a/scripts/webpack/webpack.hot.js b/scripts/webpack/webpack.hot.js index 0305a6f465c..dd3cc8c1190 100644 --- a/scripts/webpack/webpack.hot.js +++ b/scripts/webpack/webpack.hot.js @@ -87,7 +87,7 @@ module.exports = merge(common, { new CleanWebpackPlugin('../public/build', { allowExternal: true }), new HtmlWebpackPlugin({ filename: path.resolve(__dirname, '../../public/views/index.html'), - template: path.resolve(__dirname, '../../public/views/index.template.html'), + template: path.resolve(__dirname, '../../public/views/index-template.html'), inject: 'body', alwaysWriteToDisk: true }), diff --git a/scripts/webpack/webpack.prod.js b/scripts/webpack/webpack.prod.js index c4b4b27245e..5d3ffa61219 100644 --- a/scripts/webpack/webpack.prod.js +++ b/scripts/webpack/webpack.prod.js @@ -76,13 +76,13 @@ module.exports = merge(common, { new ngAnnotatePlugin(), new HtmlWebpackPlugin({ filename: path.resolve(__dirname, '../../public/views/index.html'), - template: path.resolve(__dirname, '../../public/views/index.template.html'), + template: path.resolve(__dirname, '../../public/views/index-template.html'), inject: 'body', chunks: ['vendor', 'app'], }), new HtmlWebpackPlugin({ filename: path.resolve(__dirname, '../../public/views/error.html'), - template: path.resolve(__dirname, '../../public/views/error.template.html'), + template: path.resolve(__dirname, '../../public/views/error-template.html'), inject: false, }), function () { From 61ff9fe603c1ad815f3a7b7cb12c5a1beeb64c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 2 Nov 2018 11:13:56 +0100 Subject: [PATCH 18/55] removed file I added accidentally --- .../dashboard/specs/panel_model.test.ts | 72 ------------------- 1 file changed, 72 deletions(-) delete mode 100644 public/app/features/dashboard/specs/panel_model.test.ts diff --git a/public/app/features/dashboard/specs/panel_model.test.ts b/public/app/features/dashboard/specs/panel_model.test.ts deleted file mode 100644 index b1e31ce3f91..00000000000 --- a/public/app/features/dashboard/specs/panel_model.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import _ from 'lodash'; -import { PanelModel } from '../panel_model'; - -describe('PanelModel', () => { - describe('when creating new panel model', () => { - let model; - - beforeEach(() => { - model = new PanelModel({}); - }); - - it('should apply defaults', () => { - expect(model.gridPos.h).toBe(3); - }); - - it('getSaveModel should remove defaults', () => { - const saveModel = model.getSaveModel(); - expect(saveModel.gridPos).toBe(undefined); - }); - - it('getSaveModel should remove nonPersistedProperties', () => { - const saveModel = model.getSaveModel(); - expect(saveModel.events).toBe(undefined); - }); - - describe('when calling applyDefaults', () => { - beforeEach(() => { - const defaults = { - myName: 'My name', - myBool1: true, - myBool2: false, - myNumber: 0, - nestedObj: { - myName: 'nested name', - myBool1: true, - myBool2: false, - myNumber: 0, - }, - }; - model.applyDefaults(defaults); - }); - - it('Should apply defaults', () => { - expect(model.myName).toBe('My name'); - expect(model.myBool1).toBe(true); - expect(model.myBool2).toBe(false); - expect(model.myNumber).toBe(0); - expect(model.nestedObj.myName).toBe('nested name'); - expect(model.nestedObj.myBool1).toBe(true); - expect(model.nestedObj.myBool2).toBe(false); - expect(model.nestedObj.myNumber).toBe(0); - }); - - it('getSaveModel should remove them', () => { - const saveModel = model.getSaveModel(); - expect(saveModel.myName).toBe(undefined); - expect(saveModel.nestedObj).toBe(undefined); - }); - - it('getSaveModel should remove only unchanged defaults', () => { - model.myName = 'changed'; - model.nestedObj.myBool2 = true; - - const saveModel = model.getSaveModel(); - - expect(saveModel.myName).toBe('changed'); - expect(saveModel.nestedObj.myBool2).toBe(true); - expect(saveModel.nestedObj.myBool1).toBe(undefined); - }); - }); - }); -}); From 6ef941ea17d75dfff6b22c5910f0c4a8489db62d Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 2 Nov 2018 16:03:12 +0100 Subject: [PATCH 19/55] fix failing tests --- pkg/services/sqlstore/sqlstore.go | 1 + pkg/services/sqlstore/user.go | 23 ++++++++++++++--------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 9f2d83d3284..95b53be9d4a 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -326,6 +326,7 @@ func InitTestDB(t *testing.T) *SqlStore { sqlstore := &SqlStore{} sqlstore.skipEnsureAdmin = true sqlstore.Bus = bus.New() + sqlstore.CacheService = cache.New(5*time.Minute, 10*time.Minute) dbType := migrator.SQLITE diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index 8e5d61579b9..5619cd2859b 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -16,7 +16,7 @@ import ( ) func (ss *SqlStore) addUserQueryAndCommandHandlers() { - ss.Bus.AddHandler(ss.GetSignedInUser) + ss.Bus.AddHandler(ss.GetSignedInUserWithCache) bus.AddHandler("sql", GetUserById) bus.AddHandler("sql", UpdateUser) @@ -345,18 +345,24 @@ func GetUserOrgList(query *m.GetUserOrgListQuery) error { return err } -func (ss *SqlStore) GetSignedInUser(query *m.GetSignedInUserQuery) error { - orgId := "u.org_id" - if query.OrgId > 0 { - orgId = strconv.FormatInt(query.OrgId, 10) - } - - cacheKey := fmt.Sprintf("signed-in-user-%d-%s", query.UserId, query.OrgId) +func (ss *SqlStore) GetSignedInUserWithCache(query *m.GetSignedInUserQuery) error { + cacheKey := fmt.Sprintf("signed-in-user-%d-%d", query.UserId, query.OrgId) if cached, found := ss.CacheService.Get(cacheKey); found { query.Result = cached.(*m.SignedInUser) return nil } + err := GetSignedInUser(query) + ss.CacheService.Set(cacheKey, query.Result, time.Second*5) + return err +} + +func GetSignedInUser(query *m.GetSignedInUserQuery) error { + orgId := "u.org_id" + if query.OrgId > 0 { + orgId = strconv.FormatInt(query.OrgId, 10) + } + var rawSql = `SELECT u.id as user_id, u.is_admin as is_grafana_admin, @@ -407,7 +413,6 @@ func (ss *SqlStore) GetSignedInUser(query *m.GetSignedInUserQuery) error { } query.Result = &user - ss.CacheService.Set(cacheKey, &user, time.Second*5) return err } From 5e748243af0d1a3e472fe95e02d8c6f22010debb Mon Sep 17 00:00:00 2001 From: Michael Huynh Date: Sat, 3 Nov 2018 07:56:13 +0800 Subject: [PATCH 20/55] Handle suggestions for alternate syntax aggregation contexts In aggregation contexts using the alternate syntax form, labels will precede metrics. A cursor at the label position cannot provide meaningful suggestions unless a metric is specified. In the latter case, no suggestions are presented at all. Related: #13690 --- .../prometheus/language_provider.ts | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/language_provider.ts b/public/app/plugins/datasource/prometheus/language_provider.ts index 5f97ebfa7b5..fa9c2f1310a 100644 --- a/public/app/plugins/datasource/prometheus/language_provider.ts +++ b/public/app/plugins/datasource/prometheus/language_provider.ts @@ -156,7 +156,7 @@ export default class PromQlLanguageProvider extends LanguageProvider { } getAggregationCompletionItems({ value }: TypeaheadInput): TypeaheadOutput { - let refresher: Promise = null; + const refresher: Promise = null; const suggestions: CompletionItemGroup[] = []; // Stitch all query lines together to support multi-line queries @@ -172,12 +172,30 @@ export default class PromQlLanguageProvider extends LanguageProvider { return text; }, ''); - const leftSide = queryText.slice(0, queryOffset); - const openParensAggregationIndex = leftSide.lastIndexOf('('); - const openParensSelectorIndex = leftSide.slice(0, openParensAggregationIndex).lastIndexOf('('); - const closeParensSelectorIndex = leftSide.slice(openParensSelectorIndex).indexOf(')') + openParensSelectorIndex; + // Try search for selector part on the left-hand side, such as `sum (m) by (l)` + const openParensAggregationIndex = queryText.lastIndexOf('(', queryOffset); + let openParensSelectorIndex = queryText.lastIndexOf('(', openParensAggregationIndex - 1); + let closeParensSelectorIndex = queryText.indexOf(')', openParensSelectorIndex); - let selectorString = leftSide.slice(openParensSelectorIndex + 1, closeParensSelectorIndex); + // Try search for selector part of an alternate aggregation clause, such as `sum by (l) (m)` + if (openParensSelectorIndex === -1) { + const closeParensAggregationIndex = queryText.indexOf(')', queryOffset); + closeParensSelectorIndex = queryText.indexOf(')', closeParensAggregationIndex + 1); + openParensSelectorIndex = queryText.lastIndexOf('(', closeParensSelectorIndex); + } + + const result = { + refresher, + suggestions, + context: 'context-aggregation', + }; + + // Suggestions are useless for alternative aggregation clauses without a selector in context + if (openParensSelectorIndex === -1) { + return result; + } + + let selectorString = queryText.slice(openParensSelectorIndex + 1, closeParensSelectorIndex); // Range vector syntax not accounted for by subsequent parse so discard it if present selectorString = selectorString.replace(/\[[^\]]+\]$/, ''); @@ -188,14 +206,10 @@ export default class PromQlLanguageProvider extends LanguageProvider { if (labelKeys) { suggestions.push({ label: 'Labels', items: labelKeys.map(wrapLabel) }); } else { - refresher = this.fetchSeriesLabels(selector); + result.refresher = this.fetchSeriesLabels(selector); } - return { - refresher, - suggestions, - context: 'context-aggregation', - }; + return result; } getLabelCompletionItems({ text, wrapperClasses, labelKey, value }: TypeaheadInput): TypeaheadOutput { From f79b790ef68cb05f5ac6373c8c75aa99fec0b366 Mon Sep 17 00:00:00 2001 From: Michael Huynh Date: Sat, 3 Nov 2018 08:07:08 +0800 Subject: [PATCH 21/55] Add tests covering alternate syntax for aggregation contexts Related: #13690 --- .../specs/language_provider.test.ts | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/public/app/plugins/datasource/prometheus/specs/language_provider.test.ts b/public/app/plugins/datasource/prometheus/specs/language_provider.test.ts index 20e148efd57..784a8b59739 100644 --- a/public/app/plugins/datasource/prometheus/specs/language_provider.test.ts +++ b/public/app/plugins/datasource/prometheus/specs/language_provider.test.ts @@ -269,5 +269,48 @@ describe('Language completion provider', () => { }, ]); }); + + it('returns no suggestions inside an unclear aggregation context using alternate syntax', () => { + const instance = new LanguageProvider(datasource, { + labelKeys: { '{__name__="metric"}': ['label1', 'label2', 'label3'] }, + }); + const value = Plain.deserialize('sum by ()'); + const range = value.selection.merge({ + anchorOffset: 8, + }); + const valueWithSelection = value.change().select(range).value; + const result = instance.provideCompletionItems({ + text: '', + prefix: '', + wrapperClasses: ['context-aggregation'], + value: valueWithSelection, + }); + expect(result.context).toBe('context-aggregation'); + expect(result.suggestions).toEqual([]); + }); + + it('returns label suggestions inside an aggregation context using alternate syntax', () => { + const instance = new LanguageProvider(datasource, { + labelKeys: { '{__name__="metric"}': ['label1', 'label2', 'label3'] }, + }); + const value = Plain.deserialize('sum by () (metric)'); + const range = value.selection.merge({ + anchorOffset: 8, + }); + const valueWithSelection = value.change().select(range).value; + const result = instance.provideCompletionItems({ + text: '', + prefix: '', + wrapperClasses: ['context-aggregation'], + value: valueWithSelection, + }); + expect(result.context).toBe('context-aggregation'); + expect(result.suggestions).toEqual([ + { + items: [{ label: 'label1' }, { label: 'label2' }, { label: 'label3' }], + label: 'Labels', + }, + ]); + }); }); }); From 65ace003c923393c2797d97cced53d68267ec1bf Mon Sep 17 00:00:00 2001 From: bergquist Date: Sat, 3 Nov 2018 14:02:23 +0100 Subject: [PATCH 22/55] changelog: adds note about closing #13945 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea6de0dd43b..97537ec34f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ * **Datasource Proxy**: Keep trailing slash for datasource proxy requests [#13326](https://github.com/grafana/grafana/pull/13326), thx [@ryantxu](https://github.com/ryantxu) * **DingDing**: Can't receive DingDing alert when alert is triggered [#13723](https://github.com/grafana/grafana/issues/13723), thx [@Yukinoshita-Yukino](https://github.com/Yukinoshita-Yukino) * **Internal metrics**: Renamed `grafana_info` to `grafana_build_info` and added branch, goversion and revision [#13876](https://github.com/grafana/grafana/pull/13876) +* **Alerting**: Increaste default duration for queries [#13945](https://github.com/grafana/grafana/pull/13945) ### Breaking changes From d6cd2a208573e051d37d497ea9368b8b5e1c4ac5 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Sun, 4 Nov 2018 00:48:30 -0700 Subject: [PATCH 23/55] Gitlab -> GitLab --- docs/sources/guides/whats-new-in-v5-3.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/guides/whats-new-in-v5-3.md b/docs/sources/guides/whats-new-in-v5-3.md index 5dcadc0813d..10592f51648 100644 --- a/docs/sources/guides/whats-new-in-v5-3.md +++ b/docs/sources/guides/whats-new-in-v5-3.md @@ -18,7 +18,7 @@ Grafana v5.3 brings new features, many enhancements and bug fixes. This article - [TV mode]({{< relref "#tv-and-kiosk-mode" >}}) is improved and more accessible - [Alerting]({{< relref "#notification-reminders" >}}) with notification reminders - [Postgres]({{< relref "#postgres-query-builder" >}}) gets a new query builder! -- [OAuth]({{< relref "#improved-oauth-support-for-gitlab" >}}) support for Gitlab is improved +- [OAuth]({{< relref "#improved-oauth-support-for-gitlab" >}}) support for GitLab is improved - [Annotations]({{< relref "#annotations" >}}) with template variable filtering - [Variables]({{< relref "#variables" >}}) with free text support @@ -69,9 +69,9 @@ Grafana 5.3 comes with a new graphical query builder for Postgres. This brings P {{< docs-imagebox img="/img/docs/v53/postgres_query_still.png" class="docs-image--no-shadow" animated-gif="/img/docs/v53/postgres_query.gif" >}} -## Improved OAuth Support for Gitlab +## Improved OAuth Support for GitLab -Grafana 5.3 comes with a new OAuth integration for Gitlab that enables configuration to only allow users that are a member of certain Gitlab groups to authenticate. This makes it possible to use Gitlab OAuth with Grafana in a shared environment without giving everyone access to Grafana. +Grafana 5.3 comes with a new OAuth integration for GitLab that enables configuration to only allow users that are a member of certain GitLab groups to authenticate. This makes it possible to use GitLab OAuth with Grafana in a shared environment without giving everyone access to Grafana. Learn how to enable and configure it in the [documentation](/auth/gitlab/). ## Annotations From 355493bf6ee77f5d31ae8e3b377ea91f38da065d Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Sun, 4 Nov 2018 03:22:25 -0800 Subject: [PATCH 24/55] typo fix for "has" --- public/app/partials/reset_password.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/partials/reset_password.html b/public/app/partials/reset_password.html index 92ed91d5f4d..138aa1b7c62 100644 --- a/public/app/partials/reset_password.html +++ b/public/app/partials/reset_password.html @@ -19,7 +19,7 @@
- An email with a reset link as been sent to the email address.
+ An email with a reset link has been sent to the email address.
You should receive it shortly.
From cb2a03a08c2032a28df000396472cd03cbb1febe Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Mon, 5 Nov 2018 14:27:27 +0900 Subject: [PATCH 25/55] add minimal permission --- docs/sources/features/datasources/cloudwatch.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sources/features/datasources/cloudwatch.md b/docs/sources/features/datasources/cloudwatch.md index be36d108475..e2bcb50bb1d 100644 --- a/docs/sources/features/datasources/cloudwatch.md +++ b/docs/sources/features/datasources/cloudwatch.md @@ -60,7 +60,8 @@ Here is a minimal policy example: "Effect": "Allow", "Action": [ "cloudwatch:ListMetrics", - "cloudwatch:GetMetricStatistics" + "cloudwatch:GetMetricStatistics", + "cloudwatch:GetMetricData" ], "Resource": "*" }, From 7c3dcb3702268e7b0a9055618a53f3ab43897ae2 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 5 Nov 2018 09:03:35 +0100 Subject: [PATCH 26/55] alerting: adds tests for the median reducer adds a test that verify that null values are not used when calculating the median value in alerting closes #10056 --- .../alerting/conditions/reducer_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pkg/services/alerting/conditions/reducer_test.go b/pkg/services/alerting/conditions/reducer_test.go index 9d4e1462690..7f11fc498bd 100644 --- a/pkg/services/alerting/conditions/reducer_test.go +++ b/pkg/services/alerting/conditions/reducer_test.go @@ -52,6 +52,24 @@ func TestSimpleReducer(t *testing.T) { So(result, ShouldEqual, float64(1)) }) + Convey("median should ignore null values", func() { + reducer := NewSimpleReducer("median") + series := &tsdb.TimeSeries{ + Name: "test time serie", + } + + series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1)) + series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2)) + series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 3)) + series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(float64(1)), 4)) + series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(float64(2)), 5)) + series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(float64(3)), 6)) + + result := reducer.Reduce(series) + So(result.Valid, ShouldEqual, true) + So(result.Float64, ShouldEqual, float64(2)) + }) + Convey("avg", func() { result := testReducer("avg", 1, 2, 3) So(result, ShouldEqual, float64(2)) From 5469a1a56951061fed8f5e4db3473f6a070e824e Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 5 Nov 2018 09:55:19 +0100 Subject: [PATCH 27/55] build: fixes gcp push path. --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 60b3ae91ccc..af5acef4bea 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -403,7 +403,7 @@ jobs: command: '/opt/google-cloud-sdk/bin/gcloud auth activate-service-account --key-file=/tmp/gcpkey.json' - run: name: deploy to gcp - command: '/opt/google-cloud-sdk/bin/gsutil cp ./dist/* gs://R/oss/release' + command: '/opt/google-cloud-sdk/bin/gsutil cp ./dist/* gs://$GCP_BUCKET_NAME/oss/release' - run: name: Deploy to Grafana.com command: './scripts/build/publish.sh' From 5be2332c668ab459d7f0eadd16ece31b02fa193d Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 5 Nov 2018 09:58:13 +0100 Subject: [PATCH 28/55] handle error before populating cache --- pkg/services/sqlstore/user.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index 5619cd2859b..6e4b12ca7c3 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -353,8 +353,12 @@ func (ss *SqlStore) GetSignedInUserWithCache(query *m.GetSignedInUserQuery) erro } err := GetSignedInUser(query) + if err != nil { + return err + } + ss.CacheService.Set(cacheKey, query.Result, time.Second*5) - return err + return nil } func GetSignedInUser(query *m.GetSignedInUserQuery) error { From 818d48c2c06e884a3e6e6961ca25b07af6e5aba6 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 5 Nov 2018 10:49:56 +0100 Subject: [PATCH 29/55] always execute the user teams query --- pkg/services/sqlstore/user.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index 6e4b12ca7c3..99a77ecabc3 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -403,17 +403,17 @@ func GetSignedInUser(query *m.GetSignedInUserQuery) error { if user.OrgRole == "" { user.OrgId = -1 user.OrgName = "Org missing" - } else { - getTeamsByUserQuery := &m.GetTeamsByUserQuery{OrgId: user.OrgId, UserId: user.UserId} - err = GetTeamsByUser(getTeamsByUserQuery) - if err != nil { - return err - } + } - user.Teams = make([]int64, len(getTeamsByUserQuery.Result)) - for i, t := range getTeamsByUserQuery.Result { - user.Teams[i] = t.Id - } + getTeamsByUserQuery := &m.GetTeamsByUserQuery{OrgId: user.OrgId, UserId: user.UserId} + err = GetTeamsByUser(getTeamsByUserQuery) + if err != nil { + return err + } + + user.Teams = make([]int64, len(getTeamsByUserQuery.Result)) + for i, t := range getTeamsByUserQuery.Result { + user.Teams[i] = t.Id } query.Result = &user From 32c93793a628c8e31b6252ebceff60665d927414 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 5 Nov 2018 11:34:25 +0100 Subject: [PATCH 30/55] devenv: table panel links --- devenv/dev-dashboards/panel_tests_table.json | 110 ++++++++++++++++++- 1 file changed, 108 insertions(+), 2 deletions(-) diff --git a/devenv/dev-dashboards/panel_tests_table.json b/devenv/dev-dashboards/panel_tests_table.json index 8337e9cd746..ff0288c340a 100644 --- a/devenv/dev-dashboards/panel_tests_table.json +++ b/devenv/dev-dashboards/panel_tests_table.json @@ -404,6 +404,112 @@ "title": "Column style thresholds & units", "transform": "timeseries_to_columns", "type": "table" + }, + { + "columns": [], + "datasource": "gdev-testdata", + "fontSize": "100%", + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 26 + }, + "id": 6, + "links": [], + "pageSize": 20, + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "colorMode": "cell", + "colors": [ + "rgba(245, 54, 54, 0.5)", + "rgba(237, 129, 40, 0.5)", + "rgba(50, 172, 45, 0.5)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "link": true, + "linkTargetBlank": true, + "linkTooltip": "", + "linkUrl": "http://www.grafana.com", + "mappingType": 1, + "pattern": "ColorCell", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "colorMode": "value", + "colors": [ + "rgba(245, 54, 54, 0.5)", + "rgba(237, 129, 40, 0.5)", + "rgba(50, 172, 45, 0.5)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "link": true, + "linkUrl": "http://www.grafana.com", + "mappingType": 1, + "pattern": "ColorValue", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "Bps" + }, + { + "alias": "", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "targets": [ + { + "alias": "ColorValue", + "expr": "", + "format": "table", + "intervalFactor": 1, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "null,1,20,90,30,5,0,20,10" + }, + { + "alias": "ColorCell", + "refId": "B", + "scenarioId": "csv_metric_values", + "stringInput": "null,5,1,2,3,4,5,10,20" + } + ], + "title": "Column style thresholds and links", + "transform": "timeseries_to_columns", + "type": "table" } ], "refresh": false, @@ -449,5 +555,5 @@ "timezone": "browser", "title": "Panel Tests - Table", "uid": "pttable", - "version": 1 -} + "version": 2 +} \ No newline at end of file From c5ce8536d4de872077116523f77a404273115cc7 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 5 Nov 2018 11:47:34 +0100 Subject: [PATCH 31/55] changelog: add notes about closing #13606 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97537ec34f7..d139009ac78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ * **DingDing**: Can't receive DingDing alert when alert is triggered [#13723](https://github.com/grafana/grafana/issues/13723), thx [@Yukinoshita-Yukino](https://github.com/Yukinoshita-Yukino) * **Internal metrics**: Renamed `grafana_info` to `grafana_build_info` and added branch, goversion and revision [#13876](https://github.com/grafana/grafana/pull/13876) * **Alerting**: Increaste default duration for queries [#13945](https://github.com/grafana/grafana/pull/13945) +* **Table**: Fix CSS alpha background-color applied twice in table cell with link [#13606](https://github.com/grafana/grafana/issues/13606), thx [@grisme](https://github.com/grisme) ### Breaking changes From dd4eab17222dcc8e3efc74d66a71e3845ad1e658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 5 Nov 2018 13:44:09 +0100 Subject: [PATCH 32/55] panel options wip --- public/app/plugins/panel/graph2/module.tsx | 27 ++++++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/public/app/plugins/panel/graph2/module.tsx b/public/app/plugins/panel/graph2/module.tsx index 4011458bea9..7bb2ab48e40 100644 --- a/public/app/plugins/panel/graph2/module.tsx +++ b/public/app/plugins/panel/graph2/module.tsx @@ -12,6 +12,10 @@ import { PanelProps, NullValueMode } from 'app/types'; interface Options { showBars: boolean; + showLines: boolean; + showPoints: boolean; + + onChange: (options: Options) => void; } interface Props extends PanelProps { @@ -35,14 +39,27 @@ export class Graph2 extends PureComponent { } } -export class TextOptions extends PureComponent { - onChange = () => {}; +export class TextOptions extends PureComponent { + onToggleLines = () => { + const options = this.props as Options; + + this.props.onChange({ + ...options, + showLines: !this.props.showLines, + }); + }; render() { + const { showBars, showPoints, showLines } = this.props; + return ( -
-
Draw Modes
- +
+
+
Draw Modes
+ + + +
); } From 9e0da02b6a5974cb74d230df2f7e168e46144028 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 5 Nov 2018 14:25:19 +0100 Subject: [PATCH 33/55] refactor dashboard alert extractor --- pkg/api/alerting.go | 4 ++++ pkg/models/alert.go | 3 ++- pkg/services/alerting/commands.go | 6 +++--- pkg/services/alerting/extractor.go | 19 ++++++++++++++++++- pkg/services/alerting/extractor_test.go | 16 ++++++++-------- pkg/services/alerting/test_rule.go | 3 ++- pkg/services/dashboards/dashboard_service.go | 3 ++- 7 files changed, 39 insertions(+), 15 deletions(-) diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index a936d696207..c68cee50948 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -134,12 +134,16 @@ func AlertTest(c *m.ReqContext, dto dtos.AlertTestCommand) Response { OrgId: c.OrgId, Dashboard: dto.Dashboard, PanelId: dto.PanelId, + User: c.SignedInUser, } if err := bus.Dispatch(&backendCmd); err != nil { if validationErr, ok := err.(alerting.ValidationError); ok { return Error(422, validationErr.Error(), nil) } + if err == m.ErrDataSourceAccessDenied { + return Error(403, "Access denied to datasource", err) + } return Error(500, "Failed to test rule", err) } diff --git a/pkg/models/alert.go b/pkg/models/alert.go index ba1fc0779ba..aaf9c50197a 100644 --- a/pkg/models/alert.go +++ b/pkg/models/alert.go @@ -215,13 +215,14 @@ type AlertStateInfoDTO struct { // "Internal" commands type UpdateDashboardAlertsCommand struct { - UserId int64 OrgId int64 Dashboard *Dashboard + User *SignedInUser } type ValidateDashboardAlertsCommand struct { UserId int64 OrgId int64 Dashboard *Dashboard + User *SignedInUser } diff --git a/pkg/services/alerting/commands.go b/pkg/services/alerting/commands.go index 02186d697ee..dd2ff5658d6 100644 --- a/pkg/services/alerting/commands.go +++ b/pkg/services/alerting/commands.go @@ -11,7 +11,7 @@ func init() { } func validateDashboardAlerts(cmd *m.ValidateDashboardAlertsCommand) error { - extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId) + extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId, cmd.User) return extractor.ValidateAlerts() } @@ -19,11 +19,11 @@ func validateDashboardAlerts(cmd *m.ValidateDashboardAlertsCommand) error { func updateDashboardAlerts(cmd *m.UpdateDashboardAlertsCommand) error { saveAlerts := m.SaveAlertsCommand{ OrgId: cmd.OrgId, - UserId: cmd.UserId, + UserId: cmd.User.UserId, DashboardId: cmd.Dashboard.Id, } - extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId) + extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId, cmd.User) alerts, err := extractor.GetAlerts() if err != nil { diff --git a/pkg/services/alerting/extractor.go b/pkg/services/alerting/extractor.go index edfab2dedee..0abacc91313 100644 --- a/pkg/services/alerting/extractor.go +++ b/pkg/services/alerting/extractor.go @@ -13,14 +13,16 @@ import ( // DashAlertExtractor extracts alerts from the dashboard json type DashAlertExtractor struct { + User *m.SignedInUser Dash *m.Dashboard OrgID int64 log log.Logger } // NewDashAlertExtractor returns a new DashAlertExtractor -func NewDashAlertExtractor(dash *m.Dashboard, orgID int64) *DashAlertExtractor { +func NewDashAlertExtractor(dash *m.Dashboard, orgID int64, user *m.SignedInUser) *DashAlertExtractor { return &DashAlertExtractor{ + User: user, Dash: dash, OrgID: orgID, log: log.New("alerting.extractor"), @@ -149,6 +151,21 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json, return nil, ValidationError{Reason: fmt.Sprintf("Data source used by alert rule not found, alertName=%v, datasource=%s", alert.Name, dsName)} } + dsFilterQuery := m.DatasourcesPermissionFilterQuery{ + User: e.User, + Datasources: []*m.DataSource{datasource}, + } + + if err := bus.Dispatch(&dsFilterQuery); err != nil { + if err != bus.ErrHandlerNotFound { + return nil, err + } + } else { + if len(dsFilterQuery.Result) == 0 { + return nil, m.ErrDataSourceAccessDenied + } + } + jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id) if interval, err := panel.Get("interval").String(); err == nil { diff --git a/pkg/services/alerting/extractor_test.go b/pkg/services/alerting/extractor_test.go index e2dc01a1181..0890b9e1bd1 100644 --- a/pkg/services/alerting/extractor_test.go +++ b/pkg/services/alerting/extractor_test.go @@ -69,7 +69,7 @@ func TestAlertRuleExtraction(t *testing.T) { So(getTarget(dashJson), ShouldEqual, "") }) - extractor := NewDashAlertExtractor(dash, 1) + extractor := NewDashAlertExtractor(dash, 1, nil) _, _ = extractor.GetAlerts() Convey("Dashboard json should not be updated after extracting rules", func() { @@ -83,7 +83,7 @@ func TestAlertRuleExtraction(t *testing.T) { So(err, ShouldBeNil) dash := m.NewDashboardFromJson(dashJson) - extractor := NewDashAlertExtractor(dash, 1) + extractor := NewDashAlertExtractor(dash, 1, nil) alerts, err := extractor.GetAlerts() @@ -146,7 +146,7 @@ func TestAlertRuleExtraction(t *testing.T) { dashJson, err := simplejson.NewJson(panelWithoutId) So(err, ShouldBeNil) dash := m.NewDashboardFromJson(dashJson) - extractor := NewDashAlertExtractor(dash, 1) + extractor := NewDashAlertExtractor(dash, 1, nil) _, err = extractor.GetAlerts() @@ -162,7 +162,7 @@ func TestAlertRuleExtraction(t *testing.T) { dashJson, err := simplejson.NewJson(panelWithIdZero) So(err, ShouldBeNil) dash := m.NewDashboardFromJson(dashJson) - extractor := NewDashAlertExtractor(dash, 1) + extractor := NewDashAlertExtractor(dash, 1, nil) _, err = extractor.GetAlerts() @@ -178,7 +178,7 @@ func TestAlertRuleExtraction(t *testing.T) { dashJson, err := simplejson.NewJson(json) So(err, ShouldBeNil) dash := m.NewDashboardFromJson(dashJson) - extractor := NewDashAlertExtractor(dash, 1) + extractor := NewDashAlertExtractor(dash, 1, nil) alerts, err := extractor.GetAlerts() @@ -198,7 +198,7 @@ func TestAlertRuleExtraction(t *testing.T) { dashJson, err := simplejson.NewJson(json) So(err, ShouldBeNil) dash := m.NewDashboardFromJson(dashJson) - extractor := NewDashAlertExtractor(dash, 1) + extractor := NewDashAlertExtractor(dash, 1, nil) alerts, err := extractor.GetAlerts() @@ -228,7 +228,7 @@ func TestAlertRuleExtraction(t *testing.T) { So(err, ShouldBeNil) dash := m.NewDashboardFromJson(dashJson) - extractor := NewDashAlertExtractor(dash, 1) + extractor := NewDashAlertExtractor(dash, 1, nil) alerts, err := extractor.GetAlerts() @@ -248,7 +248,7 @@ func TestAlertRuleExtraction(t *testing.T) { dashJSON, err := simplejson.NewJson(json) So(err, ShouldBeNil) dash := m.NewDashboardFromJson(dashJSON) - extractor := NewDashAlertExtractor(dash, 1) + extractor := NewDashAlertExtractor(dash, 1, nil) err = extractor.ValidateAlerts() diff --git a/pkg/services/alerting/test_rule.go b/pkg/services/alerting/test_rule.go index 88418bff14e..360ee065de0 100644 --- a/pkg/services/alerting/test_rule.go +++ b/pkg/services/alerting/test_rule.go @@ -13,6 +13,7 @@ type AlertTestCommand struct { Dashboard *simplejson.Json PanelId int64 OrgId int64 + User *m.SignedInUser Result *EvalContext } @@ -25,7 +26,7 @@ func handleAlertTestCommand(cmd *AlertTestCommand) error { dash := m.NewDashboardFromJson(cmd.Dashboard) - extractor := NewDashAlertExtractor(dash, cmd.OrgId) + extractor := NewDashAlertExtractor(dash, cmd.OrgId, cmd.User) alerts, err := extractor.GetAlerts() if err != nil { return err diff --git a/pkg/services/dashboards/dashboard_service.go b/pkg/services/dashboards/dashboard_service.go index 8eb7f4a6e72..b52d1845a0b 100644 --- a/pkg/services/dashboards/dashboard_service.go +++ b/pkg/services/dashboards/dashboard_service.go @@ -90,6 +90,7 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO, validateAlertsCmd := models.ValidateDashboardAlertsCommand{ OrgId: dto.OrgId, Dashboard: dash, + User: dto.User, } if err := bus.Dispatch(&validateAlertsCmd); err != nil { @@ -159,8 +160,8 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO, func (dr *dashboardServiceImpl) updateAlerting(cmd *models.SaveDashboardCommand, dto *SaveDashboardDTO) error { alertCmd := models.UpdateDashboardAlertsCommand{ OrgId: dto.OrgId, - UserId: dto.User.UserId, Dashboard: cmd.Result, + User: dto.User, } if err := bus.Dispatch(&alertCmd); err != nil { From 423331dae03e9abe33c6656d72d6185dd5a9bd06 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 5 Nov 2018 14:24:08 +0100 Subject: [PATCH 34/55] alerting: delete alerts when parent folder is deleted closes #13322 --- pkg/services/sqlstore/dashboard.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go index 1b853d17b5f..bad46c10af4 100644 --- a/pkg/services/sqlstore/dashboard.go +++ b/pkg/services/sqlstore/dashboard.go @@ -327,6 +327,24 @@ func DeleteDashboard(cmd *m.DeleteDashboardCommand) error { if dashboard.IsFolder { deletes = append(deletes, "DELETE FROM dashboard_provisioning WHERE dashboard_id in (select id from dashboard where folder_id = ?)") deletes = append(deletes, "DELETE FROM dashboard WHERE folder_id = ?") + + dashIds := []struct { + Id int64 + }{} + err := sess.SQL("select id from dashboard where folder_id = ?", dashboard.Id).Find(&dashIds) + if err != nil { + return err + } + + for _, id := range dashIds { + if err := deleteAlertDefinition(id.Id, sess); err != nil { + return nil + } + } + } + + if err := deleteAlertDefinition(dashboard.Id, sess); err != nil { + return nil } for _, sql := range deletes { @@ -337,10 +355,6 @@ func DeleteDashboard(cmd *m.DeleteDashboardCommand) error { } } - if err := deleteAlertDefinition(dashboard.Id, sess); err != nil { - return nil - } - return nil }) } From a1dca2117ddf6346b742da85b9c4b2218449d64e Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 5 Nov 2018 15:05:12 +0100 Subject: [PATCH 35/55] build: use build workflow id instead of build number. (#13965) * build: use build workflow id instead of build number. The workflow id is unique across the whole workflow while the build number is unique to every job in the workflow. This change means that jobs that build artifacts for the same commit but in different jobs will now have the same id. * build: fixes pkgver generation. --- build.go | 28 ++++++++++++++++++++-------- scripts/build/build-all.sh | 4 ++-- scripts/build/build.sh | 4 ++-- scripts/build/deploy.sh | 14 -------------- 4 files changed, 24 insertions(+), 26 deletions(-) delete mode 100755 scripts/build/deploy.sh diff --git a/build.go b/build.go index b136754efbc..a2a1fb825d9 100644 --- a/build.go +++ b/build.go @@ -41,8 +41,8 @@ var ( race bool phjsToRelease string workingDir string - includeBuildNumber bool = true - buildNumber int = 0 + includeBuildId bool = true + buildId string = "0" binaries []string = []string{"grafana-server", "grafana-cli"} isDev bool = false enterprise bool = false @@ -54,6 +54,8 @@ func main() { ensureGoPath() + var buildIdRaw string + flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH") flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS") flag.StringVar(&gocc, "cc", "", "CC") @@ -61,12 +63,14 @@ func main() { flag.StringVar(&pkgArch, "pkg-arch", "", "PKG ARCH") flag.StringVar(&phjsToRelease, "phjs", "", "PhantomJS binary") flag.BoolVar(&race, "race", race, "Use race detector") - flag.BoolVar(&includeBuildNumber, "includeBuildNumber", includeBuildNumber, "IncludeBuildNumber in package name") + flag.BoolVar(&includeBuildId, "includeBuildId", includeBuildId, "IncludeBuildId in package name") flag.BoolVar(&enterprise, "enterprise", enterprise, "Build enterprise version of Grafana") - flag.IntVar(&buildNumber, "buildNumber", 0, "Build number from CI system") + flag.StringVar(&buildIdRaw, "buildId", "0", "Build ID from CI system") flag.BoolVar(&isDev, "dev", isDev, "optimal for development, skips certain steps") flag.Parse() + buildId = shortenBuildId(buildIdRaw) + readVersionFromPackageJson() if pkgArch == "" { @@ -197,9 +201,9 @@ func readVersionFromPackageJson() { } // add timestamp to iteration - if includeBuildNumber { - if buildNumber != 0 { - linuxPackageIteration = fmt.Sprintf("%d%s", buildNumber, linuxPackageIteration) + if includeBuildId { + if buildId != "0" { + linuxPackageIteration = fmt.Sprintf("%s%s", buildId, linuxPackageIteration) } else { linuxPackageIteration = fmt.Sprintf("%d%s", time.Now().Unix(), linuxPackageIteration) } @@ -392,7 +396,7 @@ func grunt(params ...string) { func gruntBuildArg(task string) []string { args := []string{task} - if includeBuildNumber { + if includeBuildId { args = append(args, fmt.Sprintf("--pkgVer=%v-%v", linuxPackageVersion, linuxPackageIteration)) } else { args = append(args, fmt.Sprintf("--pkgVer=%v", version)) @@ -632,3 +636,11 @@ func shaFile(file string) error { return out.Close() } + +func shortenBuildId(buildId string) string { + buildId = strings.Replace(buildId, "-", "", -1) + if (len(buildId) < 9) { + return buildId + } + return buildId[0:8] +} diff --git a/scripts/build/build-all.sh b/scripts/build/build-all.sh index f194109ec0d..3e7058fa494 100755 --- a/scripts/build/build-all.sh +++ b/scripts/build/build-all.sh @@ -22,10 +22,10 @@ echo "current dir: $(pwd)" if [ "$CIRCLE_TAG" != "" ]; then echo "Building releases from tag $CIRCLE_TAG" - OPT="-includeBuildNumber=false ${EXTRA_OPTS}" + OPT="-includeBuildId=false ${EXTRA_OPTS}" else echo "Building incremental build for $CIRCLE_BRANCH" - OPT="-buildNumber=${CIRCLE_BUILD_NUM} ${EXTRA_OPTS}" + OPT="-buildId=${CIRCLE_WORKFLOW_ID} ${EXTRA_OPTS}" fi echo "Build arguments: $OPT" diff --git a/scripts/build/build.sh b/scripts/build/build.sh index 2cf9f5a8a21..8362942c6cd 100755 --- a/scripts/build/build.sh +++ b/scripts/build/build.sh @@ -18,10 +18,10 @@ echo "current dir: $(pwd)" if [ "$CIRCLE_TAG" != "" ]; then echo "Building releases from tag $CIRCLE_TAG" - OPT="-includeBuildNumber=false ${EXTRA_OPTS}" + OPT="-includeBuildId=false ${EXTRA_OPTS}" else echo "Building incremental build for $CIRCLE_BRANCH" - OPT="-buildNumber=${CIRCLE_BUILD_NUM} ${EXTRA_OPTS}" + OPT="-buildId=${CIRCLE_WORKFLOW_ID} ${EXTRA_OPTS}" fi echo "Build arguments: $OPT" diff --git a/scripts/build/deploy.sh b/scripts/build/deploy.sh deleted file mode 100755 index 49b2a9e3a7c..00000000000 --- a/scripts/build/deploy.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -mkdir -p dist - -echo "Circle branch: ${CIRCLE_BRANCH}" -echo "Circle tag: ${CIRCLE_TAG}" -docker run -i -t --name gfbuild \ - -v $(pwd):/go/src/github.com/grafana/grafana \ - -e "CIRCLE_BRANCH=${CIRCLE_BRANCH}" \ - -e "CIRCLE_TAG=${CIRCLE_TAG}" \ - -e "CIRCLE_BUILD_NUM=${CIRCLE_BUILD_NUM}" \ - grafana/buildcontainer - -sudo chown -R ${USER:=$(/usr/bin/id -run)}:$USER dist From 7e093a32a282f25b4922c856d9cf21e12689c7ad Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Wed, 31 Oct 2018 16:59:30 +0100 Subject: [PATCH 36/55] build: improved release publisher dry-run. --- scripts/build/release_publisher/main.go | 11 +++-- scripts/build/release_publisher/publisher.go | 46 ++++++++++---------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/scripts/build/release_publisher/main.go b/scripts/build/release_publisher/main.go index fde4317bbb9..40430aebf2b 100644 --- a/scripts/build/release_publisher/main.go +++ b/scripts/build/release_publisher/main.go @@ -7,8 +7,6 @@ import ( "os" ) -var baseUri string = "https://grafana.com/api" - func main() { var version string var whatsNewUrl string @@ -33,8 +31,13 @@ func main() { log.Println("Dry-run has been enabled.") } - p := publisher{apiKey: apiKey} - if err := p.doRelease(version, whatsNewUrl, releaseNotesUrl, dryRun); err != nil { + p := publisher{ + apiKey: apiKey, + baseUri: "https://grafana.com/api", + product: "grafana", + dryRun: dryRun, + } + if err := p.doRelease(version, whatsNewUrl, releaseNotesUrl); err != nil { log.Fatalf("error: %v", err) } } diff --git a/scripts/build/release_publisher/publisher.go b/scripts/build/release_publisher/publisher.go index 60b60ca55f7..0466aaa5687 100644 --- a/scripts/build/release_publisher/publisher.go +++ b/scripts/build/release_publisher/publisher.go @@ -13,52 +13,39 @@ import ( type publisher struct { apiKey string + baseUri string + product string + dryRun bool } -func (p *publisher) doRelease(version string, whatsNewUrl string, releaseNotesUrl string, dryRun bool) error { +func (p *publisher) doRelease(version string, whatsNewUrl string, releaseNotesUrl string) error { currentRelease, err := newRelease(version, whatsNewUrl, releaseNotesUrl, buildArtifactConfigurations, getHttpContents{}) if err != nil { return err } - if dryRun { - relJson, err := json.Marshal(currentRelease) - if err != nil { - return err - } - log.Println(string(relJson)) - - for _, b := range currentRelease.Builds { - artifactJson, err := json.Marshal(b) - if err != nil { - return err - } - log.Println(string(artifactJson)) - } - } else { - if err := p.postRelease(currentRelease); err != nil { - return err - } + if err := p.postRelease(currentRelease); err != nil { + return err } return nil } func (p *publisher) postRelease(r *release) error { - err := p.postRequest("/grafana/versions", r, fmt.Sprintf("Create Release %s", r.Version)) + err := p.postRequest("/versions", r, fmt.Sprintf("Create Release %s", r.Version)) if err != nil { return err } - err = p.postRequest("/grafana/versions/"+r.Version, r, fmt.Sprintf("Update Release %s", r.Version)) + err = p.postRequest("/versions/"+r.Version, r, fmt.Sprintf("Update Release %s", r.Version)) if err != nil { return err } for _, b := range r.Builds { - err = p.postRequest(fmt.Sprintf("/grafana/versions/%s/packages", r.Version), b, fmt.Sprintf("Create Build %s %s", b.Os, b.Arch)) + err = p.postRequest(fmt.Sprintf("/versions/%s/packages", r.Version), b, fmt.Sprintf("Create Build %s %s", b.Os, b.Arch)) if err != nil { return err } - err = p.postRequest(fmt.Sprintf("/grafana/versions/%s/packages/%s/%s", r.Version, b.Arch, b.Os), b, fmt.Sprintf("Update Build %s %s", b.Os, b.Arch)) + err = p.postRequest(fmt.Sprintf("/versions/%s/packages/%s/%s", r.Version, b.Arch, b.Os), b, fmt.Sprintf("Update Build %s %s", b.Os, b.Arch)) if err != nil { return err } @@ -185,12 +172,23 @@ func newBuild(ba buildArtifact, version string, isBeta bool, sha256 string) buil } } +func (p *publisher) apiUrl(url string) string { + return fmt.Sprintf("%s/%s%s", p.baseUri, p.product, url) +} + func (p *publisher) postRequest(url string, obj interface{}, desc string) error { jsonBytes, err := json.Marshal(obj) if err != nil { return err } - req, err := http.NewRequest(http.MethodPost, baseUri+url, bytes.NewReader(jsonBytes)) + + if p.dryRun { + log.Println(fmt.Sprintf("POST to %s:", p.apiUrl(url))) + log.Println(string(jsonBytes)) + return nil + } + + req, err := http.NewRequest(http.MethodPost, p.apiUrl(url), bytes.NewReader(jsonBytes)) if err != nil { return err } From e2d3382470a3bc3cf536254cf860907ce3fa727a Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Thu, 1 Nov 2018 13:35:31 +0100 Subject: [PATCH 37/55] build: prepares release tool for finding local releases. --- scripts/build/release_publisher/main.go | 29 +++++++++---- scripts/build/release_publisher/publisher.go | 42 ++++++++++++------- .../build/release_publisher/publisher_test.go | 9 +++- 3 files changed, 55 insertions(+), 25 deletions(-) diff --git a/scripts/build/release_publisher/main.go b/scripts/build/release_publisher/main.go index 40430aebf2b..8b3ec8da74e 100644 --- a/scripts/build/release_publisher/main.go +++ b/scripts/build/release_publisher/main.go @@ -12,6 +12,7 @@ func main() { var whatsNewUrl string var releaseNotesUrl string var dryRun bool + var enterprise bool var apiKey string flag.StringVar(&version, "version", "", "Grafana version (ex: --version v5.2.0-beta1)") @@ -19,25 +20,39 @@ func main() { flag.StringVar(&releaseNotesUrl, "rn", "", "Grafana version (ex: --rn https://community.grafana.com/t/release-notes-v5-2-x/7894)") flag.StringVar(&apiKey, "apikey", "", "Grafana.com API key (ex: --apikey ABCDEF)") flag.BoolVar(&dryRun, "dry-run", false, "--dry-run") + flag.BoolVar(&enterprise, "enterprise", false, "--enterprise") flag.Parse() if len(os.Args) == 1 { - fmt.Println("Usage: go run publisher.go main.go --version --wn --rn --apikey --dry-run false") - fmt.Println("example: go run publisher.go main.go --version v5.2.0-beta2 --wn http://docs.grafana.org/guides/whats-new-in-v5-2/ --rn https://community.grafana.com/t/release-notes-v5-2-x/7894 --apikey ASDF123 --dry-run true") + fmt.Println("Usage: go run publisher.go main.go --version --wn --rn --apikey --dry-run false --enterprise false") + fmt.Println("example: go run publisher.go main.go --version v5.2.0-beta2 --wn http://docs.grafana.org/guides/whats-new-in-v5-2/ --rn https://community.grafana.com/t/release-notes-v5-2-x/7894 --apikey ASDF123 --dry-run --enterprise") os.Exit(1) } if dryRun { log.Println("Dry-run has been enabled.") } + var baseUrl string + + if enterprise { + baseUrl = fmt.Sprintf("https://s3-us-west-2.amazonaws.com/%s", "grafana-enterprise-releases/release/grafana-enterprise") + } else { + baseUrl = fmt.Sprintf("https://s3-us-west-2.amazonaws.com/%s", "grafana-releases/release/grafana") + } p := publisher{ - apiKey: apiKey, - baseUri: "https://grafana.com/api", - product: "grafana", - dryRun: dryRun, + apiKey: apiKey, + baseUri: "https://grafana.com/api", + product: "grafana", + dryRun: dryRun, + enterprise: enterprise, + baseArchiveUrl: baseUrl, + builder: releaseFromExternalContent{ + getter: getHttpContents{}, + rawVersion: version, + }, } - if err := p.doRelease(version, whatsNewUrl, releaseNotesUrl); err != nil { + if err := p.doRelease(whatsNewUrl, releaseNotesUrl); err != nil { log.Fatalf("error: %v", err) } } diff --git a/scripts/build/release_publisher/publisher.go b/scripts/build/release_publisher/publisher.go index 0466aaa5687..2bd84462daa 100644 --- a/scripts/build/release_publisher/publisher.go +++ b/scripts/build/release_publisher/publisher.go @@ -12,14 +12,21 @@ import ( ) type publisher struct { - apiKey string - baseUri string - product string - dryRun bool + apiKey string + baseUri string + product string + dryRun bool + enterprise bool + baseArchiveUrl string + builder releaseBuilder } -func (p *publisher) doRelease(version string, whatsNewUrl string, releaseNotesUrl string) error { - currentRelease, err := newRelease(version, whatsNewUrl, releaseNotesUrl, buildArtifactConfigurations, getHttpContents{}) +type releaseBuilder interface { + prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string, artifactConfigurations []buildArtifact) (*release, error) +} + +func (p *publisher) doRelease(whatsNewUrl string, releaseNotesUrl string) error { + currentRelease, err := p.builder.prepareRelease(p.baseArchiveUrl, whatsNewUrl, releaseNotesUrl, buildArtifactConfigurations) if err != nil { return err } @@ -54,15 +61,13 @@ func (p *publisher) postRelease(r *release) error { return nil } -const baseArhiveUrl = "https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana" - type buildArtifact struct { os string arch string urlPostfix string } -func (t buildArtifact) getUrl(version string, isBeta bool) string { +func (t buildArtifact) getUrl(baseArchiveUrl, version string, isBeta bool) string { prefix := "-" rhelReleaseExtra := "" @@ -74,7 +79,7 @@ func (t buildArtifact) getUrl(version string, isBeta bool) string { rhelReleaseExtra = "-1" } - url := strings.Join([]string{baseArhiveUrl, prefix, version, rhelReleaseExtra, t.urlPostfix}, "") + url := strings.Join([]string{baseArchiveUrl, prefix, version, rhelReleaseExtra, t.urlPostfix}, "") return url } @@ -136,18 +141,23 @@ var buildArtifactConfigurations = []buildArtifact{ }, } -func newRelease(rawVersion string, whatsNewUrl string, releaseNotesUrl string, artifactConfigurations []buildArtifact, getter urlGetter) (*release, error) { - version := rawVersion[1:] +type releaseFromExternalContent struct { + getter urlGetter + rawVersion string +} + +func (re releaseFromExternalContent) prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string, artifactConfigurations []buildArtifact) (*release, error) { + version := re.rawVersion[1:] now := time.Now() isBeta := strings.Contains(version, "beta") builds := []build{} for _, ba := range artifactConfigurations { - sha256, err := getter.getContents(fmt.Sprintf("%s.sha256", ba.getUrl(version, isBeta))) + sha256, err := re.getter.getContents(fmt.Sprintf("%s.sha256", ba.getUrl(baseArchiveUrl, version, isBeta))) if err != nil { return nil, err } - builds = append(builds, newBuild(ba, version, isBeta, sha256)) + builds = append(builds, newBuild(baseArchiveUrl, ba, version, isBeta, sha256)) } r := release{ @@ -163,10 +173,10 @@ func newRelease(rawVersion string, whatsNewUrl string, releaseNotesUrl string, a return &r, nil } -func newBuild(ba buildArtifact, version string, isBeta bool, sha256 string) build { +func newBuild(baseArchiveUrl string, ba buildArtifact, version string, isBeta bool, sha256 string) build { return build{ Os: ba.os, - Url: ba.getUrl(version, isBeta), + Url: ba.getUrl(baseArchiveUrl, version, isBeta), Sha256: sha256, Arch: ba.arch, } diff --git a/scripts/build/release_publisher/publisher_test.go b/scripts/build/release_publisher/publisher_test.go index 9bc350e6a54..38a9d972380 100644 --- a/scripts/build/release_publisher/publisher_test.go +++ b/scripts/build/release_publisher/publisher_test.go @@ -9,9 +9,14 @@ func TestNewRelease(t *testing.T) { relNotesUrl := "https://relnotes.foo/" expectedArch := "amd64" expectedOs := "linux" - buildArtifacts := []buildArtifact{{expectedOs, expectedArch, ".linux-amd64.tar.gz"}} + buildArtifacts := []buildArtifact{{expectedOs,expectedArch, ".linux-amd64.tar.gz"}} - rel, _ := newRelease(versionIn, whatsNewUrl, relNotesUrl, buildArtifacts, mockHttpGetter{}) + builder := releaseFromExternalContent{ + getter: mockHttpGetter{}, + rawVersion: versionIn, + } + + rel, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana", whatsNewUrl, relNotesUrl, buildArtifacts) if !rel.Beta || rel.Stable { t.Errorf("%s should have been tagged as beta (not stable), but wasn't .", versionIn) From c5c3e08442fb2f8cb3eedceb4fc30ff7708b67ab Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Thu, 1 Nov 2018 14:49:32 +0100 Subject: [PATCH 38/55] build: refactor releaser. --- .../release_publisher/externalrelease.go | 62 +++++++++++++++++++ scripts/build/release_publisher/publisher.go | 53 ---------------- .../build/release_publisher/publisher_test.go | 7 ++- 3 files changed, 68 insertions(+), 54 deletions(-) create mode 100644 scripts/build/release_publisher/externalrelease.go diff --git a/scripts/build/release_publisher/externalrelease.go b/scripts/build/release_publisher/externalrelease.go new file mode 100644 index 00000000000..795e3bc999b --- /dev/null +++ b/scripts/build/release_publisher/externalrelease.go @@ -0,0 +1,62 @@ +package main + +import ( + "fmt" + "io/ioutil" + "net/http" + "strings" + "time" +) + +type releaseFromExternalContent struct { + getter urlGetter + rawVersion string +} + +func (re releaseFromExternalContent) prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string, artifactConfigurations []buildArtifact) (*release, error) { + version := re.rawVersion[1:] + now := time.Now() + isBeta := strings.Contains(version, "beta") + + builds := []build{} + for _, ba := range artifactConfigurations { + sha256, err := re.getter.getContents(fmt.Sprintf("%s.sha256", ba.getUrl(baseArchiveUrl, version, isBeta))) + if err != nil { + return nil, err + } + builds = append(builds, newBuild(baseArchiveUrl, ba, version, isBeta, sha256)) + } + + r := release{ + Version: version, + ReleaseDate: time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local), + Stable: !isBeta, + Beta: isBeta, + Nightly: false, + WhatsNewUrl: whatsNewUrl, + ReleaseNotesUrl: releaseNotesUrl, + Builds: builds, + } + return &r, nil +} + +type urlGetter interface { + getContents(url string) (string, error) +} + +type getHttpContents struct{} + +func (getHttpContents) getContents(url string) (string, error) { + response, err := http.Get(url) + if err != nil { + return "", err + } + + defer response.Body.Close() + all, err := ioutil.ReadAll(response.Body) + if err != nil { + return "", err + } + + return string(all), nil +} diff --git a/scripts/build/release_publisher/publisher.go b/scripts/build/release_publisher/publisher.go index 2bd84462daa..e8c3b676e59 100644 --- a/scripts/build/release_publisher/publisher.go +++ b/scripts/build/release_publisher/publisher.go @@ -141,38 +141,6 @@ var buildArtifactConfigurations = []buildArtifact{ }, } -type releaseFromExternalContent struct { - getter urlGetter - rawVersion string -} - -func (re releaseFromExternalContent) prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string, artifactConfigurations []buildArtifact) (*release, error) { - version := re.rawVersion[1:] - now := time.Now() - isBeta := strings.Contains(version, "beta") - - builds := []build{} - for _, ba := range artifactConfigurations { - sha256, err := re.getter.getContents(fmt.Sprintf("%s.sha256", ba.getUrl(baseArchiveUrl, version, isBeta))) - if err != nil { - return nil, err - } - builds = append(builds, newBuild(baseArchiveUrl, ba, version, isBeta, sha256)) - } - - r := release{ - Version: version, - ReleaseDate: time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local), - Stable: !isBeta, - Beta: isBeta, - Nightly: false, - WhatsNewUrl: whatsNewUrl, - ReleaseNotesUrl: releaseNotesUrl, - Builds: builds, - } - return &r, nil -} - func newBuild(baseArchiveUrl string, ba buildArtifact, version string, isBeta bool, sha256 string) build { return build{ Os: ba.os, @@ -251,24 +219,3 @@ type build struct { Sha256 string `json:"sha256"` Arch string `json:"arch"` } - -type urlGetter interface { - getContents(url string) (string, error) -} - -type getHttpContents struct{} - -func (getHttpContents) getContents(url string) (string, error) { - response, err := http.Get(url) - if err != nil { - return "", err - } - - defer response.Body.Close() - all, err := ioutil.ReadAll(response.Body) - if err != nil { - return "", err - } - - return string(all), nil -} diff --git a/scripts/build/release_publisher/publisher_test.go b/scripts/build/release_publisher/publisher_test.go index 38a9d972380..04a9c0ca54c 100644 --- a/scripts/build/release_publisher/publisher_test.go +++ b/scripts/build/release_publisher/publisher_test.go @@ -2,7 +2,7 @@ package main import "testing" -func TestNewRelease(t *testing.T) { +func TestPreparingReleaseFromRemote(t *testing.T) { versionIn := "v5.2.0-beta1" expectedVersion := "5.2.0-beta1" whatsNewUrl := "https://whatsnews.foo/" @@ -46,3 +46,8 @@ type mockHttpGetter struct{} func (mockHttpGetter) getContents(url string) (string, error) { return url, nil } + + +func TestPreparingReleaseFromLocal(t *testing.T) { + +} From d9eaec99e2f04eea03fa9ab0a0971d9d1290f91a Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Fri, 2 Nov 2018 14:56:46 +0100 Subject: [PATCH 39/55] build: publisher can find artifacts from local sources. --- ...nterprise-5.4.0-123pre1.linux-amd64.tar.gz | 0 ...se-5.4.0-123pre1.linux-amd64.tar.gz.sha256 | 1 + ...enterprise-5.4.0-123pre1.windows-amd64.zip | 0 ...ise-5.4.0-123pre1.windows-amd64.zip.sha256 | 1 + ...rafana-enterprise-5.4.0-123pre1.x86_64.rpm | 0 ...enterprise-5.4.0-123pre1.x86_64.rpm.sha256 | 1 + ...grafana-enterprise_5.4.0-123pre1_amd64.deb | 0 ...-enterprise_5.4.0-123pre1_amd64.deb.sha256 | 1 + .../build/release_publisher/localrelease.go | 91 +++++++++++++++++++ .../build/release_publisher/publisher_test.go | 59 +++++++++++- 10 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz create mode 100644 scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz.sha256 create mode 100644 scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.windows-amd64.zip create mode 100644 scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.windows-amd64.zip.sha256 create mode 100644 scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.x86_64.rpm create mode 100644 scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.x86_64.rpm.sha256 create mode 100644 scripts/build/release_publisher/local_test_data/grafana-enterprise_5.4.0-123pre1_amd64.deb create mode 100644 scripts/build/release_publisher/local_test_data/grafana-enterprise_5.4.0-123pre1_amd64.deb.sha256 create mode 100644 scripts/build/release_publisher/localrelease.go diff --git a/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz b/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz new file mode 100644 index 00000000000..e69de29bb2d diff --git a/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz.sha256 b/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz.sha256 new file mode 100644 index 00000000000..c3068040269 --- /dev/null +++ b/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz.sha256 @@ -0,0 +1 @@ +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 diff --git a/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.windows-amd64.zip b/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.windows-amd64.zip new file mode 100644 index 00000000000..e69de29bb2d diff --git a/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.windows-amd64.zip.sha256 b/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.windows-amd64.zip.sha256 new file mode 100644 index 00000000000..c3068040269 --- /dev/null +++ b/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.windows-amd64.zip.sha256 @@ -0,0 +1 @@ +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 diff --git a/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.x86_64.rpm b/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.x86_64.rpm new file mode 100644 index 00000000000..e69de29bb2d diff --git a/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.x86_64.rpm.sha256 b/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.x86_64.rpm.sha256 new file mode 100644 index 00000000000..c3068040269 --- /dev/null +++ b/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.x86_64.rpm.sha256 @@ -0,0 +1 @@ +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 diff --git a/scripts/build/release_publisher/local_test_data/grafana-enterprise_5.4.0-123pre1_amd64.deb b/scripts/build/release_publisher/local_test_data/grafana-enterprise_5.4.0-123pre1_amd64.deb new file mode 100644 index 00000000000..e69de29bb2d diff --git a/scripts/build/release_publisher/local_test_data/grafana-enterprise_5.4.0-123pre1_amd64.deb.sha256 b/scripts/build/release_publisher/local_test_data/grafana-enterprise_5.4.0-123pre1_amd64.deb.sha256 new file mode 100644 index 00000000000..c3068040269 --- /dev/null +++ b/scripts/build/release_publisher/local_test_data/grafana-enterprise_5.4.0-123pre1_amd64.deb.sha256 @@ -0,0 +1 @@ +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 diff --git a/scripts/build/release_publisher/localrelease.go b/scripts/build/release_publisher/localrelease.go new file mode 100644 index 00000000000..45bebc524e4 --- /dev/null +++ b/scripts/build/release_publisher/localrelease.go @@ -0,0 +1,91 @@ +package main + +import ( + "fmt" + "github.com/pkg/errors" + "io/ioutil" + "log" + "os" + "path/filepath" + "regexp" + "strings" + "time" +) + +type releaseLocalSources struct { + path string +} + +func (r releaseLocalSources) prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string, artifactConfigurations []buildArtifact) (*release, error) { + buildData := r.findBuilds(artifactConfigurations, baseArchiveUrl) + + rel := release{ + Version: buildData.version, + ReleaseDate: time.Time{}, + Stable: false, + Beta: false, + Nightly: true, + WhatsNewUrl: whatsNewUrl, + ReleaseNotesUrl: releaseNotesUrl, + Builds: buildData.builds, + } + + return &rel, nil +} + +type buildData struct { + version string + builds []build +} + +func (r releaseLocalSources) findBuilds(buildArtifacts []buildArtifact, baseArchiveUrl string) buildData { + data := buildData{} + filepath.Walk(r.path, createBuildWalker(r.path, &data, buildArtifacts, baseArchiveUrl)) + return data +} + +func createBuildWalker(path string, data *buildData, archiveTypes []buildArtifact, baseArchiveUrl string) func(path string, f os.FileInfo, err error) error { + return func(path string, f os.FileInfo, err error) error { + if err != nil { + log.Printf("error: %v", err) + } + + if f.Name() == path || strings.HasSuffix(f.Name(), ".sha256") { + return nil + } + + shaBytes, err := ioutil.ReadFile(path + ".sha256") + if err != nil { + log.Fatalf("Failed to read sha256 file %v", err) + } + + + for _, archive := range archiveTypes { + if strings.HasSuffix(f.Name(), archive.urlPostfix) { + version, err := grabVersion(f.Name(), archive.urlPostfix) + if err != nil { + log.Println(err) + continue + } + data.version = version + data.builds = append(data.builds, build{ + Os: archive.os, + Url: archive.getUrl(baseArchiveUrl, version, false), + Sha256: string(shaBytes), + Arch: archive.arch, + }) + return nil + } + } + return nil + } + +} +func grabVersion(name string, suffix string) (string, error) { + match := regexp.MustCompile(fmt.Sprintf(`grafana(-enterprise)?[-_](.*)%s`, suffix)).FindSubmatch([]byte(name)) + if len(match) > 0 { + return string(match[2]), nil + } + + return "", errors.New("No version found.") +} diff --git a/scripts/build/release_publisher/publisher_test.go b/scripts/build/release_publisher/publisher_test.go index 04a9c0ca54c..14e92f01bc0 100644 --- a/scripts/build/release_publisher/publisher_test.go +++ b/scripts/build/release_publisher/publisher_test.go @@ -11,7 +11,9 @@ func TestPreparingReleaseFromRemote(t *testing.T) { expectedOs := "linux" buildArtifacts := []buildArtifact{{expectedOs,expectedArch, ".linux-amd64.tar.gz"}} - builder := releaseFromExternalContent{ + var builder releaseBuilder + + builder = releaseFromExternalContent{ getter: mockHttpGetter{}, rawVersion: versionIn, } @@ -49,5 +51,60 @@ func (mockHttpGetter) getContents(url string) (string, error) { func TestPreparingReleaseFromLocal(t *testing.T) { + whatsNewUrl := "https://whatsnews.foo/" + relNotesUrl := "https://relnotes.foo/" + expectedVersion := "5.4.0-123pre1" + expectedBuilds := 4 + var builder releaseBuilder + builder = releaseLocalSources{ + path: "local_test_data", + } + + relAll, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-enterprise-releases/master/grafana-enterprise", whatsNewUrl, relNotesUrl, buildArtifactConfigurations) + + if relAll.Stable || !relAll.Nightly { + t.Error("Expected a nightly release but wasn't.") + } + + if relAll.ReleaseNotesUrl != relNotesUrl { + t.Errorf("expected releaseNotesUrl to be %s, but it was %s", relNotesUrl, relAll.ReleaseNotesUrl) + } + if relAll.WhatsNewUrl != whatsNewUrl { + t.Errorf("expected whatsNewUrl to be %s, but it was %s", whatsNewUrl, relAll.WhatsNewUrl) + } + + if relAll.Beta { + t.Errorf("Expected release to be nightly, not beta.") + } + + if relAll.Version != expectedVersion { + t.Errorf("Expected version=%s, but got=%s", expectedVersion, relAll.Version) + } + + if len(relAll.Builds) != expectedBuilds { + t.Errorf("Expected %v builds, but was %v", expectedBuilds, len(relAll.Builds)) + } + + expectedArch := "amd64" + expectedOs := "win" + relOne, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-enterprise-releases/master/grafana-enterprise", whatsNewUrl, relNotesUrl, []buildArtifact{{ + os: expectedOs, + arch: expectedArch, + urlPostfix: ".windows-amd64.zip", + }}) + + if len(relOne.Builds) != 1 { + t.Errorf("Expected 1 artifact, but was %v", len(relOne.Builds)) + } + + build := relOne.Builds[0] + + if build.Arch != expectedArch { + t.Fatalf("Expected arch to be %s, but was %s", expectedArch, build.Arch) + } + + if build.Os != expectedOs { + t.Fatalf("Expected os to be %s, but was %s", expectedOs, build.Os) + } } From 5da9760aebb19138c69bf7316e359730543b749a Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 5 Nov 2018 09:51:54 +0100 Subject: [PATCH 40/55] build: publisher supports both local and remote. --- .../release_publisher/externalrelease.go | 5 +-- .../build/release_publisher/localrelease.go | 23 ++++++------- scripts/build/release_publisher/main.go | 33 ++++++++++++++----- scripts/build/release_publisher/publisher.go | 8 ++--- .../build/release_publisher/publisher_test.go | 25 +++++++++----- 5 files changed, 61 insertions(+), 33 deletions(-) diff --git a/scripts/build/release_publisher/externalrelease.go b/scripts/build/release_publisher/externalrelease.go index 795e3bc999b..2d69fa604f8 100644 --- a/scripts/build/release_publisher/externalrelease.go +++ b/scripts/build/release_publisher/externalrelease.go @@ -11,15 +11,16 @@ import ( type releaseFromExternalContent struct { getter urlGetter rawVersion string + artifactConfigurations []buildArtifact } -func (re releaseFromExternalContent) prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string, artifactConfigurations []buildArtifact) (*release, error) { +func (re releaseFromExternalContent) prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string) (*release, error) { version := re.rawVersion[1:] now := time.Now() isBeta := strings.Contains(version, "beta") builds := []build{} - for _, ba := range artifactConfigurations { + for _, ba := range re.artifactConfigurations { sha256, err := re.getter.getContents(fmt.Sprintf("%s.sha256", ba.getUrl(baseArchiveUrl, version, isBeta))) if err != nil { return nil, err diff --git a/scripts/build/release_publisher/localrelease.go b/scripts/build/release_publisher/localrelease.go index 45bebc524e4..1fb266aa041 100644 --- a/scripts/build/release_publisher/localrelease.go +++ b/scripts/build/release_publisher/localrelease.go @@ -14,14 +14,16 @@ import ( type releaseLocalSources struct { path string + artifactConfigurations []buildArtifact } -func (r releaseLocalSources) prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string, artifactConfigurations []buildArtifact) (*release, error) { - buildData := r.findBuilds(artifactConfigurations, baseArchiveUrl) +func (r releaseLocalSources) prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string) (*release, error) { + buildData := r.findBuilds(baseArchiveUrl) + now := time.Now() rel := release{ Version: buildData.version, - ReleaseDate: time.Time{}, + ReleaseDate: time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local), Stable: false, Beta: false, Nightly: true, @@ -38,9 +40,9 @@ type buildData struct { builds []build } -func (r releaseLocalSources) findBuilds(buildArtifacts []buildArtifact, baseArchiveUrl string) buildData { +func (r releaseLocalSources) findBuilds(baseArchiveUrl string) buildData { data := buildData{} - filepath.Walk(r.path, createBuildWalker(r.path, &data, buildArtifacts, baseArchiveUrl)) + filepath.Walk(r.path, createBuildWalker(r.path, &data, r.artifactConfigurations, baseArchiveUrl)) return data } @@ -54,14 +56,13 @@ func createBuildWalker(path string, data *buildData, archiveTypes []buildArtifac return nil } - shaBytes, err := ioutil.ReadFile(path + ".sha256") - if err != nil { - log.Fatalf("Failed to read sha256 file %v", err) - } - - for _, archive := range archiveTypes { if strings.HasSuffix(f.Name(), archive.urlPostfix) { + shaBytes, err := ioutil.ReadFile(path + ".sha256") + if err != nil { + log.Fatalf("Failed to read sha256 file %v", err) + } + version, err := grabVersion(f.Name(), archive.urlPostfix) if err != nil { log.Println(err) diff --git a/scripts/build/release_publisher/main.go b/scripts/build/release_publisher/main.go index 8b3ec8da74e..66ab38ab00e 100644 --- a/scripts/build/release_publisher/main.go +++ b/scripts/build/release_publisher/main.go @@ -13,6 +13,7 @@ func main() { var releaseNotesUrl string var dryRun bool var enterprise bool + var fromLocal bool var apiKey string flag.StringVar(&version, "version", "", "Grafana version (ex: --version v5.2.0-beta1)") @@ -21,6 +22,7 @@ func main() { flag.StringVar(&apiKey, "apikey", "", "Grafana.com API key (ex: --apikey ABCDEF)") flag.BoolVar(&dryRun, "dry-run", false, "--dry-run") flag.BoolVar(&enterprise, "enterprise", false, "--enterprise") + flag.BoolVar(&fromLocal, "from-local", false, "--from-local") flag.Parse() if len(os.Args) == 1 { @@ -33,24 +35,39 @@ func main() { log.Println("Dry-run has been enabled.") } var baseUrl string + var builder releaseBuilder + var product string + + if fromLocal { + path, _ := os.Getwd() + builder = releaseLocalSources{ + path: path, + artifactConfigurations: buildArtifactConfigurations, + } + } else { + builder = releaseFromExternalContent{ + getter: getHttpContents{}, + rawVersion: version, + artifactConfigurations: buildArtifactConfigurations, + } + } if enterprise { - baseUrl = fmt.Sprintf("https://s3-us-west-2.amazonaws.com/%s", "grafana-enterprise-releases/release/grafana-enterprise") + baseUrl = "https://s3-us-west-2.amazonaws.com/grafana-enterprise-releases/release/grafana-enterprise" + product = "grafana-enterprise" } else { - baseUrl = fmt.Sprintf("https://s3-us-west-2.amazonaws.com/%s", "grafana-releases/release/grafana") + baseUrl = "https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana" + product = "grafana" } p := publisher{ apiKey: apiKey, - baseUri: "https://grafana.com/api", - product: "grafana", + apiUri: "https://grafana.com/api", + product: product, dryRun: dryRun, enterprise: enterprise, baseArchiveUrl: baseUrl, - builder: releaseFromExternalContent{ - getter: getHttpContents{}, - rawVersion: version, - }, + builder: builder, } if err := p.doRelease(whatsNewUrl, releaseNotesUrl); err != nil { log.Fatalf("error: %v", err) diff --git a/scripts/build/release_publisher/publisher.go b/scripts/build/release_publisher/publisher.go index e8c3b676e59..0874c1357b6 100644 --- a/scripts/build/release_publisher/publisher.go +++ b/scripts/build/release_publisher/publisher.go @@ -13,7 +13,7 @@ import ( type publisher struct { apiKey string - baseUri string + apiUri string product string dryRun bool enterprise bool @@ -22,11 +22,11 @@ type publisher struct { } type releaseBuilder interface { - prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string, artifactConfigurations []buildArtifact) (*release, error) + prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string) (*release, error) } func (p *publisher) doRelease(whatsNewUrl string, releaseNotesUrl string) error { - currentRelease, err := p.builder.prepareRelease(p.baseArchiveUrl, whatsNewUrl, releaseNotesUrl, buildArtifactConfigurations) + currentRelease, err := p.builder.prepareRelease(p.baseArchiveUrl, whatsNewUrl, releaseNotesUrl) if err != nil { return err } @@ -151,7 +151,7 @@ func newBuild(baseArchiveUrl string, ba buildArtifact, version string, isBeta bo } func (p *publisher) apiUrl(url string) string { - return fmt.Sprintf("%s/%s%s", p.baseUri, p.product, url) + return fmt.Sprintf("%s/%s%s", p.apiUri, p.product, url) } func (p *publisher) postRequest(url string, obj interface{}, desc string) error { diff --git a/scripts/build/release_publisher/publisher_test.go b/scripts/build/release_publisher/publisher_test.go index 14e92f01bc0..fed17007bb6 100644 --- a/scripts/build/release_publisher/publisher_test.go +++ b/scripts/build/release_publisher/publisher_test.go @@ -16,9 +16,10 @@ func TestPreparingReleaseFromRemote(t *testing.T) { builder = releaseFromExternalContent{ getter: mockHttpGetter{}, rawVersion: versionIn, + artifactConfigurations: buildArtifactConfigurations, } - rel, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana", whatsNewUrl, relNotesUrl, buildArtifacts) + rel, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana", whatsNewUrl, relNotesUrl) if !rel.Beta || rel.Stable { t.Errorf("%s should have been tagged as beta (not stable), but wasn't .", versionIn) @@ -57,11 +58,13 @@ func TestPreparingReleaseFromLocal(t *testing.T) { expectedBuilds := 4 var builder releaseBuilder + testDataPath := "local_test_data" builder = releaseLocalSources{ - path: "local_test_data", + path: testDataPath, + artifactConfigurations: buildArtifactConfigurations, } - relAll, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-enterprise-releases/master/grafana-enterprise", whatsNewUrl, relNotesUrl, buildArtifactConfigurations) + relAll, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-enterprise-releases/master/grafana-enterprise", whatsNewUrl, relNotesUrl) if relAll.Stable || !relAll.Nightly { t.Error("Expected a nightly release but wasn't.") @@ -88,11 +91,17 @@ func TestPreparingReleaseFromLocal(t *testing.T) { expectedArch := "amd64" expectedOs := "win" - relOne, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-enterprise-releases/master/grafana-enterprise", whatsNewUrl, relNotesUrl, []buildArtifact{{ - os: expectedOs, - arch: expectedArch, - urlPostfix: ".windows-amd64.zip", - }}) + + builder = releaseLocalSources{ + path: testDataPath, + artifactConfigurations: []buildArtifact{{ + os: expectedOs, + arch: expectedArch, + urlPostfix: ".windows-amd64.zip", + }}, + } + + relOne, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-enterprise-releases/master/grafana-enterprise", whatsNewUrl, relNotesUrl) if len(relOne.Builds) != 1 { t.Errorf("Expected 1 artifact, but was %v", len(relOne.Builds)) From d728a3c521683ec5007fc09b5dd7b62e480c6686 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 5 Nov 2018 14:52:23 +0100 Subject: [PATCH 41/55] build: publisher uses local time. Previously the local day was used but the timestamp was set to midnight. --- scripts/build/release_publisher/externalrelease.go | 3 +-- scripts/build/release_publisher/localrelease.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/build/release_publisher/externalrelease.go b/scripts/build/release_publisher/externalrelease.go index 2d69fa604f8..2c9f9e1631a 100644 --- a/scripts/build/release_publisher/externalrelease.go +++ b/scripts/build/release_publisher/externalrelease.go @@ -16,7 +16,6 @@ type releaseFromExternalContent struct { func (re releaseFromExternalContent) prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string) (*release, error) { version := re.rawVersion[1:] - now := time.Now() isBeta := strings.Contains(version, "beta") builds := []build{} @@ -30,7 +29,7 @@ func (re releaseFromExternalContent) prepareRelease(baseArchiveUrl, whatsNewUrl r := release{ Version: version, - ReleaseDate: time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local), + ReleaseDate: time.Now(), Stable: !isBeta, Beta: isBeta, Nightly: false, diff --git a/scripts/build/release_publisher/localrelease.go b/scripts/build/release_publisher/localrelease.go index 1fb266aa041..bc2e95f7cd8 100644 --- a/scripts/build/release_publisher/localrelease.go +++ b/scripts/build/release_publisher/localrelease.go @@ -20,10 +20,9 @@ type releaseLocalSources struct { func (r releaseLocalSources) prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string) (*release, error) { buildData := r.findBuilds(baseArchiveUrl) - now := time.Now() rel := release{ Version: buildData.version, - ReleaseDate: time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local), + ReleaseDate: time.Now(), Stable: false, Beta: false, Nightly: true, From 30e924611d18965e875874d8186a51cf4367f123 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 5 Nov 2018 15:42:26 +0100 Subject: [PATCH 42/55] changelog: adds note about closing #13322 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d139009ac78..2a30b44161a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ # 5.3.3 (unreleased) +* **Alerting**: Delete alerts when parent folder was deleted [#13322](https://github.com/grafana/grafana/issues/13322) * **MySQL**: Fix `$__timeFilter()` should respect local time zone [#13769](https://github.com/grafana/grafana/issues/13769) # 5.3.2 (2018-10-24) From 1de35c43a79d913cbb59cd8531df48bb267f8b61 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 5 Nov 2018 16:26:19 +0100 Subject: [PATCH 43/55] build: publishes grafana enterprise to grafana.com --- .circleci/config.yml | 3 +++ scripts/build/publish.sh | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index af5acef4bea..5907e8d4862 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -335,6 +335,9 @@ jobs: - run: name: deploy to gcp command: '/opt/google-cloud-sdk/bin/gsutil cp ./enterprise-dist/* gs://$GCP_BUCKET_NAME/enterprise/master' + - run: + name: Deploy to grafana.com + command: 'cd enterprise-dist && scripts/build/release_publisher/release_publisher -apikey ${GRAFANA_COM_API_KEY} -enterprise -from-local' deploy-enterprise-release: diff --git a/scripts/build/publish.sh b/scripts/build/publish.sh index b3fab180ac9..c03146eb910 100755 --- a/scripts/build/publish.sh +++ b/scripts/build/publish.sh @@ -1,4 +1,4 @@ -#/bin/sh +#!/bin/sh # no relation to publish.go From 272c43f7b73e49d8acaf883cb9b4b4c4ed89c386 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 5 Nov 2018 16:57:30 +0100 Subject: [PATCH 44/55] build: minor publisher fixes. --- .circleci/config.yml | 2 +- scripts/build/release_publisher/externalrelease.go | 2 +- scripts/build/release_publisher/localrelease.go | 2 +- scripts/build/release_publisher/publisher_test.go | 2 +- .../grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz | 0 .../grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz.sha256 | 0 .../grafana-enterprise-5.4.0-123pre1.windows-amd64.zip | 0 .../grafana-enterprise-5.4.0-123pre1.windows-amd64.zip.sha256 | 0 .../grafana-enterprise-5.4.0-123pre1.x86_64.rpm | 0 .../grafana-enterprise-5.4.0-123pre1.x86_64.rpm.sha256 | 0 .../grafana-enterprise_5.4.0-123pre1_amd64.deb | 0 .../grafana-enterprise_5.4.0-123pre1_amd64.deb.sha256 | 0 12 files changed, 4 insertions(+), 4 deletions(-) rename scripts/build/release_publisher/{local_test_data => testdata}/grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz (100%) rename scripts/build/release_publisher/{local_test_data => testdata}/grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz.sha256 (100%) rename scripts/build/release_publisher/{local_test_data => testdata}/grafana-enterprise-5.4.0-123pre1.windows-amd64.zip (100%) rename scripts/build/release_publisher/{local_test_data => testdata}/grafana-enterprise-5.4.0-123pre1.windows-amd64.zip.sha256 (100%) rename scripts/build/release_publisher/{local_test_data => testdata}/grafana-enterprise-5.4.0-123pre1.x86_64.rpm (100%) rename scripts/build/release_publisher/{local_test_data => testdata}/grafana-enterprise-5.4.0-123pre1.x86_64.rpm.sha256 (100%) rename scripts/build/release_publisher/{local_test_data => testdata}/grafana-enterprise_5.4.0-123pre1_amd64.deb (100%) rename scripts/build/release_publisher/{local_test_data => testdata}/grafana-enterprise_5.4.0-123pre1_amd64.deb.sha256 (100%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5907e8d4862..b173dbff481 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -337,7 +337,7 @@ jobs: command: '/opt/google-cloud-sdk/bin/gsutil cp ./enterprise-dist/* gs://$GCP_BUCKET_NAME/enterprise/master' - run: name: Deploy to grafana.com - command: 'cd enterprise-dist && scripts/build/release_publisher/release_publisher -apikey ${GRAFANA_COM_API_KEY} -enterprise -from-local' + command: 'cd enterprise-dist && ../scripts/build/release_publisher/release_publisher -apikey ${GRAFANA_COM_API_KEY} -enterprise -from-local' deploy-enterprise-release: diff --git a/scripts/build/release_publisher/externalrelease.go b/scripts/build/release_publisher/externalrelease.go index 2c9f9e1631a..d6e6a669293 100644 --- a/scripts/build/release_publisher/externalrelease.go +++ b/scripts/build/release_publisher/externalrelease.go @@ -29,7 +29,7 @@ func (re releaseFromExternalContent) prepareRelease(baseArchiveUrl, whatsNewUrl r := release{ Version: version, - ReleaseDate: time.Now(), + ReleaseDate: time.Now().UTC(), Stable: !isBeta, Beta: isBeta, Nightly: false, diff --git a/scripts/build/release_publisher/localrelease.go b/scripts/build/release_publisher/localrelease.go index bc2e95f7cd8..898820f97da 100644 --- a/scripts/build/release_publisher/localrelease.go +++ b/scripts/build/release_publisher/localrelease.go @@ -22,7 +22,7 @@ func (r releaseLocalSources) prepareRelease(baseArchiveUrl, whatsNewUrl string, rel := release{ Version: buildData.version, - ReleaseDate: time.Now(), + ReleaseDate: time.Now().UTC(), Stable: false, Beta: false, Nightly: true, diff --git a/scripts/build/release_publisher/publisher_test.go b/scripts/build/release_publisher/publisher_test.go index fed17007bb6..4e553362dcb 100644 --- a/scripts/build/release_publisher/publisher_test.go +++ b/scripts/build/release_publisher/publisher_test.go @@ -58,7 +58,7 @@ func TestPreparingReleaseFromLocal(t *testing.T) { expectedBuilds := 4 var builder releaseBuilder - testDataPath := "local_test_data" + testDataPath := "testdata" builder = releaseLocalSources{ path: testDataPath, artifactConfigurations: buildArtifactConfigurations, diff --git a/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz b/scripts/build/release_publisher/testdata/grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz similarity index 100% rename from scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz rename to scripts/build/release_publisher/testdata/grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz diff --git a/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz.sha256 b/scripts/build/release_publisher/testdata/grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz.sha256 similarity index 100% rename from scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz.sha256 rename to scripts/build/release_publisher/testdata/grafana-enterprise-5.4.0-123pre1.linux-amd64.tar.gz.sha256 diff --git a/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.windows-amd64.zip b/scripts/build/release_publisher/testdata/grafana-enterprise-5.4.0-123pre1.windows-amd64.zip similarity index 100% rename from scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.windows-amd64.zip rename to scripts/build/release_publisher/testdata/grafana-enterprise-5.4.0-123pre1.windows-amd64.zip diff --git a/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.windows-amd64.zip.sha256 b/scripts/build/release_publisher/testdata/grafana-enterprise-5.4.0-123pre1.windows-amd64.zip.sha256 similarity index 100% rename from scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.windows-amd64.zip.sha256 rename to scripts/build/release_publisher/testdata/grafana-enterprise-5.4.0-123pre1.windows-amd64.zip.sha256 diff --git a/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.x86_64.rpm b/scripts/build/release_publisher/testdata/grafana-enterprise-5.4.0-123pre1.x86_64.rpm similarity index 100% rename from scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.x86_64.rpm rename to scripts/build/release_publisher/testdata/grafana-enterprise-5.4.0-123pre1.x86_64.rpm diff --git a/scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.x86_64.rpm.sha256 b/scripts/build/release_publisher/testdata/grafana-enterprise-5.4.0-123pre1.x86_64.rpm.sha256 similarity index 100% rename from scripts/build/release_publisher/local_test_data/grafana-enterprise-5.4.0-123pre1.x86_64.rpm.sha256 rename to scripts/build/release_publisher/testdata/grafana-enterprise-5.4.0-123pre1.x86_64.rpm.sha256 diff --git a/scripts/build/release_publisher/local_test_data/grafana-enterprise_5.4.0-123pre1_amd64.deb b/scripts/build/release_publisher/testdata/grafana-enterprise_5.4.0-123pre1_amd64.deb similarity index 100% rename from scripts/build/release_publisher/local_test_data/grafana-enterprise_5.4.0-123pre1_amd64.deb rename to scripts/build/release_publisher/testdata/grafana-enterprise_5.4.0-123pre1_amd64.deb diff --git a/scripts/build/release_publisher/local_test_data/grafana-enterprise_5.4.0-123pre1_amd64.deb.sha256 b/scripts/build/release_publisher/testdata/grafana-enterprise_5.4.0-123pre1_amd64.deb.sha256 similarity index 100% rename from scripts/build/release_publisher/local_test_data/grafana-enterprise_5.4.0-123pre1_amd64.deb.sha256 rename to scripts/build/release_publisher/testdata/grafana-enterprise_5.4.0-123pre1_amd64.deb.sha256 From 35e62bbbe0eebfc9b2cc025800c4f3982c55e2c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 5 Nov 2018 17:46:09 +0100 Subject: [PATCH 45/55] wip: react panel options architecture --- .../dashboard/dashgrid/PanelChrome.tsx | 25 +++++- .../dashboard/dashgrid/PanelEditor.tsx | 28 ++++--- public/app/features/dashboard/panel_model.ts | 19 ++++- .../features/dashboard/settings/settings.ts | 2 +- public/app/plugins/panel/graph2/module.tsx | 35 +++++--- public/app/types/panel.ts | 9 +- public/app/types/plugins.ts | 11 ++- public/app/viz/Graph.tsx | 84 +++++++++++-------- 8 files changed, 148 insertions(+), 65 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index 82b366d8126..953dfd62368 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -21,6 +21,7 @@ export interface Props { export interface State { refreshCounter: number; + renderCounter: number; timeRange?: TimeRange; } @@ -30,11 +31,13 @@ export class PanelChrome extends PureComponent { this.state = { refreshCounter: 0, + renderCounter: 0, }; } componentDidMount() { this.props.panel.events.on('refresh', this.onRefresh); + this.props.panel.events.on('render', this.onRender); this.props.dashboard.panelInitialized(this.props.panel); } @@ -52,6 +55,13 @@ export class PanelChrome extends PureComponent { }); }; + onRender = () => { + console.log('onRender'); + this.setState({ + renderCounter: this.state.renderCounter + 1, + }); + }; + get isVisible() { return !this.props.dashboard.otherPanelInFullscreen(this.props.panel); } @@ -59,9 +69,11 @@ export class PanelChrome extends PureComponent { render() { const { panel, dashboard } = this.props; const { datasource, targets } = panel; - const { refreshCounter, timeRange } = this.state; + const { timeRange, renderCounter, refreshCounter } = this.state; const PanelComponent = this.props.component; + console.log('Panel chrome render'); + return (
@@ -74,7 +86,16 @@ export class PanelChrome extends PureComponent { refreshCounter={refreshCounter} > {({ loading, timeSeries }) => { - return ; + console.log('panelcrome inner render'); + return ( + + ); }}
diff --git a/public/app/features/dashboard/dashgrid/PanelEditor.tsx b/public/app/features/dashboard/dashgrid/PanelEditor.tsx index 26ac8b7d2c1..9d98621f9b6 100644 --- a/public/app/features/dashboard/dashgrid/PanelEditor.tsx +++ b/public/app/features/dashboard/dashgrid/PanelEditor.tsx @@ -1,12 +1,15 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; import classNames from 'classnames'; + +import { QueriesTab } from './QueriesTab'; +import { VizTypePicker } from './VizTypePicker'; + +import { store } from 'app/store/configureStore'; +import { updateLocation } from 'app/core/actions'; + import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; -import { store } from 'app/store/configureStore'; -import { QueriesTab } from './QueriesTab'; import { PanelPlugin, PluginExports } from 'app/types/plugins'; -import { VizTypePicker } from './VizTypePicker'; -import { updateLocation } from 'app/core/actions'; interface PanelEditorProps { panel: PanelModel; @@ -22,7 +25,7 @@ interface PanelEditorTab { icon: string; } -export class PanelEditor extends React.Component { +export class PanelEditor extends PureComponent { tabs: PanelEditorTab[]; constructor(props) { @@ -39,16 +42,20 @@ export class PanelEditor extends React.Component { } renderPanelOptions() { - const { pluginExports } = this.props; + const { pluginExports, panel } = this.props; - if (pluginExports.PanelOptions) { - const PanelOptions = pluginExports.PanelOptions; - return ; + if (pluginExports.PanelOptionsComponent) { + const OptionsComponent = pluginExports.PanelOptionsComponent; + return ; } else { return

Visualization has no options

; } } + onPanelOptionsChanged = (options: any) => { + this.props.panel.updateOptions(options); + }; + renderVizTab() { return (
@@ -70,6 +77,7 @@ export class PanelEditor extends React.Component { partial: true, }) ); + this.forceUpdate(); }; render() { diff --git a/public/app/features/dashboard/panel_model.ts b/public/app/features/dashboard/panel_model.ts index d82368d8dd7..ed032a118fe 100644 --- a/public/app/features/dashboard/panel_model.ts +++ b/public/app/features/dashboard/panel_model.ts @@ -60,6 +60,21 @@ export class PanelModel { _.defaultsDeep(this, _.cloneDeep(defaults)); } + getOptions() { + return this[this.getOptionsKey()] || {}; + } + + updateOptions(options: object) { + const update: any = {}; + update[this.getOptionsKey()] = options; + Object.assign(this, update); + this.render(); + } + + private getOptionsKey() { + return this.type + 'Options'; + } + getSaveModel() { const model: any = {}; for (const property in this) { @@ -121,10 +136,6 @@ export class PanelModel { this.events.emit('panel-initialized'); } - initEditMode() { - this.events.emit('panel-init-edit-mode'); - } - changeType(pluginId: string) { this.type = pluginId; diff --git a/public/app/features/dashboard/settings/settings.ts b/public/app/features/dashboard/settings/settings.ts index b6a70ee4b98..1e8d96a54cb 100755 --- a/public/app/features/dashboard/settings/settings.ts +++ b/public/app/features/dashboard/settings/settings.ts @@ -32,9 +32,9 @@ export class SettingsCtrl { this.$scope.$on('$destroy', () => { this.dashboard.updateSubmenuVisibility(); - this.dashboard.startRefresh(); setTimeout(() => { this.$rootScope.appEvent('dash-scroll', { restore: true }); + this.dashboard.startRefresh(); }); }); diff --git a/public/app/plugins/panel/graph2/module.tsx b/public/app/plugins/panel/graph2/module.tsx index 7bb2ab48e40..68068268dd4 100644 --- a/public/app/plugins/panel/graph2/module.tsx +++ b/public/app/plugins/panel/graph2/module.tsx @@ -1,13 +1,10 @@ -// Libraries import _ from 'lodash'; import React, { PureComponent } from 'react'; -// Components import Graph from 'app/viz/Graph'; -import { getTimeSeriesVMs } from 'app/viz/state/timeSeries'; import { Switch } from 'app/core/components/Switch/Switch'; -// Types +import { getTimeSeriesVMs } from 'app/viz/state/timeSeries'; import { PanelProps, NullValueMode } from 'app/types'; interface Options { @@ -18,9 +15,7 @@ interface Options { onChange: (options: Options) => void; } -interface Props extends PanelProps { - options: Options; -} +interface Props extends PanelProps {} export class Graph2 extends PureComponent { constructor(props) { @@ -29,17 +24,26 @@ export class Graph2 extends PureComponent { render() { const { timeSeries, timeRange } = this.props; + const { showLines, showBars, showPoints } = this.props.options; const vmSeries = getTimeSeriesVMs({ timeSeries: timeSeries, nullValueMode: NullValueMode.Ignore, }); - return ; + return ( + + ); } } -export class TextOptions extends PureComponent { +export class GraphOptions extends PureComponent { onToggleLines = () => { const options = this.props as Options; @@ -49,6 +53,15 @@ export class TextOptions extends PureComponent { }); }; + onTogglePoints = () => { + const options = this.props as Options; + + this.props.onChange({ + ...options, + showPoints: !this.props.showPoints, + }); + }; + render() { const { showBars, showPoints, showLines } = this.props; @@ -58,11 +71,11 @@ export class TextOptions extends PureComponent {
Draw Modes
- +
); } } -export { Graph2 as PanelComponent, TextOptions as PanelOptions }; +export { Graph2 as PanelComponent, GraphOptions as PanelOptionsComponent }; diff --git a/public/app/types/panel.ts b/public/app/types/panel.ts index 5ece77fc5aa..5207c17ada9 100644 --- a/public/app/types/panel.ts +++ b/public/app/types/panel.ts @@ -1,7 +1,14 @@ import { LoadingState, TimeSeries, TimeRange } from './series'; -export interface PanelProps { +export interface PanelProps { timeSeries: TimeSeries[]; timeRange: TimeRange; loading: LoadingState; + options: T; + renderCounter: number; +} + +export interface PanelOptionProps { + options: T; + onChange: (options: T) => void; } diff --git a/public/app/types/plugins.ts b/public/app/types/plugins.ts index 1b5499f88b7..4b172c0eef4 100644 --- a/public/app/types/plugins.ts +++ b/public/app/types/plugins.ts @@ -1,13 +1,18 @@ +import { ComponentClass } from 'react'; +import { PanelProps, PanelOptionProps } from './panel'; + export interface PluginExports { - PanelCtrl?; - PanelComponent?: any; Datasource?: any; QueryCtrl?: any; ConfigCtrl?: any; AnnotationsQueryCtrl?: any; - PanelOptions?: any; ExploreQueryField?: any; ExploreStartPage?: any; + + // Panel plugin + PanelCtrl?; + PanelComponent?: ComponentClass; + PanelOptionsComponent: ComponentClass; } export interface PanelPlugin { diff --git a/public/app/viz/Graph.tsx b/public/app/viz/Graph.tsx index fab65225715..5d99f4e0c7f 100644 --- a/public/app/viz/Graph.tsx +++ b/public/app/viz/Graph.tsx @@ -34,37 +34,22 @@ function time_format(ticks, min, max) { return '%H:%M'; } -const FLOT_OPTIONS = { - legend: { - show: false, - }, - series: { - lines: { - linewidth: 1, - zero: false, - }, - shadowSize: 0, - }, - grid: { - minBorderMargin: 0, - markings: [], - backgroundColor: null, - borderWidth: 0, - // hoverable: true, - clickable: true, - color: '#a1a1a1', - margin: { left: 0, right: 0 }, - labelMarginX: 0, - }, -}; - interface GraphProps { timeSeries: TimeSeriesVMs; timeRange: TimeRange; + showLines?: boolean; + showPoints?: boolean; + showBars?: boolean; size?: { width: number; height: number }; } export class Graph extends PureComponent { + static defaultProps = { + showLines: true, + showPoints: false, + showBars: false, + }; + element: any; componentDidUpdate(prevProps: GraphProps) { @@ -82,7 +67,7 @@ export class Graph extends PureComponent { } draw() { - const { size, timeSeries, timeRange } = this.props; + const { size, timeSeries, timeRange, showLines, showBars, showPoints } = this.props; if (!size) { return; @@ -92,7 +77,31 @@ export class Graph extends PureComponent { const min = timeRange.from.valueOf(); const max = timeRange.to.valueOf(); - const dynamicOptions = { + const flotOptions = { + legend: { + show: false, + }, + series: { + lines: { + show: showLines, + linewidth: 1, + zero: false, + }, + points: { + show: showPoints, + fill: 1, + fillColor: false, + radius: 2, + }, + bars: { + show: showBars, + fill: 1, + barWidth: 1, + zero: false, + lineWidth: 0, + }, + shadowSize: 0, + }, xaxis: { mode: 'time', min: min, @@ -101,15 +110,24 @@ export class Graph extends PureComponent { ticks: ticks, timeformat: time_format(ticks, min, max), }, + grid: { + minBorderMargin: 0, + markings: [], + backgroundColor: null, + borderWidth: 0, + // hoverable: true, + clickable: true, + color: '#a1a1a1', + margin: { left: 0, right: 0 }, + labelMarginX: 0, + }, }; - const options = { - ...FLOT_OPTIONS, - ...dynamicOptions, - }; - - console.log('plot', timeSeries, options); - $.plot(this.element, timeSeries, options); + try { + $.plot(this.element, timeSeries, flotOptions); + } catch (err) { + console.log('Graph rendering error', err, flotOptions, timeSeries); + } } render() { From 9cce0f553a554d037b581d132cad69f1d07d5818 Mon Sep 17 00:00:00 2001 From: Zac Coffy Date: Mon, 5 Nov 2018 13:15:03 -0500 Subject: [PATCH 46/55] Adding Cloudwatch AWS/Connect metrics and dimensions --- pkg/tsdb/cloudwatch/metric_find_query.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go index 718f9e0d253..1a860519f2b 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query.go +++ b/pkg/tsdb/cloudwatch/metric_find_query.go @@ -46,6 +46,7 @@ func init() { "AWS/Billing": {"EstimatedCharges"}, "AWS/CloudFront": {"Requests", "BytesDownloaded", "BytesUploaded", "TotalErrorRate", "4xxErrorRate", "5xxErrorRate"}, "AWS/CloudSearch": {"SuccessfulRequests", "SearchableDocuments", "IndexUtilization", "Partitions"}, + "AWS/Connect": {"CallsBreachingConcurrencyQuota", "CallBackNotDialableNumber", "CallRecordingUploadError", "CallsPerInterval", "ConcurrentCalls", "ConcurrentCallsPercentage", "ContactFlowErrors", "ContactFlowFatalErrors", "LongestQueueWaitTime", "MissedCalls", "MisconfiguredPhoneNumbers", "PublicSigningKeyUsage", "QueueCapacityExceededError", "QueueSize", "ThrottledCalls", "ToInstancePacketLossRate"}, "AWS/DMS": {"FreeableMemory", "WriteIOPS", "ReadIOPS", "WriteThroughput", "ReadThroughput", "WriteLatency", "ReadLatency", "SwapUsage", "NetworkTransmitThroughput", "NetworkReceiveThroughput", "FullLoadThroughputBandwidthSource", "FullLoadThroughputBandwidthTarget", "FullLoadThroughputRowsSource", "FullLoadThroughputRowsTarget", "CDCIncomingChanges", "CDCChangesMemorySource", "CDCChangesMemoryTarget", "CDCChangesDiskSource", "CDCChangesDiskTarget", "CDCThroughputBandwidthTarget", "CDCThroughputRowsSource", "CDCThroughputRowsTarget", "CDCLatencySource", "CDCLatencyTarget"}, "AWS/DX": {"ConnectionState", "ConnectionBpsEgress", "ConnectionBpsIngress", "ConnectionPpsEgress", "ConnectionPpsIngress", "ConnectionCRCErrorCount", "ConnectionLightLevelTx", "ConnectionLightLevelRx"}, "AWS/DynamoDB": {"ConditionalCheckFailedRequests", "ConsumedReadCapacityUnits", "ConsumedWriteCapacityUnits", "OnlineIndexConsumedWriteCapacity", "OnlineIndexPercentageProgress", "OnlineIndexThrottleEvents", "ProvisionedReadCapacityUnits", "ProvisionedWriteCapacityUnits", "ReadThrottleEvents", "ReturnedBytes", "ReturnedItemCount", "ReturnedRecordsCount", "SuccessfulRequestLatency", "SystemErrors", "TimeToLiveDeletedItemCount", "ThrottledRequests", "UserErrors", "WriteThrottleEvents"}, @@ -120,6 +121,7 @@ func init() { "AWS/Billing": {"ServiceName", "LinkedAccount", "Currency"}, "AWS/CloudFront": {"DistributionId", "Region"}, "AWS/CloudSearch": {}, + "AWS/Connect": {"InstanceId", "MetricGroup", "Participant", "QueueName", "Stream Type", "Type of Connection"}, "AWS/DMS": {"ReplicationInstanceIdentifier", "ReplicationTaskIdentifier"}, "AWS/DX": {"ConnectionId"}, "AWS/DynamoDB": {"TableName", "GlobalSecondaryIndexName", "Operation", "StreamLabel"}, From 9393b06166564bd80d483aff48fef145d35e6e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 5 Nov 2018 10:31:39 -0800 Subject: [PATCH 47/55] basic panel options working --- .../dashboard/dashgrid/PanelEditor.tsx | 1 + public/app/plugins/panel/graph2/module.tsx | 27 ++++++++++--------- public/app/types/index.ts | 3 ++- public/app/types/panel.ts | 2 +- public/app/types/plugins.ts | 4 +-- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelEditor.tsx b/public/app/features/dashboard/dashgrid/PanelEditor.tsx index 9d98621f9b6..371ed22fff7 100644 --- a/public/app/features/dashboard/dashgrid/PanelEditor.tsx +++ b/public/app/features/dashboard/dashgrid/PanelEditor.tsx @@ -54,6 +54,7 @@ export class PanelEditor extends PureComponent { onPanelOptionsChanged = (options: any) => { this.props.panel.updateOptions(options); + this.forceUpdate(); }; renderVizTab() { diff --git a/public/app/plugins/panel/graph2/module.tsx b/public/app/plugins/panel/graph2/module.tsx index 68068268dd4..a666d762062 100644 --- a/public/app/plugins/panel/graph2/module.tsx +++ b/public/app/plugins/panel/graph2/module.tsx @@ -5,7 +5,7 @@ import Graph from 'app/viz/Graph'; import { Switch } from 'app/core/components/Switch/Switch'; import { getTimeSeriesVMs } from 'app/viz/state/timeSeries'; -import { PanelProps, NullValueMode } from 'app/types'; +import { PanelProps, PanelOptionsProps, NullValueMode } from 'app/types'; interface Options { showBars: boolean; @@ -43,34 +43,37 @@ export class Graph2 extends PureComponent { } } -export class GraphOptions extends PureComponent { +export class GraphOptions extends PureComponent> { onToggleLines = () => { - const options = this.props as Options; - this.props.onChange({ - ...options, - showLines: !this.props.showLines, + ...this.props.options, + showLines: !this.props.options.showLines, + }); + }; + + onToggleBars = () => { + this.props.onChange({ + ...this.props.options, + showBars: !this.props.options.showBars, }); }; onTogglePoints = () => { - const options = this.props as Options; - this.props.onChange({ - ...options, - showPoints: !this.props.showPoints, + ...this.props.options, + showPoints: !this.props.options.showPoints, }); }; render() { - const { showBars, showPoints, showLines } = this.props; + const { showBars, showPoints, showLines } = this.props.options; return (
Draw Modes
- +
diff --git a/public/app/types/index.ts b/public/app/types/index.ts index c51622682d4..fc176fed7e2 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -20,7 +20,7 @@ import { DataQueryResponse, DataQueryOptions, } from './series'; -import { PanelProps } from './panel'; +import { PanelProps, PanelOptionsProps } from './panel'; import { PluginDashboard, PluginMeta, Plugin, PluginsState } from './plugins'; import { Organization, OrganizationPreferences, OrganizationState } from './organization'; import { @@ -69,6 +69,7 @@ export { TimeRange, LoadingState, PanelProps, + PanelOptionsProps, TimeSeries, TimeSeriesVM, TimeSeriesVMs, diff --git a/public/app/types/panel.ts b/public/app/types/panel.ts index 5207c17ada9..7febd0cad26 100644 --- a/public/app/types/panel.ts +++ b/public/app/types/panel.ts @@ -8,7 +8,7 @@ export interface PanelProps { renderCounter: number; } -export interface PanelOptionProps { +export interface PanelOptionsProps { options: T; onChange: (options: T) => void; } diff --git a/public/app/types/plugins.ts b/public/app/types/plugins.ts index 4b172c0eef4..817777669d8 100644 --- a/public/app/types/plugins.ts +++ b/public/app/types/plugins.ts @@ -1,5 +1,5 @@ import { ComponentClass } from 'react'; -import { PanelProps, PanelOptionProps } from './panel'; +import { PanelProps, PanelOptionsProps } from './panel'; export interface PluginExports { Datasource?: any; @@ -12,7 +12,7 @@ export interface PluginExports { // Panel plugin PanelCtrl?; PanelComponent?: ComponentClass; - PanelOptionsComponent: ComponentClass; + PanelOptionsComponent: ComponentClass; } export interface PanelPlugin { From b9612aaa231b54cf2973b486fb35916a38e9e0e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 5 Nov 2018 10:38:55 -0800 Subject: [PATCH 48/55] minor code style change --- public/app/plugins/panel/graph2/module.tsx | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/public/app/plugins/panel/graph2/module.tsx b/public/app/plugins/panel/graph2/module.tsx index a666d762062..837109f6be7 100644 --- a/public/app/plugins/panel/graph2/module.tsx +++ b/public/app/plugins/panel/graph2/module.tsx @@ -45,24 +45,15 @@ export class Graph2 extends PureComponent { export class GraphOptions extends PureComponent> { onToggleLines = () => { - this.props.onChange({ - ...this.props.options, - showLines: !this.props.options.showLines, - }); + this.props.onChange({ showLines: !this.props.options.showLines, ...this.props.options }); }; onToggleBars = () => { - this.props.onChange({ - ...this.props.options, - showBars: !this.props.options.showBars, - }); + this.props.onChange({ showBars: !this.props.options.showBars, ...this.props.options }); }; onTogglePoints = () => { - this.props.onChange({ - ...this.props.options, - showPoints: !this.props.options.showPoints, - }); + this.props.onChange({ showPoints: !this.props.options.showPoints, ...this.props.options }); }; render() { From 562411af1af4c62cbbc1f92ee6e231b65f785651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 6 Nov 2018 07:23:02 +0100 Subject: [PATCH 49/55] fixed options --- public/app/plugins/panel/graph2/module.tsx | 6 +-- public/app/viz/Graph.tsx | 52 +++++++++++----------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/public/app/plugins/panel/graph2/module.tsx b/public/app/plugins/panel/graph2/module.tsx index 837109f6be7..b132d3374f1 100644 --- a/public/app/plugins/panel/graph2/module.tsx +++ b/public/app/plugins/panel/graph2/module.tsx @@ -45,15 +45,15 @@ export class Graph2 extends PureComponent { export class GraphOptions extends PureComponent> { onToggleLines = () => { - this.props.onChange({ showLines: !this.props.options.showLines, ...this.props.options }); + this.props.onChange({ ...this.props.options, showLines: !this.props.options.showLines }); }; onToggleBars = () => { - this.props.onChange({ showBars: !this.props.options.showBars, ...this.props.options }); + this.props.onChange({ ...this.props.options, showBars: !this.props.options.showBars }); }; onTogglePoints = () => { - this.props.onChange({ showPoints: !this.props.options.showPoints, ...this.props.options }); + this.props.onChange({ ...this.props.options, showPoints: !this.props.options.showPoints }); }; render() { diff --git a/public/app/viz/Graph.tsx b/public/app/viz/Graph.tsx index 5d99f4e0c7f..566080fbc92 100644 --- a/public/app/viz/Graph.tsx +++ b/public/app/viz/Graph.tsx @@ -8,32 +8,6 @@ import 'vendor/flot/jquery.flot.time'; // Types import { TimeRange, TimeSeriesVMs } from 'app/types'; -// Copied from graph.ts -function time_format(ticks, min, max) { - if (min && max && ticks) { - const range = max - min; - const secPerTick = range / ticks / 1000; - const oneDay = 86400000; - const oneYear = 31536000000; - - if (secPerTick <= 45) { - return '%H:%M:%S'; - } - if (secPerTick <= 7200 || range <= oneDay) { - return '%H:%M'; - } - if (secPerTick <= 80000) { - return '%m/%d %H:%M'; - } - if (secPerTick <= 2419200 || range <= oneYear) { - return '%m/%d'; - } - return '%Y-%m'; - } - - return '%H:%M'; -} - interface GraphProps { timeSeries: TimeSeriesVMs; timeRange: TimeRange; @@ -139,4 +113,30 @@ export class Graph extends PureComponent { } } +// Copied from graph.ts +function time_format(ticks, min, max) { + if (min && max && ticks) { + const range = max - min; + const secPerTick = range / ticks / 1000; + const oneDay = 86400000; + const oneYear = 31536000000; + + if (secPerTick <= 45) { + return '%H:%M:%S'; + } + if (secPerTick <= 7200 || range <= oneDay) { + return '%H:%M'; + } + if (secPerTick <= 80000) { + return '%m/%d %H:%M'; + } + if (secPerTick <= 2419200 || range <= oneYear) { + return '%m/%d'; + } + return '%Y-%m'; + } + + return '%H:%M'; +} + export default withSize()(Graph); From c9a4da42706ed634f6f2b487905b87a5b2d1c59e Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Tue, 6 Nov 2018 09:40:41 +0100 Subject: [PATCH 50/55] build: publisher handles nightly builds. --- .../release_publisher/externalrelease.go | 6 ++--- .../build/release_publisher/localrelease.go | 4 +-- scripts/build/release_publisher/main.go | 25 +++++++++++++++---- scripts/build/release_publisher/publisher.go | 6 ++--- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/scripts/build/release_publisher/externalrelease.go b/scripts/build/release_publisher/externalrelease.go index d6e6a669293..327fb335dbe 100644 --- a/scripts/build/release_publisher/externalrelease.go +++ b/scripts/build/release_publisher/externalrelease.go @@ -14,7 +14,7 @@ type releaseFromExternalContent struct { artifactConfigurations []buildArtifact } -func (re releaseFromExternalContent) prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string) (*release, error) { +func (re releaseFromExternalContent) prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string, nightly bool) (*release, error) { version := re.rawVersion[1:] isBeta := strings.Contains(version, "beta") @@ -30,9 +30,9 @@ func (re releaseFromExternalContent) prepareRelease(baseArchiveUrl, whatsNewUrl r := release{ Version: version, ReleaseDate: time.Now().UTC(), - Stable: !isBeta, + Stable: !isBeta && !nightly, Beta: isBeta, - Nightly: false, + Nightly: nightly, WhatsNewUrl: whatsNewUrl, ReleaseNotesUrl: releaseNotesUrl, Builds: builds, diff --git a/scripts/build/release_publisher/localrelease.go b/scripts/build/release_publisher/localrelease.go index 898820f97da..7c0a3b1d085 100644 --- a/scripts/build/release_publisher/localrelease.go +++ b/scripts/build/release_publisher/localrelease.go @@ -17,7 +17,7 @@ type releaseLocalSources struct { artifactConfigurations []buildArtifact } -func (r releaseLocalSources) prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string) (*release, error) { +func (r releaseLocalSources) prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string, nightly bool) (*release, error) { buildData := r.findBuilds(baseArchiveUrl) rel := release{ @@ -25,7 +25,7 @@ func (r releaseLocalSources) prepareRelease(baseArchiveUrl, whatsNewUrl string, ReleaseDate: time.Now().UTC(), Stable: false, Beta: false, - Nightly: true, + Nightly: nightly, WhatsNewUrl: whatsNewUrl, ReleaseNotesUrl: releaseNotesUrl, Builds: buildData.builds, diff --git a/scripts/build/release_publisher/main.go b/scripts/build/release_publisher/main.go index 66ab38ab00e..c4004fe7b4c 100644 --- a/scripts/build/release_publisher/main.go +++ b/scripts/build/release_publisher/main.go @@ -14,6 +14,7 @@ func main() { var dryRun bool var enterprise bool var fromLocal bool + var nightly bool var apiKey string flag.StringVar(&version, "version", "", "Grafana version (ex: --version v5.2.0-beta1)") @@ -22,11 +23,13 @@ func main() { flag.StringVar(&apiKey, "apikey", "", "Grafana.com API key (ex: --apikey ABCDEF)") flag.BoolVar(&dryRun, "dry-run", false, "--dry-run") flag.BoolVar(&enterprise, "enterprise", false, "--enterprise") - flag.BoolVar(&fromLocal, "from-local", false, "--from-local") + flag.BoolVar(&fromLocal, "from-local", false, "--from-local (builds will be tagged as nightly)") flag.Parse() + nightly = fromLocal + if len(os.Args) == 1 { - fmt.Println("Usage: go run publisher.go main.go --version --wn --rn --apikey --dry-run false --enterprise false") + fmt.Println("Usage: go run publisher.go main.go --version --wn --rn --apikey --dry-run false --enterprise false --nightly false") fmt.Println("example: go run publisher.go main.go --version v5.2.0-beta2 --wn http://docs.grafana.org/guides/whats-new-in-v5-2/ --rn https://community.grafana.com/t/release-notes-v5-2-x/7894 --apikey ASDF123 --dry-run --enterprise") os.Exit(1) } @@ -52,12 +55,14 @@ func main() { } } + archiveProviderRoot := "https://s3-us-west-2.amazonaws.com" + if enterprise { - baseUrl = "https://s3-us-west-2.amazonaws.com/grafana-enterprise-releases/release/grafana-enterprise" product = "grafana-enterprise" + baseUrl = createBaseUrl(archiveProviderRoot, "grafana-enterprise-releases", product, nightly) } else { - baseUrl = "https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana" product = "grafana" + baseUrl = createBaseUrl(archiveProviderRoot, "grafana-releases", product, nightly) } p := publisher{ @@ -69,7 +74,17 @@ func main() { baseArchiveUrl: baseUrl, builder: builder, } - if err := p.doRelease(whatsNewUrl, releaseNotesUrl); err != nil { + if err := p.doRelease(whatsNewUrl, releaseNotesUrl, nightly); err != nil { log.Fatalf("error: %v", err) } } +func createBaseUrl(root string, bucketName string, product string, nightly bool) string { + var subPath string + if nightly { + subPath = "master" + } else { + subPath = "release" + } + + return fmt.Sprintf("%s/%s/%s/%s", root, bucketName, subPath, product) +} diff --git a/scripts/build/release_publisher/publisher.go b/scripts/build/release_publisher/publisher.go index 0874c1357b6..d2c10d1640f 100644 --- a/scripts/build/release_publisher/publisher.go +++ b/scripts/build/release_publisher/publisher.go @@ -22,11 +22,11 @@ type publisher struct { } type releaseBuilder interface { - prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string) (*release, error) + prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string, nightly bool) (*release, error) } -func (p *publisher) doRelease(whatsNewUrl string, releaseNotesUrl string) error { - currentRelease, err := p.builder.prepareRelease(p.baseArchiveUrl, whatsNewUrl, releaseNotesUrl) +func (p *publisher) doRelease(whatsNewUrl string, releaseNotesUrl string, nightly bool) error { + currentRelease, err := p.builder.prepareRelease(p.baseArchiveUrl, whatsNewUrl, releaseNotesUrl, nightly) if err != nil { return err } From 34f531e113c67891d0558e23761051edbec75ebf Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Tue, 6 Nov 2018 09:52:41 +0100 Subject: [PATCH 51/55] build: fixes --- scripts/build/release_publisher/publisher_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/build/release_publisher/publisher_test.go b/scripts/build/release_publisher/publisher_test.go index 4e553362dcb..ee491ce0b98 100644 --- a/scripts/build/release_publisher/publisher_test.go +++ b/scripts/build/release_publisher/publisher_test.go @@ -19,7 +19,7 @@ func TestPreparingReleaseFromRemote(t *testing.T) { artifactConfigurations: buildArtifactConfigurations, } - rel, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana", whatsNewUrl, relNotesUrl) + rel, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana", whatsNewUrl, relNotesUrl, false) if !rel.Beta || rel.Stable { t.Errorf("%s should have been tagged as beta (not stable), but wasn't .", versionIn) @@ -64,7 +64,7 @@ func TestPreparingReleaseFromLocal(t *testing.T) { artifactConfigurations: buildArtifactConfigurations, } - relAll, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-enterprise-releases/master/grafana-enterprise", whatsNewUrl, relNotesUrl) + relAll, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-enterprise-releases/master/grafana-enterprise", whatsNewUrl, relNotesUrl, true) if relAll.Stable || !relAll.Nightly { t.Error("Expected a nightly release but wasn't.") @@ -101,7 +101,7 @@ func TestPreparingReleaseFromLocal(t *testing.T) { }}, } - relOne, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-enterprise-releases/master/grafana-enterprise", whatsNewUrl, relNotesUrl) + relOne, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-enterprise-releases/master/grafana-enterprise", whatsNewUrl, relNotesUrl, true) if len(relOne.Builds) != 1 { t.Errorf("Expected 1 artifact, but was %v", len(relOne.Builds)) From a3196a130e2c3a90db0148e39b141f4275d84ba6 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 6 Nov 2018 09:53:02 +0100 Subject: [PATCH 52/55] changelog: add notes about closing #13970 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a30b44161a..fb947317f64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Minor * **Cloudwatch**: Show all available CloudWatch regions [#12308](https://github.com/grafana/grafana/issues/12308), thx [@mtanda](https://github.com/mtanda) +* **Cloudwatch**: AWS/Connect metrics and dimensions [#13970](https://github.com/grafana/grafana/pull/13970), thx [@zcoffy](https://github.com/zcoffy) * **Postgres**: Add delta window function to postgres query builder [#13925](https://github.com/grafana/grafana/issues/13925), thx [svenklemm](https://github.com/svenklemm) * **Units**: New clock time format, to format ms or second values as for example `01h:59m`, [#13635](https://github.com/grafana/grafana/issues/13635), thx [@franciscocpg](https://github.com/franciscocpg) * **Datasource Proxy**: Keep trailing slash for datasource proxy requests [#13326](https://github.com/grafana/grafana/pull/13326), thx [@ryantxu](https://github.com/ryantxu) From e5e886ccb7bacd483f2423cc9cdc2dea0891e7f0 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 6 Nov 2018 11:49:22 +0100 Subject: [PATCH 53/55] fix selecting datasource using enter key --- public/app/core/components/form_dropdown/form_dropdown.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/core/components/form_dropdown/form_dropdown.ts b/public/app/core/components/form_dropdown/form_dropdown.ts index 6e863e1cb5d..81d4b336443 100644 --- a/public/app/core/components/form_dropdown/form_dropdown.ts +++ b/public/app/core/components/form_dropdown/form_dropdown.ts @@ -88,7 +88,7 @@ export class FormDropdownCtrl { if (evt.keyCode === 13) { setTimeout(() => { this.inputElement.blur(); - }, 100); + }, 300); } }); From a66dba160811286bd3c40b1840ff4689ed865436 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 6 Nov 2018 13:47:05 +0100 Subject: [PATCH 54/55] changelog: add notes about closing #13932 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb947317f64..756b0778cee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ * **Alerting**: Delete alerts when parent folder was deleted [#13322](https://github.com/grafana/grafana/issues/13322) * **MySQL**: Fix `$__timeFilter()` should respect local time zone [#13769](https://github.com/grafana/grafana/issues/13769) +* **Dashboard**: Fix datasource selection in panel by enter key [#13932](https://github.com/grafana/grafana/issues/13932) # 5.3.2 (2018-10-24) From b28b79100ab2e404539a9843b4002652a4939347 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 6 Nov 2018 13:50:30 +0100 Subject: [PATCH 55/55] changelog: add notes about closing #13903 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 756b0778cee..a98fca96fc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ * **Alerting**: Delete alerts when parent folder was deleted [#13322](https://github.com/grafana/grafana/issues/13322) * **MySQL**: Fix `$__timeFilter()` should respect local time zone [#13769](https://github.com/grafana/grafana/issues/13769) * **Dashboard**: Fix datasource selection in panel by enter key [#13932](https://github.com/grafana/grafana/issues/13932) +* **Graph**: Fix table legend height when positioned below graph and using Internet Explorer 11 [#13903](https://github.com/grafana/grafana/issues/13903) # 5.3.2 (2018-10-24)