From a948dfe5149a9845edde752d36f0e9b9d38a3be9 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 10 Nov 2016 14:16:18 +0100 Subject: [PATCH 01/53] fix(influxdb): fixes broken raw query usage --- pkg/tsdb/influxdb/model_parser.go | 2 ++ pkg/tsdb/influxdb/models.go | 1 + pkg/tsdb/influxdb/query_builder.go | 2 +- pkg/tsdb/influxdb/query_builder_test.go | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/tsdb/influxdb/model_parser.go b/pkg/tsdb/influxdb/model_parser.go index 2db7a78ed75..410bf8f6e82 100644 --- a/pkg/tsdb/influxdb/model_parser.go +++ b/pkg/tsdb/influxdb/model_parser.go @@ -12,6 +12,7 @@ type InfluxdbQueryParser struct{} func (qp *InfluxdbQueryParser) Parse(model *simplejson.Json, dsInfo *tsdb.DataSourceInfo) (*Query, error) { policy := model.Get("policy").MustString("default") rawQuery := model.Get("query").MustString("") + useRawQuery := model.Get("rawQuery").MustBool(false) alias := model.Get("alias").MustString("") measurement := model.Get("measurement").MustString("") @@ -54,6 +55,7 @@ func (qp *InfluxdbQueryParser) Parse(model *simplejson.Json, dsInfo *tsdb.DataSo RawQuery: rawQuery, Interval: interval, Alias: alias, + UseRawQuery: useRawQuery, }, nil } diff --git a/pkg/tsdb/influxdb/models.go b/pkg/tsdb/influxdb/models.go index 0dcecd20773..44e05608290 100644 --- a/pkg/tsdb/influxdb/models.go +++ b/pkg/tsdb/influxdb/models.go @@ -8,6 +8,7 @@ type Query struct { GroupBy []*QueryPart Selects []*Select RawQuery string + UseRawQuery bool Alias string Interval string diff --git a/pkg/tsdb/influxdb/query_builder.go b/pkg/tsdb/influxdb/query_builder.go index b783ff5c603..8b3146d59fc 100644 --- a/pkg/tsdb/influxdb/query_builder.go +++ b/pkg/tsdb/influxdb/query_builder.go @@ -17,7 +17,7 @@ var ( type QueryBuilder struct{} func (qb *QueryBuilder) Build(query *Query, queryContext *tsdb.QueryContext) (string, error) { - if query.RawQuery != "" { + if query.UseRawQuery && query.RawQuery != "" { q := query.RawQuery q = strings.Replace(q, "$timeFilter", qb.renderTimeFilter(query, queryContext), 1) diff --git a/pkg/tsdb/influxdb/query_builder_test.go b/pkg/tsdb/influxdb/query_builder_test.go index 408db18e549..e97c7d8cd52 100644 --- a/pkg/tsdb/influxdb/query_builder_test.go +++ b/pkg/tsdb/influxdb/query_builder_test.go @@ -79,6 +79,7 @@ func TestInfluxdbQueryBuilder(t *testing.T) { GroupBy: []*QueryPart{groupBy1, groupBy3}, Interval: "10s", RawQuery: "Raw query", + UseRawQuery: true, } rawQuery, err := builder.Build(query, queryContext) From f924b241ae9109025311f4af3374f0fd0b92fb16 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 10 Nov 2016 14:38:06 +0100 Subject: [PATCH 02/53] tech(influxdb): refactor query builder trying to reduce the amounts of moving parts for influxdb --- pkg/tsdb/influxdb/influxdb.go | 4 +-- .../influxdb/{query_builder.go => query.go} | 30 +++++++++---------- .../{query_builder_test.go => query_test.go} | 24 +++++++-------- 3 files changed, 26 insertions(+), 32 deletions(-) rename pkg/tsdb/influxdb/{query_builder.go => query.go} (71%) rename pkg/tsdb/influxdb/{query_builder_test.go => query_test.go} (77%) diff --git a/pkg/tsdb/influxdb/influxdb.go b/pkg/tsdb/influxdb/influxdb.go index a64a6af2ee9..4efb495dd05 100644 --- a/pkg/tsdb/influxdb/influxdb.go +++ b/pkg/tsdb/influxdb/influxdb.go @@ -18,7 +18,6 @@ import ( type InfluxDBExecutor struct { *tsdb.DataSourceInfo QueryParser *InfluxdbQueryParser - QueryBuilder *QueryBuilder ResponseParser *ResponseParser } @@ -26,7 +25,6 @@ func NewInfluxDBExecutor(dsInfo *tsdb.DataSourceInfo) tsdb.Executor { return &InfluxDBExecutor{ DataSourceInfo: dsInfo, QueryParser: &InfluxdbQueryParser{}, - QueryBuilder: &QueryBuilder{}, ResponseParser: &ResponseParser{}, } } @@ -51,7 +49,7 @@ func (e *InfluxDBExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, return result.WithError(err) } - rawQuery, err := e.QueryBuilder.Build(query, context) + rawQuery, err := query.Build(context) if err != nil { return result.WithError(err) } diff --git a/pkg/tsdb/influxdb/query_builder.go b/pkg/tsdb/influxdb/query.go similarity index 71% rename from pkg/tsdb/influxdb/query_builder.go rename to pkg/tsdb/influxdb/query.go index 8b3146d59fc..d9208aaee3a 100644 --- a/pkg/tsdb/influxdb/query_builder.go +++ b/pkg/tsdb/influxdb/query.go @@ -14,28 +14,26 @@ var ( regexpOperatorPattern *regexp.Regexp = regexp.MustCompile(`^\/.*\/$`) ) -type QueryBuilder struct{} - -func (qb *QueryBuilder) Build(query *Query, queryContext *tsdb.QueryContext) (string, error) { +func (query *Query) Build(queryContext *tsdb.QueryContext) (string, error) { if query.UseRawQuery && query.RawQuery != "" { q := query.RawQuery - q = strings.Replace(q, "$timeFilter", qb.renderTimeFilter(query, queryContext), 1) + q = strings.Replace(q, "$timeFilter", query.renderTimeFilter(queryContext), 1) q = strings.Replace(q, "$interval", tsdb.CalculateInterval(queryContext.TimeRange), 1) return q, nil } - res := qb.renderSelectors(query, queryContext) - res += qb.renderMeasurement(query) - res += qb.renderWhereClause(query) - res += qb.renderTimeFilter(query, queryContext) - res += qb.renderGroupBy(query, queryContext) + res := query.renderSelectors(queryContext) + res += query.renderMeasurement() + res += query.renderWhereClause() + res += query.renderTimeFilter(queryContext) + res += query.renderGroupBy(queryContext) return res, nil } -func (qb *QueryBuilder) renderTags(query *Query) []string { +func (query *Query) renderTags() []string { var res []string for i, tag := range query.Tags { str := "" @@ -76,7 +74,7 @@ func (qb *QueryBuilder) renderTags(query *Query) []string { return res } -func (qb *QueryBuilder) renderTimeFilter(query *Query, queryContext *tsdb.QueryContext) string { +func (query *Query) renderTimeFilter(queryContext *tsdb.QueryContext) string { from := "now() - " + queryContext.TimeRange.From to := "" @@ -87,7 +85,7 @@ func (qb *QueryBuilder) renderTimeFilter(query *Query, queryContext *tsdb.QueryC return fmt.Sprintf("time > %s%s", from, to) } -func (qb *QueryBuilder) renderSelectors(query *Query, queryContext *tsdb.QueryContext) string { +func (query *Query) renderSelectors(queryContext *tsdb.QueryContext) string { res := "SELECT " var selectors []string @@ -103,7 +101,7 @@ func (qb *QueryBuilder) renderSelectors(query *Query, queryContext *tsdb.QueryCo return res + strings.Join(selectors, ", ") } -func (qb *QueryBuilder) renderMeasurement(query *Query) string { +func (query *Query) renderMeasurement() string { policy := "" if query.Policy == "" || query.Policy == "default" { policy = "" @@ -113,9 +111,9 @@ func (qb *QueryBuilder) renderMeasurement(query *Query) string { return fmt.Sprintf(` FROM %s"%s"`, policy, query.Measurement) } -func (qb *QueryBuilder) renderWhereClause(query *Query) string { +func (query *Query) renderWhereClause() string { res := " WHERE " - conditions := qb.renderTags(query) + conditions := query.renderTags() res += strings.Join(conditions, " ") if len(conditions) > 0 { res += " AND " @@ -124,7 +122,7 @@ func (qb *QueryBuilder) renderWhereClause(query *Query) string { return res } -func (qb *QueryBuilder) renderGroupBy(query *Query, queryContext *tsdb.QueryContext) string { +func (query *Query) renderGroupBy(queryContext *tsdb.QueryContext) string { groupBy := "" for i, group := range query.GroupBy { if i == 0 { diff --git a/pkg/tsdb/influxdb/query_builder_test.go b/pkg/tsdb/influxdb/query_test.go similarity index 77% rename from pkg/tsdb/influxdb/query_builder_test.go rename to pkg/tsdb/influxdb/query_test.go index e97c7d8cd52..e8c1312d673 100644 --- a/pkg/tsdb/influxdb/query_builder_test.go +++ b/pkg/tsdb/influxdb/query_test.go @@ -12,7 +12,6 @@ import ( func TestInfluxdbQueryBuilder(t *testing.T) { Convey("Influxdb query builder", t, func() { - builder := QueryBuilder{} qp1, _ := NewQueryPart("field", []string{"value"}) qp2, _ := NewQueryPart("mean", []string{}) @@ -37,7 +36,7 @@ func TestInfluxdbQueryBuilder(t *testing.T) { Interval: "10s", } - rawQuery, err := builder.Build(query, queryContext) + rawQuery, err := query.Build(queryContext) So(err, ShouldBeNil) So(rawQuery, ShouldEqual, `SELECT mean("value") FROM "policy"."cpu" WHERE time > now() - 5m GROUP BY time(10s) fill(null)`) }) @@ -51,23 +50,22 @@ func TestInfluxdbQueryBuilder(t *testing.T) { Interval: "5s", } - rawQuery, err := builder.Build(query, queryContext) + rawQuery, err := query.Build(queryContext) So(err, ShouldBeNil) So(rawQuery, ShouldEqual, `SELECT mean("value") FROM "cpu" WHERE "hostname" = 'server1' OR "hostname" = 'server2' AND time > now() - 5m GROUP BY time(5s), "datacenter" fill(null)`) }) Convey("can render time range", func() { query := Query{} - builder := &QueryBuilder{} Convey("render from: 2h to now-1h", func() { query := Query{} queryContext := &tsdb.QueryContext{TimeRange: tsdb.NewTimeRange("2h", "now-1h")} - So(builder.renderTimeFilter(&query, queryContext), ShouldEqual, "time > now() - 2h and time < now() - 1h") + So(query.renderTimeFilter(queryContext), ShouldEqual, "time > now() - 2h and time < now() - 1h") }) Convey("render from: 10m", func() { queryContext := &tsdb.QueryContext{TimeRange: tsdb.NewTimeRange("10m", "now")} - So(builder.renderTimeFilter(&query, queryContext), ShouldEqual, "time > now() - 10m") + So(query.renderTimeFilter(queryContext), ShouldEqual, "time > now() - 10m") }) }) @@ -82,7 +80,7 @@ func TestInfluxdbQueryBuilder(t *testing.T) { UseRawQuery: true, } - rawQuery, err := builder.Build(query, queryContext) + rawQuery, err := query.Build(queryContext) So(err, ShouldBeNil) So(rawQuery, ShouldEqual, `Raw query`) }) @@ -90,37 +88,37 @@ func TestInfluxdbQueryBuilder(t *testing.T) { Convey("can render normal tags without operator", func() { query := &Query{Tags: []*Tag{&Tag{Operator: "", Value: `value`, Key: "key"}}} - So(strings.Join(builder.renderTags(query), ""), ShouldEqual, `"key" = 'value'`) + So(strings.Join(query.renderTags(), ""), ShouldEqual, `"key" = 'value'`) }) Convey("can render regex tags without operator", func() { query := &Query{Tags: []*Tag{&Tag{Operator: "", Value: `/value/`, Key: "key"}}} - So(strings.Join(builder.renderTags(query), ""), ShouldEqual, `"key" =~ /value/`) + So(strings.Join(query.renderTags(), ""), ShouldEqual, `"key" =~ /value/`) }) Convey("can render regex tags", func() { query := &Query{Tags: []*Tag{&Tag{Operator: "=~", Value: `/value/`, Key: "key"}}} - So(strings.Join(builder.renderTags(query), ""), ShouldEqual, `"key" =~ /value/`) + So(strings.Join(query.renderTags(), ""), ShouldEqual, `"key" =~ /value/`) }) Convey("can render number tags", func() { query := &Query{Tags: []*Tag{&Tag{Operator: "=", Value: "10001", Key: "key"}}} - So(strings.Join(builder.renderTags(query), ""), ShouldEqual, `"key" = 10001`) + So(strings.Join(query.renderTags(), ""), ShouldEqual, `"key" = 10001`) }) Convey("can render number tags with decimals", func() { query := &Query{Tags: []*Tag{&Tag{Operator: "=", Value: "10001.1", Key: "key"}}} - So(strings.Join(builder.renderTags(query), ""), ShouldEqual, `"key" = 10001.1`) + So(strings.Join(query.renderTags(), ""), ShouldEqual, `"key" = 10001.1`) }) Convey("can render string tags", func() { query := &Query{Tags: []*Tag{&Tag{Operator: "=", Value: "value", Key: "key"}}} - So(strings.Join(builder.renderTags(query), ""), ShouldEqual, `"key" = 'value'`) + So(strings.Join(query.renderTags(), ""), ShouldEqual, `"key" = 'value'`) }) }) } From abb8f33d77c394ed257d10dea3351949943b23ba Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 10 Nov 2016 16:07:13 +0100 Subject: [PATCH 03/53] feat(alertlist): make it possible to filter on alerts from current dashboard --- public/app/plugins/panel/alertlist/editor.html | 1 + public/app/plugins/panel/alertlist/module.ts | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/panel/alertlist/editor.html b/public/app/plugins/panel/alertlist/editor.html index b2038df34b8..344af7408fc 100644 --- a/public/app/plugins/panel/alertlist/editor.html +++ b/public/app/plugins/panel/alertlist/editor.html @@ -11,6 +11,7 @@ Max items +
State filter
diff --git a/public/app/plugins/panel/alertlist/module.ts b/public/app/plugins/panel/alertlist/module.ts index 543c5f04833..d03fad49073 100644 --- a/public/app/plugins/panel/alertlist/module.ts +++ b/public/app/plugins/panel/alertlist/module.ts @@ -25,7 +25,8 @@ class AlertListPanel extends PanelCtrl { panelDefaults = { show: 'current', limit: 10, - stateFilter: [] + stateFilter: [], + onlyAlertsOnDashboard: false }; @@ -71,9 +72,13 @@ class AlertListPanel extends PanelCtrl { var params: any = { limit: this.panel.limit, type: 'alert', - newState: this.panel.stateFilter + newState: this.panel.stateFilter, }; + if (this.panel.onlyAlertsOnDashboard) { + params.dashboardId = this.dashboard.id; + } + params.from = dateMath.parse(this.dashboard.time.from).unix() * 1000; params.to = dateMath.parse(this.dashboard.time.to).unix() * 1000; @@ -93,6 +98,10 @@ class AlertListPanel extends PanelCtrl { state: this.panel.stateFilter }; + if (this.panel.onlyAlertsOnDashboard) { + params.dashboardId = this.dashboard.id; + } + this.backendSrv.get(`/api/alerts`, params) .then(res => { this.currentAlerts = _.map(res, al => { From a51de8e987311a0da3ac479dd37d12b10796e71d Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 10 Nov 2016 16:15:48 +0100 Subject: [PATCH 04/53] tech(conf): remove dragoo :( --- conf/defaults.ini | 17 +---------------- conf/sample.ini | 15 --------------- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index b25f3ab6f28..7ead103d027 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -229,7 +229,7 @@ auth_url = https://accounts.google.com/o/oauth2/auth token_url = https://accounts.google.com/o/oauth2/token api_url = https://www.googleapis.com/oauth2/v1/userinfo allowed_domains = -hosted_domain = +hosted_domain = #################################### Grafana.net Auth #################### [auth.grafananet] @@ -390,21 +390,6 @@ global_api_key = -1 global_session = -1 #################################### Alerting ############################ -# docs about alerting can be found in /docs/sources/alerting/ -# __.-/| -# \`o_O' -# =( )= +----------------------------+ -# U| | Alerting is still in alpha | -# /\ /\ / | +----------------------------+ -# ) /^\) ^\/ _)\ | -# ) /^\/ _) \ | -# ) _ / / _) \___|_ -# /\ )/\/ || | )_)\___,|)) -# < > |(,,) )__) | -# || / \)___)\ -# | \____( )___) )____ -# \______(_______;;;)__;;;) - [alerting] # Makes it possible to turn off alert rule execution. execute_alerts = true diff --git a/conf/sample.ini b/conf/sample.ini index e1ed408210a..e8c9c990a63 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -339,21 +339,6 @@ ;path = /var/lib/grafana/dashboards #################################### Alerting ###################################### -# docs about alerting can be found in /docs/sources/alerting/ -# __.-/| -# \`o_O' -# =( )= +----------------------------+ -# U| | Alerting is still in alpha | -# /\ /\ / | +----------------------------+ -# ) /^\) ^\/ _)\ | -# ) /^\/ _) \ | -# ) _ / / _) \___|_ -# /\ )/\/ || | )_)\___,|)) -# < > |(,,) )__) | -# || / \)___)\ -# | \____( )___) )____ -# \______(_______;;;)__;;;) - [alerting] # Makes it possible to turn off alert rule execution. ;execute_alerts = true From e04d27c0b0d18cf2f92190e6f09ad564b46161c1 Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 11 Nov 2016 08:46:52 +0100 Subject: [PATCH 05/53] fix(influxdb): return internal influxdb errors ref #6523 --- pkg/tsdb/influxdb/influxdb.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/tsdb/influxdb/influxdb.go b/pkg/tsdb/influxdb/influxdb.go index 4efb495dd05..5043a2deffc 100644 --- a/pkg/tsdb/influxdb/influxdb.go +++ b/pkg/tsdb/influxdb/influxdb.go @@ -82,6 +82,10 @@ func (e *InfluxDBExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, return result.WithError(err) } + if response.Err != nil { + return result.WithError(response.Err) + } + result.QueryResults = make(map[string]*tsdb.QueryResult) result.QueryResults["A"] = e.ResponseParser.Parse(&response, query) From b98f817d682a957da112df38a012c0bf5f7f9903 Mon Sep 17 00:00:00 2001 From: Andrew McDonald Date: Thu, 10 Nov 2016 23:54:44 -0800 Subject: [PATCH 06/53] Fix for cloudwatch datasource requesting too many datapoints (#6544) The range checking in _getPeriod appeared to be using a different variable than the period that had just been calculated for its bounds checks. --- public/app/plugins/datasource/cloudwatch/datasource.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index fde9e8f09ea..105b08c2b17 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -78,10 +78,10 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) { } else { period = kbn.interval_to_seconds(templateSrv.replace(target.period, options.scopedVars)); } - if (query.period < 60) { + if (period < 60) { period = 60; } - if (range / query.period >= 1440) { + if (range / period >= 1440) { period = Math.ceil(range / 1440 / 60) * 60; } From f5a804a5584cc06a24670d02cba0b8a94d9489b1 Mon Sep 17 00:00:00 2001 From: huydx Date: Fri, 11 Nov 2016 17:59:27 +0900 Subject: [PATCH 07/53] Fix typo (password strenght -> password strength) --- public/app/core/core.ts | 2 +- .../directives/{password_strenght.js => password_strength.js} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename public/app/core/directives/{password_strenght.js => password_strength.js} (100%) diff --git a/public/app/core/core.ts b/public/app/core/core.ts index 567df736a4e..8e9f64fd17f 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -10,7 +10,7 @@ import "./directives/grafana_version_check"; import "./directives/metric_segment"; import "./directives/misc"; import "./directives/ng_model_on_blur"; -import "./directives/password_strenght"; +import "./directives/password_strength"; import "./directives/spectrum_picker"; import "./directives/tags"; import "./directives/value_select_dropdown"; diff --git a/public/app/core/directives/password_strenght.js b/public/app/core/directives/password_strength.js similarity index 100% rename from public/app/core/directives/password_strenght.js rename to public/app/core/directives/password_strength.js From ad97db937c0d8de14a8a3779154df51b5b11a36a Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 11 Nov 2016 13:57:11 +0100 Subject: [PATCH 08/53] feat(stats_usage): add stats about alerts --- pkg/metrics/publish.go | 1 + pkg/models/stats.go | 1 + pkg/services/sqlstore/stats.go | 6 +++++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/metrics/publish.go b/pkg/metrics/publish.go index 4255481b8d1..70db3fc0f86 100644 --- a/pkg/metrics/publish.go +++ b/pkg/metrics/publish.go @@ -101,6 +101,7 @@ func sendUsageStats() { metrics["stats.plugins.apps.count"] = len(plugins.Apps) metrics["stats.plugins.panels.count"] = len(plugins.Panels) metrics["stats.plugins.datasources.count"] = len(plugins.DataSources) + metrics["stats.alerts.count"] = statsQuery.Result.AlertCount dsStats := m.GetDataSourceStatsQuery{} if err := bus.Dispatch(&dsStats); err != nil { diff --git a/pkg/models/stats.go b/pkg/models/stats.go index 067dec763e5..9db645f76ac 100644 --- a/pkg/models/stats.go +++ b/pkg/models/stats.go @@ -5,6 +5,7 @@ type SystemStats struct { UserCount int64 OrgCount int64 PlaylistCount int64 + AlertCount int64 } type DataSourceStats struct { diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index 7580996ad57..eaf461c4d28 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -39,7 +39,11 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error { ( SELECT COUNT(*) FROM ` + dialect.Quote("playlist") + ` - ) AS playlist_count + ) AS playlist_count, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("alert") + ` + ) AS alert_count ` var stats m.SystemStats From a87fd11f26ece20ed48d4cbae6a59025c44a5f0f Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 11 Nov 2016 14:04:38 +0100 Subject: [PATCH 09/53] feat(stats): add alerts to global admin stats --- pkg/models/stats.go | 1 + pkg/services/sqlstore/stats.go | 6 +++++- public/app/features/admin/partials/stats.html | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/models/stats.go b/pkg/models/stats.go index 9db645f76ac..09c251b6cd7 100644 --- a/pkg/models/stats.go +++ b/pkg/models/stats.go @@ -30,6 +30,7 @@ type AdminStats struct { DataSourceCount int `json:"data_source_count"` PlaylistCount int `json:"playlist_count"` StarredDbCount int `json:"starred_db_count"` + AlertCount int `json:"alert_count"` } type GetAdminStatsQuery struct { diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index eaf461c4d28..dd1a8111332 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -89,7 +89,11 @@ func GetAdminStats(query *m.GetAdminStatsQuery) error { ( SELECT COUNT(DISTINCT ` + dialect.Quote("dashboard_id") + ` ) FROM ` + dialect.Quote("star") + ` - ) AS starred_db_count + ) AS starred_db_count, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("alert") + ` + ) AS alert_count ` var stats m.AdminStats diff --git a/public/app/features/admin/partials/stats.html b/public/app/features/admin/partials/stats.html index bcc246b9e82..92e32f4f947 100644 --- a/public/app/features/admin/partials/stats.html +++ b/public/app/features/admin/partials/stats.html @@ -46,6 +46,10 @@ Total starred dashboards {{ctrl.stats.starred_db_count}} + + Total alerts + {{ctrl.stats.alert_count}} +
From 4fdfee739a9fc3d5dd812658fd29bf5a9e2a890f Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 14 Nov 2016 08:47:45 +0100 Subject: [PATCH 10/53] fix(influxdb): add support for regex measurments closes #6560 --- pkg/tsdb/influxdb/query.go | 12 ++++++++++-- pkg/tsdb/influxdb/query_test.go | 12 ++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/pkg/tsdb/influxdb/query.go b/pkg/tsdb/influxdb/query.go index d9208aaee3a..e95b54b0a18 100644 --- a/pkg/tsdb/influxdb/query.go +++ b/pkg/tsdb/influxdb/query.go @@ -11,7 +11,8 @@ import ( ) var ( - regexpOperatorPattern *regexp.Regexp = regexp.MustCompile(`^\/.*\/$`) + regexpOperatorPattern *regexp.Regexp = regexp.MustCompile(`^\/.*\/$`) + regexpMeasurementPattern *regexp.Regexp = regexp.MustCompile(`^\/.*\/$`) ) func (query *Query) Build(queryContext *tsdb.QueryContext) (string, error) { @@ -108,7 +109,14 @@ func (query *Query) renderMeasurement() string { } else { policy = `"` + query.Policy + `".` } - return fmt.Sprintf(` FROM %s"%s"`, policy, query.Measurement) + + measurement := query.Measurement + + if !regexpMeasurementPattern.Match([]byte(measurement)) { + measurement = fmt.Sprintf(`"%s"`, measurement) + } + + return fmt.Sprintf(` FROM %s%s`, policy, measurement) } func (query *Query) renderWhereClause() string { diff --git a/pkg/tsdb/influxdb/query_test.go b/pkg/tsdb/influxdb/query_test.go index e8c1312d673..ee045444c9b 100644 --- a/pkg/tsdb/influxdb/query_test.go +++ b/pkg/tsdb/influxdb/query_test.go @@ -120,5 +120,17 @@ func TestInfluxdbQueryBuilder(t *testing.T) { So(strings.Join(query.renderTags(), ""), ShouldEqual, `"key" = 'value'`) }) + + Convey("can render regular measurement", func() { + query := &Query{Measurement: `apa`, Policy: "policy"} + + So(query.renderMeasurement(), ShouldEqual, ` FROM "policy"."apa"`) + }) + + Convey("can render regexp measurement", func() { + query := &Query{Measurement: `/apa/`, Policy: "policy"} + + So(query.renderMeasurement(), ShouldEqual, ` FROM "policy"./apa/`) + }) }) } From 62ff85e7748f46f63b74055b1ddbacbbb65cb45f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 14 Nov 2016 11:34:58 +0100 Subject: [PATCH 11/53] docs(): fixed ldap link --- docs/sources/installation/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index 2fc727d659f..f3b40b206bf 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -413,7 +413,7 @@ Set to `true` to enable LDAP integration (default: `false`) ### config_file Path to the LDAP specific configuration file (default: `/etc/grafana/ldap.toml`) -> For details on LDAP Configuration, go to the [LDAP Integration](ldap.md) page. +> For details on LDAP Configuration, go to the [LDAP Integration]({{< relref "ldap.md" >}}) page.
From 0cdf05ae5d22ba22409e5ffdf71d9b865a833ac2 Mon Sep 17 00:00:00 2001 From: Nic Roland Date: Mon, 14 Nov 2016 16:04:33 +0000 Subject: [PATCH 12/53] docs(datasources/influxdb): Fix broken image link Twas missing a leading `/`. --- docs/sources/datasources/influxdb.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/datasources/influxdb.md b/docs/sources/datasources/influxdb.md index a5eab80af06..01b68d5b7dc 100644 --- a/docs/sources/datasources/influxdb.md +++ b/docs/sources/datasources/influxdb.md @@ -118,7 +118,7 @@ SHOW TAG VALUES WITH KEY = "hostname" WHERE region =~ /$region/ > Always you `regex values` or `regex wildcard` for All format or multi select format. -![](img/docs/influxdb/templating_simple_ex1.png) +![](/img/docs/influxdb/templating_simple_ex1.png) ## Annotations Annotations allows you to overlay rich event information on top of graphs. From b2db2b26dd88ceae51f986782323265858ac5231 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sat, 12 Nov 2016 13:16:46 -0800 Subject: [PATCH 13/53] Added OR to alert_tab --- public/app/features/alerting/alert_def.ts | 6 ++++++ public/app/features/alerting/alert_tab_ctrl.ts | 4 ++++ public/app/features/alerting/partials/alert_tab.html | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/public/app/features/alerting/alert_def.ts b/public/app/features/alerting/alert_def.ts index 8ee1981f3d8..52089560296 100644 --- a/public/app/features/alerting/alert_def.ts +++ b/public/app/features/alerting/alert_def.ts @@ -28,6 +28,11 @@ var evalFunctions = [ {text: 'HAS NO VALUE' , value: 'no_value'} ]; +var evalOperators = [ + {text: 'OR', value: 'or'}, + {text: 'AND', value: 'and'}, +]; + var reducerTypes = [ {text: 'avg()', value: 'avg'}, {text: 'min()', value: 'min'}, @@ -116,6 +121,7 @@ export default { getStateDisplayModel: getStateDisplayModel, conditionTypes: conditionTypes, evalFunctions: evalFunctions, + evalOperators: evalOperators, noDataModes: noDataModes, executionErrorModes: executionErrorModes, reducerTypes: reducerTypes, diff --git a/public/app/features/alerting/alert_tab_ctrl.ts b/public/app/features/alerting/alert_tab_ctrl.ts index c882a6ac3bf..6757cfc0d91 100644 --- a/public/app/features/alerting/alert_tab_ctrl.ts +++ b/public/app/features/alerting/alert_tab_ctrl.ts @@ -18,6 +18,7 @@ export class AlertTabCtrl { alert: any; conditionModels: any; evalFunctions: any; + evalOperators: any; noDataModes: any; executionErrorModes: any; addNotificationSegment; @@ -41,6 +42,7 @@ export class AlertTabCtrl { this.$scope.ctrl = this; this.subTabIndex = 0; this.evalFunctions = alertDef.evalFunctions; + this.evalOperators = alertDef.evalOperators; this.conditionTypes = alertDef.conditionTypes; this.noDataModes = alertDef.noDataModes; this.executionErrorModes = alertDef.executionErrorModes; @@ -194,6 +196,7 @@ export class AlertTabCtrl { query: {params: ['A', '5m', 'now']}, reducer: {type: 'avg', params: []}, evaluator: {type: 'gt', params: [null]}, + operator: {type: 'and'}, }; } @@ -250,6 +253,7 @@ export class AlertTabCtrl { cm.queryPart = new QueryPart(source.query, alertDef.alertQueryDef); cm.reducerPart = alertDef.createReducerPart(source.reducer); cm.evaluator = source.evaluator; + cm.operator = source.operator; return cm; } diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index 5b85ec105ae..2bc687aee19 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -38,7 +38,7 @@
Conditions
- AND + WHEN
From b9d709ab27a9905ded1df5d7e670f2c92a12a9cb Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 15 Nov 2016 11:47:44 +0100 Subject: [PATCH 14/53] feat(alerting): improve error logging for extracting alerts ref #6576 --- pkg/services/alerting/rule.go | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/pkg/services/alerting/rule.go b/pkg/services/alerting/rule.go index 809640ed4a7..bdf53798e34 100644 --- a/pkg/services/alerting/rule.go +++ b/pkg/services/alerting/rule.go @@ -26,11 +26,32 @@ type Rule struct { } type ValidationError struct { - Reason string + Reason string + Err error + Alertid int64 + DashboardId int64 + PanelId int64 } func (e ValidationError) Error() string { - return e.Reason + extraInfo := "" + if e.Alertid != 0 { + extraInfo = fmt.Sprintf("%s AlertId: %v", extraInfo, e.Alertid) + } + + if e.PanelId != 0 { + extraInfo = fmt.Sprintf("%s PanelId: %v ", extraInfo, e.PanelId) + } + + if e.DashboardId != 0 { + extraInfo = fmt.Sprintf("%s DashboardId: %v", extraInfo, e.DashboardId) + } + + if e.Err != nil { + return fmt.Sprintf("%s %s%s", e.Err.Error(), e.Reason, extraInfo) + } + + return fmt.Sprintf("Failed to extract alert.Reason: %s %s", e.Reason, extraInfo) } var ( @@ -83,7 +104,7 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) { for _, v := range ruleDef.Settings.Get("notifications").MustArray() { jsonModel := simplejson.NewFromAny(v) if id, err := jsonModel.Get("id").Int64(); err != nil { - return nil, ValidationError{Reason: "Invalid notification schema"} + return nil, ValidationError{Reason: "Invalid notification schema", DashboardId: model.DashboardId, Alertid: model.Id, PanelId: model.PanelId} } else { model.Notifications = append(model.Notifications, id) } @@ -93,10 +114,10 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) { conditionModel := simplejson.NewFromAny(condition) conditionType := conditionModel.Get("type").MustString() if factory, exist := conditionFactories[conditionType]; !exist { - return nil, ValidationError{Reason: "Unknown alert condition: " + conditionType} + return nil, ValidationError{Reason: "Unknown alert condition: " + conditionType, DashboardId: model.DashboardId, Alertid: model.Id, PanelId: model.PanelId} } else { if queryCondition, err := factory(conditionModel, index); err != nil { - return nil, err + return nil, ValidationError{Err: err, DashboardId: model.DashboardId, Alertid: model.Id, PanelId: model.PanelId} } else { model.Conditions = append(model.Conditions, queryCondition) } From dfb1b1918c293c052ee286eefc020dc148172e73 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Tue, 15 Nov 2016 06:35:25 -0800 Subject: [PATCH 15/53] Implemented operator based firiing in backend --- pkg/services/alerting/conditions/query.go | 8 +++++++- pkg/services/alerting/eval_handler.go | 9 +++++---- pkg/services/alerting/interfaces.go | 1 + 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pkg/services/alerting/conditions/query.go b/pkg/services/alerting/conditions/query.go index b73db9d590e..394c1557490 100644 --- a/pkg/services/alerting/conditions/query.go +++ b/pkg/services/alerting/conditions/query.go @@ -23,6 +23,7 @@ type QueryCondition struct { Query AlertQuery Reducer QueryReducer Evaluator AlertEvaluator + Operator string HandleRequest tsdb.HandleRequestFunc } @@ -72,6 +73,7 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.Conditio return &alerting.ConditionResult{ Firing: evalMatchCount > 0, NoDataFound: emptySerieCount == len(seriesList), + Operator: c.Operator, EvalMatches: matches, }, nil } @@ -168,8 +170,12 @@ func NewQueryCondition(model *simplejson.Json, index int) (*QueryCondition, erro if err != nil { return nil, err } - condition.Evaluator = evaluator + + operatorJson := model.Get("operator") + operator := operatorJson.Get("type").MustString() + condition.Operator = operator + return &condition, nil } diff --git a/pkg/services/alerting/eval_handler.go b/pkg/services/alerting/eval_handler.go index 538c639abb8..7079eac5f38 100644 --- a/pkg/services/alerting/eval_handler.go +++ b/pkg/services/alerting/eval_handler.go @@ -32,10 +32,11 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) { break } - // break if result has not triggered yet - if cr.Firing == false { - firing = false - break + // calculating Firing based on operator + if cr.Operator == "or" { + firing = firing || cr.Firing + } else { + firing = firing && cr.Firing } context.EvalMatches = append(context.EvalMatches, cr.EvalMatches...) diff --git a/pkg/services/alerting/interfaces.go b/pkg/services/alerting/interfaces.go index cc2561473e3..566fbdb2898 100644 --- a/pkg/services/alerting/interfaces.go +++ b/pkg/services/alerting/interfaces.go @@ -24,6 +24,7 @@ type Notifier interface { type ConditionResult struct { Firing bool NoDataFound bool + Operator string EvalMatches []*EvalMatch } From 8d0bcd23f872429b0c2f0e841ee4e47ee56218b2 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Tue, 15 Nov 2016 07:54:09 -0800 Subject: [PATCH 16/53] Added tests for checking nested operators --- pkg/services/alerting/eval_handler_test.go | 83 +++++++++++++++++++++- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/pkg/services/alerting/eval_handler_test.go b/pkg/services/alerting/eval_handler_test.go index 4c2ec24b506..4ef79504e78 100644 --- a/pkg/services/alerting/eval_handler_test.go +++ b/pkg/services/alerting/eval_handler_test.go @@ -8,12 +8,13 @@ import ( ) type conditionStub struct { - firing bool - matches []*EvalMatch + firing bool + operator string + matches []*EvalMatch } func (c *conditionStub) Eval(context *EvalContext) (*ConditionResult, error) { - return &ConditionResult{Firing: c.firing, EvalMatches: c.matches}, nil + return &ConditionResult{Firing: c.firing, EvalMatches: c.matches, Operator: c.operator}, nil } func TestAlertingExecutor(t *testing.T) { @@ -42,5 +43,81 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, false) }) + + Convey("Show return true if any of the condition is passing with OR operator", func() { + context := NewEvalContext(context.TODO(), &Rule{ + Conditions: []Condition{ + &conditionStub{firing: true, operator: "and"}, + &conditionStub{firing: false, operator: "or"}, + }, + }) + + handler.Eval(context) + So(context.Firing, ShouldEqual, true) + }) + + Convey("Show return false if any of the condition is failing with AND operator", func() { + context := NewEvalContext(context.TODO(), &Rule{ + Conditions: []Condition{ + &conditionStub{firing: true, operator: "and"}, + &conditionStub{firing: false, operator: "and"}, + }, + }) + + handler.Eval(context) + So(context.Firing, ShouldEqual, false) + }) + + Convey("Show return true if one condition is failing with nested OR operator", func() { + context := NewEvalContext(context.TODO(), &Rule{ + Conditions: []Condition{ + &conditionStub{firing: true, operator: "and"}, + &conditionStub{firing: true, operator: "and"}, + &conditionStub{firing: false, operator: "or"}, + }, + }) + + handler.Eval(context) + So(context.Firing, ShouldEqual, true) + }) + + Convey("Show return false if one condition is passing with nested OR operator", func() { + context := NewEvalContext(context.TODO(), &Rule{ + Conditions: []Condition{ + &conditionStub{firing: true, operator: "and"}, + &conditionStub{firing: false, operator: "and"}, + &conditionStub{firing: false, operator: "or"}, + }, + }) + + handler.Eval(context) + So(context.Firing, ShouldEqual, false) + }) + + Convey("Show return false if a condition is failing with nested AND operator", func() { + context := NewEvalContext(context.TODO(), &Rule{ + Conditions: []Condition{ + &conditionStub{firing: true, operator: "and"}, + &conditionStub{firing: false, operator: "and"}, + &conditionStub{firing: true, operator: "and"}, + }, + }) + + handler.Eval(context) + So(context.Firing, ShouldEqual, false) + }) + + Convey("Show return true if a condition is passing with nested OR operator", func() { + context := NewEvalContext(context.TODO(), &Rule{ + Conditions: []Condition{ + &conditionStub{firing: true, operator: "and"}, + &conditionStub{firing: false, operator: "or"}, + &conditionStub{firing: true, operator: "or"}, + }, + }) + + handler.Eval(context) + So(context.Firing, ShouldEqual, true) + }) }) } From a4de6da9a0c8a77d22b905d35ec01fc5e6f14ab6 Mon Sep 17 00:00:00 2001 From: Ben RUBSON Date: Tue, 15 Nov 2016 18:59:17 +0100 Subject: [PATCH 17/53] Correct series highlight (#6578) solves issue #6573 --- public/app/plugins/panel/graph/graph_tooltip.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/panel/graph/graph_tooltip.js b/public/app/plugins/panel/graph/graph_tooltip.js index 3aec815428f..40ed293ff4e 100644 --- a/public/app/plugins/panel/graph/graph_tooltip.js +++ b/public/app/plugins/panel/graph/graph_tooltip.js @@ -195,7 +195,7 @@ function ($) { } var highlightClass = ''; - if (item && i === item.seriesIndex) { + if (item && hoverInfo.index === item.seriesIndex) { highlightClass = 'graph-tooltip-list-item--highlight'; } From f46c4c88caacf71149460225a3991afc9e2604e1 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Tue, 15 Nov 2016 22:43:09 -0800 Subject: [PATCH 18/53] Added OR condition in docs --- docs/sources/alerting/rules.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/sources/alerting/rules.md b/docs/sources/alerting/rules.md index ecab88f48d2..af3cfecfdb8 100644 --- a/docs/sources/alerting/rules.md +++ b/docs/sources/alerting/rules.md @@ -55,7 +55,10 @@ Currently the only condition type that exists is a `Query` condition that allows specify a query letter, time range and an aggregation function. The letter refers to a query you already have added in the **Metrics** tab. The result from the query and the aggregation function is a single value that is then used in the threshold check. The query used in an alert rule cannot -contain any template variables. Currently we only support `AND` operator between conditions. +contain any template variables. Currently we only support `AND` and `OR` operators between conditions and they are executed serially. +For example, we have 3 conditions in the following order: +`condition:A(evaluates to: TRUE) OR condition:B(evaluates to: FALSE) AND condition:C(evaluates to: TRUE)` +so the result will be calculated as ((TRUE OR FALSE) AND TRUE) = TRUE. We plan to add other condition types in the future, like `Other Alert`, where you can include the state of another alert in your conditions, and `Time Of Day`. From cbe8af967d0cbe9a81f437cea4fb92a5908742f9 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 16 Nov 2016 09:06:05 +0100 Subject: [PATCH 19/53] docs(api): add docs about creating new org close #6588 --- docs/sources/http_api/org.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/sources/http_api/org.md b/docs/sources/http_api/org.md index adb5d5cd31e..36188075124 100644 --- a/docs/sources/http_api/org.md +++ b/docs/sources/http_api/org.md @@ -85,6 +85,34 @@ page_keywords: grafana, admin, http, api, documentation, orgs, organisation } } +## Create Organisation + +`POST /api/org` + +**Example Request**: + + POST /api/org HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + + { + "name":"New Org." + } + + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + { + "orgId":"1", + "message":"Organization created" + } + + + ## Update current Organisation `PUT /api/org` From f91a833e6a3d3c81dc68ab14999460ccb3902057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 16 Nov 2016 09:43:55 +0100 Subject: [PATCH 20/53] fix(panel): set initial transparency state at first link --- public/app/features/panel/panel_directive.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/public/app/features/panel/panel_directive.ts b/public/app/features/panel/panel_directive.ts index 3af80d25483..28ef0d2bd63 100644 --- a/public/app/features/panel/panel_directive.ts +++ b/public/app/features/panel/panel_directive.ts @@ -68,8 +68,8 @@ module.directive('grafanaPanel', function($rootScope) { // the reason for handling these classes this way is for performance // limit the watchers on panels etc - var transparentLastState; - var lastHasAlertRule; + var transparentLastState = false; + var lastHasAlertRule = false; var lastAlertState; var hasAlertRule; var lastHeight = 0; @@ -91,6 +91,12 @@ module.directive('grafanaPanel', function($rootScope) { lastHeight = ctrl.containerHeight; } + // set initial transparency + if (ctrl.panel.transparent) { + transparentLastState = true; + panelContainer.addClass('panel-transparent', true); + } + ctrl.events.on('render', () => { if (lastHeight !== ctrl.containerHeight) { panelContainer.css({minHeight: ctrl.containerHeight}); From 10cf32f835c24fa013e3a823353be2e4a916847f Mon Sep 17 00:00:00 2001 From: Matt Toback Date: Wed, 16 Nov 2016 04:50:01 -0500 Subject: [PATCH 21/53] Removed green border from OK alerts. Makes dashboards too busy, competes for attention where action is unnceessary (#6555) --- public/sass/pages/_alerting.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/public/sass/pages/_alerting.scss b/public/sass/pages/_alerting.scss index b739485f170..70124f0cd85 100644 --- a/public/sass/pages/_alerting.scss +++ b/public/sass/pages/_alerting.scss @@ -61,7 +61,6 @@ } &--ok { - box-shadow: 0 0 5px rgba(0,200,0,10.8); .panel-alert-icon:before { color: $online; content: "\e611"; From 9740752c1e5859c42e27c9e4097fe6d5dd5b3a2d Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Wed, 16 Nov 2016 18:50:33 +0900 Subject: [PATCH 22/53] (cloudwatch) long retention support (#6547) --- .../plugins/datasource/cloudwatch/datasource.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index 105b08c2b17..07648779648 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -37,7 +37,8 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) { query.dimensions = self.convertDimensionFormat(target.dimensions, options.scopedVars); query.statistics = target.statistics; - var period = this._getPeriod(target, query, options, start, end); + var now = Math.round(Date.now() / 1000); + var period = this._getPeriod(target, query, options, start, end, now); target.period = period; query.period = period; @@ -67,11 +68,19 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) { }); }; - this._getPeriod = function(target, query, options, start, end) { + this._getPeriod = function(target, query, options, start, end, now) { var period; var range = end - start; - if (!target.period) { + var daySec = 60 * 60 * 24; + var periodUnit = 60; + if (now - start > (daySec * 15)) { // until 63 days ago + periodUnit = period = 60 * 5; + } else if (now - start > (daySec * 63)) { // until 455 days ago + periodUnit = period = 60 * 60; + } else if (now - start > (daySec * 455)) { // over 455 days, should return error, but try to long period + periodUnit = period = 60 * 60; + } else if (!target.period) { period = (query.namespace === 'AWS/EC2') ? 300 : 60; } else if (/^\d+$/.test(target.period)) { period = parseInt(target.period, 10); @@ -82,7 +91,7 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) { period = 60; } if (range / period >= 1440) { - period = Math.ceil(range / 1440 / 60) * 60; + period = Math.ceil(range / 1440 / periodUnit) * periodUnit; } return period; From e12a4f6b4619a9759c594e4f7ff61ee2fdc25a3f Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 16 Nov 2016 10:55:39 +0100 Subject: [PATCH 23/53] docs(notifications): fixes typo --- docs/sources/alerting/notifications.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/alerting/notifications.md b/docs/sources/alerting/notifications.md index ce9dcdf1b93..dfe84f142ec 100644 --- a/docs/sources/alerting/notifications.md +++ b/docs/sources/alerting/notifications.md @@ -98,6 +98,6 @@ Amazon S3 for this and Webdav. So to set that up you need to configure the [external image uploader](/installation/configuration/#external-image-storage) in your grafana-server ini config file. -This is not an optional requirement, you can get slack and email notifications without setting this up. +This is an optional requirement, you can get slack and email notifications without setting this up. From 171335bfefc3cc3f7581fa1b47f071e8a066e1f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 16 Nov 2016 14:28:32 +0100 Subject: [PATCH 24/53] fix(ux): fixed dropzone display issues, fixes #6598 --- CHANGELOG.md | 1 + public/app/features/dashboard/model.ts | 4 +--- public/app/features/panel/panel_ctrl.ts | 3 +++ public/sass/pages/_dashboard.scss | 6 ++++++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dd0adba4c5..e61f1d0a516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * **Graph Panel**: Log base scale on right Y-axis had no effect, max value calc was not applied, [#6534](https://github.com/grafana/grafana/issues/6534) * **Graph Panel**: Bar width if bars was only used in series override, [#6528](https://github.com/grafana/grafana/issues/6528) * **UI/Browser**: Fixed issue with page/view header gradient border not showing in Safari, [#6530](https://github.com/grafana/grafana/issues/6530) +* **UX**: Panel Drop zone visible after duplicating panel, and when entering fullscreen/edit view, [#6598](https://github.com/grafana/grafana/issues/6598) # 4.0-beta1 (2016-11-09) diff --git a/public/app/features/dashboard/model.ts b/public/app/features/dashboard/model.ts index ad28ce4643c..169bad79830 100644 --- a/public/app/features/dashboard/model.ts +++ b/public/app/features/dashboard/model.ts @@ -233,7 +233,6 @@ export class DashboardModel { } duplicatePanel(panel, row) { - var rowIndex = _.indexOf(this.rows, row); var newPanel = angular.copy(panel); newPanel.id = this.getNextPanelId(); @@ -242,8 +241,7 @@ export class DashboardModel { delete newPanel.repeatPanelId; delete newPanel.scopedVars; - var currentRow = this.rows[rowIndex]; - currentRow.panels.push(newPanel); + row.addPanel(newPanel); return newPanel; } diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 40b7365da23..1d1b7c7f4a2 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -188,6 +188,9 @@ export class PanelCtrl { duplicate() { this.dashboard.duplicatePanel(this.panel, this.row); + this.$timeout(() => { + this.$scope.$root.$broadcast('render'); + }); } updateColumnSpan(span) { diff --git a/public/sass/pages/_dashboard.scss b/public/sass/pages/_dashboard.scss index 213220674cd..73b2772abc8 100644 --- a/public/sass/pages/_dashboard.scss +++ b/public/sass/pages/_dashboard.scss @@ -172,6 +172,12 @@ div.flot-text { } } +.panel-in-fullscreen { + .panel-drop-zone { + display: none !important; + } +} + .panel-time-info { font-weight: bold; float: right; From 9c7f8268dc623bf4307bdc22c3e5d9c02b88e3e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 16 Nov 2016 14:39:17 +0100 Subject: [PATCH 25/53] fix(build): fixed failing unit test --- .../app/features/dashboard/specs/dashboard_srv_specs.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/specs/dashboard_srv_specs.ts b/public/app/features/dashboard/specs/dashboard_srv_specs.ts index 520216f18ec..6646851e597 100644 --- a/public/app/features/dashboard/specs/dashboard_srv_specs.ts +++ b/public/app/features/dashboard/specs/dashboard_srv_specs.ts @@ -62,7 +62,9 @@ describe('dashboardSrv', function() { it('duplicate panel should try to add it to same row', function() { var panel = { span: 4, attr: '123', id: 10 }; - dashboard.rows = [{ panels: [panel] }]; + + dashboard.addEmptyRow(); + dashboard.rows[0].addPanel(panel); dashboard.duplicatePanel(panel, dashboard.rows[0]); expect(dashboard.rows[0].panels[0].span).to.be(4); @@ -73,7 +75,9 @@ describe('dashboardSrv', function() { it('duplicate panel should remove repeat data', function() { var panel = { span: 4, attr: '123', id: 10, repeat: 'asd', scopedVars: { test: 'asd' }}; - dashboard.rows = [{ panels: [panel] }]; + + dashboard.addEmptyRow(); + dashboard.rows[0].addPanel(panel); dashboard.duplicatePanel(panel, dashboard.rows[0]); expect(dashboard.rows[0].panels[1].repeat).to.be(undefined); From 4fa995eafa9d05ef666222ec2f4b3c2025504ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 16 Nov 2016 14:44:55 +0100 Subject: [PATCH 26/53] ux(view/tv mode): treat tab change as user activity, resets in activity timer --- public/app/core/components/grafana_app.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/public/app/core/components/grafana_app.ts b/public/app/core/components/grafana_app.ts index ed3425f670f..40797e9a47c 100644 --- a/public/app/core/components/grafana_app.ts +++ b/public/app/core/components/grafana_app.ts @@ -122,7 +122,7 @@ export function grafanaAppDirective(playlistSrv, contextSrv) { // handle in active view state class var lastActivity = new Date().getTime(); var activeUser = true; - var inActiveTimeLimit = 60 * 1000; + var inActiveTimeLimit = 10 * 1000; function checkForInActiveUser() { if (!activeUser) { @@ -147,9 +147,14 @@ export function grafanaAppDirective(playlistSrv, contextSrv) { } } + // mouse and keyboard is user activity body.mousemove(userActivityDetected); body.keydown(userActivityDetected); - setInterval(checkForInActiveUser, 1000); + // treat tab change as activity + document.addEventListener('visibilitychange', userActivityDetected); + + // check every 2 seconds + setInterval(checkForInActiveUser, 2000); appEvents.on('toggle-view-mode', () => { lastActivity = 0; From a353c8d1bb2601efbcefa3d2055c79eba9fe9799 Mon Sep 17 00:00:00 2001 From: Tom Kozlowski Date: Tue, 15 Nov 2016 14:36:21 -0500 Subject: [PATCH 27/53] added explicitly setting token as Bearer Type --- pkg/api/login_oauth.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/api/login_oauth.go b/pkg/api/login_oauth.go index b174f88d649..2e58d9155fc 100644 --- a/pkg/api/login_oauth.go +++ b/pkg/api/login_oauth.go @@ -96,7 +96,7 @@ func OAuthLogin(ctx *middleware.Context) { } sslcli := &http.Client{Transport: tr} - oauthCtx = context.TODO() + oauthCtx = context.Background() oauthCtx = context.WithValue(oauthCtx, oauth2.HTTPClient, sslcli) } @@ -106,6 +106,8 @@ func OAuthLogin(ctx *middleware.Context) { ctx.Handle(500, "login.OAuthLogin(NewTransportWithCode)", err) return } + // token.TokenType was defaulting to "bearer", which is out of spec, so we explicitly set to "Bearer" + token.TokenType = "Bearer" ctx.Logger.Debug("OAuthLogin Got token") From e7a30ac46487eaf1f477a6f62be1382592f89de1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 16 Nov 2016 16:29:10 +0100 Subject: [PATCH 28/53] fix(units): fixed issue with data rate bit units, fixes #6602 --- public/app/core/utils/kbn.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/app/core/utils/kbn.js b/public/app/core/utils/kbn.js index a807a249235..78489dcae58 100644 --- a/public/app/core/utils/kbn.js +++ b/public/app/core/utils/kbn.js @@ -420,11 +420,11 @@ function($, _, moment) { kbn.valueFormats.bps = kbn.formatBuilders.decimalSIPrefix('bps'); kbn.valueFormats.Bps = kbn.formatBuilders.decimalSIPrefix('Bps'); kbn.valueFormats.KBs = kbn.formatBuilders.decimalSIPrefix('Bs', 1); - kbn.valueFormats.Kbits = kbn.formatBuilders.decimalSIPrefix('bits', 1); + kbn.valueFormats.Kbits = kbn.formatBuilders.decimalSIPrefix('bps', 1); kbn.valueFormats.MBs = kbn.formatBuilders.decimalSIPrefix('Bs', 2); - kbn.valueFormats.Mbits = kbn.formatBuilders.decimalSIPrefix('bits', 2); + kbn.valueFormats.Mbits = kbn.formatBuilders.decimalSIPrefix('bps', 2); kbn.valueFormats.GBs = kbn.formatBuilders.decimalSIPrefix('Bs', 3); - kbn.valueFormats.Gbits = kbn.formatBuilders.decimalSIPrefix('bits', 3); + kbn.valueFormats.Gbits = kbn.formatBuilders.decimalSIPrefix('bps', 3); // Throughput kbn.valueFormats.ops = kbn.formatBuilders.simpleCountUnit('ops'); From c980efd4701561e28ba55ea5e3a4ce933aa57674 Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Wed, 16 Nov 2016 11:08:38 -0500 Subject: [PATCH 29/53] fix up sass lint warnings (#6603) --- public/sass/components/_gfbox.scss | 2 +- public/sass/components/_row.scss | 3 +-- public/sass/components/_shortcuts.scss | 4 ++-- public/sass/components/_submenu.scss | 11 ++++++----- public/sass/components/_tabbed_view.scss | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/public/sass/components/_gfbox.scss b/public/sass/components/_gfbox.scss index b4e31a2397d..4dbff112e35 100644 --- a/public/sass/components/_gfbox.scss +++ b/public/sass/components/_gfbox.scss @@ -16,10 +16,10 @@ background-color: transparent; border: none; padding: 8px; + color: $text-color; i { font-size: 120%; } - color: $text-color; &:hover { color: $white; } diff --git a/public/sass/components/_row.scss b/public/sass/components/_row.scss index 25d6b9eb622..840db839607 100644 --- a/public/sass/components/_row.scss +++ b/public/sass/components/_row.scss @@ -74,12 +74,11 @@ .add-panel-panels-scroll { width: 100%; overflow: auto; + -ms-overflow-style: none; &::-webkit-scrollbar { display: none } - - -ms-overflow-style: none; } .add-panel-panels { diff --git a/public/sass/components/_shortcuts.scss b/public/sass/components/_shortcuts.scss index 4e2d56503d9..1dedb062183 100644 --- a/public/sass/components/_shortcuts.scss +++ b/public/sass/components/_shortcuts.scss @@ -6,6 +6,8 @@ } .shortcut-table { + margin-bottom: $spacer; + .shortcut-table-category-header { font-weight: normal; font-size: $font-size-h6; @@ -26,8 +28,6 @@ text-align: right; color: $text-color; } - - margin-bottom: $spacer; } .shortcut-table-key { diff --git a/public/sass/components/_submenu.scss b/public/sass/components/_submenu.scss index 14f0658f7cb..93376e0d106 100644 --- a/public/sass/components/_submenu.scss +++ b/public/sass/components/_submenu.scss @@ -7,11 +7,12 @@ } .annotation-segment { + padding: 8px 7px; + label.cr1 { margin-left: 5px; margin-top: 3px; } - padding: 8px 7px; } .submenu-item { @@ -31,14 +32,14 @@ .variable-value-link { padding-right: 10px; - .label-tag { - margin: 0 5px; - } - padding: 8px 7px; box-sizing: content-box; display: inline-block; color: $text-color; + + .label-tag { + margin: 0 5px; + } } .variable-link-wrapper { diff --git a/public/sass/components/_tabbed_view.scss b/public/sass/components/_tabbed_view.scss index f1b59fa2363..e72252330a1 100644 --- a/public/sass/components/_tabbed_view.scss +++ b/public/sass/components/_tabbed_view.scss @@ -38,10 +38,10 @@ background-color: transparent; border: none; padding: ($tabs-padding-top + $tabs-top-margin) $spacer $tabs-padding-bottom; + color: $text-color; i { font-size: 120%; } - color: $text-color; &:hover { color: $white; } From e3564d12a124a6deeb2edeca682c3cf4a74c6a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 16 Nov 2016 17:08:49 +0100 Subject: [PATCH 30/53] fix(404): fixed 404 page --- pkg/api/api.go | 1 + public/app/partials/error.html | 15 ++++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index ed73f2dc76d..4fa28f799b0 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -307,4 +307,5 @@ func Register(r *macaron.Macaron) { InitAppPluginRoutes(r) + r.NotFound(NotFoundHandler) } diff --git a/public/app/partials/error.html b/public/app/partials/error.html index d38f71ac5d8..0401d6d98db 100644 --- a/public/app/partials/error.html +++ b/public/app/partials/error.html @@ -1,11 +1,12 @@ + + -
-
+
-
-

Page not found (404)

-
- -
+
From 18e965c775022f9a88ceb7bcddd56d16e2d1bce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 16 Nov 2016 17:41:44 +0100 Subject: [PATCH 31/53] fix(error handling): fixed server side error handling page --- pkg/middleware/middleware.go | 1 + pkg/middleware/recovery.go | 74 ++++++++++++------------------------ public/views/500.html | 28 ++++++++------ 3 files changed, 41 insertions(+), 62 deletions(-) diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index cb3f4480821..a546d7e76fc 100644 --- a/pkg/middleware/middleware.go +++ b/pkg/middleware/middleware.go @@ -187,6 +187,7 @@ func (ctx *Context) Handle(status int, title string, err error) { } ctx.Data["Title"] = title + ctx.Data["AppSubUrl"] = setting.AppSubUrl ctx.HTML(status, strconv.Itoa(status)) } diff --git a/pkg/middleware/recovery.go b/pkg/middleware/recovery.go index 8843f2e55d3..b63bc623549 100644 --- a/pkg/middleware/recovery.go +++ b/pkg/middleware/recovery.go @@ -19,53 +19,14 @@ import ( "bytes" "fmt" "io/ioutil" - "net/http" "runtime" "gopkg.in/macaron.v1" - "github.com/go-macaron/inject" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/setting" ) -const ( - panicHtml = ` -PANIC: %s - - - -

PANIC

-
%s
-
%s
- -` -) - var ( dunno = []byte("???") centerDot = []byte("ยท") @@ -151,21 +112,34 @@ func Recovery() macaron.Handler { panicLogger.Error("Request error", "error", err, "stack", string(stack)) - // Lookup the current responsewriter - val := c.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil))) - res := val.Interface().(http.ResponseWriter) + c.Data["Title"] = "Server Error" + c.Data["AppSubUrl"] = setting.AppSubUrl + + if theErr, ok := err.(error); ok { + c.Data["Title"] = theErr.Error() + } - // respond with panic message while in development mode - var body []byte if setting.Env == setting.DEV { - res.Header().Set("Content-Type", "text/html") - body = []byte(fmt.Sprintf(panicHtml, err, err, stack)) + c.Data["ErrorMsg"] = string(stack) } - res.WriteHeader(http.StatusInternalServerError) - if nil != body { - res.Write(body) - } + c.HTML(500, "500") + + // // Lookup the current responsewriter + // val := c.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil))) + // res := val.Interface().(http.ResponseWriter) + // + // // respond with panic message while in development mode + // var body []byte + // if setting.Env == setting.DEV { + // res.Header().Set("Content-Type", "text/html") + // body = []byte(fmt.Sprintf(panicHtml, err, err, stack)) + // } + // + // res.WriteHeader(http.StatusInternalServerError) + // if nil != body { + // res.Write(body) + // } } }() diff --git a/public/views/500.html b/public/views/500.html index 9565304e7ff..cb2e99595f1 100644 --- a/public/views/500.html +++ b/public/views/500.html @@ -5,28 +5,32 @@ - Grafana + Grafana - Error + + + + - + + -
-
- +
+ -
-

[[.Title]]

- [[.ErrorMsg]] -
+

[[.Title]]

+ +
[[.ErrorMsg]]
+
- - + From 465451c289bf4dbabc9a3387eb98724ab8ee40a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 16 Nov 2016 17:47:37 +0100 Subject: [PATCH 32/53] cleanup(): removed unused uio-angular tab component --- public/app/partials/bootstrap/tab.html | 3 - public/app/partials/bootstrap/tabset.html | 11 - public/sass/components/_navs.scss | 2 +- public/sass/layout/_page.scss | 6 - public/vendor/angular-ui/ui-bootstrap-tpls.js | 322 +----------------- 5 files changed, 3 insertions(+), 341 deletions(-) delete mode 100644 public/app/partials/bootstrap/tab.html delete mode 100644 public/app/partials/bootstrap/tabset.html diff --git a/public/app/partials/bootstrap/tab.html b/public/app/partials/bootstrap/tab.html deleted file mode 100644 index d76dd67caf2..00000000000 --- a/public/app/partials/bootstrap/tab.html +++ /dev/null @@ -1,3 +0,0 @@ -
  • - {{heading}} -
  • diff --git a/public/app/partials/bootstrap/tabset.html b/public/app/partials/bootstrap/tabset.html deleted file mode 100644 index a811f83fbb5..00000000000 --- a/public/app/partials/bootstrap/tabset.html +++ /dev/null @@ -1,11 +0,0 @@ -
    - -
    -
    -
    -
    -
    diff --git a/public/sass/components/_navs.scss b/public/sass/components/_navs.scss index 4bb02d7b514..abbb4ba5042 100644 --- a/public/sass/components/_navs.scss +++ b/public/sass/components/_navs.scss @@ -87,7 +87,7 @@ } // temp hack -.modal-body, .gf-box { +.modal-body { .nav-tabs { border-bottom: none; } diff --git a/public/sass/layout/_page.scss b/public/sass/layout/_page.scss index 6203f4be083..5702a1aa260 100644 --- a/public/sass/layout/_page.scss +++ b/public/sass/layout/_page.scss @@ -62,12 +62,6 @@ .admin-page { max-width: 800px; margin-left: 10px; - .gf-box { - margin-top: 0; - } - .gf-box-body { - min-height: 0; - } h2 { margin-left: 15px; margin-bottom: 0px; diff --git a/public/vendor/angular-ui/ui-bootstrap-tpls.js b/public/vendor/angular-ui/ui-bootstrap-tpls.js index 2e7fd621809..87120b66ce1 100644 --- a/public/vendor/angular-ui/ui-bootstrap-tpls.js +++ b/public/vendor/angular-ui/ui-bootstrap-tpls.js @@ -5,8 +5,8 @@ * Version: 0.13.4 - 2015-09-03 * License: MIT */ -angular.module("ui.bootstrap", ["ui.bootstrap.tpls","ui.bootstrap.position","ui.bootstrap.dateparser","ui.bootstrap.datepicker","ui.bootstrap.tabs"]); -angular.module("ui.bootstrap.tpls", ["template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/tabs/tab.html","template/tabs/tabset.html"]); +angular.module("ui.bootstrap", ["ui.bootstrap.tpls","ui.bootstrap.position","ui.bootstrap.dateparser","ui.bootstrap.datepicker"]); +angular.module("ui.bootstrap.tpls", ["template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html"]); angular.module('ui.bootstrap.position', []) /** @@ -1180,302 +1180,6 @@ function($compile, $parse, $document, $rootScope, $position, dateFilter, datePar }); -/** - * @ngdoc overview - * @name ui.bootstrap.tabs - * - * @description - * AngularJS version of the tabs directive. - */ - -angular.module('ui.bootstrap.tabs', []) - -.controller('TabsetController', ['$scope', function TabsetCtrl($scope) { - var ctrl = this, - tabs = ctrl.tabs = $scope.tabs = []; - - ctrl.select = function(selectedTab) { - angular.forEach(tabs, function(tab) { - if (tab.active && tab !== selectedTab) { - tab.active = false; - tab.onDeselect(); - selectedTab.selectCalled = false; - } - }); - selectedTab.active = true; - // only call select if it has not already been called - if (!selectedTab.selectCalled) { - selectedTab.onSelect(); - selectedTab.selectCalled = true; - } - }; - - ctrl.addTab = function addTab(tab) { - tabs.push(tab); - // we can't run the select function on the first tab - // since that would select it twice - if (tabs.length === 1 && tab.active !== false) { - tab.active = true; - } else if (tab.active) { - ctrl.select(tab); - } else { - tab.active = false; - } - }; - - ctrl.removeTab = function removeTab(tab) { - var index = tabs.indexOf(tab); - //Select a new tab if the tab to be removed is selected and not destroyed - if (tab.active && tabs.length > 1 && !destroyed) { - //If this is the last tab, select the previous tab. else, the next tab. - var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1; - ctrl.select(tabs[newActiveIndex]); - } - tabs.splice(index, 1); - }; - - var destroyed; - $scope.$on('$destroy', function() { - destroyed = true; - }); -}]) - -/** - * @ngdoc directive - * @name ui.bootstrap.tabs.directive:tabset - * @restrict EA - * - * @description - * Tabset is the outer container for the tabs directive - * - * @param {boolean=} vertical Whether or not to use vertical styling for the tabs. - * @param {boolean=} justified Whether or not to use justified styling for the tabs. - * - * @example - - - - First Content! - Second Content! - -
    - - First Vertical Content! - Second Vertical Content! - - - First Justified Content! - Second Justified Content! - -
    -
    - */ -.directive('tabset', function() { - return { - restrict: 'EA', - transclude: true, - replace: true, - scope: { - type: '@' - }, - controller: 'TabsetController', - templateUrl: 'template/tabs/tabset.html', - link: function(scope, element, attrs) { - scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; - scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; - } - }; -}) - -/** - * @ngdoc directive - * @name ui.bootstrap.tabs.directive:tab - * @restrict EA - * - * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}. - * @param {string=} select An expression to evaluate when the tab is selected. - * @param {boolean=} active A binding, telling whether or not this tab is selected. - * @param {boolean=} disabled A binding, telling whether or not this tab is disabled. - * - * @description - * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}. - * - * @example - - -
    - - -
    - - First Tab - - Alert me! - Second Tab, with alert callback and html heading! - - - {{item.content}} - - -
    -
    - - function TabsDemoCtrl($scope) { - $scope.items = [ - { title:"Dynamic Title 1", content:"Dynamic Item 0" }, - { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true } - ]; - - $scope.alertMe = function() { - setTimeout(function() { - alert("You've selected the alert tab!"); - }); - }; - }; - -
    - */ - -/** - * @ngdoc directive - * @name ui.bootstrap.tabs.directive:tabHeading - * @restrict EA - * - * @description - * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element. - * - * @example - - - - - HTML in my titles?! - And some content, too! - - - Icon heading?!? - That's right. - - - - - */ -.directive('tab', ['$parse', '$log', function($parse, $log) { - return { - require: '^tabset', - restrict: 'EA', - replace: true, - templateUrl: 'template/tabs/tab.html', - transclude: true, - scope: { - active: '=?', - heading: '@', - onSelect: '&select', //This callback is called in contentHeadingTransclude - //once it inserts the tab's content into the dom - onDeselect: '&deselect' - }, - controller: function() { - //Empty controller so other directives can require being 'under' a tab - }, - link: function(scope, elm, attrs, tabsetCtrl, transclude) { - scope.$watch('active', function(active) { - if (active) { - tabsetCtrl.select(scope); - } - }); - - scope.disabled = false; - if (attrs.disable) { - scope.$parent.$watch($parse(attrs.disable), function(value) { - scope.disabled = !! value; - }); - } - - // Deprecation support of "disabled" parameter - // fix(tab): IE9 disabled attr renders grey text on enabled tab #2677 - // This code is duplicated from the lines above to make it easy to remove once - // the feature has been completely deprecated - if (attrs.disabled) { - $log.warn('Use of "disabled" attribute has been deprecated, please use "disable"'); - scope.$parent.$watch($parse(attrs.disabled), function(value) { - scope.disabled = !! value; - }); - } - - scope.select = function() { - if (!scope.disabled) { - scope.active = true; - } - }; - - tabsetCtrl.addTab(scope); - scope.$on('$destroy', function() { - tabsetCtrl.removeTab(scope); - }); - - //We need to transclude later, once the content container is ready. - //when this link happens, we're inside a tab heading. - scope.$transcludeFn = transclude; - } - }; -}]) - -.directive('tabHeadingTransclude', function() { - return { - restrict: 'A', - require: '^tab', - link: function(scope, elm, attrs, tabCtrl) { - scope.$watch('headingElement', function updateHeadingElement(heading) { - if (heading) { - elm.html(''); - elm.append(heading); - } - }); - } - }; -}) - -.directive('tabContentTransclude', function() { - return { - restrict: 'A', - require: '^tabset', - link: function(scope, elm, attrs) { - var tab = scope.$eval(attrs.tabContentTransclude); - - //Now our tab is ready to be transcluded: both the tab heading area - //and the tab content area are loaded. Transclude 'em both. - tab.$transcludeFn(tab.$parent, function(contents) { - angular.forEach(contents, function(node) { - if (isTabHeading(node)) { - //Let tabHeadingTransclude know. - tab.headingElement = node; - } else { - elm.append(node); - } - }); - }); - } - }; - - function isTabHeading(node) { - return node.tagName && ( - node.hasAttribute('tab-heading') || - node.hasAttribute('data-tab-heading') || - node.hasAttribute('x-tab-heading') || - node.tagName.toLowerCase() === 'tab-heading' || - node.tagName.toLowerCase() === 'data-tab-heading' || - node.tagName.toLowerCase() === 'x-tab-heading' - ); - } -}); - angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/datepicker/datepicker.html", "
    \n" + @@ -1568,25 +1272,3 @@ angular.module("template/datepicker/year.html", []).run(["$templateCache", funct ""); }]); -angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/tabs/tab.html", - "
  • \n" + - " {{heading}}\n" + - "
  • \n" + - ""); -}]); - -angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/tabs/tabset.html", - "
    \n" + - "
      \n" + - "
      \n" + - "
      \n" + - "
      \n" + - "
      \n" + - "
      \n" + - ""); -}]); From f64385cfc9ef5f1629d4a241694066613c5a0bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 16 Nov 2016 17:53:56 +0100 Subject: [PATCH 33/53] ux(tweak): minor polish to date picker styling --- public/app/app.ts | 1 - public/app/core/core.ts | 1 - .../core/directives/grafana_version_check.js | 31 ------------------- public/sass/components/_timepicker.scss | 5 +-- 4 files changed, 3 insertions(+), 35 deletions(-) delete mode 100644 public/app/core/directives/grafana_version_check.js diff --git a/public/app/app.ts b/public/app/app.ts index c004bac4177..22431a5110c 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -40,7 +40,6 @@ export class GrafanaApp { init() { var app = angular.module('grafana', []); - app.constant('grafanaVersion', "@grafanaVersion@"); moment.locale(config.bootData.user.locale); diff --git a/public/app/core/core.ts b/public/app/core/core.ts index 8e9f64fd17f..4aa2e7eb64a 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -6,7 +6,6 @@ import "./directives/dash_class"; import "./directives/confirm_click"; import "./directives/dash_edit_link"; import "./directives/dropdown_typeahead"; -import "./directives/grafana_version_check"; import "./directives/metric_segment"; import "./directives/misc"; import "./directives/ng_model_on_blur"; diff --git a/public/app/core/directives/grafana_version_check.js b/public/app/core/directives/grafana_version_check.js deleted file mode 100644 index bee437b8183..00000000000 --- a/public/app/core/directives/grafana_version_check.js +++ /dev/null @@ -1,31 +0,0 @@ -define([ - '../core_module', -], -function (coreModule) { - 'use strict'; - - coreModule.default.directive('grafanaVersionCheck', function($http, contextSrv) { - return { - restrict: 'A', - link: function(scope, elem) { - if (contextSrv.version === 'master') { - return; - } - - $http({ method: 'GET', url: 'https://grafanarel.s3.amazonaws.com/latest.json' }) - .then(function(response) { - if (!response.data || !response.data.version) { - return; - } - - if (contextSrv.version !== response.data.version) { - elem.append(' ' + - ' ' + - 'New version available: ' + response.data.version + - ''); - } - }); - } - }; - }); -}); diff --git a/public/sass/components/_timepicker.scss b/public/sass/components/_timepicker.scss index 1ecc2c7a166..4046e6496b4 100644 --- a/public/sass/components/_timepicker.scss +++ b/public/sass/components/_timepicker.scss @@ -65,7 +65,8 @@ } .gf-timepicker-component { - margin-bottom: 10px; + padding: $spacer/2 0; + td { padding: 1px; } @@ -73,7 +74,7 @@ @include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl); background-image: none; border: none; - padding: 6px 10px; + padding: 5px 9px; color: $text-color; &.active span { color: $blue; From a50178eab1bc6a60982496157ed1fd6c495f85d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 16 Nov 2016 17:56:42 +0100 Subject: [PATCH 34/53] ux(tweak): minor polish to date picker styling --- public/sass/components/_timepicker.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/public/sass/components/_timepicker.scss b/public/sass/components/_timepicker.scss index 4046e6496b4..a5f311cdb46 100644 --- a/public/sass/components/_timepicker.scss +++ b/public/sass/components/_timepicker.scss @@ -65,16 +65,17 @@ } .gf-timepicker-component { - padding: $spacer/2 0; + padding: $spacer/2 0 $spacer 0; td { padding: 1px; } button.btn-sm { @include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl); + font-size: $font-size-sm; background-image: none; border: none; - padding: 5px 9px; + padding: 5px 11px; color: $text-color; &.active span { color: $blue; From 079b0ef44ad5ee30b8be9f98e85e482c7efdb31a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 16 Nov 2016 18:03:00 +0100 Subject: [PATCH 35/53] change(alerting): delete alert rule from panel model when duplicating panel --- public/app/features/dashboard/model.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/public/app/features/dashboard/model.ts b/public/app/features/dashboard/model.ts index 169bad79830..999c0470fdd 100644 --- a/public/app/features/dashboard/model.ts +++ b/public/app/features/dashboard/model.ts @@ -240,6 +240,7 @@ export class DashboardModel { delete newPanel.repeatIteration; delete newPanel.repeatPanelId; delete newPanel.scopedVars; + delete newPanel.alert; row.addPanel(newPanel); return newPanel; From d7e8753c594b9f3b52d37bb2207c7da3087a399d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 16 Nov 2016 18:08:45 +0100 Subject: [PATCH 36/53] cleanup(): removed unused gfbox style --- public/sass/_grafana.scss | 1 - public/sass/components/_gfbox.scss | 69 ------------------------------ 2 files changed, 70 deletions(-) delete mode 100644 public/sass/components/_gfbox.scss diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index dfb8506d295..28ab2922864 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -54,7 +54,6 @@ @import "components/gf-form"; @import "components/sidemenu"; @import "components/navbar"; -@import "components/gfbox"; @import "components/timepicker"; @import "components/filter-controls"; @import "components/filter-list"; diff --git a/public/sass/components/_gfbox.scss b/public/sass/components/_gfbox.scss deleted file mode 100644 index 4dbff112e35..00000000000 --- a/public/sass/components/_gfbox.scss +++ /dev/null @@ -1,69 +0,0 @@ -.gf-box { - margin: 10px 5px; - background-color: $page-bg; - position: relative; - border: 1px solid $tight-form-func-bg; -} - -.gf-box-no-margin { - margin: 0; -} - -.gf-box-header-close-btn { - float: right; - padding: 0; - margin: 0; - background-color: transparent; - border: none; - padding: 8px; - color: $text-color; - i { - font-size: 120%; - } - &:hover { - color: $white; - } -} - -.gf-box-header-save-btn { - padding: 7px 0; - float: right; - color: $gray-2; - font-style: italic; -} - -.gf-box-body { - padding: 20px; - min-height: 150px; -} - -.gf-box-footer { - overflow: hidden; -} - -.gf-box-header { - border-bottom: 1px solid $tight-form-func-bg; - overflow: hidden; - background-color: $tight-form-bg; - .tabs { - float: left; - } - .nav { - margin: 0; - } -} - -.gf-box-title { - padding-right: 20px; - padding-left: 10px; - float: left; - color: $link-color; - font-size: 18px; - font-weight: normal; - line-height: 38px; - margin: 0; - .fa { - padding: 0 8px 0 5px; - color: $text-color; - } -} From e6776f71d9b19b61997bf59920ffa72085698810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 16 Nov 2016 18:26:39 +0100 Subject: [PATCH 37/53] cleanup(sass): removed tight-form styles --- docs/sources/datasources/plugin_api.md | 6 - .../dashboard/partials/globalAlerts.html | 282 ------------------ .../panel/partials/query_editor_row.html | 56 ---- public/sass/_grafana.scss | 1 - public/sass/components/_query_editor.scss | 79 +++++ public/sass/components/_tightform.scss | 235 --------------- 6 files changed, 79 insertions(+), 580 deletions(-) delete mode 100644 public/app/features/dashboard/partials/globalAlerts.html delete mode 100644 public/sass/components/_tightform.scss diff --git a/docs/sources/datasources/plugin_api.md b/docs/sources/datasources/plugin_api.md index cdcaca29460..2e2121ed21a 100644 --- a/docs/sources/datasources/plugin_api.md +++ b/docs/sources/datasources/plugin_api.md @@ -30,11 +30,5 @@ Even though the data source type name is with lowercase `g`, the directive uses that is how angular directives needs to be named in order to match an element with name ``. You also specify the query controller here instead of in the query.editor.html partial like before. -### query.editor.html - -This partial needs to be updated, remove the `np-repeat` this is done in the outer partial now,m the query.editor.html -should only render a single query. Take a look at the Graphite or InfluxDB partials for `query.editor.html` for reference. -You should also add a `tight-form-item` with `{{target.refId}}`, all queries needs to be assigned a letter (`refId`). -These query reference letters are going to be utilized in a later feature. diff --git a/public/app/features/dashboard/partials/globalAlerts.html b/public/app/features/dashboard/partials/globalAlerts.html deleted file mode 100644 index 2c065c714fb..00000000000 --- a/public/app/features/dashboard/partials/globalAlerts.html +++ /dev/null @@ -1,282 +0,0 @@ - - - - -
      -
      -

      Global alerts

      - -
      -
      -
        -
      • Filters:
      • -
      • Alert State
      • -
      • -
      • Dashboards
      • -
      • -
      • - - - -
      • -
      -
      -
      -
      -
        -
      • - -
      • -
      • - -
      • -
      • - -
      • -
      • - 2 selected, showing 6 of 6 total -
      • -
      -
        -
      • - -
        -
        Alert query configure alerting
        -
        -
          -
        • A
        • -
        • apps
        • -
        • -
        • fakesite
        • -
        • counters
        • -
        • requests
        • -
        • count
        • -
        • scaleToSeconds(1)
        • -
        • aliasByNode(2)
        • -
        -
        -
        -
        -
      • -
      • - -
        -
        Alert query configure alerting
        -
        -
          -
        • A
        • -
        • apps
        • -
        • -
        • fakesite
        • -
        • counters
        • -
        • requests
        • -
        • count
        • -
        • scaleToSeconds(1)
        • -
        • aliasByNode(2)
        • -
        -
        -
        -
        -
      • -
      • - -
        -
        Alert query configure alerting
        -
        -
          -
        • A
        • -
        • apps
        • -
        • -
        • fakesite
        • -
        • counters
        • -
        • requests
        • -
        • count
        • -
        • scaleToSeconds(1)
        • -
        • aliasByNode(2)
        • -
        -
        -
        -
        -
      • -
      • - -
        -
        Alert query configure alerting
        -
        -
          -
        • A
        • -
        • apps
        • -
        • -
        • fakesite
        • -
        • counters
        • -
        • requests
        • -
        • count
        • -
        • scaleToSeconds(1)
        • -
        • aliasByNode(2)
        • -
        -
        -
        -
        -
      • -
      • - -
        -
        Alert query configure alerting
        -
        -
          -
        • A
        • -
        • apps
        • -
        • -
        • fakesite
        • -
        • counters
        • -
        • requests
        • -
        • count
        • -
        • scaleToSeconds(1)
        • -
        • aliasByNode(2)
        • -
        -
        -
        -
        -
      • -
      -
      -
      diff --git a/public/app/features/panel/partials/query_editor_row.html b/public/app/features/panel/partials/query_editor_row.html index e8dbe1434e7..55933bbbae8 100644 --- a/public/app/features/panel/partials/query_editor_row.html +++ b/public/app/features/panel/partials/query_editor_row.html @@ -57,59 +57,3 @@
      -
      - - -
        -
      • - {{ctrl.target.refId}} -
      • -
      • - - - -
      • -
      - -
        -
      - -
      -
      diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index 28ab2922864..53a96cf9291 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -50,7 +50,6 @@ @import "components/tagsinput"; @import "components/tables_lists"; @import "components/search"; -@import "components/tightform"; @import "components/gf-form"; @import "components/sidemenu"; @import "components/navbar"; diff --git a/public/sass/components/_query_editor.scss b/public/sass/components/_query_editor.scss index 4c807ed0e6b..f2d29572e37 100644 --- a/public/sass/components/_query_editor.scss +++ b/public/sass/components/_query_editor.scss @@ -67,3 +67,82 @@ } } +.grafana-metric-options { + margin-top: 25px; +} + +.tight-form-func { + background: $tight-form-func-bg; + + &.show-function-controls { + padding-top: 5px; + min-width: 100px; + text-align: center; + } +} + +input[type="text"].tight-form-func-param { + background: transparent; + border: none; + margin: 0; + padding: 0; +} + +.tight-form-func-controls { + display: none; + text-align: center; + + .fa-arrow-left { + float: left; + position: relative; + top: 2px; + } + .fa-arrow-right { + float: right; + position: relative; + top: 2px; + } + .fa-remove { + margin-left: 10px; + } +} + +.grafana-metric-options { + margin-top: 25px; +} + +.tight-form-func { + background: $tight-form-func-bg; + + &.show-function-controls { + padding-top: 5px; + min-width: 100px; + text-align: center; + } +} + +input[type="text"].tight-form-func-param { + background: transparent; + border: none; + margin: 0; + padding: 0; +} + +.tight-form-func-controls { + display: none; + text-align: center; + + .fa-arrow-left { + float: left; + position: relative; + top: 2px; + } + .fa-arrow-right { + float: right; + position: relative; + top: 2px; + } + .fa-remove { + margin-left: 10px; + } +} diff --git a/public/sass/components/_tightform.scss b/public/sass/components/_tightform.scss deleted file mode 100644 index 7c43854b06c..00000000000 --- a/public/sass/components/_tightform.scss +++ /dev/null @@ -1,235 +0,0 @@ -.tight-form { - border-top: 1px solid $tight-form-border; - border-left: 1px solid $tight-form-border; - border-right: 1px solid $tight-form-border; - background: $tight-form-bg; - - &.last { - border-bottom: 1px solid $tight-form-border; - } - - &.borderless { - background: transparent; - border: none; - } - - .checkbox-label { - display: inline; - padding-right: 4px; - margin-bottom: 0; - cursor: pointer; - } -} - -.tight-form-container-no-item-borders { - border: 1px solid $tight-form-border; - border-bottom: none; - - .tight-form, .tight-form-item, [type="text"].tight-form-input, [type="text"].tight-form-clear-input { - border: none; - } -} - -.spaced-form { - .tight-form { - margin: 7px 0; - } -} - -.borderless { - .tight-form-item, - .tight-form-input { - border: none; - } -} - -.tight-form-container { - border-bottom: 1px solid $tight-form-border; -} - -.tight-form-btn { - padding: 7px 12px; -} - -.tight-form-list { - list-style: none; - margin: 0; - >li { - float: left; - } -} - -.tight-form-flex-wrapper { - display: flex; - flex-direction: row; - float: none !important; -} - -.grafana-metric-options { - margin-top: 25px; -} - -.tight-form-item { - padding: 8px 7px; - box-sizing: content-box; - display: inline-block; - font-weight: normal; - border-right: 1px solid $tight-form-border; - display: inline-block; - color: $text-color; - - .has-open-function & { - padding-top: 25px; - } - - .tight-form-disabled & { - color: $link-color-disabled; - a { - color: $link-color-disabled; - } - } - - &:hover, &:focus { - text-decoration: none; - } - - &a:hover { - background: $tight-form-func-bg; - } - - &.last { - border-right: none; - } -} - - -.tight-form-item-icon { - i { - width: 15px; - text-align: center; - display: inline-block; - } -} - -.tight-form-func { - background: $tight-form-func-bg; - - &.show-function-controls { - padding-top: 5px; - min-width: 100px; - text-align: center; - } -} - -input[type="text"].tight-form-func-param { - background: transparent; - border: none; - margin: 0; - padding: 0; -} - -input[type="text"].tight-form-clear-input { - padding: 8px 7px; - border: none; - margin: 0px; - background: transparent; - border-radius: 0; - border-right: 1px solid $tight-form-border; -} - -[type="text"], -[type="email"], -[type="number"], -[type="password"] { - &.tight-form-input { - background-color: $input-bg; - border: none; - border-right: 1px solid $tight-form-border; - margin: 0px; - border-radius: 0; - padding: 8px 6px; - height: 100%; - box-sizing: border-box; - &.last { - border-right: none; - } - } -} - -input[type="checkbox"].tight-form-checkbox { - margin: 0; -} - -.tight-form-textarea { - height: 200px; - margin: 0; - box-sizing: border-box; -} - -select.tight-form-input { - border: none; - border-right: 1px solid $tight-form-border; - background-color: $input-bg; - margin: 0px; - border-radius: 0; - height: 36px; - padding: 9px 3px; - &.last { - border-right: none; - } -} - -.tight-form-func-controls { - display: none; - text-align: center; - - .fa-arrow-left { - float: left; - position: relative; - top: 2px; - } - .fa-arrow-right { - float: right; - position: relative; - top: 2px; - } - .fa-remove { - margin-left: 10px; - } -} - -.tight-form-radio { - input[type="radio"] { - margin: 0; - } - label { - display: inline; - } -} - -.tight-form-section { - margin-bottom: 20px; - margin-right: 40px; - vertical-align: top; - display: inline-block; - .tight-form { - margin-left: 20px; - } -} - -.tight-form-align { - padding-left: 66px; -} - -.tight-form-item-large { width: 115px; } -.tight-form-item-xlarge { width: 150px; } -.tight-form-item-xxlarge { width: 200px; } - -.tight-form-input.tight-form-item-xxlarge { - width: 215px; -} - -.tight-form-inner-box { - margin: 20px 0 20px 148px; - display: inline-block; -} From 4581bb4a7a23da86b54cdbdad53c1b74919959ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 16 Nov 2016 18:44:39 +0100 Subject: [PATCH 38/53] fix(singletat): Support repeated template variables in prefix/postfix, fixes #6595 --- CHANGELOG.md | 3 +++ public/app/features/dashboard/dynamic_dashboard_srv.ts | 2 ++ public/app/plugins/panel/singlestat/module.ts | 9 +++------ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e61f1d0a516..73d7aad8fb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ * **UI/Browser**: Fixed issue with page/view header gradient border not showing in Safari, [#6530](https://github.com/grafana/grafana/issues/6530) * **UX**: Panel Drop zone visible after duplicating panel, and when entering fullscreen/edit view, [#6598](https://github.com/grafana/grafana/issues/6598) +### Enhancements +* **Singlestat**: Support repeated template variables in prefix/postfix [#6595](https://github.com/grafana/grafana/issues/6595) + # 4.0-beta1 (2016-11-09) ### Enhancements diff --git a/public/app/features/dashboard/dynamic_dashboard_srv.ts b/public/app/features/dashboard/dynamic_dashboard_srv.ts index 9f8fe7f7ab2..e5f1c6a9fa1 100644 --- a/public/app/features/dashboard/dynamic_dashboard_srv.ts +++ b/public/app/features/dashboard/dynamic_dashboard_srv.ts @@ -64,6 +64,8 @@ export class DynamicDashboardSrv { j = j - 1; } } + + row.panelSpanChanged(); } } diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index 8247b2cd690..3af89abf1d7 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -208,11 +208,8 @@ class SingleStatCtrl extends MetricsPanelCtrl { } // Add $__name variable for using in prefix or postfix - data.scopedVars = { - __name: { - value: this.series[0].label - } - }; + data.scopedVars = _.extend({}, this.panel.scopedVars); + data.scopedVars["__name"] = {value: this.series[0].label}; } // check value to text mappings if its enabled @@ -526,7 +523,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { elem.toggleClass('pointer', panel.links.length > 0); if (panel.links.length > 0) { - linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], panel.scopedVars); + linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], data.scopedVars); } else { linkInfo = null; } From f52c6aafe7ac36deb7d297510293c6b083a1feb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 16 Nov 2016 18:49:14 +0100 Subject: [PATCH 39/53] docs(): minor update to centos docs about font packages needed for phantomjs --- docs/sources/installation/rpm.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/sources/installation/rpm.md b/docs/sources/installation/rpm.md index cf33e69aa12..ca71a2355f9 100644 --- a/docs/sources/installation/rpm.md +++ b/docs/sources/installation/rpm.md @@ -141,6 +141,18 @@ those options. - [OpenTSDB]({{< relref "datasources/opentsdb.md" >}}) - [Prometheus]({{< relref "datasources/prometheus.md" >}}) +### Server side image rendering + +Server side image (png) rendering is a feature that is optional but very useful when sharing visualizations, +for example in alert notifications. + +If the image is missing text make sure you have font packages installed. + +``` +yum install fontconfig +yum install freetype* +yum install urw-fonts +``` ## Installing from binary tar file From 42167a65c6ec7e3124122469299140ffd516d7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 16 Nov 2016 19:00:23 +0100 Subject: [PATCH 40/53] testdata(): added test graph for null stacking scenario, #6566 --- .../testdata/dashboards/graph_last_1h.json | 285 ++++++++++++++---- public/app/plugins/app/testdata/plugin.json | 2 +- 2 files changed, 235 insertions(+), 52 deletions(-) diff --git a/public/app/plugins/app/testdata/dashboards/graph_last_1h.json b/public/app/plugins/app/testdata/dashboards/graph_last_1h.json index c64ab84f338..c314feb56ad 100644 --- a/public/app/plugins/app/testdata/dashboards/graph_last_1h.json +++ b/public/app/plugins/app/testdata/dashboards/graph_last_1h.json @@ -1,5 +1,5 @@ { - "revision": 5, + "revision": 6, "title": "TestData - Graph Panel Last 1h", "tags": [ "grafana-test" @@ -7,8 +7,48 @@ "style": "dark", "timezone": "browser", "editable": true, - "hideControls": false, "sharedCrosshair": false, + "hideControls": false, + "time": { + "from": "2016-11-16T16:59:38.294Z", + "to": "2016-11-16T17:09:01.532Z" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "templating": { + "list": [] + }, + "annotations": { + "list": [] + }, + "refresh": false, + "schemaVersion": 13, + "version": 4, + "links": [], + "gnetId": null, "rows": [ { "collapse": false, @@ -238,7 +278,13 @@ ] } ], - "title": "New row" + "title": "New row", + "showTitle": false, + "titleSize": "h6", + "isNew": false, + "repeat": null, + "repeatRowId": null, + "repeatIteration": null }, { "collapse": false, @@ -332,7 +378,13 @@ "type": "text" } ], - "title": "New row" + "title": "New row", + "showTitle": false, + "titleSize": "h6", + "isNew": false, + "repeat": null, + "repeatRowId": null, + "repeatIteration": null }, { "collapse": false, @@ -371,7 +423,7 @@ "yaxis": 2 } ], - "span": 7.99561403508772, + "span": 8, "stack": false, "steppedLine": false, "targets": [ @@ -432,12 +484,18 @@ "isNew": true, "links": [], "mode": "markdown", - "span": 4.00438596491228, + "span": 4, "title": "", "type": "text" } ], - "title": "New row" + "title": "New row", + "showTitle": false, + "titleSize": "h6", + "isNew": false, + "repeat": null, + "repeatRowId": null, + "repeatIteration": null }, { "collapse": false, @@ -545,7 +603,7 @@ "points": false, "renderer": "flot", "seriesOverrides": [], - "span": 3, + "span": 4, "stack": false, "steppedLine": false, "targets": [ @@ -592,6 +650,31 @@ } ] }, + { + "content": "Should be a long line connecting the null region in the `connected` mode, and in zero it should just be a line with zero value at the null points. ", + "editable": true, + "error": false, + "id": 13, + "isNew": true, + "links": [], + "mode": "markdown", + "span": 4, + "title": "", + "type": "text" + } + ], + "title": "New row", + "showTitle": false, + "titleSize": "h6", + "isNew": false, + "repeat": null, + "repeatRowId": null, + "repeatIteration": null + }, + { + "isNew": false, + "title": "Dashboard Row", + "panels": [ { "aliasColors": {}, "bars": false, @@ -624,7 +707,7 @@ "zindex": -3 } ], - "span": 5, + "span": 8, "stack": true, "steppedLine": false, "targets": [ @@ -687,49 +770,149 @@ "show": true } ] + }, + { + "content": "Stacking values on top of nulls, should treat the null values as zero. ", + "editable": true, + "error": false, + "id": 14, + "isNew": true, + "links": [], + "mode": "markdown", + "span": 4, + "title": "", + "type": "text" } ], - "title": "New row" + "showTitle": false, + "titleSize": "h6", + "height": 250, + "repeat": null, + "repeatRowId": null, + "repeatIteration": null, + "collapse": false + }, + { + "isNew": false, + "title": "Dashboard Row", + "panels": [ + { + "aliasColors": {}, + "bars": false, + "datasource": "Grafana TestData", + "editable": true, + "error": false, + "fill": 1, + "id": 12, + "isNew": true, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "B-series", + "zindex": -3 + } + ], + "span": 8, + "stack": true, + "steppedLine": false, + "targets": [ + { + "hide": false, + "refId": "B", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,40,null,null,null,null,null,null,100,10,10,20,30,40,10", + "target": "", + "alias": "" + }, + { + "alias": "", + "hide": false, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,40,null,null,null,null,null,null,100,10,10,20,30,40,10", + "target": "" + }, + { + "alias": "", + "hide": false, + "refId": "C", + "scenarioId": "csv_metric_values", + "stringInput": "1,20,40,null,null,null,null,null,null,100,10,10,20,30,40,10", + "target": "" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Stacking all series null segment", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + }, + { + "content": "Stacking when all values are null should leave a gap in the graph", + "editable": true, + "error": false, + "id": 15, + "isNew": true, + "links": [], + "mode": "markdown", + "span": 4, + "title": "", + "type": "text" + } + ], + "showTitle": false, + "titleSize": "h6", + "height": 250, + "repeat": null, + "repeatRowId": null, + "repeatIteration": null, + "collapse": false } - ], - "time": { - "from": "now-1h", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, - "templating": { - "list": [] - }, - "annotations": { - "list": [] - }, - "refresh": false, - "schemaVersion": 13, - "version": 13, - "links": [], - "gnetId": null + ] } diff --git a/public/app/plugins/app/testdata/plugin.json b/public/app/plugins/app/testdata/plugin.json index 63f88df8140..f8723f95a5f 100644 --- a/public/app/plugins/app/testdata/plugin.json +++ b/public/app/plugins/app/testdata/plugin.json @@ -9,7 +9,7 @@ "name": "Grafana Project", "url": "http://grafana.org" }, - "version": "1.0.14", + "version": "1.0.15", "updated": "2016-09-26" }, From a45fdfdd3f0322e4a1ba35b949f759915764b26b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 17 Nov 2016 10:11:59 +0100 Subject: [PATCH 41/53] fix(dashboard): minor fixes, and restored in-activity timer to 1min --- public/app/core/components/grafana_app.ts | 2 +- public/app/features/dashboard/row/add_panel.html | 2 +- public/app/features/dashboard/row/add_panel.ts | 6 ------ public/app/features/dashboard/row/row_ctrl.ts | 2 -- public/app/features/dashboard/row/row_model.ts | 4 ++++ 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/public/app/core/components/grafana_app.ts b/public/app/core/components/grafana_app.ts index 40797e9a47c..83b55ac476f 100644 --- a/public/app/core/components/grafana_app.ts +++ b/public/app/core/components/grafana_app.ts @@ -122,7 +122,7 @@ export function grafanaAppDirective(playlistSrv, contextSrv) { // handle in active view state class var lastActivity = new Date().getTime(); var activeUser = true; - var inActiveTimeLimit = 10 * 1000; + var inActiveTimeLimit = 60 * 1000; function checkForInActiveUser() { if (!activeUser) { diff --git a/public/app/features/dashboard/row/add_panel.html b/public/app/features/dashboard/row/add_panel.html index 7f79e697484..5a4ecc118cd 100644 --- a/public/app/features/dashboard/row/add_panel.html +++ b/public/app/features/dashboard/row/add_panel.html @@ -5,7 +5,7 @@
      - +
      diff --git a/public/app/features/dashboard/row/add_panel.ts b/public/app/features/dashboard/row/add_panel.ts index 3e2dc9e31d8..771f5e00b64 100644 --- a/public/app/features/dashboard/row/add_panel.ts +++ b/public/app/features/dashboard/row/add_panel.ts @@ -45,12 +45,6 @@ export class AddPanelCtrl { } } - panelSearchBlur() { - // this.$timeout(() => { - // this.rowCtrl.dropView = 0; - // }, 400); - } - moveSelection(direction) { var max = this.panelHits.length; var newIndex = this.activeIndex + direction; diff --git a/public/app/features/dashboard/row/row_ctrl.ts b/public/app/features/dashboard/row/row_ctrl.ts index f82f91e04af..5953889a9b1 100644 --- a/public/app/features/dashboard/row/row_ctrl.ts +++ b/public/app/features/dashboard/row/row_ctrl.ts @@ -19,7 +19,6 @@ export class DashRowCtrl { if (this.row.isNew) { this.dropView = 1; - delete this.row.isNew; } } @@ -36,7 +35,6 @@ export class DashRowCtrl { type: panelId, id: this.dashboard.getNextPanelId(), }, - isNew: true, }; } else { dragObject = this.dashboard.getPanelInfoById(panelId); diff --git a/public/app/features/dashboard/row/row_model.ts b/public/app/features/dashboard/row/row_model.ts index 0729d43bf0a..9e443feec10 100644 --- a/public/app/features/dashboard/row/row_model.ts +++ b/public/app/features/dashboard/row/row_model.ts @@ -34,6 +34,10 @@ export class DashboardRow { getSaveModel() { assignModelProperties(this.model, this, this.defaults); + + // remove properties that dont server persisted purpose + delete this.model.isNew; + return this.model; } From 690868c837ef93ab7d25f82446fcf02b75680cf0 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Thu, 17 Nov 2016 01:28:17 -0800 Subject: [PATCH 42/53] Added firingEvalution to Rule test --- pkg/api/alerting.go | 3 ++- pkg/api/dtos/alerting.go | 1 + pkg/services/alerting/eval_context.go | 1 + pkg/services/alerting/eval_handler.go | 14 +++++++++++++- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index ab9f4b7c80b..5ba5ea277bf 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -119,7 +119,8 @@ func AlertTest(c *middleware.Context, dto dtos.AlertTestCommand) Response { res := backendCmd.Result dtoRes := &dtos.AlertTestResult{ - Firing: res.Firing, + Firing: res.Firing, + FiringEval: res.FiringEval, } if res.Error != nil { diff --git a/pkg/api/dtos/alerting.go b/pkg/api/dtos/alerting.go index bf4d7f4353e..fb3130ce636 100644 --- a/pkg/api/dtos/alerting.go +++ b/pkg/api/dtos/alerting.go @@ -36,6 +36,7 @@ type AlertTestCommand struct { type AlertTestResult struct { Firing bool `json:"firing"` + FiringEval string `json:"firingEvaluation"` TimeMs string `json:"timeMs"` Error string `json:"error,omitempty"` EvalMatches []*EvalMatch `json:"matches,omitempty"` diff --git a/pkg/services/alerting/eval_context.go b/pkg/services/alerting/eval_context.go index 711416970c5..a19312c0768 100644 --- a/pkg/services/alerting/eval_context.go +++ b/pkg/services/alerting/eval_context.go @@ -18,6 +18,7 @@ type EvalContext struct { Logs []*ResultLogEntry Error error Description string + FiringEval string StartTime time.Time EndTime time.Time Rule *Rule diff --git a/pkg/services/alerting/eval_handler.go b/pkg/services/alerting/eval_handler.go index 7079eac5f38..e0ccea24ade 100644 --- a/pkg/services/alerting/eval_handler.go +++ b/pkg/services/alerting/eval_handler.go @@ -1,6 +1,7 @@ package alerting import ( + "strconv" "time" "github.com/grafana/grafana/pkg/log" @@ -21,7 +22,9 @@ func NewEvalHandler() *DefaultEvalHandler { func (e *DefaultEvalHandler) Eval(context *EvalContext) { firing := true - for _, condition := range context.Rule.Conditions { + firingEval := "" + for i := 0; i < len(context.Rule.Conditions); i++ { + condition := context.Rule.Conditions[i] cr, err := condition.Eval(context) if err != nil { context.Error = err @@ -33,15 +36,24 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) { } // calculating Firing based on operator + operator := "AND" if cr.Operator == "or" { firing = firing || cr.Firing + operator = "OR" } else { firing = firing && cr.Firing } + if i > 0 { + firingEval = "[" + firingEval + " " + operator + " " + strconv.FormatBool(cr.Firing) + "]" + } else { + firingEval = strconv.FormatBool(firing) + } + context.EvalMatches = append(context.EvalMatches, cr.EvalMatches...) } + context.FiringEval = firingEval + " = " + strconv.FormatBool(firing) context.Firing = firing context.EndTime = time.Now() elapsedTime := context.EndTime.Sub(context.StartTime) / time.Millisecond From fc82dac86815cfc8a93e767f3750b4c3752d4329 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Thu, 17 Nov 2016 01:30:50 -0800 Subject: [PATCH 43/53] Added braces to single condition firingEvaluation string --- pkg/services/alerting/eval_handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/alerting/eval_handler.go b/pkg/services/alerting/eval_handler.go index e0ccea24ade..620f8c9a121 100644 --- a/pkg/services/alerting/eval_handler.go +++ b/pkg/services/alerting/eval_handler.go @@ -47,7 +47,7 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) { if i > 0 { firingEval = "[" + firingEval + " " + operator + " " + strconv.FormatBool(cr.Firing) + "]" } else { - firingEval = strconv.FormatBool(firing) + firingEval = "[" + strconv.FormatBool(firing) + "]" } context.EvalMatches = append(context.EvalMatches, cr.EvalMatches...) From 196fdbfd3104d0e5f0be10132f7b5d9a5bf47121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 17 Nov 2016 10:31:37 +0100 Subject: [PATCH 44/53] fix(dashboard): fixed issue when dragging new panel to drop zone --- public/app/features/dashboard/row/row_ctrl.ts | 3 ++- public/app/features/panel/panel_ctrl.ts | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard/row/row_ctrl.ts b/public/app/features/dashboard/row/row_ctrl.ts index 5953889a9b1..e28f576c8ba 100644 --- a/public/app/features/dashboard/row/row_ctrl.ts +++ b/public/app/features/dashboard/row/row_ctrl.ts @@ -34,6 +34,7 @@ export class DashRowCtrl { title: config.new_panel_title, type: panelId, id: this.dashboard.getNextPanelId(), + isNew: true, }, }; } else { @@ -63,7 +64,7 @@ export class DashRowCtrl { this.row.panels.push(dragObject.panel); // if not new remove from source row - if (!dragObject.isNew) { + if (!dragObject.panel.isNew) { dragObject.row.removePanel(dragObject.panel, false); } } diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 1d1b7c7f4a2..3bd6cef569d 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -54,6 +54,12 @@ export class PanelCtrl { this.events.emit('panel-teardown'); this.events.removeAllListeners(); }); + + // we should do something interesting + // with newly added panels + if (this.panel.isNew) { + delete this.panel.isNew; + } } init() { From aae33b36ddd9c92d5b4904a6593eedb0a16694d4 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Thu, 17 Nov 2016 01:41:23 -0800 Subject: [PATCH 45/53] Added tests for firingEvaluation string --- pkg/services/alerting/eval_handler.go | 2 +- pkg/services/alerting/eval_handler_test.go | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/services/alerting/eval_handler.go b/pkg/services/alerting/eval_handler.go index 620f8c9a121..e0ccea24ade 100644 --- a/pkg/services/alerting/eval_handler.go +++ b/pkg/services/alerting/eval_handler.go @@ -47,7 +47,7 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) { if i > 0 { firingEval = "[" + firingEval + " " + operator + " " + strconv.FormatBool(cr.Firing) + "]" } else { - firingEval = "[" + strconv.FormatBool(firing) + "]" + firingEval = strconv.FormatBool(firing) } context.EvalMatches = append(context.EvalMatches, cr.EvalMatches...) diff --git a/pkg/services/alerting/eval_handler_test.go b/pkg/services/alerting/eval_handler_test.go index 4ef79504e78..1bda0c7c609 100644 --- a/pkg/services/alerting/eval_handler_test.go +++ b/pkg/services/alerting/eval_handler_test.go @@ -30,6 +30,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, true) + So(context.FiringEval, ShouldEqual, "true = true") }) Convey("Show return false with not passing asdf", func() { @@ -42,6 +43,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, false) + So(context.FiringEval, ShouldEqual, "[true AND false] = false") }) Convey("Show return true if any of the condition is passing with OR operator", func() { @@ -54,6 +56,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, true) + So(context.FiringEval, ShouldEqual, "[true OR false] = true") }) Convey("Show return false if any of the condition is failing with AND operator", func() { @@ -66,6 +69,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, false) + So(context.FiringEval, ShouldEqual, "[true AND false] = false") }) Convey("Show return true if one condition is failing with nested OR operator", func() { @@ -79,6 +83,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, true) + So(context.FiringEval, ShouldEqual, "[[true AND true] OR false] = true") }) Convey("Show return false if one condition is passing with nested OR operator", func() { @@ -92,6 +97,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, false) + So(context.FiringEval, ShouldEqual, "[[true AND false] OR false] = false") }) Convey("Show return false if a condition is failing with nested AND operator", func() { @@ -105,6 +111,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, false) + So(context.FiringEval, ShouldEqual, "[[true AND false] AND true] = false") }) Convey("Show return true if a condition is passing with nested OR operator", func() { @@ -118,6 +125,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, true) + So(context.FiringEval, ShouldEqual, "[[true OR false] OR true] = true") }) }) } From eafe0d6bfad219d04a7346c2454edb650e6362dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 17 Nov 2016 11:28:33 +0100 Subject: [PATCH 46/53] fix(templating): fixed issue when adding template variable, fixes #6622 --- CHANGELOG.md | 1 + public/app/features/dashboard/model.ts | 8 ++++++-- public/app/features/templating/adhoc_variable.ts | 2 +- public/app/features/templating/constant_variable.ts | 2 +- public/app/features/templating/custom_variable.ts | 2 +- public/app/features/templating/datasource_variable.ts | 2 +- public/app/features/templating/interval_variable.ts | 2 +- public/app/features/templating/partials/editor.html | 2 +- public/app/features/templating/query_variable.ts | 2 +- .../features/templating/specs/query_variable_specs.ts | 2 +- public/app/features/templating/variable.ts | 2 +- public/app/features/templating/variable_srv.ts | 11 +---------- 12 files changed, 17 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73d7aad8fb9..9e3f3766162 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * **Graph Panel**: Bar width if bars was only used in series override, [#6528](https://github.com/grafana/grafana/issues/6528) * **UI/Browser**: Fixed issue with page/view header gradient border not showing in Safari, [#6530](https://github.com/grafana/grafana/issues/6530) * **UX**: Panel Drop zone visible after duplicating panel, and when entering fullscreen/edit view, [#6598](https://github.com/grafana/grafana/issues/6598) +* **Templating**: Newly added variable was not visible directly only after dashboard reload, [#6622](https://github.com/grafana/grafana/issues/6622) ### Enhancements * **Singlestat**: Support repeated template variables in prefix/postfix [#6595](https://github.com/grafana/grafana/issues/6595) diff --git a/public/app/features/dashboard/model.ts b/public/app/features/dashboard/model.ts index 999c0470fdd..6b32004cfd5 100644 --- a/public/app/features/dashboard/model.ts +++ b/public/app/features/dashboard/model.ts @@ -98,12 +98,14 @@ export class DashboardModel { var events = this.events; var meta = this.meta; var rows = this.rows; + var variables = this.templating.list; + delete this.events; delete this.meta; // prepare save model - this.rows = _.map(this.rows, row => row.getSaveModel()); - events.emit('prepare-save-model'); + this.rows = _.map(rows, row => row.getSaveModel()); + this.templating.list = _.map(variables, variable => variable.getSaveModel()); var copy = $.extend(true, {}, this); @@ -111,6 +113,8 @@ export class DashboardModel { this.events = events; this.meta = meta; this.rows = rows; + this.templating.list = variables; + return copy; } diff --git a/public/app/features/templating/adhoc_variable.ts b/public/app/features/templating/adhoc_variable.ts index 5ebffb0b0e6..b3c9980c7c7 100644 --- a/public/app/features/templating/adhoc_variable.ts +++ b/public/app/features/templating/adhoc_variable.ts @@ -26,7 +26,7 @@ export class AdhocVariable implements Variable { return Promise.resolve(); } - getModel() { + getSaveModel() { assignModelProperties(this.model, this, this.defaults); return this.model; } diff --git a/public/app/features/templating/constant_variable.ts b/public/app/features/templating/constant_variable.ts index 59659459f85..1dcc473740f 100644 --- a/public/app/features/templating/constant_variable.ts +++ b/public/app/features/templating/constant_variable.ts @@ -24,7 +24,7 @@ export class ConstantVariable implements Variable { assignModelProperties(this, model, this.defaults); } - getModel() { + getSaveModel() { assignModelProperties(this.model, this, this.defaults); return this.model; } diff --git a/public/app/features/templating/custom_variable.ts b/public/app/features/templating/custom_variable.ts index 90ce08cf9e4..4fadcd26742 100644 --- a/public/app/features/templating/custom_variable.ts +++ b/public/app/features/templating/custom_variable.ts @@ -34,7 +34,7 @@ export class CustomVariable implements Variable { return this.variableSrv.setOptionAsCurrent(this, option); } - getModel() { + getSaveModel() { assignModelProperties(this.model, this, this.defaults); return this.model; } diff --git a/public/app/features/templating/datasource_variable.ts b/public/app/features/templating/datasource_variable.ts index d43c0dd486d..234c8c13fd5 100644 --- a/public/app/features/templating/datasource_variable.ts +++ b/public/app/features/templating/datasource_variable.ts @@ -30,7 +30,7 @@ export class DatasourceVariable implements Variable { this.refresh = 1; } - getModel() { + getSaveModel() { assignModelProperties(this.model, this, this.defaults); return this.model; } diff --git a/public/app/features/templating/interval_variable.ts b/public/app/features/templating/interval_variable.ts index a1cfbf324c0..ab1b0e59442 100644 --- a/public/app/features/templating/interval_variable.ts +++ b/public/app/features/templating/interval_variable.ts @@ -34,7 +34,7 @@ export class IntervalVariable implements Variable { this.refresh = 2; } - getModel() { + getSaveModel() { assignModelProperties(this.model, this, this.defaults); return this.model; } diff --git a/public/app/features/templating/partials/editor.html b/public/app/features/templating/partials/editor.html index e485072eed0..8006e356d85 100644 --- a/public/app/features/templating/partials/editor.html +++ b/public/app/features/templating/partials/editor.html @@ -136,7 +136,7 @@
      Custom Options
      - Values separated by comma + Values separated by comma
      diff --git a/public/app/features/templating/query_variable.ts b/public/app/features/templating/query_variable.ts index c67a4d09209..baabc554b23 100644 --- a/public/app/features/templating/query_variable.ts +++ b/public/app/features/templating/query_variable.ts @@ -47,7 +47,7 @@ export class QueryVariable implements Variable { assignModelProperties(this, model, this.defaults); } - getModel() { + getSaveModel() { // copy back model properties to model assignModelProperties(this.model, this, this.defaults); return this.model; diff --git a/public/app/features/templating/specs/query_variable_specs.ts b/public/app/features/templating/specs/query_variable_specs.ts index 8a2aef65be2..6ddaaf3630f 100644 --- a/public/app/features/templating/specs/query_variable_specs.ts +++ b/public/app/features/templating/specs/query_variable_specs.ts @@ -25,7 +25,7 @@ describe('QueryVariable', function() { variable.regex = 'asd'; variable.sort = 50; - var model = variable.getModel(); + var model = variable.getSaveModel(); expect(model.options.length).to.be(1); expect(model.options[0].text).to.be('test'); expect(model.datasource).to.be('google'); diff --git a/public/app/features/templating/variable.ts b/public/app/features/templating/variable.ts index 0cd0cb9f847..381f1ea7a3c 100644 --- a/public/app/features/templating/variable.ts +++ b/public/app/features/templating/variable.ts @@ -10,7 +10,7 @@ export interface Variable { dependsOn(variable); setValueFromUrl(urlValue); getValueForUrl(); - getModel(); + getSaveModel(); } export var variableTypes = {}; diff --git a/public/app/features/templating/variable_srv.ts b/public/app/features/templating/variable_srv.ts index bb6f4f7cde3..ac2948dbd4b 100644 --- a/public/app/features/templating/variable_srv.ts +++ b/public/app/features/templating/variable_srv.ts @@ -20,12 +20,9 @@ export class VariableSrv { this.dashboard = dashboard; // create working class models representing variables - this.variables = dashboard.templating.list.map(this.createVariableFromModel.bind(this)); + this.variables = dashboard.templating.list = dashboard.templating.list.map(this.createVariableFromModel.bind(this)); this.templateSrv.init(this.variables); - // register event to sync back to persisted model - this.dashboard.events.on('prepare-save-model', this.syncToDashboardModel.bind(this)); - // init variables for (let variable of this.variables) { variable.initLock = this.$q.defer(); @@ -99,12 +96,6 @@ export class VariableSrv { return variable; } - syncToDashboardModel() { - this.dashboard.templating.list = this.variables.map(variable => { - return variable.getModel(); - }); - } - updateOptions(variable) { return variable.updateOptions(); } From 9d5928ddd6d08b746dd22b074c47f3a254e12d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 17 Nov 2016 11:38:06 +0100 Subject: [PATCH 47/53] feat(templating): don't persist template variable options when variable has refresh enabled, closes #6586 --- CHANGELOG.md | 1 + public/app/features/templating/datasource_variable.ts | 6 ++++++ public/app/features/templating/query_variable.ts | 6 ++++++ .../features/templating/specs/query_variable_specs.ts | 10 +++++++++- 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e3f3766162..aa1be53c522 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Enhancements * **Singlestat**: Support repeated template variables in prefix/postfix [#6595](https://github.com/grafana/grafana/issues/6595) +* **Templating**: Don't persist variable options with refresh option [#6586](https://github.com/grafana/grafana/issues/6586) # 4.0-beta1 (2016-11-09) diff --git a/public/app/features/templating/datasource_variable.ts b/public/app/features/templating/datasource_variable.ts index 234c8c13fd5..17ba3522d00 100644 --- a/public/app/features/templating/datasource_variable.ts +++ b/public/app/features/templating/datasource_variable.ts @@ -32,6 +32,12 @@ export class DatasourceVariable implements Variable { getSaveModel() { assignModelProperties(this.model, this, this.defaults); + + // dont persist options + if (this.refresh !== 0) { + this.model.options = []; + } + return this.model; } diff --git a/public/app/features/templating/query_variable.ts b/public/app/features/templating/query_variable.ts index baabc554b23..e083aa2aab5 100644 --- a/public/app/features/templating/query_variable.ts +++ b/public/app/features/templating/query_variable.ts @@ -50,6 +50,12 @@ export class QueryVariable implements Variable { getSaveModel() { // copy back model properties to model assignModelProperties(this.model, this, this.defaults); + + // remove options + if (this.refresh !== 0) { + this.model.options = []; + } + return this.model; } diff --git a/public/app/features/templating/specs/query_variable_specs.ts b/public/app/features/templating/specs/query_variable_specs.ts index 6ddaaf3630f..7f695081ba0 100644 --- a/public/app/features/templating/specs/query_variable_specs.ts +++ b/public/app/features/templating/specs/query_variable_specs.ts @@ -33,7 +33,15 @@ describe('QueryVariable', function() { expect(model.sort).to.be(50); }); - }); + it('if refresh != 0 then remove options in presisted mode', () => { + var variable = new QueryVariable({}, null, null, null, null); + variable.options = [{text: 'test'}]; + variable.refresh = 1; + var model = variable.getSaveModel(); + expect(model.options.length).to.be(0); + }); + + }); }); From 98d1748e43b2c35db0fe5d03a6265215c1c4675b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 17 Nov 2016 12:32:11 +0100 Subject: [PATCH 48/53] fix(templating): work on fixing exporting issues when using templating variables, like data source variables, and panel repeats, requires more work #6189 --- .../app/core/directives/ng_model_on_blur.js | 2 +- .../app/features/dashboard/dashboard_ctrl.ts | 2 +- .../dashboard/dynamic_dashboard_srv.ts | 8 ++-- .../features/dashboard/export/export_modal.ts | 4 +- .../app/features/dashboard/export/exporter.ts | 37 +++++++++++++++---- public/app/features/dashboard/model.ts | 2 +- .../app/features/dashboard/row/row_model.ts | 2 +- .../specs/dynamic_dashboard_srv_specs.ts | 4 +- .../dashboard/specs/exporter_specs.ts | 11 +++++- .../templating/datasource_variable.ts | 5 +-- .../templating/specs/query_variable_specs.ts | 1 - 11 files changed, 51 insertions(+), 27 deletions(-) diff --git a/public/app/core/directives/ng_model_on_blur.js b/public/app/core/directives/ng_model_on_blur.js index 6f4a55b53f0..09c87f5fabe 100644 --- a/public/app/core/directives/ng_model_on_blur.js +++ b/public/app/core/directives/ng_model_on_blur.js @@ -47,7 +47,7 @@ function (coreModule, kbn, rangeUtil) { if (ctrl.$isEmpty(modelValue)) { return true; } - if (viewValue.indexOf('$') === 0) { + if (viewValue.indexOf('$') === 0 || viewValue.indexOf('+$') === 0) { return true; // allow template variable } var info = rangeUtil.describeTextRange(viewValue); diff --git a/public/app/features/dashboard/dashboard_ctrl.ts b/public/app/features/dashboard/dashboard_ctrl.ts index cf72bb87f61..c07f84e820c 100644 --- a/public/app/features/dashboard/dashboard_ctrl.ts +++ b/public/app/features/dashboard/dashboard_ctrl.ts @@ -52,7 +52,7 @@ export class DashboardCtrl { .catch($scope.onInitFailed.bind(this, 'Templating init failed', false)) // continue .finally(function() { - dynamicDashboardSrv.init(dashboard, variableSrv); + dynamicDashboardSrv.init(dashboard); dynamicDashboardSrv.process(); unsavedChangesSrv.init(dashboard, $scope); diff --git a/public/app/features/dashboard/dynamic_dashboard_srv.ts b/public/app/features/dashboard/dynamic_dashboard_srv.ts index e5f1c6a9fa1..629098e290b 100644 --- a/public/app/features/dashboard/dynamic_dashboard_srv.ts +++ b/public/app/features/dashboard/dynamic_dashboard_srv.ts @@ -12,12 +12,12 @@ export class DynamicDashboardSrv { dashboard: any; variables: any; - init(dashboard, variableSrv) { + init(dashboard) { this.dashboard = dashboard; - this.variables = variableSrv.variables; + this.variables = dashboard.templating.list; } - process(options) { + process(options?) { if (this.dashboard.snapshot || this.variables.length === 0) { return; } @@ -31,6 +31,8 @@ export class DynamicDashboardSrv { // cleanup scopedVars for (i = 0; i < this.dashboard.rows.length; i++) { row = this.dashboard.rows[i]; + delete row.scopedVars; + for (j = 0; j < row.panels.length; j++) { delete row.panels[j].scopedVars; } diff --git a/public/app/features/dashboard/export/export_modal.ts b/public/app/features/dashboard/export/export_modal.ts index 57af9d9caf8..c334818b3e7 100644 --- a/public/app/features/dashboard/export/export_modal.ts +++ b/public/app/features/dashboard/export/export_modal.ts @@ -17,9 +17,7 @@ export class DashExportCtrl { constructor(private backendSrv, dashboardSrv, datasourceSrv, $scope) { this.exporter = new DashboardExporter(datasourceSrv); - var current = dashboardSrv.getCurrent().getSaveModelClone(); - - this.exporter.makeExportable(current).then(dash => { + this.exporter.makeExportable(dashboardSrv.getCurrent()).then(dash => { $scope.$apply(() => { this.dash = dash; }); diff --git a/public/app/features/dashboard/export/exporter.ts b/public/app/features/dashboard/export/exporter.ts index 9f9e326e848..5efcd498f67 100644 --- a/public/app/features/dashboard/export/exporter.ts +++ b/public/app/features/dashboard/export/exporter.ts @@ -11,19 +11,40 @@ export class DashboardExporter { constructor(private datasourceSrv) { } - makeExportable(dash) { + makeExportable(dashboard) { var dynSrv = new DynamicDashboardSrv(); - dynSrv.init(dash, {variables: dash.templating.list}); + + // clean up repeated rows and panels, + // this is done on the live real dashboard instance, not on a clone + // so we need to undo this + // this is pretty hacky and needs to be changed + dynSrv.init(dashboard); dynSrv.process({cleanUpOnly: true}); - dash.id = null; + var saveModel = dashboard.getSaveModelClone(); + saveModel.id = null; + + // undo repeat cleanup + dynSrv.process(); var inputs = []; var requires = {}; var datasources = {}; var promises = []; + var variableLookup: any = {}; + + for (let variable of saveModel.templating.list) { + variableLookup[variable.name] = variable; + } var templateizeDatasourceUsage = obj => { + // ignore data source properties that contain a variable + if (obj.datasource && obj.datasource.indexOf('$') === 0) { + if (variableLookup[obj.datasource.substring(1)]){ + return; + } + } + promises.push(this.datasourceSrv.get(obj.datasource).then(ds => { if (ds.meta.builtIn) { return; @@ -50,7 +71,7 @@ export class DashboardExporter { }; // check up panel data sources - for (let row of dash.rows) { + for (let row of saveModel.rows) { for (let panel of row.panels) { if (panel.datasource !== undefined) { templateizeDatasourceUsage(panel); @@ -77,7 +98,7 @@ export class DashboardExporter { } // templatize template vars - for (let variable of dash.templating.list) { + for (let variable of saveModel.templating.list) { if (variable.type === 'query') { templateizeDatasourceUsage(variable); variable.options = []; @@ -87,7 +108,7 @@ export class DashboardExporter { } // templatize annotations vars - for (let annotationDef of dash.annotations.list) { + for (let annotationDef of saveModel.annotations.list) { templateizeDatasourceUsage(annotationDef); } @@ -105,7 +126,7 @@ export class DashboardExporter { }); // templatize constants - for (let variable of dash.templating.list) { + for (let variable of saveModel.templating.list) { if (variable.type === 'constant') { var refName = 'VAR_' + variable.name.replace(' ', '_').toUpperCase(); inputs.push({ @@ -133,7 +154,7 @@ export class DashboardExporter { newObj["__inputs"] = inputs; newObj["__requires"] = requires; - _.defaults(newObj, dash); + _.defaults(newObj, saveModel); return newObj; }).catch(err => { diff --git a/public/app/features/dashboard/model.ts b/public/app/features/dashboard/model.ts index 6b32004cfd5..f0a32fd58fc 100644 --- a/public/app/features/dashboard/model.ts +++ b/public/app/features/dashboard/model.ts @@ -105,7 +105,7 @@ export class DashboardModel { // prepare save model this.rows = _.map(rows, row => row.getSaveModel()); - this.templating.list = _.map(variables, variable => variable.getSaveModel()); + this.templating.list = _.map(variables, variable => variable.getSaveModel ? variable.getSaveModel() : variable); var copy = $.extend(true, {}, this); diff --git a/public/app/features/dashboard/row/row_model.ts b/public/app/features/dashboard/row/row_model.ts index 9e443feec10..d99e75b3621 100644 --- a/public/app/features/dashboard/row/row_model.ts +++ b/public/app/features/dashboard/row/row_model.ts @@ -33,11 +33,11 @@ export class DashboardRow { } getSaveModel() { + this.model = {}; assignModelProperties(this.model, this, this.defaults); // remove properties that dont server persisted purpose delete this.model.isNew; - return this.model; } diff --git a/public/app/features/dashboard/specs/dynamic_dashboard_srv_specs.ts b/public/app/features/dashboard/specs/dynamic_dashboard_srv_specs.ts index 95de65b2459..93feddc0654 100644 --- a/public/app/features/dashboard/specs/dynamic_dashboard_srv_specs.ts +++ b/public/app/features/dashboard/specs/dynamic_dashboard_srv_specs.ts @@ -20,7 +20,6 @@ function dynamicDashScenario(desc, func) { beforeEach(angularMocks.inject(function(dashboardSrv) { ctx.dashboardSrv = dashboardSrv; - ctx.variableSrv = {}; var model = { rows: [], @@ -29,9 +28,8 @@ function dynamicDashScenario(desc, func) { setupFunc(model); ctx.dash = ctx.dashboardSrv.create(model); - ctx.variableSrv.variables = ctx.dash.templating.list; ctx.dynamicDashboardSrv = new DynamicDashboardSrv(); - ctx.dynamicDashboardSrv.init(ctx.dash, ctx.variableSrv); + ctx.dynamicDashboardSrv.init(ctx.dash); ctx.dynamicDashboardSrv.process(); ctx.rows = ctx.dash.rows; })); diff --git a/public/app/features/dashboard/specs/exporter_specs.ts b/public/app/features/dashboard/specs/exporter_specs.ts index fd3973206d1..0aaadae2b63 100644 --- a/public/app/features/dashboard/specs/exporter_specs.ts +++ b/public/app/features/dashboard/specs/exporter_specs.ts @@ -34,6 +34,14 @@ describe('given dashboard with repeated panels', function() { options: [] }); + dash.templating.list.push({ + name: 'ds', + type: 'datasource', + query: 'testdb', + current: {value: 'prod', text: 'prod'}, + options: [] + }); + dash.annotations.list.push({ name: 'logs', datasource: 'gfdb', @@ -49,6 +57,7 @@ describe('given dashboard with repeated panels', function() { datasource: '-- Mixed --', targets: [{datasource: 'other'}], }, + {id: 5, datasource: '$ds'}, ] }); @@ -87,7 +96,7 @@ describe('given dashboard with repeated panels', function() { }); it('exported dashboard should not contain repeated panels', function() { - expect(exported.rows[0].panels.length).to.be(2); + expect(exported.rows[0].panels.length).to.be(3); }); it('exported dashboard should not contain repeated rows', function() { diff --git a/public/app/features/templating/datasource_variable.ts b/public/app/features/templating/datasource_variable.ts index 17ba3522d00..bfd4d965029 100644 --- a/public/app/features/templating/datasource_variable.ts +++ b/public/app/features/templating/datasource_variable.ts @@ -34,10 +34,7 @@ export class DatasourceVariable implements Variable { assignModelProperties(this.model, this, this.defaults); // dont persist options - if (this.refresh !== 0) { - this.model.options = []; - } - + this.model.options = []; return this.model; } diff --git a/public/app/features/templating/specs/query_variable_specs.ts b/public/app/features/templating/specs/query_variable_specs.ts index 7f695081ba0..591362e0d84 100644 --- a/public/app/features/templating/specs/query_variable_specs.ts +++ b/public/app/features/templating/specs/query_variable_specs.ts @@ -41,7 +41,6 @@ describe('QueryVariable', function() { var model = variable.getSaveModel(); expect(model.options.length).to.be(0); }); - }); }); From 3ea66ebe3fb7c2a0ece248f1c7fc524f225d8234 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 17 Nov 2016 15:20:14 +0100 Subject: [PATCH 49/53] fix(alerting): remove possible divide by zero panic ref #6599 --- pkg/services/alerting/scheduler.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/services/alerting/scheduler.go b/pkg/services/alerting/scheduler.go index b6ef1a63ff8..151f802ec15 100644 --- a/pkg/services/alerting/scheduler.go +++ b/pkg/services/alerting/scheduler.go @@ -39,6 +39,9 @@ func (s *SchedulerImpl) Update(rules []*Rule) { offset := ((rule.Frequency * 1000) / int64(len(rules))) * int64(i) job.Offset = int64(math.Floor(float64(offset) / 1000)) + if job.Offset == 0 { //zero offset causes division with 0 panics. + job.Offset = 1 + } jobs[rule.Id] = job } From 62e8a039a19a5b2b682a4e1bd5557f72a56612b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 17 Nov 2016 15:48:15 +0100 Subject: [PATCH 50/53] refactor(alerting): refactoring PR for OR conditions, #6579 --- pkg/api/alerting.go | 4 ++-- pkg/api/dtos/alerting.go | 12 ++++++------ pkg/services/alerting/conditions/query.go | 2 +- pkg/services/alerting/eval_context.go | 3 +-- pkg/services/alerting/eval_handler.go | 12 ++++++------ pkg/services/alerting/eval_handler_test.go | 16 ++++++++-------- .../features/alerting/partials/alert_tab.html | 10 +++++----- public/app/features/dashboard/row/row_model.ts | 3 +++ 8 files changed, 32 insertions(+), 30 deletions(-) diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index 5ba5ea277bf..6d55f03bea3 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -119,8 +119,8 @@ func AlertTest(c *middleware.Context, dto dtos.AlertTestCommand) Response { res := backendCmd.Result dtoRes := &dtos.AlertTestResult{ - Firing: res.Firing, - FiringEval: res.FiringEval, + Firing: res.Firing, + ConditionEvals: res.ConditionEvals, } if res.Error != nil { diff --git a/pkg/api/dtos/alerting.go b/pkg/api/dtos/alerting.go index fb3130ce636..c9ba30e2407 100644 --- a/pkg/api/dtos/alerting.go +++ b/pkg/api/dtos/alerting.go @@ -35,12 +35,12 @@ type AlertTestCommand struct { } type AlertTestResult struct { - Firing bool `json:"firing"` - FiringEval string `json:"firingEvaluation"` - TimeMs string `json:"timeMs"` - Error string `json:"error,omitempty"` - EvalMatches []*EvalMatch `json:"matches,omitempty"` - Logs []*AlertTestResultLog `json:"logs,omitempty"` + Firing bool `json:"firing"` + ConditionEvals string `json:"conditionEvals"` + TimeMs string `json:"timeMs"` + Error string `json:"error,omitempty"` + EvalMatches []*EvalMatch `json:"matches,omitempty"` + Logs []*AlertTestResultLog `json:"logs,omitempty"` } type AlertTestResultLog struct { diff --git a/pkg/services/alerting/conditions/query.go b/pkg/services/alerting/conditions/query.go index 394c1557490..e58dbf1d583 100644 --- a/pkg/services/alerting/conditions/query.go +++ b/pkg/services/alerting/conditions/query.go @@ -173,7 +173,7 @@ func NewQueryCondition(model *simplejson.Json, index int) (*QueryCondition, erro condition.Evaluator = evaluator operatorJson := model.Get("operator") - operator := operatorJson.Get("type").MustString() + operator := operatorJson.Get("type").MustString("and") condition.Operator = operator return &condition, nil diff --git a/pkg/services/alerting/eval_context.go b/pkg/services/alerting/eval_context.go index a19312c0768..2b252da8c4b 100644 --- a/pkg/services/alerting/eval_context.go +++ b/pkg/services/alerting/eval_context.go @@ -17,8 +17,7 @@ type EvalContext struct { EvalMatches []*EvalMatch Logs []*ResultLogEntry Error error - Description string - FiringEval string + ConditionEvals string StartTime time.Time EndTime time.Time Rule *Rule diff --git a/pkg/services/alerting/eval_handler.go b/pkg/services/alerting/eval_handler.go index e0ccea24ade..075d0833fdf 100644 --- a/pkg/services/alerting/eval_handler.go +++ b/pkg/services/alerting/eval_handler.go @@ -2,6 +2,7 @@ package alerting import ( "strconv" + "strings" "time" "github.com/grafana/grafana/pkg/log" @@ -22,7 +23,8 @@ func NewEvalHandler() *DefaultEvalHandler { func (e *DefaultEvalHandler) Eval(context *EvalContext) { firing := true - firingEval := "" + conditionEvals := "" + for i := 0; i < len(context.Rule.Conditions); i++ { condition := context.Rule.Conditions[i] cr, err := condition.Eval(context) @@ -36,24 +38,22 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) { } // calculating Firing based on operator - operator := "AND" if cr.Operator == "or" { firing = firing || cr.Firing - operator = "OR" } else { firing = firing && cr.Firing } if i > 0 { - firingEval = "[" + firingEval + " " + operator + " " + strconv.FormatBool(cr.Firing) + "]" + conditionEvals = "[" + conditionEvals + " " + strings.ToUpper(cr.Operator) + " " + strconv.FormatBool(cr.Firing) + "]" } else { - firingEval = strconv.FormatBool(firing) + conditionEvals = strconv.FormatBool(firing) } context.EvalMatches = append(context.EvalMatches, cr.EvalMatches...) } - context.FiringEval = firingEval + " = " + strconv.FormatBool(firing) + context.ConditionEvals = conditionEvals + " = " + strconv.FormatBool(firing) context.Firing = firing context.EndTime = time.Now() elapsedTime := context.EndTime.Sub(context.StartTime) / time.Millisecond diff --git a/pkg/services/alerting/eval_handler_test.go b/pkg/services/alerting/eval_handler_test.go index 1bda0c7c609..03fa0142b10 100644 --- a/pkg/services/alerting/eval_handler_test.go +++ b/pkg/services/alerting/eval_handler_test.go @@ -30,7 +30,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, true) - So(context.FiringEval, ShouldEqual, "true = true") + So(context.ConditionEvals, ShouldEqual, "true = true") }) Convey("Show return false with not passing asdf", func() { @@ -43,7 +43,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, false) - So(context.FiringEval, ShouldEqual, "[true AND false] = false") + So(context.ConditionEvals, ShouldEqual, "[true AND false] = false") }) Convey("Show return true if any of the condition is passing with OR operator", func() { @@ -56,7 +56,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, true) - So(context.FiringEval, ShouldEqual, "[true OR false] = true") + So(context.ConditionEvals, ShouldEqual, "[true OR false] = true") }) Convey("Show return false if any of the condition is failing with AND operator", func() { @@ -69,7 +69,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, false) - So(context.FiringEval, ShouldEqual, "[true AND false] = false") + So(context.ConditionEvals, ShouldEqual, "[true AND false] = false") }) Convey("Show return true if one condition is failing with nested OR operator", func() { @@ -83,7 +83,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, true) - So(context.FiringEval, ShouldEqual, "[[true AND true] OR false] = true") + So(context.ConditionEvals, ShouldEqual, "[[true AND true] OR false] = true") }) Convey("Show return false if one condition is passing with nested OR operator", func() { @@ -97,7 +97,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, false) - So(context.FiringEval, ShouldEqual, "[[true AND false] OR false] = false") + So(context.ConditionEvals, ShouldEqual, "[[true AND false] OR false] = false") }) Convey("Show return false if a condition is failing with nested AND operator", func() { @@ -111,7 +111,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, false) - So(context.FiringEval, ShouldEqual, "[[true AND false] AND true] = false") + So(context.ConditionEvals, ShouldEqual, "[[true AND false] AND true] = false") }) Convey("Show return true if a condition is passing with nested OR operator", func() { @@ -125,7 +125,7 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.Firing, ShouldEqual, true) - So(context.FiringEval, ShouldEqual, "[[true OR false] OR true] = true") + So(context.ConditionEvals, ShouldEqual, "[[true OR false] OR true] = true") }) }) } diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index 2bc687aee19..f3fcf31fad6 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -38,23 +38,23 @@
      Conditions
      - + WHEN
      - + OF
      - +
      - + - +