From 5854eddb3df49e9298f415942919b3e762cfefa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Fri, 22 Feb 2019 09:11:21 +0100 Subject: [PATCH 01/23] Fixed bug with getting teams for user (cherry picked from commit dafcfd70a709f0111ccb6b3bd74d4659c3f51a99) --- pkg/api/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/api/user.go b/pkg/api/user.go index 6db9c6f6baf..9eb7e75eab0 100644 --- a/pkg/api/user.go +++ b/pkg/api/user.go @@ -121,7 +121,7 @@ func GetUserTeams(c *m.ReqContext) Response { return getUserTeamList(c.OrgId, c.ParamsInt64(":id")) } -func getUserTeamList(userID int64, orgID int64) Response { +func getUserTeamList(orgID int64, userID int64) Response { query := m.GetTeamsByUserQuery{OrgId: orgID, UserId: userID} if err := bus.Dispatch(&query); err != nil { From 3344c53cd50f64e575aa28e5b8f18c14bc7b9ce2 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Sat, 23 Feb 2019 15:52:40 +0100 Subject: [PATCH 02/23] stackdriver: change reducer mapping for distribution metrics - Distribution metrics are now mapped to more reducers when the metric kind is cumulative. - The witdth of the metrics dropdown is now much wider. - Changed the text from Select Aggregation to Select Reducer to line up with the UI in Stackdriver. (cherry picked from commit 35fc0c532994159f5200e4e2a6906175c16f3a35) --- .../stackdriver/components/Aggregations.test.tsx | 3 ++- .../datasource/stackdriver/components/Aggregations.tsx | 2 +- .../datasource/stackdriver/components/Metrics.tsx | 2 +- .../__snapshots__/Aggregations.test.tsx.snap | 2 +- .../components/__snapshots__/QueryEditor.test.tsx.snap | 4 ++-- public/app/plugins/datasource/stackdriver/constants.ts | 10 +++++----- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/components/Aggregations.test.tsx b/public/app/plugins/datasource/stackdriver/components/Aggregations.test.tsx index 2402cc5da2c..5b14fa73a72 100644 --- a/public/app/plugins/datasource/stackdriver/components/Aggregations.test.tsx +++ b/public/app/plugins/datasource/stackdriver/components/Aggregations.test.tsx @@ -49,7 +49,8 @@ describe('Aggregations', () => { }); it('', () => { const options = wrapper.state().aggOptions[0].options; - expect(options.length).toEqual(5); + + expect(options.length).toEqual(10); expect(options.map(o => o.value)).toEqual(expect.arrayContaining(['REDUCE_NONE'])); }); }); diff --git a/public/app/plugins/datasource/stackdriver/components/Aggregations.tsx b/public/app/plugins/datasource/stackdriver/components/Aggregations.tsx index c616d55ba7a..9b8dca01151 100644 --- a/public/app/plugins/datasource/stackdriver/components/Aggregations.tsx +++ b/public/app/plugins/datasource/stackdriver/components/Aggregations.tsx @@ -73,7 +73,7 @@ export class Aggregations extends React.Component { value={crossSeriesReducer} variables={templateSrv.variables} options={aggOptions} - placeholder="Select Aggregation" + placeholder="Select Reducer" className="width-15" /> diff --git a/public/app/plugins/datasource/stackdriver/components/Metrics.tsx b/public/app/plugins/datasource/stackdriver/components/Metrics.tsx index 06094d0c1e9..a09b6108370 100644 --- a/public/app/plugins/datasource/stackdriver/components/Metrics.tsx +++ b/public/app/plugins/datasource/stackdriver/components/Metrics.tsx @@ -185,7 +185,7 @@ export class Metrics extends React.Component { }, ]} placeholder="Select Metric" - className="width-15" + className="width-26" />
diff --git a/public/app/plugins/datasource/stackdriver/components/__snapshots__/Aggregations.test.tsx.snap b/public/app/plugins/datasource/stackdriver/components/__snapshots__/Aggregations.test.tsx.snap index defd8ef01a4..ed9fe98bbf8 100644 --- a/public/app/plugins/datasource/stackdriver/components/__snapshots__/Aggregations.test.tsx.snap +++ b/public/app/plugins/datasource/stackdriver/components/__snapshots__/Aggregations.test.tsx.snap @@ -28,7 +28,7 @@ Array [
- Select Aggregation + Select Reducer
- Select Aggregation + Select Reducer
Date: Sun, 24 Feb 2019 17:32:47 +0100 Subject: [PATCH 03/23] stackdriver: fix for float64 bounds for distribution metrics Adds support for explicit distribution metrics and float64 bounds Fixes #14509 (cherry picked from commit d1e249a8039aa068af1de9d92d6aaf482e26d461) --- pkg/tsdb/stackdriver/stackdriver.go | 4 +- pkg/tsdb/stackdriver/stackdriver_test.go | 51 ++++- ...es-response-distribution-exponential.json} | 0 ...series-response-distribution-explicit.json | 209 ++++++++++++++++++ pkg/tsdb/stackdriver/types.go | 2 +- 5 files changed, 262 insertions(+), 4 deletions(-) rename pkg/tsdb/stackdriver/test-data/{3-series-response-distribution.json => 3-series-response-distribution-exponential.json} (100%) create mode 100644 pkg/tsdb/stackdriver/test-data/4-series-response-distribution-explicit.json diff --git a/pkg/tsdb/stackdriver/stackdriver.go b/pkg/tsdb/stackdriver/stackdriver.go index 8317e2a748a..76b346553ad 100644 --- a/pkg/tsdb/stackdriver/stackdriver.go +++ b/pkg/tsdb/stackdriver/stackdriver.go @@ -336,6 +336,8 @@ func (e *StackdriverExecutor) unmarshalResponse(res *http.Response) (Stackdriver return StackdriverResponse{}, err } + // slog.Info("stackdriver", "response", string(body)) + if res.StatusCode/100 != 2 { slog.Error("Request failed", "status", res.Status, "body", string(body)) return StackdriverResponse{}, fmt.Errorf(string(body)) @@ -559,7 +561,7 @@ func calcBucketBound(bucketOptions StackdriverBucketOptions, n int) string { } else if bucketOptions.ExponentialBuckets != nil { bucketBound = strconv.FormatInt(int64(bucketOptions.ExponentialBuckets.Scale*math.Pow(bucketOptions.ExponentialBuckets.GrowthFactor, float64(n-1))), 10) } else if bucketOptions.ExplicitBuckets != nil { - bucketBound = strconv.FormatInt(bucketOptions.ExplicitBuckets.Bounds[(n-1)], 10) + bucketBound = fmt.Sprintf("%g", bucketOptions.ExplicitBuckets.Bounds[n]) } return bucketBound } diff --git a/pkg/tsdb/stackdriver/stackdriver_test.go b/pkg/tsdb/stackdriver/stackdriver_test.go index 4b16b6b5294..78c3086a94b 100644 --- a/pkg/tsdb/stackdriver/stackdriver_test.go +++ b/pkg/tsdb/stackdriver/stackdriver_test.go @@ -344,8 +344,8 @@ func TestStackdriver(t *testing.T) { }) }) - Convey("when data from query is distribution", func() { - data, err := loadTestFile("./test-data/3-series-response-distribution.json") + Convey("when data from query is distribution with exponential bounds", func() { + data, err := loadTestFile("./test-data/3-series-response-distribution-exponential.json") So(err, ShouldBeNil) So(len(data.TimeSeries), ShouldEqual, 1) @@ -370,6 +370,14 @@ func TestStackdriver(t *testing.T) { So(res.Series[0].Points[2][1].Float64, ShouldEqual, 1536669060000) }) + Convey("bucket bounds should be correct", func() { + So(res.Series[0].Name, ShouldEqual, "0") + So(res.Series[1].Name, ShouldEqual, "1") + So(res.Series[2].Name, ShouldEqual, "2") + So(res.Series[3].Name, ShouldEqual, "4") + So(res.Series[4].Name, ShouldEqual, "8") + }) + Convey("value should be correct", func() { So(res.Series[8].Points[0][0].Float64, ShouldEqual, 1) So(res.Series[9].Points[0][0].Float64, ShouldEqual, 1) @@ -383,6 +391,45 @@ func TestStackdriver(t *testing.T) { }) }) + Convey("when data from query is distribution with explicit bounds", func() { + data, err := loadTestFile("./test-data/4-series-response-distribution-explicit.json") + So(err, ShouldBeNil) + So(len(data.TimeSeries), ShouldEqual, 1) + + res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"} + query := &StackdriverQuery{AliasBy: "{{bucket}}"} + err = executor.parseResponse(res, data, query) + So(err, ShouldBeNil) + + So(len(res.Series), ShouldEqual, 33) + for i := 0; i < 33; i++ { + if i == 0 { + So(res.Series[i].Name, ShouldEqual, "0") + } + So(len(res.Series[i].Points), ShouldEqual, 2) + } + + Convey("timestamps should be in ascending order", func() { + So(res.Series[0].Points[0][1].Float64, ShouldEqual, 1550859086000) + So(res.Series[0].Points[1][1].Float64, ShouldEqual, 1550859146000) + }) + + Convey("bucket bounds should be correct", func() { + So(res.Series[0].Name, ShouldEqual, "0") + So(res.Series[1].Name, ShouldEqual, "0.01") + So(res.Series[2].Name, ShouldEqual, "0.05") + So(res.Series[3].Name, ShouldEqual, "0.1") + }) + + Convey("value should be correct", func() { + So(res.Series[8].Points[0][0].Float64, ShouldEqual, 381) + So(res.Series[9].Points[0][0].Float64, ShouldEqual, 212) + So(res.Series[10].Points[0][0].Float64, ShouldEqual, 56) + So(res.Series[8].Points[1][0].Float64, ShouldEqual, 375) + So(res.Series[9].Points[1][0].Float64, ShouldEqual, 213) + So(res.Series[10].Points[1][0].Float64, ShouldEqual, 56) + }) + }) }) Convey("when interpolating filter wildcards", func() { diff --git a/pkg/tsdb/stackdriver/test-data/3-series-response-distribution.json b/pkg/tsdb/stackdriver/test-data/3-series-response-distribution-exponential.json similarity index 100% rename from pkg/tsdb/stackdriver/test-data/3-series-response-distribution.json rename to pkg/tsdb/stackdriver/test-data/3-series-response-distribution-exponential.json diff --git a/pkg/tsdb/stackdriver/test-data/4-series-response-distribution-explicit.json b/pkg/tsdb/stackdriver/test-data/4-series-response-distribution-explicit.json new file mode 100644 index 00000000000..98435294762 --- /dev/null +++ b/pkg/tsdb/stackdriver/test-data/4-series-response-distribution-explicit.json @@ -0,0 +1,209 @@ +{ + "timeSeries": [ + { + "metric": { + "type": "custom.googleapis.com\/opencensus\/grpc.io\/client\/roundtrip_latency" + }, + "resource": { + "type": "global", + "labels": { + "project_id": "grafana-demo" + } + }, + "metricKind": "DELTA", + "valueType": "DISTRIBUTION", + "points": [ + { + "interval": { + "startTime": "2019-02-22T18:11:26Z", + "endTime": "2019-02-22T18:12:26Z" + }, + "value": { + "distributionValue": { + "count": "1878", + "mean": 17.813718392255, + "sumOfSquaredDeviation": 7141630.651914, + "bucketOptions": { + "explicitBuckets": { + "bounds": [ + 0, + 0.01, + 0.05, + 0.1, + 0.3, + 0.6, + 0.8, + 1, + 2, + 3, + 4, + 5, + 6, + 8, + 10, + 13, + 16, + 20, + 25, + 30, + 40, + 50, + 65, + 80, + 100, + 130, + 160, + 200, + 250, + 300, + 400, + 500, + 650, + 800, + 1000, + 2000, + 5000, + 10000, + 20000, + 50000, + 100000 + ] + } + }, + "bucketCounts": [ + "0", + "0", + "0", + "0", + "8", + "403", + "297", + "184", + "375", + "213", + "56", + "31", + "15", + "13", + "4", + "1", + "5", + "2", + "8", + "13", + "26", + "13", + "45", + "48", + "61", + "10", + "3", + "6", + "7", + "4", + "7", + "12", + "8" + ] + } + } + }, + { + "interval": { + "startTime": "2019-02-22T18:10:26Z", + "endTime": "2019-02-22T18:11:26Z" + }, + "value": { + "distributionValue": { + "count": "1887", + "mean": 17.654277577766, + "sumOfSquaredDeviation": 7082587.2133073, + "bucketOptions": { + "explicitBuckets": { + "bounds": [ + 0, + 0.01, + 0.05, + 0.1, + 0.3, + 0.6, + 0.8, + 1, + 2, + 3, + 4, + 5, + 6, + 8, + 10, + 13, + 16, + 20, + 25, + 30, + 40, + 50, + 65, + 80, + 100, + 130, + 160, + 200, + 250, + 300, + 400, + 500, + 650, + 800, + 1000, + 2000, + 5000, + 10000, + 20000, + 50000, + 100000 + ] + } + }, + "bucketCounts": [ + "0", + "0", + "0", + "0", + "8", + "404", + "298", + "187", + "381", + "212", + "56", + "31", + "15", + "14", + "4", + "1", + "4", + "2", + "9", + "13", + "24", + "13", + "46", + "46", + "61", + "11", + "3", + "6", + "7", + "5", + "7", + "11", + "8" + ] + } + } + } + ] + } + ] +} diff --git a/pkg/tsdb/stackdriver/types.go b/pkg/tsdb/stackdriver/types.go index 3821ce7ceda..e4ede41d269 100644 --- a/pkg/tsdb/stackdriver/types.go +++ b/pkg/tsdb/stackdriver/types.go @@ -26,7 +26,7 @@ type StackdriverBucketOptions struct { Scale float64 `json:"scale"` } `json:"exponentialBuckets"` ExplicitBuckets *struct { - Bounds []int64 `json:"bounds"` + Bounds []float64 `json:"bounds"` } `json:"explicitBuckets"` } From 1a48d82133d7fb1eacf549ab5e12909b23aafbfd Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Tue, 26 Feb 2019 11:41:55 +0100 Subject: [PATCH 04/23] service: fix for disabled internal metrics. Update of the internal metrics for Grafana was disabled by mistake when refactoring the code. Fixes #15651 (cherry picked from commit 36788183d84e50ecfaddeaf20ed6eec055921d83) --- pkg/cmd/grafana-server/server.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index f663e6be895..cd026af2fc3 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -29,6 +29,8 @@ import ( // self registering services _ "github.com/grafana/grafana/pkg/extensions" _ "github.com/grafana/grafana/pkg/infra/serverlock" + + _ "github.com/grafana/grafana/pkg/infra/usagestats" _ "github.com/grafana/grafana/pkg/metrics" _ "github.com/grafana/grafana/pkg/plugins" _ "github.com/grafana/grafana/pkg/services/alerting" From c946a1fe2f9cdb115be7644a75c115b0fff7d9ba Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Sat, 2 Mar 2019 13:51:55 -0800 Subject: [PATCH 05/23] fix (cherry picked from commit a9ca8e9dec8243eb077ab8c9a524e525ff565fab) --- packages/grafana-ui/src/utils/valueFormats/categories.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/utils/valueFormats/categories.ts b/packages/grafana-ui/src/utils/valueFormats/categories.ts index efba2cc5b79..bcd8741f851 100644 --- a/packages/grafana-ui/src/utils/valueFormats/categories.ts +++ b/packages/grafana-ui/src/utils/valueFormats/categories.ts @@ -125,7 +125,7 @@ export const getCategories = (): ValueFormatCategory[] => [ { name: 'Data (Metric)', formats: [ - { name: 'bits', id: 'decbits', fn: decimalSIPrefix('d') }, + { name: 'bits', id: 'decbits', fn: decimalSIPrefix('b') }, { name: 'bytes', id: 'decbytes', fn: decimalSIPrefix('B') }, { name: 'kilobytes', id: 'deckbytes', fn: decimalSIPrefix('B', 1) }, { name: 'megabytes', id: 'decmbytes', fn: decimalSIPrefix('B', 2) }, From e610e4ca41b1e2192c5ab2881555171226c81905 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Thu, 28 Feb 2019 10:35:53 +0100 Subject: [PATCH 06/23] fix: Return url when query dashboards by tag (cherry picked from commit 8d5ccc7831361bf6159a9850d1bd967d8c1a073e) --- pkg/api/playlist_play.go | 2 ++ pkg/services/search/models.go | 1 + 2 files changed, 3 insertions(+) diff --git a/pkg/api/playlist_play.go b/pkg/api/playlist_play.go index 5ca136c32c4..2757f245060 100644 --- a/pkg/api/playlist_play.go +++ b/pkg/api/playlist_play.go @@ -52,8 +52,10 @@ func populateDashboardsByTag(orgID int64, signedInUser *m.SignedInUser, dashboar for _, item := range searchQuery.Result { result = append(result, dtos.PlaylistDashboard{ Id: item.Id, + Slug: item.Slug, Title: item.Title, Uri: item.Uri, + Url: m.GetDashboardUrl(item.Uid, item.Slug), Order: dashboardTagOrder[tag], }) } diff --git a/pkg/services/search/models.go b/pkg/services/search/models.go index 2da09672f13..475cb4a3777 100644 --- a/pkg/services/search/models.go +++ b/pkg/services/search/models.go @@ -17,6 +17,7 @@ type Hit struct { Title string `json:"title"` Uri string `json:"uri"` Url string `json:"url"` + Slug string `json:"slug"` Type HitType `json:"type"` Tags []string `json:"tags"` IsStarred bool `json:"isStarred"` From 1dadcb0a5be54768424b8e247c6582d5f45321ed Mon Sep 17 00:00:00 2001 From: Jon Ferreira Date: Fri, 22 Feb 2019 14:56:13 -0500 Subject: [PATCH 07/23] Toggle stack should trigger a render, not a refresh (cherry picked from commit 0bdca7957aa0ce01bccea90f0f9ad7069ad57168) --- public/app/plugins/panel/graph/tab_display.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/panel/graph/tab_display.html b/public/app/plugins/panel/graph/tab_display.html index a6287922cfe..e6f5b597e1b 100644 --- a/public/app/plugins/panel/graph/tab_display.html +++ b/public/app/plugins/panel/graph/tab_display.html @@ -114,7 +114,7 @@ label="Stack" label-class="width-7" checked="ctrl.panel.stack" - on-change="ctrl.refresh()" + on-change="ctrl.render()" > Date: Mon, 4 Mar 2019 16:33:00 +0100 Subject: [PATCH 08/23] fix: Kiosk mode should have &kiosk appended to the url #15765 (cherry picked from commit 92ec8757d3f20e5b5f0d9d530028577cbc4fa94f) --- public/app/routes/GrafanaCtrl.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/app/routes/GrafanaCtrl.ts b/public/app/routes/GrafanaCtrl.ts index d327bc0cf7d..479c5e77f3d 100644 --- a/public/app/routes/GrafanaCtrl.ts +++ b/public/app/routes/GrafanaCtrl.ts @@ -75,7 +75,7 @@ export class GrafanaCtrl { } } -function setViewModeBodyClass(body, mode: KioskUrlValue, sidemenuOpen: boolean) { +function setViewModeBodyClass(body: JQuery, mode: KioskUrlValue, sidemenuOpen: boolean) { body.removeClass('view-mode--tv'); body.removeClass('view-mode--kiosk'); body.removeClass('view-mode--inactive'); @@ -174,8 +174,8 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop }); // handle kiosk mode - appEvents.on('toggle-kiosk-mode', options => { - const search = $location.search(); + appEvents.on('toggle-kiosk-mode', (options: { exit?: boolean }) => { + const search: { kiosk?: KioskUrlValue } = $location.search(); if (options && options.exit) { search.kiosk = '1'; @@ -197,7 +197,7 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop } } - $location.search(search); + $timeout(() => $location.search(search)); setViewModeBodyClass(body, search.kiosk, sidemenuOpen); }); From 18ac0824bd56a8d5e02ccb0b7228c07b34d21637 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Mon, 4 Mar 2019 13:46:10 +0100 Subject: [PATCH 09/23] fix: When in tv-mode, autofitpanel should not take space from the navbar #15650 (cherry picked from commit cc40a515be3362e5fd213f794d05579e446aec18) --- public/app/features/dashboard/state/DashboardModel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts index 17af2dbd801..2a445c9a58c 100644 --- a/public/app/features/dashboard/state/DashboardModel.ts +++ b/public/app/features/dashboard/state/DashboardModel.ts @@ -887,8 +887,8 @@ export class DashboardModel { } // add back navbar height - if (kioskMode === KIOSK_MODE_TV) { - visibleHeight += 55; + if (kioskMode && kioskMode !== KIOSK_MODE_TV) { + visibleHeight += navbarHeight; } const visibleGridHeight = Math.floor(visibleHeight / (GRID_CELL_HEIGHT + GRID_CELL_VMARGIN)); From 9044b269a3ee040e9fb7adae645c23baa15a1f18 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 4 Mar 2019 15:48:07 +0100 Subject: [PATCH 10/23] only editor/admin should have access to alert list/notifications pages (cherry picked from commit a29b99b96b1e668640c7d9ce10b78f55e74fba6e) --- pkg/api/api.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index 6da127fb550..82f660a2bd6 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -77,8 +77,8 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/playlists/", reqSignedIn, hs.Index) r.Get("/playlists/*", reqSignedIn, hs.Index) - r.Get("/alerting/", reqSignedIn, hs.Index) - r.Get("/alerting/*", reqSignedIn, hs.Index) + r.Get("/alerting/", reqEditorRole, hs.Index) + r.Get("/alerting/*", reqEditorRole, hs.Index) // sign up r.Get("/signup", hs.Index) From 073186bd3e7e8c5ad957c09978650eaab19522a4 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 4 Mar 2019 15:51:18 +0100 Subject: [PATCH 11/23] org admins should only be able to access org admin pages (cherry picked from commit 5638c67be89df2fa72b9821f89d7af15affddf86) --- pkg/api/api.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index 82f660a2bd6..c80129eac6f 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -33,17 +33,17 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/profile/", reqSignedIn, hs.Index) r.Get("/profile/password", reqSignedIn, hs.Index) r.Get("/profile/switch-org/:id", reqSignedIn, hs.ChangeActiveOrgAndRedirectToHome) - r.Get("/org/", reqSignedIn, hs.Index) - r.Get("/org/new", reqSignedIn, hs.Index) - r.Get("/datasources/", reqSignedIn, hs.Index) - r.Get("/datasources/new", reqSignedIn, hs.Index) - r.Get("/datasources/edit/*", reqSignedIn, hs.Index) - r.Get("/org/users", reqSignedIn, hs.Index) - r.Get("/org/users/new", reqSignedIn, hs.Index) - r.Get("/org/users/invite", reqSignedIn, hs.Index) - r.Get("/org/teams", reqSignedIn, hs.Index) - r.Get("/org/teams/*", reqSignedIn, hs.Index) - r.Get("/org/apikeys/", reqSignedIn, hs.Index) + r.Get("/org/", reqOrgAdmin, hs.Index) + r.Get("/org/new", reqGrafanaAdmin, hs.Index) + r.Get("/datasources/", reqOrgAdmin, hs.Index) + r.Get("/datasources/new", reqOrgAdmin, hs.Index) + r.Get("/datasources/edit/*", reqOrgAdmin, hs.Index) + r.Get("/org/users", reqOrgAdmin, hs.Index) + r.Get("/org/users/new", reqOrgAdmin, hs.Index) + r.Get("/org/users/invite", reqOrgAdmin, hs.Index) + r.Get("/org/teams", reqOrgAdmin, hs.Index) + r.Get("/org/teams/*", reqOrgAdmin, hs.Index) + r.Get("/org/apikeys/", reqOrgAdmin, hs.Index) r.Get("/dashboard/import/", reqSignedIn, hs.Index) r.Get("/configuration", reqGrafanaAdmin, hs.Index) r.Get("/admin", reqGrafanaAdmin, hs.Index) From 78d614ab9c08e0714b119795a517c7e9a6bf4a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Tue, 5 Mar 2019 10:49:45 +0100 Subject: [PATCH 12/23] Made sure that DataSourceOption displays value and fires onChange/onBlur events (#15757) * Fixed #15682 * fix: Add hideTimeOverride to state since we need to control the Switch * fix: Back the maxDataPoints change, we need to keep it as a string Co-authored-by:johannes.schill@polyester.se (cherry picked from commit 48570c627299e151ee5538230954cfe9777785d7) --- .../panel_editor/DataSourceOption.tsx | 17 +- .../dashboard/panel_editor/QueryOptions.tsx | 149 ++++++++++-------- 2 files changed, 93 insertions(+), 73 deletions(-) diff --git a/public/app/features/dashboard/panel_editor/DataSourceOption.tsx b/public/app/features/dashboard/panel_editor/DataSourceOption.tsx index 08285960805..a72eaae6ed9 100644 --- a/public/app/features/dashboard/panel_editor/DataSourceOption.tsx +++ b/public/app/features/dashboard/panel_editor/DataSourceOption.tsx @@ -1,16 +1,17 @@ -import React, { FC } from 'react'; +import React, { FC, ChangeEvent } from 'react'; import { FormLabel } from '@grafana/ui'; interface Props { label: string; placeholder?: string; - name?: string; - value?: string; - onChange?: (evt: any) => void; + name: string; + value: string; + onBlur: (event: ChangeEvent) => void; + onChange: (event: ChangeEvent) => void; tooltipInfo?: any; } -export const DataSourceOptions: FC = ({ label, placeholder, name, value, onChange, tooltipInfo }) => { +export const DataSourceOption: FC = ({ label, placeholder, name, value, onBlur, onChange, tooltipInfo }) => { return (
{label} @@ -20,10 +21,10 @@ export const DataSourceOptions: FC = ({ label, placeholder, name, value, placeholder={placeholder} name={name} spellCheck={false} - onBlur={evt => onChange(evt.target.value)} + onBlur={onBlur} + onChange={onChange} + value={value} />
); }; - -export default DataSourceOptions; diff --git a/public/app/features/dashboard/panel_editor/QueryOptions.tsx b/public/app/features/dashboard/panel_editor/QueryOptions.tsx index d203f3bc25f..8c59edf456d 100644 --- a/public/app/features/dashboard/panel_editor/QueryOptions.tsx +++ b/public/app/features/dashboard/panel_editor/QueryOptions.tsx @@ -1,5 +1,5 @@ // Libraries -import React, { PureComponent } from 'react'; +import React, { PureComponent, ChangeEvent, FocusEvent } from 'react'; // Utils import { isValidTimeSpan } from 'app/core/utils/rangeutil'; @@ -9,7 +9,7 @@ import { Switch } from '@grafana/ui'; import { Input } from 'app/core/components/Form'; import { EventsWithValidation } from 'app/core/components/Form/Input'; import { InputStatus } from 'app/core/components/Form/Input'; -import DataSourceOption from './DataSourceOption'; +import { DataSourceOption } from './DataSourceOption'; import { FormLabel } from '@grafana/ui'; // Types @@ -43,32 +43,79 @@ interface Props { interface State { relativeTime: string; timeShift: string; + cacheTimeout: string; + maxDataPoints: string; + interval: string; + hideTimeOverride: boolean; } export class QueryOptions extends PureComponent { + allOptions = { + cacheTimeout: { + label: 'Cache timeout', + placeholder: '60', + name: 'cacheTimeout', + tooltipInfo: ( + <> + If your time series store has a query cache this option can override the default cache timeout. Specify a + numeric value in seconds. + + ), + }, + maxDataPoints: { + label: 'Max data points', + placeholder: 'auto', + name: 'maxDataPoints', + tooltipInfo: ( + <> + The maximum data points the query should return. For graphs this is automatically set to one data point per + pixel. + + ), + }, + minInterval: { + label: 'Min time interval', + placeholder: '0', + name: 'minInterval', + panelKey: 'interval', + tooltipInfo: ( + <> + A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example{' '} + 1m if your data is written every minute. Access auto interval via variable{' '} + $__interval for time range string and $__interval_ms for numeric variable that can + be used in math expressions. + + ), + }, + }; + constructor(props) { super(props); this.state = { relativeTime: props.panel.timeFrom || '', timeShift: props.panel.timeShift || '', + cacheTimeout: props.panel.cacheTimeout || '', + maxDataPoints: props.panel.maxDataPoints || '', + interval: props.panel.interval || '', + hideTimeOverride: props.panel.hideTimeOverride || false, }; } - onRelativeTimeChange = event => { + onRelativeTimeChange = (event: ChangeEvent) => { this.setState({ relativeTime: event.target.value, }); }; - onTimeShiftChange = event => { + onTimeShiftChange = (event: ChangeEvent) => { this.setState({ timeShift: event.target.value, }); }; - onOverrideTime = (evt, status: InputStatus) => { - const { value } = evt.target; + onOverrideTime = (event: FocusEvent, status: InputStatus) => { + const { value } = event.target; const { panel } = this.props; const emptyToNullValue = emptyToNull(value); if (status === InputStatus.Valid && panel.timeFrom !== emptyToNullValue) { @@ -77,8 +124,8 @@ export class QueryOptions extends PureComponent { } }; - onTimeShift = (evt, status: InputStatus) => { - const { value } = evt.target; + onTimeShift = (event: FocusEvent, status: InputStatus) => { + const { value } = event.target; const { panel } = this.props; const emptyToNullValue = emptyToNull(value); if (status === InputStatus.Valid && panel.timeShift !== emptyToNullValue) { @@ -89,77 +136,49 @@ export class QueryOptions extends PureComponent { onToggleTimeOverride = () => { const { panel } = this.props; - panel.hideTimeOverride = !panel.hideTimeOverride; + this.setState({ hideTimeOverride: !this.state.hideTimeOverride }, () => { + panel.hideTimeOverride = this.state.hideTimeOverride; + panel.refresh(); + }); + }; + + onDataSourceOptionBlur = (panelKey: string) => () => { + const { panel } = this.props; + + panel[panelKey] = this.state[panelKey]; panel.refresh(); }; - renderOptions() { - const { datasource, panel } = this.props; + onDataSourceOptionChange = (panelKey: string) => (event: ChangeEvent) => { + this.setState({ ...this.state, [panelKey]: event.target.value }); + }; + + renderOptions = () => { + const { datasource } = this.props; const { queryOptions } = datasource.meta; if (!queryOptions) { return null; } - const onChangeFn = (panelKey: string) => { - return (value: string | number) => { - panel[panelKey] = value; - panel.refresh(); - }; - }; - - const allOptions = { - cacheTimeout: { - label: 'Cache timeout', - placeholder: '60', - name: 'cacheTimeout', - value: panel.cacheTimeout, - tooltipInfo: ( - <> - If your time series store has a query cache this option can override the default cache timeout. Specify a - numeric value in seconds. - - ), - }, - maxDataPoints: { - label: 'Max data points', - placeholder: 'auto', - name: 'maxDataPoints', - value: panel.maxDataPoints, - tooltipInfo: ( - <> - The maximum data points the query should return. For graphs this is automatically set to one data point per - pixel. - - ), - }, - minInterval: { - label: 'Min time interval', - placeholder: '0', - name: 'minInterval', - value: panel.interval, - panelKey: 'interval', - tooltipInfo: ( - <> - A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example{' '} - 1m if your data is written every minute. Access auto interval via variable{' '} - $__interval for time range string and $__interval_ms for numeric variable that can - be used in math expressions. - - ), - }, - }; - return Object.keys(queryOptions).map(key => { - const options = allOptions[key]; - return ; + const options = this.allOptions[key]; + const panelKey = options.panelKey || key; + return ( + + ); }); - } + }; render() { - const hideTimeOverride = this.props.panel.hideTimeOverride; + const { hideTimeOverride } = this.state; const { relativeTime, timeShift } = this.state; - return (
{this.renderOptions()} From eff00e8cc47d63ec1fc639980f7173b6854ed056 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 5 Mar 2019 09:32:02 +0100 Subject: [PATCH 13/23] utils: show string errors. Fixes #15782 (cherry picked from commit 8b1e25b50a5943b856127a5d01506d7917acf235) --- public/app/core/utils/errors.test.ts | 55 ++++++++++++++++++++++++++++ public/app/core/utils/errors.ts | 2 +- 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 public/app/core/utils/errors.test.ts diff --git a/public/app/core/utils/errors.test.ts b/public/app/core/utils/errors.test.ts new file mode 100644 index 00000000000..a5783fe7204 --- /dev/null +++ b/public/app/core/utils/errors.test.ts @@ -0,0 +1,55 @@ +import { getMessageFromError } from 'app/core/utils/errors'; + +describe('errors functions', () => { + let message; + + describe('when getMessageFromError gets an error string', () => { + beforeEach(() => { + message = getMessageFromError('error string'); + }); + + it('should return the string', () => { + expect(message).toBe('error string'); + }); + }); + + describe('when getMessageFromError gets an error object with message field', () => { + beforeEach(() => { + message = getMessageFromError({ message: 'error string' }); + }); + + it('should return the message text', () => { + expect(message).toBe('error string'); + }); + }); + + describe('when getMessageFromError gets an error object with data.message field', () => { + beforeEach(() => { + message = getMessageFromError({ data: { message: 'error string' } }); + }); + + it('should return the message text', () => { + expect(message).toBe('error string'); + }); + }); + + describe('when getMessageFromError gets an error object with statusText field', () => { + beforeEach(() => { + message = getMessageFromError({ statusText: 'error string' }); + }); + + it('should return the statusText text', () => { + expect(message).toBe('error string'); + }); + }); + + describe('when getMessageFromError gets an error object', () => { + beforeEach(() => { + message = getMessageFromError({ customError: 'error string' }); + }); + + it('should return the stringified error', () => { + expect(message).toBe('{"customError":"error string"}'); + }); + }); +}); diff --git a/public/app/core/utils/errors.ts b/public/app/core/utils/errors.ts index 3f6f1cfbc8d..afdf5270ade 100644 --- a/public/app/core/utils/errors.ts +++ b/public/app/core/utils/errors.ts @@ -13,5 +13,5 @@ export function getMessageFromError(err: any): string | null { } } - return null; + return err; } From 94e21de199f3457cb6b271ad1637bf51840147aa Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 5 Mar 2019 12:41:01 +0100 Subject: [PATCH 14/23] Viewers with viewers_can_edit should be able to access /explore (#15787) * fix: Viewers with viewers_can_edit should be able to access /explore #15773 * refactoring initial PR a bit to simplify function and reduce duplication (cherry picked from commit a81d5486b096c77b4d39ee0483a0c127f4480881) --- pkg/api/api.go | 2 +- pkg/middleware/auth.go | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index c80129eac6f..81ea83eae61 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -73,7 +73,7 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/dashboards/", reqSignedIn, hs.Index) r.Get("/dashboards/*", reqSignedIn, hs.Index) - r.Get("/explore", reqEditorRole, hs.Index) + r.Get("/explore", reqSignedIn, middleware.EnsureEditorOrViewerCanEdit, hs.Index) r.Get("/playlists/", reqSignedIn, hs.Index) r.Get("/playlists/*", reqSignedIn, hs.Index) diff --git a/pkg/middleware/auth.go b/pkg/middleware/auth.go index 27248342c8d..e06409211eb 100644 --- a/pkg/middleware/auth.go +++ b/pkg/middleware/auth.go @@ -4,7 +4,7 @@ import ( "net/url" "strings" - "gopkg.in/macaron.v1" + macaron "gopkg.in/macaron.v1" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" @@ -52,6 +52,12 @@ func notAuthorized(c *m.ReqContext) { c.Redirect(setting.AppSubUrl + "/login") } +func EnsureEditorOrViewerCanEdit(c *m.ReqContext) { + if !c.SignedInUser.HasRole(m.ROLE_EDITOR) && !setting.ViewersCanEdit { + accessForbidden(c) + } +} + func RoleAuth(roles ...m.RoleType) macaron.Handler { return func(c *m.ReqContext) { ok := false From 3e243adc29c395f6fccf125125c23858854077ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 5 Mar 2019 12:37:19 +0100 Subject: [PATCH 15/23] Fixed scrolling issue that caused scroll to be locked to the bottom of a long dashboard, fixes #15712 (cherry picked from commit e6a83bf0e1aba6577544736f5fdec4b5c8508842) --- .../src/components/CustomScrollbar/CustomScrollbar.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx index 40f6c6c3c37..a8391644ee9 100644 --- a/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx +++ b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx @@ -42,11 +42,7 @@ export class CustomScrollbar extends PureComponent { const ref = this.ref.current; if (ref && !_.isNil(this.props.scrollTop)) { - if (this.props.scrollTop > 10000) { - ref.scrollToBottom(); - } else { - ref.scrollTop(this.props.scrollTop); - } + ref.scrollTop(this.props.scrollTop); } } From 20d7d4b8c3aec3dac4604bdb99c694f185a10643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Tue, 5 Mar 2019 13:08:31 +0100 Subject: [PATCH 16/23] fix: update datasource in componentDidUpdate Closes #15751 (cherry picked from commit 09b036dc937475e2659bec96b629ac56f1f3bd25) --- .../datasources/settings/DataSourceSettingsPage.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/public/app/features/datasources/settings/DataSourceSettingsPage.tsx b/public/app/features/datasources/settings/DataSourceSettingsPage.tsx index 01eea098ea4..27f60865c21 100644 --- a/public/app/features/datasources/settings/DataSourceSettingsPage.tsx +++ b/public/app/features/datasources/settings/DataSourceSettingsPage.tsx @@ -64,6 +64,14 @@ export class DataSourceSettingsPage extends PureComponent { await loadDataSource(pageId); } + componentDidUpdate(prevProps: Props) { + const { dataSource } = this.props; + + if (prevProps.dataSource !== dataSource) { + this.setState({ dataSource }); + } + } + onSubmit = async (evt: React.FormEvent) => { evt.preventDefault(); @@ -95,9 +103,7 @@ export class DataSourceSettingsPage extends PureComponent { }; onModelChange = (dataSource: DataSourceSettings) => { - this.setState({ - dataSource: dataSource, - }); + this.setState({ dataSource }); }; isReadOnly() { From 816e81ac0a03808c7bf1bd971acfa0d6f79071d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 5 Mar 2019 14:32:33 +0100 Subject: [PATCH 17/23] Fixed scrollbar not visible due to content being added a bit after mount, fixes #15711 (cherry picked from commit cd78f0bef21238fff5ea29f4f80df9be3f69c4ef) --- .../CustomScrollbar/CustomScrollbar.tsx | 15 +++++++++++++++ .../dashboard/containers/DashboardPage.tsx | 7 ++++++- .../__snapshots__/DashboardPage.test.tsx.snap | 2 ++ .../dashboard/panel_editor/EditorTabBody.tsx | 2 +- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx index a8391644ee9..c628d685f5c 100644 --- a/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx +++ b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx @@ -14,6 +14,7 @@ interface Props { scrollTop?: number; setScrollTop: (event: any) => void; autoHeightMin?: number | string; + updateAfterMountMs?: number; } /** @@ -48,6 +49,20 @@ export class CustomScrollbar extends PureComponent { componentDidMount() { this.updateScroll(); + + // this logic is to make scrollbar visible when content is added body after mount + if (this.props.updateAfterMountMs) { + setTimeout(() => this.updateAfterMount(), this.props.updateAfterMountMs); + } + } + + updateAfterMount() { + if (this.ref && this.ref.current) { + const scrollbar = this.ref.current as any; + if (scrollbar.update) { + scrollbar.update(); + } + } } componentDidUpdate() { diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index c52774f7e0e..34483324fa9 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -268,7 +268,12 @@ export class DashboardPage extends PureComponent { onAddPanel={this.onAddPanel} />
- + {editview && } {initError && this.renderInitFailedState()} diff --git a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap index f60e60c43a8..83b5aaaa959 100644 --- a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap +++ b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap @@ -113,6 +113,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` hideTracksWhenNotNeeded={false} scrollTop={0} setScrollTop={[Function]} + updateAfterMountMs={500} >
{ {toolbarItems.map(item => this.renderButton(item))}
- +
{openView && this.renderOpenView(openView)} From 24da15314724c6d86a3e3597530a82631bd41814 Mon Sep 17 00:00:00 2001 From: Jon Ferreira Date: Tue, 5 Mar 2019 15:04:10 -0500 Subject: [PATCH 18/23] Expose onQueryChange to angular plugins (cherry picked from commit a3da8dc6739735372397f3f0e7f3c845f62e4f48) --- public/app/features/explore/QueryEditor.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/app/features/explore/QueryEditor.tsx b/public/app/features/explore/QueryEditor.tsx index 1d329f1c56e..d158f6bb9f3 100644 --- a/public/app/features/explore/QueryEditor.tsx +++ b/public/app/features/explore/QueryEditor.tsx @@ -43,6 +43,9 @@ export default class QueryEditor extends PureComponent { this.props.onQueryChange(target); this.props.onExecuteQuery(); }, + onQueryChange: () => { + this.props.onQueryChange(target); + }, events: exploreEvents, panel: { datasource, targets: [target] }, dashboard: {}, From ef3531312cc3ff13fbe67669a92b6652594dc490 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 5 Mar 2019 18:03:07 +0100 Subject: [PATCH 19/23] fix allow anonymous initial bind for ldap search (cherry picked from commit 3b9f0e6ef2cb2c37db20ff94bbdeb10e2c2008bc) --- pkg/login/ldap.go | 13 ++++++- pkg/login/ldap_test.go | 84 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 93 insertions(+), 4 deletions(-) diff --git a/pkg/login/ldap.go b/pkg/login/ldap.go index c15cb865bd3..8bb331b7e59 100644 --- a/pkg/login/ldap.go +++ b/pkg/login/ldap.go @@ -18,6 +18,7 @@ import ( type ILdapConn interface { Bind(username, password string) error + UnauthenticatedBind(username string) error Search(*ldap.SearchRequest) (*ldap.SearchResult, error) StartTLS(*tls.Config) error Close() @@ -259,7 +260,17 @@ func (a *ldapAuther) initialBind(username, userPassword string) error { bindPath = fmt.Sprintf(a.server.BindDN, username) } - if err := a.conn.Bind(bindPath, userPassword); err != nil { + bindFn := func() error { + return a.conn.Bind(bindPath, userPassword) + } + + if userPassword == "" { + bindFn = func() error { + return a.conn.UnauthenticatedBind(bindPath) + } + } + + if err := bindFn(); err != nil { a.log.Info("Initial bind failed", "error", err) if ldapErr, ok := err.(*ldap.Error); ok { diff --git a/pkg/login/ldap_test.go b/pkg/login/ldap_test.go index ef20feb1373..dabafee65a6 100644 --- a/pkg/login/ldap_test.go +++ b/pkg/login/ldap_test.go @@ -13,6 +13,70 @@ import ( ) func TestLdapAuther(t *testing.T) { + Convey("initialBind", t, func() { + Convey("Given bind dn and password configured", func() { + conn := &mockLdapConn{} + var actualUsername, actualPassword string + conn.bindProvider = func(username, password string) error { + actualUsername = username + actualPassword = password + return nil + } + ldapAuther := &ldapAuther{ + conn: conn, + server: &LdapServerConf{ + BindDN: "cn=%s,o=users,dc=grafana,dc=org", + BindPassword: "bindpwd", + }, + } + err := ldapAuther.initialBind("user", "pwd") + So(err, ShouldBeNil) + So(ldapAuther.requireSecondBind, ShouldBeTrue) + So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org") + So(actualPassword, ShouldEqual, "bindpwd") + }) + + Convey("Given bind dn configured", func() { + conn := &mockLdapConn{} + var actualUsername, actualPassword string + conn.bindProvider = func(username, password string) error { + actualUsername = username + actualPassword = password + return nil + } + ldapAuther := &ldapAuther{ + conn: conn, + server: &LdapServerConf{ + BindDN: "cn=%s,o=users,dc=grafana,dc=org", + }, + } + err := ldapAuther.initialBind("user", "pwd") + So(err, ShouldBeNil) + So(ldapAuther.requireSecondBind, ShouldBeFalse) + So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org") + So(actualPassword, ShouldEqual, "pwd") + }) + + Convey("Given empty bind dn and password", func() { + conn := &mockLdapConn{} + unauthenticatedBindWasCalled := false + var actualUsername string + conn.unauthenticatedBindProvider = func(username string) error { + unauthenticatedBindWasCalled = true + actualUsername = username + return nil + } + ldapAuther := &ldapAuther{ + conn: conn, + server: &LdapServerConf{}, + } + err := ldapAuther.initialBind("user", "pwd") + So(err, ShouldBeNil) + So(ldapAuther.requireSecondBind, ShouldBeTrue) + So(unauthenticatedBindWasCalled, ShouldBeTrue) + So(actualUsername, ShouldBeEmpty) + }) + }) Convey("When translating ldap user to grafana user", t, func() { @@ -365,12 +429,26 @@ func TestLdapAuther(t *testing.T) { } type mockLdapConn struct { - result *ldap.SearchResult - searchCalled bool - searchAttributes []string + result *ldap.SearchResult + searchCalled bool + searchAttributes []string + bindProvider func(username, password string) error + unauthenticatedBindProvider func(username string) error } func (c *mockLdapConn) Bind(username, password string) error { + if c.bindProvider != nil { + return c.bindProvider(username, password) + } + + return nil +} + +func (c *mockLdapConn) UnauthenticatedBind(username string) error { + if c.unauthenticatedBindProvider != nil { + return c.unauthenticatedBindProvider(username) + } + return nil } From 177bee85c678d9a10c5e4fe825574e758437c7a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 6 Mar 2019 11:46:38 +0100 Subject: [PATCH 20/23] Fixed image rendering issue for dashboards with auto refresh, casued by missing reloadOnSearch flag on route, fixes #15631 (cherry picked from commit 70f1abbe375a210971983865abe4aa2299d43832) --- public/app/routes/routes.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/app/routes/routes.ts b/public/app/routes/routes.ts index 4c9c5fd5304..442fb5acb0c 100644 --- a/public/app/routes/routes.ts +++ b/public/app/routes/routes.ts @@ -81,6 +81,7 @@ export function setupAngularRoutes($routeProvider, $locationProvider) { template: '', pageClass: 'dashboard-solo', routeInfo: DashboardRouteInfo.Normal, + reloadOnSearch: false, resolve: { component: () => SoloPanelPage, }, @@ -89,6 +90,7 @@ export function setupAngularRoutes($routeProvider, $locationProvider) { template: '', pageClass: 'dashboard-solo', routeInfo: DashboardRouteInfo.Normal, + reloadOnSearch: false, resolve: { component: () => SoloPanelPage, }, From 97a193d7a57ca5c1ddb62079cc50c0a3a535efd9 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 6 Mar 2019 11:58:27 +0100 Subject: [PATCH 21/23] log phantomjs output even if it timeout and include orgId when render alert (cherry picked from commit 36f3accf0d6a86a2749db8ea846d7e56d2da1612) --- pkg/log/log.go | 11 +++++++++-- pkg/services/alerting/notifier.go | 2 +- pkg/services/rendering/phantomjs.go | 19 +++++++++++++++---- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/pkg/log/log.go b/pkg/log/log.go index 2e3b6303a6e..eb739f855ea 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -25,6 +25,7 @@ var filters map[string]log15.Lvl func init() { loggersToClose = make([]DisposableHandler, 0) loggersToReload = make([]ReloadableHandler, 0) + filters = map[string]log15.Lvl{} Root = log15.Root() Root.SetHandler(log15.DiscardHandler()) } @@ -197,7 +198,7 @@ func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) { // Log level. _, level := getLogLevelFromConfig("log."+mode, defaultLevelName, cfg) - filters := getFilters(util.SplitString(sec.Key("filters").String())) + modeFilters := getFilters(util.SplitString(sec.Key("filters").String())) format := getLogFormat(sec.Key("format").MustString("")) var handler log15.Handler @@ -230,12 +231,18 @@ func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) { } for key, value := range defaultFilters { + if _, exist := modeFilters[key]; !exist { + modeFilters[key] = value + } + } + + for key, value := range modeFilters { if _, exist := filters[key]; !exist { filters[key] = value } } - handler = LogFilterHandler(level, filters, handler) + handler = LogFilterHandler(level, modeFilters, handler) handlers = append(handlers, handler) } diff --git a/pkg/services/alerting/notifier.go b/pkg/services/alerting/notifier.go index 59d459f122e..0dc15badcb0 100644 --- a/pkg/services/alerting/notifier.go +++ b/pkg/services/alerting/notifier.go @@ -138,7 +138,7 @@ func (n *notificationService) uploadImage(context *EvalContext) (err error) { return err } - renderOpts.Path = fmt.Sprintf("d-solo/%s/%s?panelId=%d", ref.Uid, ref.Slug, context.Rule.PanelId) + renderOpts.Path = fmt.Sprintf("d-solo/%s/%s?orgId=%d&panelId=%d", ref.Uid, ref.Slug, context.Rule.OrgId, context.Rule.PanelId) result, err := n.renderService.Render(context.Ctx, renderOpts) if err != nil { diff --git a/pkg/services/rendering/phantomjs.go b/pkg/services/rendering/phantomjs.go index 1bd7489c153..29c2f39fd77 100644 --- a/pkg/services/rendering/phantomjs.go +++ b/pkg/services/rendering/phantomjs.go @@ -36,7 +36,7 @@ func (rs *RenderingService) renderViaPhantomJS(ctx context.Context, opts Opts) ( defer middleware.RemoveRenderAuthKey(renderKey) phantomDebugArg := "--debug=false" - if log.GetLogLevelFor("renderer") >= log.LvlDebug { + if log.GetLogLevelFor("rendering") >= log.LvlDebug { phantomDebugArg = "--debug=true" } @@ -64,13 +64,26 @@ func (rs *RenderingService) renderViaPhantomJS(ctx context.Context, opts Opts) ( cmd := exec.CommandContext(commandCtx, binPath, cmdArgs...) cmd.Stderr = cmd.Stdout + timezone := "" + if opts.Timezone != "" { + timezone = isoTimeOffsetToPosixTz(opts.Timezone) baseEnviron := os.Environ() - cmd.Env = appendEnviron(baseEnviron, "TZ", isoTimeOffsetToPosixTz(opts.Timezone)) + cmd.Env = appendEnviron(baseEnviron, "TZ", timezone) } + rs.log.Debug("executing Phantomjs", "binPath", binPath, "cmdArgs", cmdArgs, "timezone", timezone) + out, err := cmd.Output() + if out != nil { + rs.log.Debug("Phantomjs output", "out", string(out)) + } + + if err != nil { + rs.log.Debug("Phantomjs error", "error", err) + } + // check for timeout first if commandCtx.Err() == context.DeadlineExceeded { rs.log.Info("Rendering timed out") @@ -82,8 +95,6 @@ func (rs *RenderingService) renderViaPhantomJS(ctx context.Context, opts Opts) ( return nil, err } - rs.log.Debug("Phantomjs output", "out", string(out)) - rs.log.Debug("Image rendered", "path", pngPath) return &RenderResult{FilePath: pngPath}, nil } From 3d4f08bea5785c888192a56746f6498ba25f9558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 6 Mar 2019 14:31:05 +0100 Subject: [PATCH 22/23] Temp fix for scrollbar issue PR that was tricky to cherry pick (#15713) --- .../src/components/CustomScrollbar/_CustomScrollbar.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grafana-ui/src/components/CustomScrollbar/_CustomScrollbar.scss b/packages/grafana-ui/src/components/CustomScrollbar/_CustomScrollbar.scss index f5adcedf0fe..07b6ae1b5b6 100644 --- a/packages/grafana-ui/src/components/CustomScrollbar/_CustomScrollbar.scss +++ b/packages/grafana-ui/src/components/CustomScrollbar/_CustomScrollbar.scss @@ -1,4 +1,4 @@ -.custom-scrollbars { +.custom-scrollbars { // Fix for Firefox. For some reason sometimes .view container gets a height of its content, but in order to // make scroll working it should fit outer container size (scroll appears only when inner container size is // greater than outer one). @@ -14,7 +14,7 @@ .track-vertical { border-radius: 3px; width: 6px !important; - right: 2px; + right: 0px; bottom: 2px; top: 2px; } From ae4bdf940396b0d953cfd39f6bc6e019debde986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 6 Mar 2019 14:32:38 +0100 Subject: [PATCH 23/23] Bumped version to 6.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2a5c0312983..00e14eb48a5 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "company": "Grafana Labs" }, "name": "grafana", - "version": "6.0.0", + "version": "6.0.1", "repository": { "type": "git", "url": "http://github.com/grafana/grafana.git"