From 3a3272e225c986cdeb762197a82f84b84b9e769f Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 12 Dec 2017 09:35:57 +0100 Subject: [PATCH 1/6] annotations: allows template variables to be used in tag filter When filtering built in annotations by tag, interpolates the tag with template variables. Fixes #9587 --- .../plugins/datasource/grafana/datasource.ts | 7 +- .../grafana/specs/datasource.jest.ts | 65 +++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 public/app/plugins/datasource/grafana/specs/datasource.jest.ts diff --git a/public/app/plugins/datasource/grafana/datasource.ts b/public/app/plugins/datasource/grafana/datasource.ts index 5ca3c433476..9eb9862094a 100644 --- a/public/app/plugins/datasource/grafana/datasource.ts +++ b/public/app/plugins/datasource/grafana/datasource.ts @@ -3,7 +3,7 @@ import _ from 'lodash'; class GrafanaDatasource { /** @ngInject */ - constructor(private backendSrv, private $q) {} + constructor(private backendSrv, private $q, private templateSrv) {} query(options) { return this.backendSrv @@ -58,6 +58,11 @@ class GrafanaDatasource { if (!_.isArray(options.annotation.tags) || options.annotation.tags.length === 0) { return this.$q.when([]); } + const tags = []; + for (let t of params.tags) { + tags.push(this.templateSrv.replace(t)); + } + params.tags = tags; } return this.backendSrv.get('/api/annotations', params); diff --git a/public/app/plugins/datasource/grafana/specs/datasource.jest.ts b/public/app/plugins/datasource/grafana/specs/datasource.jest.ts new file mode 100644 index 00000000000..544b04056ac --- /dev/null +++ b/public/app/plugins/datasource/grafana/specs/datasource.jest.ts @@ -0,0 +1,65 @@ +import {GrafanaDatasource} from "../datasource"; +import q from 'q'; +import moment from 'moment'; + +describe('grafana data source', () => { + describe('when executing an annotations query', () => { + let calledBackendSrvParams; + const backendSrvStub = { + get: (url, options) => { + calledBackendSrvParams = options; + return q.resolve([]); + } + }; + + const templateSrvStub = { + replace: val => val.replace('$var', 'replaced') + }; + + const ds = new GrafanaDatasource(backendSrvStub, q, templateSrvStub); + + describe('with tags that have template variables', () => { + const options = setupAnnotationQueryOptions( + {tags: ['tag1:$var']} + ); + + beforeEach(() => { + return ds.annotationQuery(options); + }); + + it('should interpolate template variables in tags in query options', () => { + expect(calledBackendSrvParams.tags[0]).toBe('tag1:replaced'); + }); + }); + + describe('with type dashboard', () => { + const options = setupAnnotationQueryOptions( + { + type: 'dashboard', + tags: ['tag1'] + }, + {id: 1} + ); + + beforeEach(() => { + return ds.annotationQuery(options); + }); + + it('should remove tags from query options', () => { + expect(calledBackendSrvParams.tags).toBe(undefined); + }); + }); + }); +}); + +function setupAnnotationQueryOptions(annotation, dashboard?) { + return { + annotation: annotation, + dashboard: dashboard, + range: { + from: moment(1432288354), + to: moment(1432288401) + }, + rangeRaw: {from: "now-24h", to: "now"} + }; +} From 6ba5550f5f3115aa0cf23e958dd460677b81dc90 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 11 Sep 2018 14:09:08 +0200 Subject: [PATCH 2/6] renames jest files to match new convention --- ...{datasource.jest.ts => datasource.test.ts} | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) rename public/app/plugins/datasource/grafana/specs/{datasource.jest.ts => datasource.test.ts} (72%) diff --git a/public/app/plugins/datasource/grafana/specs/datasource.jest.ts b/public/app/plugins/datasource/grafana/specs/datasource.test.ts similarity index 72% rename from public/app/plugins/datasource/grafana/specs/datasource.jest.ts rename to public/app/plugins/datasource/grafana/specs/datasource.test.ts index 544b04056ac..b3afe7207f2 100644 --- a/public/app/plugins/datasource/grafana/specs/datasource.jest.ts +++ b/public/app/plugins/datasource/grafana/specs/datasource.test.ts @@ -13,7 +13,11 @@ describe('grafana data source', () => { }; const templateSrvStub = { - replace: val => val.replace('$var', 'replaced') + replace: val => { + return val + .replace('$var2', 'replaced|replaced2') + .replace('$var', 'replaced'); + } }; const ds = new GrafanaDatasource(backendSrvStub, q, templateSrvStub); @@ -32,6 +36,21 @@ describe('grafana data source', () => { }); }); + describe('with tags that have multi value template variables', () => { + const options = setupAnnotationQueryOptions( + {tags: ['$var2']} + ); + + beforeEach(() => { + return ds.annotationQuery(options); + }); + + it('should interpolate template variables in tags in query options', () => { + expect(calledBackendSrvParams.tags[0]).toBe('replaced'); + expect(calledBackendSrvParams.tags[1]).toBe('replaced2'); + }); + }); + describe('with type dashboard', () => { const options = setupAnnotationQueryOptions( { From 19c7dd9834f88b2e8aa6623ad3d758c41fdeee69 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 11 Sep 2018 14:25:25 +0200 Subject: [PATCH 3/6] support template variables with multiple values --- public/app/plugins/datasource/grafana/datasource.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/grafana/datasource.ts b/public/app/plugins/datasource/grafana/datasource.ts index 3d788378045..b3de9a9c85a 100644 --- a/public/app/plugins/datasource/grafana/datasource.ts +++ b/public/app/plugins/datasource/grafana/datasource.ts @@ -57,8 +57,11 @@ class GrafanaDatasource { return this.$q.when([]); } const tags = []; - for (let t of params.tags) { - tags.push(this.templateSrv.replace(t)); + for (const t of params.tags) { + const renderedValues = this.templateSrv.replace(t, {}, 'pipe'); + for (const tt of renderedValues.split('|')) { + tags.push(tt); + } } params.tags = tags; } From 1638c6bea11f196b69611b28890eddebc91d933e Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 11 Sep 2018 15:50:04 +0200 Subject: [PATCH 4/6] enable partial tag matches for annotations --- pkg/api/annotations.go | 21 +++++----- pkg/services/annotations/annotations.go | 1 + pkg/services/sqlstore/annotation.go | 7 +++- pkg/services/sqlstore/annotation_test.go | 41 ++++++++++++++++++- .../plugins/datasource/grafana/datasource.ts | 1 + .../grafana/partials/annotations.editor.html | 27 ++++++++---- 6 files changed, 76 insertions(+), 22 deletions(-) diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index 55c9c954940..eec07bb9f81 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -14,16 +14,17 @@ import ( func GetAnnotations(c *m.ReqContext) Response { query := &annotations.ItemQuery{ - From: c.QueryInt64("from"), - To: c.QueryInt64("to"), - OrgId: c.OrgId, - UserId: c.QueryInt64("userId"), - AlertId: c.QueryInt64("alertId"), - DashboardId: c.QueryInt64("dashboardId"), - PanelId: c.QueryInt64("panelId"), - Limit: c.QueryInt64("limit"), - Tags: c.QueryStrings("tags"), - Type: c.Query("type"), + From: c.QueryInt64("from"), + To: c.QueryInt64("to"), + OrgId: c.OrgId, + UserId: c.QueryInt64("userId"), + AlertId: c.QueryInt64("alertId"), + DashboardId: c.QueryInt64("dashboardId"), + PanelId: c.QueryInt64("panelId"), + Limit: c.QueryInt64("limit"), + Tags: c.QueryStrings("tags"), + Type: c.Query("type"), + PartialMatch: c.QueryBool("partialMatch"), } repo := annotations.GetRepository() diff --git a/pkg/services/annotations/annotations.go b/pkg/services/annotations/annotations.go index 9b490169d3b..daea43863f4 100644 --- a/pkg/services/annotations/annotations.go +++ b/pkg/services/annotations/annotations.go @@ -21,6 +21,7 @@ type ItemQuery struct { RegionId int64 `json:"regionId"` Tags []string `json:"tags"` Type string `json:"type"` + PartialMatch bool `json:"partialMatch"` Limit int64 `json:"limit"` } diff --git a/pkg/services/sqlstore/annotation.go b/pkg/services/sqlstore/annotation.go index a65bc136554..6e25ce432f3 100644 --- a/pkg/services/sqlstore/annotation.go +++ b/pkg/services/sqlstore/annotation.go @@ -211,7 +211,12 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I ) `, strings.Join(keyValueFilters, " OR ")) - sql.WriteString(fmt.Sprintf(" AND (%s) = %d ", tagsSubQuery, len(tags))) + if query.PartialMatch { + sql.WriteString(fmt.Sprintf(" AND (%s) > 0 ", tagsSubQuery)) + } else { + sql.WriteString(fmt.Sprintf(" AND (%s) = %d ", tagsSubQuery, len(tags))) + } + } } diff --git a/pkg/services/sqlstore/annotation_test.go b/pkg/services/sqlstore/annotation_test.go index c0d267f2578..4c31e0442c8 100644 --- a/pkg/services/sqlstore/annotation_test.go +++ b/pkg/services/sqlstore/annotation_test.go @@ -78,7 +78,31 @@ func TestAnnotations(t *testing.T) { So(err, ShouldBeNil) So(annotation2.Id, ShouldBeGreaterThan, 0) - Convey("Can query for annotation", func() { + globalAnnotation1 := &annotations.Item{ + OrgId: 1, + UserId: 1, + Text: "deploy", + Type: "", + Epoch: 15, + Tags: []string{"deploy"}, + } + err = repo.Save(globalAnnotation1) + So(err, ShouldBeNil) + So(globalAnnotation1.Id, ShouldBeGreaterThan, 0) + + globalAnnotation2 := &annotations.Item{ + OrgId: 1, + UserId: 1, + Text: "rollback", + Type: "", + Epoch: 17, + Tags: []string{"rollback"}, + } + err = repo.Save(globalAnnotation2) + So(err, ShouldBeNil) + So(globalAnnotation2.Id, ShouldBeGreaterThan, 0) + + Convey("Can query for annotation by dashboard id", func() { items, err := repo.Find(&annotations.ItemQuery{ OrgId: 1, DashboardId: 1, @@ -165,7 +189,7 @@ func TestAnnotations(t *testing.T) { OrgId: 1, DashboardId: 1, From: 1, - To: 15, + To: 15, //this will exclude the second test annotation Tags: []string{"outage", "error"}, }) @@ -173,6 +197,19 @@ func TestAnnotations(t *testing.T) { So(items, ShouldHaveLength, 1) }) + Convey("Should find two annotations using partial match", func() { + items, err := repo.Find(&annotations.ItemQuery{ + OrgId: 1, + From: 1, + To: 25, + PartialMatch: true, + Tags: []string{"rollback", "deploy"}, + }) + + So(err, ShouldBeNil) + So(items, ShouldHaveLength, 2) + }) + Convey("Should find one when all key value tag filters does match", func() { items, err := repo.Find(&annotations.ItemQuery{ OrgId: 1, diff --git a/public/app/plugins/datasource/grafana/datasource.ts b/public/app/plugins/datasource/grafana/datasource.ts index b3de9a9c85a..4ddfa8df40d 100644 --- a/public/app/plugins/datasource/grafana/datasource.ts +++ b/public/app/plugins/datasource/grafana/datasource.ts @@ -40,6 +40,7 @@ class GrafanaDatasource { to: options.range.to.valueOf(), limit: options.annotation.limit, tags: options.annotation.tags, + partialMatch: options.annotation.partialMatch, }; if (options.annotation.type === 'dashboard') { diff --git a/public/app/plugins/datasource/grafana/partials/annotations.editor.html b/public/app/plugins/datasource/grafana/partials/annotations.editor.html index 4289a58e5cb..ba68a08cefd 100644 --- a/public/app/plugins/datasource/grafana/partials/annotations.editor.html +++ b/public/app/plugins/datasource/grafana/partials/annotations.editor.html @@ -2,7 +2,7 @@
- + Filter by
    @@ -11,18 +11,11 @@
-
+
- -
- Tags - - -
-
Max limit
@@ -31,6 +24,22 @@
+
+
+ +
+
+ Tags + + +
+
From aed8208d780cbfe68030ec94575cbdb8c61a5dd9 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 13 Sep 2018 15:15:42 +0200 Subject: [PATCH 5/6] renames PartialMatch to MatchAny --- pkg/api/annotations.go | 22 +++++++++---------- pkg/services/annotations/annotations.go | 2 +- pkg/services/sqlstore/annotation.go | 2 +- pkg/services/sqlstore/annotation_test.go | 10 ++++----- .../plugins/datasource/grafana/datasource.ts | 2 +- .../grafana/partials/annotations.editor.html | 10 ++++----- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index eec07bb9f81..242b5531f51 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -14,17 +14,17 @@ import ( func GetAnnotations(c *m.ReqContext) Response { query := &annotations.ItemQuery{ - From: c.QueryInt64("from"), - To: c.QueryInt64("to"), - OrgId: c.OrgId, - UserId: c.QueryInt64("userId"), - AlertId: c.QueryInt64("alertId"), - DashboardId: c.QueryInt64("dashboardId"), - PanelId: c.QueryInt64("panelId"), - Limit: c.QueryInt64("limit"), - Tags: c.QueryStrings("tags"), - Type: c.Query("type"), - PartialMatch: c.QueryBool("partialMatch"), + From: c.QueryInt64("from"), + To: c.QueryInt64("to"), + OrgId: c.OrgId, + UserId: c.QueryInt64("userId"), + AlertId: c.QueryInt64("alertId"), + DashboardId: c.QueryInt64("dashboardId"), + PanelId: c.QueryInt64("panelId"), + Limit: c.QueryInt64("limit"), + Tags: c.QueryStrings("tags"), + Type: c.Query("type"), + MatchAny: c.QueryBool("matchAny"), } repo := annotations.GetRepository() diff --git a/pkg/services/annotations/annotations.go b/pkg/services/annotations/annotations.go index daea43863f4..60a92aa897a 100644 --- a/pkg/services/annotations/annotations.go +++ b/pkg/services/annotations/annotations.go @@ -21,7 +21,7 @@ type ItemQuery struct { RegionId int64 `json:"regionId"` Tags []string `json:"tags"` Type string `json:"type"` - PartialMatch bool `json:"partialMatch"` + MatchAny bool `json:"matchAny"` Limit int64 `json:"limit"` } diff --git a/pkg/services/sqlstore/annotation.go b/pkg/services/sqlstore/annotation.go index 6e25ce432f3..ceafaaad0e3 100644 --- a/pkg/services/sqlstore/annotation.go +++ b/pkg/services/sqlstore/annotation.go @@ -211,7 +211,7 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I ) `, strings.Join(keyValueFilters, " OR ")) - if query.PartialMatch { + if query.MatchAny { sql.WriteString(fmt.Sprintf(" AND (%s) > 0 ", tagsSubQuery)) } else { sql.WriteString(fmt.Sprintf(" AND (%s) = %d ", tagsSubQuery, len(tags))) diff --git a/pkg/services/sqlstore/annotation_test.go b/pkg/services/sqlstore/annotation_test.go index 4c31e0442c8..d3459527e7d 100644 --- a/pkg/services/sqlstore/annotation_test.go +++ b/pkg/services/sqlstore/annotation_test.go @@ -199,11 +199,11 @@ func TestAnnotations(t *testing.T) { Convey("Should find two annotations using partial match", func() { items, err := repo.Find(&annotations.ItemQuery{ - OrgId: 1, - From: 1, - To: 25, - PartialMatch: true, - Tags: []string{"rollback", "deploy"}, + OrgId: 1, + From: 1, + To: 25, + MatchAny: true, + Tags: []string{"rollback", "deploy"}, }) So(err, ShouldBeNil) diff --git a/public/app/plugins/datasource/grafana/datasource.ts b/public/app/plugins/datasource/grafana/datasource.ts index 4ddfa8df40d..3bf772d160c 100644 --- a/public/app/plugins/datasource/grafana/datasource.ts +++ b/public/app/plugins/datasource/grafana/datasource.ts @@ -40,7 +40,7 @@ class GrafanaDatasource { to: options.range.to.valueOf(), limit: options.annotation.limit, tags: options.annotation.tags, - partialMatch: options.annotation.partialMatch, + matchAny: options.annotation.matchAny, }; if (options.annotation.type === 'dashboard') { diff --git a/public/app/plugins/datasource/grafana/partials/annotations.editor.html b/public/app/plugins/datasource/grafana/partials/annotations.editor.html index ba68a08cefd..e5a67d6a7dc 100644 --- a/public/app/plugins/datasource/grafana/partials/annotations.editor.html +++ b/public/app/plugins/datasource/grafana/partials/annotations.editor.html @@ -26,11 +26,11 @@
-
From 758828328772edb95dc46c294efe8946836ba792 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 13 Sep 2018 17:00:01 +0200 Subject: [PATCH 6/6] docs: template variable support for annotations --- docs/sources/reference/annotations.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/sources/reference/annotations.md b/docs/sources/reference/annotations.md index bfc104ef522..3bb50f4badf 100644 --- a/docs/sources/reference/annotations.md +++ b/docs/sources/reference/annotations.md @@ -45,8 +45,9 @@ can still show them if you add a new **Annotation Query** and filter by tags. Bu ### Query by tag You can create new annotation queries that fetch annotations from the native annotation store via the `-- Grafana --` data source and by setting *Filter by* to `Tags`. Specify at least -one tag. For example create an annotation query name `outages` and specify a tag named `outage`. This query will show all annotations you create (from any dashboard or via API) that -have the `outage` tag. +one tag. For example create an annotation query name `outages` and specify a tag named `outage`. This query will show all annotations you create (from any dashboard or via API) that have the `outage` tag. By default, if you add multiple tags in the annotation query, Grafana will only show annotations that have all the tags you supplied. You can invert the behavior by enabling `Match any` which means that Grafana will show annotations that contains at least one of the tags you supplied. + +In 5.4+ it's possible to use template variables in the tag query. So if you have a dashboard showing stats for different services and an template variable that dictates which services to show, you can now use the same template variable in your annotation query to only show annotations for those services. ## Querying other data sources