From b4646b6c3a4256a17cab41dacc496d9c885a2544 Mon Sep 17 00:00:00 2001 From: Kevin Fitzpatrick Date: Tue, 12 Apr 2016 17:54:45 -0700 Subject: [PATCH 01/54] Allow users to use a generic oauth that conforms to the github style. Enables users to set their own link text. --- pkg/api/login.go | 2 ++ pkg/setting/setting_oauth.go | 5 +++-- pkg/social/social.go | 18 +++++++++++++++++- public/app/core/controllers/login_ctrl.js | 4 +++- public/app/partials/login.html | 4 ++++ 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/pkg/api/login.go b/pkg/api/login.go index 4f976f753a2..789765ee01e 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -27,6 +27,8 @@ func LoginView(c *middleware.Context) { viewData.Settings["googleAuthEnabled"] = setting.OAuthService.Google viewData.Settings["githubAuthEnabled"] = setting.OAuthService.GitHub + viewData.Settings["genericOAuthEnabled"] = setting.OAuthService.Generic + viewData.Settings["oauthProviderName"] = setting.OAuthService.OAuthProviderName viewData.Settings["disableUserSignUp"] = !setting.AllowUserSignUp viewData.Settings["loginHint"] = setting.LoginHint viewData.Settings["allowUserPassLogin"] = setting.AllowUserPassLogin diff --git a/pkg/setting/setting_oauth.go b/pkg/setting/setting_oauth.go index db2f0fb3802..71c4ade1468 100644 --- a/pkg/setting/setting_oauth.go +++ b/pkg/setting/setting_oauth.go @@ -11,8 +11,9 @@ type OAuthInfo struct { } type OAuther struct { - GitHub, Google, Twitter bool - OAuthInfos map[string]*OAuthInfo + GitHub, Google, Twitter, Generic bool + OAuthInfos map[string]*OAuthInfo + OAuthProviderName string } var OAuthService *OAuther diff --git a/pkg/social/social.go b/pkg/social/social.go index 4a8cb8bac5d..402f02449ff 100644 --- a/pkg/social/social.go +++ b/pkg/social/social.go @@ -42,7 +42,7 @@ func NewOAuthService() { setting.OAuthService = &setting.OAuther{} setting.OAuthService.OAuthInfos = make(map[string]*setting.OAuthInfo) - allOauthes := []string{"github", "google"} + allOauthes := []string{"github", "google", "generic-oauth"} for _, name := range allOauthes { sec := setting.Cfg.Section("auth." + name) @@ -98,6 +98,22 @@ func NewOAuthService() { allowSignup: info.AllowSignup, } } + + // Generic - Uses the same scheme as Github. + if name == "generic-oauth" { + setting.OAuthService.Generic = true + setting.OAuthService.OAuthProviderName = sec.Key("oauth_provider_name").String() + teamIds := sec.Key("team_ids").Ints(",") + allowedOrganizations := sec.Key("allowed_organizations").Strings(" ") + SocialMap["generic-oauth"] = &SocialGithub{ + Config: &config, + allowedDomains: info.AllowedDomains, + apiUrl: info.ApiUrl, + allowSignup: info.AllowSignup, + teamIds: teamIds, + allowedOrganizations: allowedOrganizations, + } + } } } diff --git a/public/app/core/controllers/login_ctrl.js b/public/app/core/controllers/login_ctrl.js index 8696d94f4f3..60136a05c0c 100644 --- a/public/app/core/controllers/login_ctrl.js +++ b/public/app/core/controllers/login_ctrl.js @@ -17,8 +17,10 @@ function (angular, coreModule, config) { $scope.googleAuthEnabled = config.googleAuthEnabled; $scope.githubAuthEnabled = config.githubAuthEnabled; - $scope.oauthEnabled = config.githubAuthEnabled || config.googleAuthEnabled; + $scope.oauthEnabled = config.githubAuthEnabled || config.googleAuthEnabled || config.genericOAuthEnabled; $scope.allowUserPassLogin = config.allowUserPassLogin; + $scope.genericOAuthEnabled = config.genericOAuthEnabled; + $scope.oauthProviderName = config.oauthProviderName; $scope.disableUserSignUp = config.disableUserSignUp; $scope.loginHint = config.loginHint; diff --git a/public/app/partials/login.html b/public/app/partials/login.html index 5bee63e6214..200200ee4b9 100644 --- a/public/app/partials/login.html +++ b/public/app/partials/login.html @@ -59,6 +59,10 @@ with Github + + + with {{oauthProviderName || "OAuth 2"}} + From f2baa5b210b2000509bbb2aa319a6bedf238903f Mon Sep 17 00:00:00 2001 From: Kevin Fitzpatrick Date: Wed, 18 May 2016 13:37:04 -0700 Subject: [PATCH 02/54] Make Generic OAuth Configs ENV VARS-friendly Use underscores instead of dashes. --- pkg/social/social.go | 6 +++--- public/app/partials/login.html | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/social/social.go b/pkg/social/social.go index 402f02449ff..3c970f89e4f 100644 --- a/pkg/social/social.go +++ b/pkg/social/social.go @@ -42,7 +42,7 @@ func NewOAuthService() { setting.OAuthService = &setting.OAuther{} setting.OAuthService.OAuthInfos = make(map[string]*setting.OAuthInfo) - allOauthes := []string{"github", "google", "generic-oauth"} + allOauthes := []string{"github", "google", "generic_oauth"} for _, name := range allOauthes { sec := setting.Cfg.Section("auth." + name) @@ -100,12 +100,12 @@ func NewOAuthService() { } // Generic - Uses the same scheme as Github. - if name == "generic-oauth" { + if name == "generic_oauth" { setting.OAuthService.Generic = true setting.OAuthService.OAuthProviderName = sec.Key("oauth_provider_name").String() teamIds := sec.Key("team_ids").Ints(",") allowedOrganizations := sec.Key("allowed_organizations").Strings(" ") - SocialMap["generic-oauth"] = &SocialGithub{ + SocialMap["generic_oauth"] = &SocialGithub{ Config: &config, allowedDomains: info.AllowedDomains, apiUrl: info.ApiUrl, diff --git a/public/app/partials/login.html b/public/app/partials/login.html index 200200ee4b9..f4f5fb26d7e 100644 --- a/public/app/partials/login.html +++ b/public/app/partials/login.html @@ -59,7 +59,7 @@ with Github - + with {{oauthProviderName || "OAuth 2"}} From 5ba6bab237cdca6fdefb67b33165d6a708df456e Mon Sep 17 00:00:00 2001 From: paulroche Date: Sat, 27 Aug 2016 00:50:35 -0700 Subject: [PATCH 03/54] redact provider_config (#5915) --- pkg/setting/setting.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 830291b7ffa..fd010c42ca2 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -190,7 +190,7 @@ func ToAbsUrl(relativeUrl string) string { func shouldRedactKey(s string) bool { uppercased := strings.ToUpper(s) - return strings.Contains(uppercased, "PASSWORD") || strings.Contains(uppercased, "SECRET") + return strings.Contains(uppercased, "PASSWORD") || strings.Contains(uppercased, "SECRET") || strings.Contains(uppercased, "PROVIDER_CONFIG") } func shouldRedactURLKey(s string) bool { From 9c08d7aef5a35f967480e2bd531bd9b6dca93a7a Mon Sep 17 00:00:00 2001 From: cmartin0077 Date: Mon, 29 Aug 2016 04:03:54 -0700 Subject: [PATCH 04/54] fix(influxdb) fix tag value autocomplete with upper case values, fixes #5399 (#5919) --- public/app/core/directives/metric_segment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/core/directives/metric_segment.js b/public/app/core/directives/metric_segment.js index 4b3cd2e8de3..7669f2fb709 100644 --- a/public/app/core/directives/metric_segment.js +++ b/public/app/core/directives/metric_segment.js @@ -113,7 +113,7 @@ function (_, $, coreModule) { if (str[0] === '/') { str = str.substring(1); } if (str[str.length - 1] === '/') { str = str.substring(0, str.length-1); } try { - return item.toLowerCase().match(str); + return item.toLowerCase().match(str.toLowerCase()); } catch(e) { return false; } From 0050707134030b254b830141d763d3d08a4f1294 Mon Sep 17 00:00:00 2001 From: chrismartin0077 Date: Mon, 29 Aug 2016 05:45:28 -0700 Subject: [PATCH 05/54] feat(api) HEAD response, fixes #3854 --- pkg/api/api.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/api/api.go b/pkg/api/api.go index 31184a5a095..25158586822 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -19,6 +19,9 @@ func Register(r *macaron.Macaron) { quota := middleware.Quota bind := binding.Bind + // automatically set HEAD for every GET + r.SetAutoHead(true) + // not logged in views r.Get("/", reqSignedIn, Index) r.Get("/logout", Logout) From c0697b99d611c79b103e389fe1bc64929f913774 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 29 Aug 2016 15:49:25 +0200 Subject: [PATCH 06/54] fix(alerting): adds support for basic auth closes #5897 --- pkg/services/alerting/conditions/query.go | 14 ++++++++++---- pkg/tsdb/graphite/graphite.go | 9 +++++++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/pkg/services/alerting/conditions/query.go b/pkg/services/alerting/conditions/query.go index d94c2d468f2..0a661e8aaab 100644 --- a/pkg/services/alerting/conditions/query.go +++ b/pkg/services/alerting/conditions/query.go @@ -106,10 +106,16 @@ func (c *QueryCondition) getRequestForAlertRule(datasource *m.DataSource) *tsdb. RefId: "A", Query: c.Query.Model.Get("target").MustString(), DataSource: &tsdb.DataSourceInfo{ - Id: datasource.Id, - Name: datasource.Name, - PluginId: datasource.Type, - Url: datasource.Url, + Id: datasource.Id, + Name: datasource.Name, + PluginId: datasource.Type, + Url: datasource.Url, + User: datasource.User, + Password: datasource.Password, + Database: datasource.Database, + BasicAuth: datasource.BasicAuth, + BasicAuthUser: datasource.BasicAuthUser, + BasicAuthPassword: datasource.BasicAuthPassword, }, }, }, diff --git a/pkg/tsdb/graphite/graphite.go b/pkg/tsdb/graphite/graphite.go index 2a68b23df71..68f92d7527e 100644 --- a/pkg/tsdb/graphite/graphite.go +++ b/pkg/tsdb/graphite/graphite.go @@ -43,12 +43,17 @@ func (e *GraphiteExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryC } client := http.Client{Timeout: time.Duration(10 * time.Second)} - res, err := client.PostForm(e.Url+"/render?", params) + req, _ := http.NewRequest(http.MethodPost, e.Url+"/render?", strings.NewReader(params.Encode())) + if e.BasicAuth { + req.SetBasicAuth("carl", "carl") + } + + res, err := client.Do(req) + defer res.Body.Close() if err != nil { result.Error = err return result } - defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { From 88ca69536193acb8c398028e9942f70bee088189 Mon Sep 17 00:00:00 2001 From: chrismartin0077 Date: Mon, 29 Aug 2016 06:16:57 -0700 Subject: [PATCH 07/54] feat(influxdb) add elapsed(), fixes #5827 --- public/app/plugins/datasource/influxdb/query_part.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index 0081481437d..c0592204bc8 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -254,6 +254,15 @@ register({ renderer: functionRenderer, }); +register({ + type: 'elapsed', + addStrategy: addTransformationStrategy, + category: categories.Transformations, + params: [{ name: "duration", type: "interval", options: ['1s', '10s', '1m', '5m', '10m', '15m', '1h']}], + defaultParams: ['10s'], + renderer: functionRenderer, +}); + // Selectors register({ type: 'bottom', From 9c92d8c4e98f1f288a79ddb77cbd7cd34612ea51 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 29 Aug 2016 16:10:19 +0200 Subject: [PATCH 08/54] fix(circle-ci): change to ubuntu 14 for CI --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index ef11d6b038e..5640980b702 100644 --- a/circle.yml +++ b/circle.yml @@ -31,4 +31,4 @@ deployment: branch: master owner: grafana commands: - - ./trigger_grafana_packer.sh ${TRIGGER_GRAFANA_PACKER_CIRCLECI_TOKEN} + - ./trigger_grafana_packer.sh ${TRIGGER_GRAFANA_PACKER_CIRCLECI_TOKEN} \ No newline at end of file From ff9b95f4b30ceac16b733712227f4c1b407004b8 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 29 Aug 2016 16:24:13 +0200 Subject: [PATCH 09/54] docs(changelog): adds note about closing #5827 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0352a2a3f8f..b9cf39f89a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * **Navigation**: Add search to org swithcer, closes [#2609](https://github.com/grafana/grafana/issues/2609) * **Database**: Allow database config using one propertie, closes [#5456](https://github.com/grafana/grafana/pull/5456) * **Graphite**: Add support for groupByNode, closes [#5613](https://github.com/grafana/grafana/pull/5613) +* **Influxdb**: Add support for elapsed(), closes [#5827](https://github.com/grafana/grafana/pull/5827) # 3.1.2 (unreleased) * **Templating**: Fixed issue when combining row & panel repeats, fixes [#5790](https://github.com/grafana/grafana/issues/5790) From a2525f77d9bd0c01eabc1b8a6e303087a28d2b79 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 29 Aug 2016 21:14:47 +0200 Subject: [PATCH 10/54] fix(config): comment sample value --- conf/sample.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/sample.ini b/conf/sample.ini index 4fcbe5d1157..949f64df025 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -318,7 +318,7 @@ check_for_updates = true # \______(_______;;;)__;;;) [alerting] -enabled = false +;enabled = false #################################### Internal Grafana Metrics ########################## # Metrics available at HTTP API Url /api/metrics From 1f1d232ea67525e75e8e0f1567737a43ec4c4253 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 30 Aug 2016 07:48:15 +0200 Subject: [PATCH 11/54] tech(githooks): remove symlink bash script --- symlink_git_hooks.sh | 5 ----- 1 file changed, 5 deletions(-) delete mode 100755 symlink_git_hooks.sh diff --git a/symlink_git_hooks.sh b/symlink_git_hooks.sh deleted file mode 100755 index 9e272f82ed6..00000000000 --- a/symlink_git_hooks.sh +++ /dev/null @@ -1,5 +0,0 @@ -#/bin/bash - -#ln -s -f .hooks/* .git/hooks/ -cd .git/hooks/ -cp --symbolic-link -f ../../.hooks/* . From 650a87dc05b4a69da8b42b7c79d820e206e40197 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 30 Aug 2016 09:32:56 +0200 Subject: [PATCH 12/54] feat(alerting): add alert history api endpoint ref #5850 --- pkg/api/alerting.go | 32 ++++++++++++++++++++ pkg/api/api.go | 3 +- pkg/api/dtos/alerting.go | 11 +++++++ pkg/services/annotations/annotations.go | 9 ++++++ pkg/services/sqlstore/annotation.go | 39 ++++++++++++++++++++++++- 5 files changed, 92 insertions(+), 2 deletions(-) diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index f20e08a5cad..63a080a74d2 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/annotations" ) func ValidateOrgAlert(c *middleware.Context) { @@ -212,3 +213,34 @@ func DeleteAlertNotification(c *middleware.Context) Response { return ApiSuccess("Notification deleted") } + +func GetAlertHistory(c *middleware.Context) Response { + query := &annotations.ItemQuery{ + AlertId: c.ParamsInt64("alertId"), + Type: annotations.AlertType, + OrgId: c.OrgId, + Limit: c.QueryInt64("limit"), + } + + repo := annotations.GetRepository() + + items, err := repo.Find(query) + if err != nil { + return ApiError(500, "Failed to get history for alert", err) + } + + var result []dtos.AlertHistory + for _, item := range items { + result = append(result, dtos.AlertHistory{ + AlertId: item.AlertId, + Timestamp: item.Timestamp, + Data: item.Data, + NewState: item.NewState, + Text: item.Text, + Metric: item.Metric, + Title: item.Title, + }) + } + + return Json(200, result) +} diff --git a/pkg/api/api.go b/pkg/api/api.go index 25158586822..966f950ee53 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -250,11 +250,12 @@ func Register(r *macaron.Macaron) { r.Group("/alerts", func() { r.Post("/test", bind(dtos.AlertTestCommand{}), wrap(AlertTest)) - //r.Get("/:alertId/states", wrap(GetAlertStates)) r.Get("/:alertId", ValidateOrgAlert, wrap(GetAlert)) r.Get("/", wrap(GetAlerts)) }) + r.Get("/alert-history/:alertId", ValidateOrgAlert, wrap(GetAlertHistory)) + r.Get("/alert-notifications", wrap(GetAlertNotifications)) r.Group("/alert-notifications", func() { diff --git a/pkg/api/dtos/alerting.go b/pkg/api/dtos/alerting.go index 0ec0174a949..c6cafddead0 100644 --- a/pkg/api/dtos/alerting.go +++ b/pkg/api/dtos/alerting.go @@ -52,3 +52,14 @@ type EvalMatch struct { Metric string `json:"metric"` Value float64 `json:"value"` } + +type AlertHistory struct { + AlertId int64 `json:"alertId"` + NewState string `json:"netState"` + Timestamp time.Time `json:"timestamp"` + Title string `json:"title"` + Text string `json:"text"` + Metric string `json:"metric"` + + Data *simplejson.Json `json:"data"` +} diff --git a/pkg/services/annotations/annotations.go b/pkg/services/annotations/annotations.go index fd8c8ebdb9a..06be152ec31 100644 --- a/pkg/services/annotations/annotations.go +++ b/pkg/services/annotations/annotations.go @@ -8,6 +8,15 @@ import ( type Repository interface { Save(item *Item) error + Find(query *ItemQuery) ([]*Item, error) +} + +type ItemQuery struct { + OrgId int64 `json:"orgId"` + Type ItemType `json:"type"` + AlertId int64 `json:"alertId"` + + Limit int64 `json:"alertId"` } var repositoryInstance Repository diff --git a/pkg/services/sqlstore/annotation.go b/pkg/services/sqlstore/annotation.go index 0530952144e..5cf4461b370 100644 --- a/pkg/services/sqlstore/annotation.go +++ b/pkg/services/sqlstore/annotation.go @@ -1,6 +1,9 @@ package sqlstore import ( + "bytes" + "fmt" + "github.com/go-xorm/xorm" "github.com/grafana/grafana/pkg/services/annotations" ) @@ -17,5 +20,39 @@ func (r *SqlAnnotationRepo) Save(item *annotations.Item) error { return nil }) - +} + +func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.Item, error) { + var sql bytes.Buffer + params := make([]interface{}, 0) + + sql.WriteString(`SELECT * + from annotation + `) + + sql.WriteString(`WHERE org_id = ?`) + params = append(params, query.OrgId) + + if query.AlertId != 0 { + sql.WriteString(` AND alert_id = ?`) + params = append(params, query.AlertId) + } + + if query.Type != "" { + sql.WriteString(` AND type = ?`) + params = append(params, string(query.Type)) + } + + if query.Limit == 0 { + query.Limit = 10 + } + + sql.WriteString(fmt.Sprintf("ORDER BY timestamp DESC LIMIT %v", query.Limit)) + + items := make([]*annotations.Item, 0) + if err := x.Sql(sql.String(), params...).Find(&items); err != nil { + return nil, err + } + + return items, nil } From 11a4ff0f8a635fc4ed8a140f30a889d1dd601605 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 30 Aug 2016 13:22:59 +0200 Subject: [PATCH 13/54] feat(alerting): add basic UI for history in alert tab ref #5850 --- pkg/api/alerting.go | 38 ++++++++++++++++++- pkg/api/api.go | 2 +- pkg/api/dtos/alerting.go | 2 +- pkg/services/alerting/result_handler.go | 2 + .../app/features/alerting/alert_tab_ctrl.ts | 16 ++++++++ .../features/alerting/partials/alert_tab.html | 22 +++++++++++ public/sass/components/edit_sidemenu.scss | 2 +- 7 files changed, 80 insertions(+), 4 deletions(-) diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index 63a080a74d2..80b5108bbad 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -215,8 +215,13 @@ func DeleteAlertNotification(c *middleware.Context) Response { } func GetAlertHistory(c *middleware.Context) Response { + alertId, err := getAlertIdForRequest(c) + if err != nil { + return ApiError(400, "Invalid request", err) + } + query := &annotations.ItemQuery{ - AlertId: c.ParamsInt64("alertId"), + AlertId: alertId, Type: annotations.AlertType, OrgId: c.OrgId, Limit: c.QueryInt64("limit"), @@ -244,3 +249,34 @@ func GetAlertHistory(c *middleware.Context) Response { return Json(200, result) } + +func getAlertIdForRequest(c *middleware.Context) (int64, error) { + alertId := c.QueryInt64("alertId") + panelId := c.QueryInt64("panelId") + dashboardId := c.QueryInt64("dashboardId") + + if alertId == 0 && dashboardId == 0 && panelId == 0 { + return 0, fmt.Errorf("Missing alertId or dashboardId and panelId") + } + + if alertId == 0 { + //fetch alertId + query := models.GetAlertsQuery{ + OrgId: c.OrgId, + DashboardId: dashboardId, + PanelId: panelId, + } + + if err := bus.Dispatch(&query); err != nil { + return 0, err + } + + if len(query.Result) != 1 { + return 0, fmt.Errorf("PanelId is not unique on dashboard") + } + + alertId = query.Result[0].Id + } + + return alertId, nil +} diff --git a/pkg/api/api.go b/pkg/api/api.go index 966f950ee53..132c609dde4 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -254,7 +254,7 @@ func Register(r *macaron.Macaron) { r.Get("/", wrap(GetAlerts)) }) - r.Get("/alert-history/:alertId", ValidateOrgAlert, wrap(GetAlertHistory)) + r.Get("/alert-history", wrap(GetAlertHistory)) r.Get("/alert-notifications", wrap(GetAlertNotifications)) diff --git a/pkg/api/dtos/alerting.go b/pkg/api/dtos/alerting.go index c6cafddead0..d5fbb2c3a9f 100644 --- a/pkg/api/dtos/alerting.go +++ b/pkg/api/dtos/alerting.go @@ -55,7 +55,7 @@ type EvalMatch struct { type AlertHistory struct { AlertId int64 `json:"alertId"` - NewState string `json:"netState"` + NewState string `json:"newState"` Timestamp time.Time `json:"timestamp"` Title string `json:"title"` Text string `json:"text"` diff --git a/pkg/services/alerting/result_handler.go b/pkg/services/alerting/result_handler.go index 09c77c8edd7..eca10f87494 100644 --- a/pkg/services/alerting/result_handler.go +++ b/pkg/services/alerting/result_handler.go @@ -4,6 +4,7 @@ import ( "time" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/metrics" m "github.com/grafana/grafana/pkg/models" @@ -65,6 +66,7 @@ func (handler *DefaultResultHandler) Handle(ctx *EvalContext) { NewState: string(ctx.Rule.State), PrevState: string(oldState), Timestamp: time.Now(), + Data: simplejson.NewFromAny(ctx.EvalMatches), } annotationRepo := annotations.GetRepository() diff --git a/public/app/features/alerting/alert_tab_ctrl.ts b/public/app/features/alerting/alert_tab_ctrl.ts index 2f222802438..0c42c2dbe7e 100644 --- a/public/app/features/alerting/alert_tab_ctrl.ts +++ b/public/app/features/alerting/alert_tab_ctrl.ts @@ -5,6 +5,7 @@ import {ThresholdMapper} from './threshold_mapper'; import {QueryPart} from 'app/core/components/query_part/query_part'; import alertDef from './alert_def'; import config from 'app/core/config'; +import moment from 'moment'; export class AlertTabCtrl { panel: any; @@ -22,6 +23,7 @@ export class AlertTabCtrl { alertNotifications; error: string; appSubUrl: string; + alertHistory: any; /** @ngInject */ constructor(private $scope, @@ -60,6 +62,7 @@ export class AlertTabCtrl { // build notification model this.notifications = []; this.alertNotifications = []; + this.alertHistory = []; return this.backendSrv.get('/api/alert-notifications').then(res => { this.notifications = res; @@ -71,6 +74,19 @@ export class AlertTabCtrl { this.alertNotifications.push(model); } }); + }).then(() => { + this.backendSrv.get(`/api/alert-history?dashboardId=${this.panelCtrl.dashboard.id}&panelId=${this.panel.id}`).then(res => { + this.alertHistory = _.map(res, (ah) => { + ah.time = moment(ah.timestamp).format('MMM D, YYYY HH:mm:ss'); + ah.stateModel = alertDef.getStateDisplayModel(ah.newState); + + ah.metrics = _.map(ah.data, (ev) => { + return ev.Metric + "=" + ev.Value; + }).join(', '); + + return ah; + }); + }); }); } diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index a386d23945b..7a98cf38a31 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -122,6 +122,28 @@ + +
+
Alert history
+
+
    +
  1. +
    +
    +
    +
    + + + {{ah.text}} + at {{ah.time}} {{ah.metrics}} +
    +
    +
    +
    +
  2. +
+
+
diff --git a/public/sass/components/edit_sidemenu.scss b/public/sass/components/edit_sidemenu.scss index de3e92ee5e2..d6c56fcc521 100644 --- a/public/sass/components/edit_sidemenu.scss +++ b/public/sass/components/edit_sidemenu.scss @@ -5,7 +5,7 @@ } .edit-sidemenu-aside { - width: 14rem; + width: 16rem; } .edit-sidemenu { From b55b7cde4bd13546e8b4d430de28d9529789f37b Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 30 Aug 2016 13:36:13 +0200 Subject: [PATCH 14/54] fix(alerting): remove error message when model is valid again closes #5921 --- public/app/features/alerting/alert_tab_ctrl.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/app/features/alerting/alert_tab_ctrl.ts b/public/app/features/alerting/alert_tab_ctrl.ts index 0c42c2dbe7e..546aee776a7 100644 --- a/public/app/features/alerting/alert_tab_ctrl.ts +++ b/public/app/features/alerting/alert_tab_ctrl.ts @@ -208,6 +208,8 @@ export class AlertTabCtrl { this.error = 'Currently the alerting backend only supports Graphite queries'; } else if (this.templateSrv.variableExists(foundTarget.target)) { this.error = 'Template variables are not supported in alert queries'; + } else { + this.error = ''; } }); } From 645293e590429bdfd4f91acebb575cc064f79c60 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 30 Aug 2016 13:47:42 +0200 Subject: [PATCH 15/54] fix(cli): improve error message for upgrade-all closes #5885 --- pkg/cmd/grafana-cli/commands/commands.go | 2 +- pkg/cmd/grafana-cli/commands/upgrade_all_command.go | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/grafana-cli/commands/commands.go b/pkg/cmd/grafana-cli/commands/commands.go index 2099a576647..b301924d956 100644 --- a/pkg/cmd/grafana-cli/commands/commands.go +++ b/pkg/cmd/grafana-cli/commands/commands.go @@ -14,7 +14,7 @@ func runCommand(command func(commandLine CommandLine) error) func(context *cli.C cmd := &contextCommandLine{context} if err := command(cmd); err != nil { logger.Errorf("\n%s: ", color.RedString("Error")) - logger.Errorf("%s\n\n", err) + logger.Errorf("%s %s\n\n", color.RedString("✗"), err) cmd.ShowHelp() os.Exit(1) diff --git a/pkg/cmd/grafana-cli/commands/upgrade_all_command.go b/pkg/cmd/grafana-cli/commands/upgrade_all_command.go index 1a6df719053..636292cce11 100644 --- a/pkg/cmd/grafana-cli/commands/upgrade_all_command.go +++ b/pkg/cmd/grafana-cli/commands/upgrade_all_command.go @@ -53,8 +53,16 @@ func upgradeAllCommand(c CommandLine) error { for _, p := range pluginsToUpgrade { logger.Infof("Updating %v \n", p.Id) - s.RemoveInstalledPlugin(pluginsDir, p.Id) - InstallPlugin(p.Id, "", c) + var err error + err = s.RemoveInstalledPlugin(pluginsDir, p.Id) + if err != nil { + return err + } + + err = InstallPlugin(p.Id, "", c) + if err != nil { + return err + } } return nil From ae428b917796a06da517c7c94465e65012d3824a Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 30 Aug 2016 13:49:50 +0200 Subject: [PATCH 16/54] docs(changelog): add note about cli fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9cf39f89a9..c4614d024d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * **Drag&Drop**: Fixed issue with drag and drop in latest Chrome(51+), fixes [#5767](https://github.com/grafana/grafana/issues/5767) * **Internal Metrics**: Fixed issue with dots in instance_name when sending internal metrics to Graphite, fixes [#5739](https://github.com/grafana/grafana/issues/5739) * **Grafana-CLI**: Add default plugin path for MAC OS, fixes [#5806](https://github.com/grafana/grafana/issues/5806) +* **Grafana-CLI**: Improve error message for upgrade-all command, fixes [#5885](https://github.com/grafana/grafana/issues/5885) # 3.1.1 (2016-08-01) * **IFrame embedding**: Fixed issue of using full iframe height, fixes [#5605](https://github.com/grafana/grafana/issues/5606) From a5d49c98202f72696bcc64a17c333f12138ed0c9 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 31 Aug 2016 08:33:58 +0200 Subject: [PATCH 17/54] docs(config): add note about poodle vulnerabillity in <3.0 closes #3483 --- docs/sources/installation/configuration.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index af3bb796e2d..4bb4d262d46 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -88,6 +88,8 @@ Another way is put a webserver like Nginx or Apache in front of Grafana and have `http` or `https` +> **Note** Grafana versions earlier than 3.0 are vulnerable to [POODLE](https://en.wikipedia.org/wiki/POODLE). So we strongly recommend to upgrade to 3.x or use a reverse proxy for ssl termination. + ### domain This setting is only used in as a part of the `root_url` setting (see below). Important if you From 53a9dec407b86eb3b2e22803bf97513cefd9581a Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 31 Aug 2016 09:48:58 +0200 Subject: [PATCH 18/54] feat(alerting): move timestamp to new row ref #5850 --- public/app/features/alerting/alert_tab_ctrl.ts | 4 ++-- public/app/features/alerting/partials/alert_tab.html | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/public/app/features/alerting/alert_tab_ctrl.ts b/public/app/features/alerting/alert_tab_ctrl.ts index 546aee776a7..c5ed6187f35 100644 --- a/public/app/features/alerting/alert_tab_ctrl.ts +++ b/public/app/features/alerting/alert_tab_ctrl.ts @@ -76,11 +76,11 @@ export class AlertTabCtrl { }); }).then(() => { this.backendSrv.get(`/api/alert-history?dashboardId=${this.panelCtrl.dashboard.id}&panelId=${this.panel.id}`).then(res => { - this.alertHistory = _.map(res, (ah) => { + this.alertHistory = _.map(res, ah => { ah.time = moment(ah.timestamp).format('MMM D, YYYY HH:mm:ss'); ah.stateModel = alertDef.getStateDisplayModel(ah.newState); - ah.metrics = _.map(ah.data, (ev) => { + ah.metrics = _.map(ah.data, ev=> { return ev.Metric + "=" + ev.Value; }).join(', '); diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index 7a98cf38a31..497027203bc 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -134,8 +134,11 @@
- {{ah.text}} - at {{ah.time}} {{ah.metrics}} + {{ah.stateModel.text}} + {{ah.metrics}} +
+
+ {{ah.time}}
From e493015f54137c5cb1d0ae5cf356d52244d25d1b Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 31 Aug 2016 10:10:39 +0200 Subject: [PATCH 19/54] fix(alerting): replace one hack with another hack --- public/app/features/alerting/partials/alert_tab.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index 497027203bc..e755496b732 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -123,7 +123,7 @@ -
+
Alert history
    From c624f3d47048a538a27bddf3144a70c3bb6872af Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 31 Aug 2016 11:55:35 +0200 Subject: [PATCH 20/54] fix(alerting): measure state result instead of severity --- pkg/metrics/metrics.go | 69 +++++++++++++------------ pkg/services/alerting/result_handler.go | 24 +++++---- 2 files changed, 51 insertions(+), 42 deletions(-) diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index 9477f2738b5..80c7520eaf2 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -9,34 +9,36 @@ func init() { } var ( - M_Instance_Start Counter - M_Page_Status_200 Counter - M_Page_Status_500 Counter - M_Page_Status_404 Counter - M_Api_Status_500 Counter - M_Api_Status_404 Counter - M_Api_User_SignUpStarted Counter - M_Api_User_SignUpCompleted Counter - M_Api_User_SignUpInvite Counter - M_Api_Dashboard_Save Timer - M_Api_Dashboard_Get Timer - M_Api_Dashboard_Search Timer - M_Api_Admin_User_Create Counter - M_Api_Login_Post Counter - M_Api_Login_OAuth Counter - M_Api_Org_Create Counter - M_Api_Dashboard_Snapshot_Create Counter - M_Api_Dashboard_Snapshot_External Counter - M_Api_Dashboard_Snapshot_Get Counter - M_Models_Dashboard_Insert Counter - M_Alerting_Result_Critical Counter - M_Alerting_Result_Warning Counter - M_Alerting_Result_Info Counter - M_Alerting_Result_Ok Counter - M_Alerting_Active_Alerts Counter - M_Alerting_Notification_Sent_Slack Counter - M_Alerting_Notification_Sent_Email Counter - M_Alerting_Notification_Sent_Webhook Counter + M_Instance_Start Counter + M_Page_Status_200 Counter + M_Page_Status_500 Counter + M_Page_Status_404 Counter + M_Api_Status_500 Counter + M_Api_Status_404 Counter + M_Api_User_SignUpStarted Counter + M_Api_User_SignUpCompleted Counter + M_Api_User_SignUpInvite Counter + M_Api_Dashboard_Save Timer + M_Api_Dashboard_Get Timer + M_Api_Dashboard_Search Timer + M_Api_Admin_User_Create Counter + M_Api_Login_Post Counter + M_Api_Login_OAuth Counter + M_Api_Org_Create Counter + M_Api_Dashboard_Snapshot_Create Counter + M_Api_Dashboard_Snapshot_External Counter + M_Api_Dashboard_Snapshot_Get Counter + M_Models_Dashboard_Insert Counter + M_Alerting_Result_State_Critical Counter + M_Alerting_Result_State_Warning Counter + M_Alerting_Result_State_Ok Counter + M_Alerting_Result_State_Paused Counter + M_Alerting_Result_State_Pending Counter + M_Alerting_Result_State_ExecutionError Counter + M_Alerting_Active_Alerts Counter + M_Alerting_Notification_Sent_Slack Counter + M_Alerting_Notification_Sent_Email Counter + M_Alerting_Notification_Sent_Webhook Counter // Timers M_DataSource_ProxyReq_Timer Timer @@ -75,10 +77,13 @@ func initMetricVars(settings *MetricSettings) { M_Models_Dashboard_Insert = RegCounter("models.dashboard.insert") - M_Alerting_Result_Critical = RegCounter("alerting.result", "severity", "critical") - M_Alerting_Result_Warning = RegCounter("alerting.result", "severity", "warning") - M_Alerting_Result_Info = RegCounter("alerting.result", "severity", "info") - M_Alerting_Result_Ok = RegCounter("alerting.result", "severity", "ok") + M_Alerting_Result_State_Critical = RegCounter("alerting.result", "state", "critical") + M_Alerting_Result_State_Warning = RegCounter("alerting.result", "state", "warning") + M_Alerting_Result_State_Ok = RegCounter("alerting.result", "state", "ok") + M_Alerting_Result_State_Paused = RegCounter("alerting.result", "state", "paused") + M_Alerting_Result_State_Pending = RegCounter("alerting.result", "state", "pending") + M_Alerting_Result_State_ExecutionError = RegCounter("alerting.result", "state", "execution_error") + M_Alerting_Active_Alerts = RegCounter("alerting.active_alerts") M_Alerting_Notification_Sent_Slack = RegCounter("alerting.notifications_sent", "type", "slack") M_Alerting_Notification_Sent_Email = RegCounter("alerting.notifications_sent", "type", "email") diff --git a/pkg/services/alerting/result_handler.go b/pkg/services/alerting/result_handler.go index eca10f87494..6f19c0e7e52 100644 --- a/pkg/services/alerting/result_handler.go +++ b/pkg/services/alerting/result_handler.go @@ -41,7 +41,7 @@ func (handler *DefaultResultHandler) Handle(ctx *EvalContext) { ctx.Rule.State = m.AlertStateOK } - countSeverity(ctx.Rule.Severity) + countStateResult(ctx.Rule.State) if ctx.Rule.State != oldState { handler.log.Info("New state change", "alertId", ctx.Rule.Id, "newState", ctx.Rule.State, "oldState", oldState) @@ -78,15 +78,19 @@ func (handler *DefaultResultHandler) Handle(ctx *EvalContext) { } } -func countSeverity(state m.AlertSeverityType) { +func countStateResult(state m.AlertStateType) { switch state { - case m.AlertSeverityOK: - metrics.M_Alerting_Result_Ok.Inc(1) - case m.AlertSeverityInfo: - metrics.M_Alerting_Result_Info.Inc(1) - case m.AlertSeverityWarning: - metrics.M_Alerting_Result_Warning.Inc(1) - case m.AlertSeverityCritical: - metrics.M_Alerting_Result_Critical.Inc(1) + case m.AlertStateCritical: + metrics.M_Alerting_Result_State_Critical.Inc(1) + case m.AlertStateWarning: + metrics.M_Alerting_Result_State_Warning.Inc(1) + case m.AlertStateOK: + metrics.M_Alerting_Result_State_Ok.Inc(1) + case m.AlertStatePaused: + metrics.M_Alerting_Result_State_Paused.Inc(1) + case m.AlertStatePending: + metrics.M_Alerting_Result_State_Pending.Inc(1) + case m.AlertStateExeuctionError: + metrics.M_Alerting_Result_State_ExecutionError.Inc(1) } } From 4619a05f43a12b8592a46f10db8ff3f37c28f9f5 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 31 Aug 2016 14:06:54 +0200 Subject: [PATCH 21/54] feat(alerting): save execution error message to annotations --- pkg/services/alerting/result_handler.go | 5 ++++- pkg/tsdb/graphite/graphite.go | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/services/alerting/result_handler.go b/pkg/services/alerting/result_handler.go index 6f19c0e7e52..f6c4e90138f 100644 --- a/pkg/services/alerting/result_handler.go +++ b/pkg/services/alerting/result_handler.go @@ -31,12 +31,15 @@ func (handler *DefaultResultHandler) Handle(ctx *EvalContext) { oldState := ctx.Rule.State exeuctionError := "" + annotationData := simplejson.New() if ctx.Error != nil { handler.log.Error("Alert Rule Result Error", "ruleId", ctx.Rule.Id, "error", ctx.Error) ctx.Rule.State = m.AlertStateExeuctionError exeuctionError = ctx.Error.Error() + annotationData.Set("errorMessage", exeuctionError) } else if ctx.Firing { ctx.Rule.State = m.AlertStateType(ctx.Rule.Severity) + annotationData = simplejson.NewFromAny(ctx.EvalMatches) } else { ctx.Rule.State = m.AlertStateOK } @@ -66,7 +69,7 @@ func (handler *DefaultResultHandler) Handle(ctx *EvalContext) { NewState: string(ctx.Rule.State), PrevState: string(oldState), Timestamp: time.Now(), - Data: simplejson.NewFromAny(ctx.EvalMatches), + Data: annotationData, } annotationRepo := annotations.GetRepository() diff --git a/pkg/tsdb/graphite/graphite.go b/pkg/tsdb/graphite/graphite.go index 68f92d7527e..874f2b7959b 100644 --- a/pkg/tsdb/graphite/graphite.go +++ b/pkg/tsdb/graphite/graphite.go @@ -49,12 +49,12 @@ func (e *GraphiteExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryC } res, err := client.Do(req) - defer res.Body.Close() if err != nil { result.Error = err return result } + defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { result.Error = err From 7ddd625e9dd253b33654104771f0e3119d9fdbb7 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 31 Aug 2016 15:55:01 +0200 Subject: [PATCH 22/54] feat(alerting): add slack/email support for execution errors --- pkg/services/alerting/eval_context.go | 60 ++++++++++++++---------- pkg/services/alerting/notifier.go | 1 - pkg/services/alerting/notifiers/email.go | 2 +- pkg/services/alerting/notifiers/slack.go | 17 ++++++- pkg/services/alerting/result_handler.go | 2 +- 5 files changed, 52 insertions(+), 30 deletions(-) diff --git a/pkg/services/alerting/eval_context.go b/pkg/services/alerting/eval_context.go index 70dc6b17a2f..29c51e02abd 100644 --- a/pkg/services/alerting/eval_context.go +++ b/pkg/services/alerting/eval_context.go @@ -28,36 +28,46 @@ type EvalContext struct { ImageOnDiskPath string } +type StateDescription struct { + Color string + Text string + Data string +} + +func (c *EvalContext) GetStateModel() *StateDescription { + if c.Error != nil { + return &StateDescription{ + Color: "#D63232", + Text: "EXECUTION ERROR", + } + } + + if !c.Firing { + return &StateDescription{ + Color: "#36a64f", + Text: "OK", + } + } + + if c.Rule.Severity == m.AlertSeverityWarning { + return &StateDescription{ + Color: "#fd821b", + Text: "WARNING", + } + } else { + return &StateDescription{ + Color: "#D63232", + Text: "CRITICAL", + } + } +} + func (a *EvalContext) GetDurationMs() float64 { return float64(a.EndTime.Nanosecond()-a.StartTime.Nanosecond()) / float64(1000000) } -func (c *EvalContext) GetColor() string { - if !c.Firing { - return "#36a64f" - } - - if c.Rule.Severity == m.AlertSeverityWarning { - return "#fd821b" - } else { - return "#D63232" - } -} - -func (c *EvalContext) GetStateText() string { - if !c.Firing { - return "OK" - } - - if c.Rule.Severity == m.AlertSeverityWarning { - return "WARNING" - } else { - return "CRITICAL" - } -} - func (c *EvalContext) GetNotificationTitle() string { - return "[" + c.GetStateText() + "] " + c.Rule.Name + return "[" + c.GetStateModel().Text + "] " + c.Rule.Name } func (c *EvalContext) getDashboardSlug() (string, error) { diff --git a/pkg/services/alerting/notifier.go b/pkg/services/alerting/notifier.go index 826aede2d11..c345594de2c 100644 --- a/pkg/services/alerting/notifier.go +++ b/pkg/services/alerting/notifier.go @@ -48,7 +48,6 @@ func (n *RootNotifier) Notify(context *EvalContext) { for _, notifier := range notifiers { n.log.Info("Sending notification", "firing", context.Firing, "type", notifier.GetType()) - go notifier.Notify(context) } } diff --git a/pkg/services/alerting/notifiers/email.go b/pkg/services/alerting/notifiers/email.go index 20b0bc06463..d63ca7d13f5 100644 --- a/pkg/services/alerting/notifiers/email.go +++ b/pkg/services/alerting/notifiers/email.go @@ -54,7 +54,7 @@ func (this *EmailNotifier) Notify(context *alerting.EvalContext) { "State": context.Rule.State, "Name": context.Rule.Name, "Severity": context.Rule.Severity, - "SeverityColor": context.GetColor(), + "SeverityColor": context.GetStateModel().Color, "Message": context.Rule.Message, "RuleUrl": ruleUrl, "ImageLink": context.ImagePublicUrl, diff --git a/pkg/services/alerting/notifiers/slack.go b/pkg/services/alerting/notifiers/slack.go index e099c7da690..837621e80bb 100644 --- a/pkg/services/alerting/notifiers/slack.go +++ b/pkg/services/alerting/notifiers/slack.go @@ -61,13 +61,26 @@ func (this *SlackNotifier) Notify(context *alerting.EvalContext) { } } + if context.Error != nil { + fields = append(fields, map[string]interface{}{ + "title": "Error message", + "value": context.Error.Error(), + "short": false, + }) + } + + message := "" + if context.Rule.State != m.AlertStateOK { //dont add message when going back to alert state ok. + message = context.Rule.Message + } + body := map[string]interface{}{ "attachments": []map[string]interface{}{ { - "color": context.GetColor(), + "color": context.GetStateModel().Color, "title": context.GetNotificationTitle(), "title_link": ruleUrl, - "text": context.Rule.Message, + "text": message, "fields": fields, "image_url": context.ImagePublicUrl, "footer": "Grafana v" + setting.BuildVersion, diff --git a/pkg/services/alerting/result_handler.go b/pkg/services/alerting/result_handler.go index f6c4e90138f..f22afb279d8 100644 --- a/pkg/services/alerting/result_handler.go +++ b/pkg/services/alerting/result_handler.go @@ -65,7 +65,7 @@ func (handler *DefaultResultHandler) Handle(ctx *EvalContext) { Type: annotations.AlertType, AlertId: ctx.Rule.Id, Title: ctx.Rule.Name, - Text: ctx.GetStateText(), + Text: ctx.GetStateModel().Text, NewState: string(ctx.Rule.State), PrevState: string(oldState), Timestamp: time.Now(), From 000eb669dfcb0969b75e309f820fe186ef78c624 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 1 Sep 2016 08:13:48 +0200 Subject: [PATCH 23/54] tech(build): change min go version to 1.6 --- README.md | 2 +- build.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6dbfc5388c2..f8d1db6cd07 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ the latest master builds [here](http://grafana.org/download/builds) ### Dependencies -- Go 1.5 +- Go 1.6 - NodeJS v4+ - [Godep](https://github.com/tools/godep) diff --git a/build.go b/build.go index 4347c486063..9b8aae6a68a 100644 --- a/build.go +++ b/build.go @@ -34,7 +34,7 @@ var ( binaries []string = []string{"grafana-server", "grafana-cli"} ) -const minGoVersion = 1.3 +const minGoVersion = 1.6 func main() { log.SetOutput(os.Stdout) From 29b60329cc5763f9c7341a574f15c27e15ebc278 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 1 Sep 2016 09:17:57 +0200 Subject: [PATCH 24/54] stupid stupid stupid me --- pkg/tsdb/graphite/graphite.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tsdb/graphite/graphite.go b/pkg/tsdb/graphite/graphite.go index 874f2b7959b..267f360f797 100644 --- a/pkg/tsdb/graphite/graphite.go +++ b/pkg/tsdb/graphite/graphite.go @@ -45,7 +45,7 @@ func (e *GraphiteExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryC client := http.Client{Timeout: time.Duration(10 * time.Second)} req, _ := http.NewRequest(http.MethodPost, e.Url+"/render?", strings.NewReader(params.Encode())) if e.BasicAuth { - req.SetBasicAuth("carl", "carl") + req.SetBasicAuth(e.BasicAuthPassword, e.BasicAuthPassword) } res, err := client.Do(req) From 94389a3a4460bd291a9ff86051ec04752c6f9863 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 1 Sep 2016 09:38:43 +0200 Subject: [PATCH 25/54] tech(tsdb): improve logging for graphite client --- pkg/tsdb/graphite/graphite.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/tsdb/graphite/graphite.go b/pkg/tsdb/graphite/graphite.go index 267f360f797..21db69ec488 100644 --- a/pkg/tsdb/graphite/graphite.go +++ b/pkg/tsdb/graphite/graphite.go @@ -2,6 +2,7 @@ package graphite import ( "encoding/json" + "fmt" "io/ioutil" "net/http" "net/url" @@ -45,7 +46,7 @@ func (e *GraphiteExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryC client := http.Client{Timeout: time.Duration(10 * time.Second)} req, _ := http.NewRequest(http.MethodPost, e.Url+"/render?", strings.NewReader(params.Encode())) if e.BasicAuth { - req.SetBasicAuth(e.BasicAuthPassword, e.BasicAuthPassword) + req.SetBasicAuth(e.BasicAuthUser, e.BasicAuthPassword) } res, err := client.Do(req) @@ -61,10 +62,16 @@ func (e *GraphiteExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryC return result } + if res.StatusCode == http.StatusUnauthorized { + glog.Info("Request is Unauthorized", "status", res.Status, "body", string(body)) + result.Error = fmt.Errorf("Request is Unauthorized status: %v body: %s", res.Status, string(body)) + return result + } + var data []TargetResponseDTO err = json.Unmarshal(body, &data) if err != nil { - glog.Info("Failed to unmarshal graphite response", "error", err, "body", string(body)) + glog.Info("Failed to unmarshal graphite response", "error", err, "status", res.Status, "body", string(body)) result.Error = err return result } From 3d3365f2428773f6852b3e89ea6cfbe70bcbd5dd Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 1 Sep 2016 10:09:13 +0200 Subject: [PATCH 26/54] tech(graphite): add more logging --- pkg/tsdb/graphite/graphite.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/pkg/tsdb/graphite/graphite.go b/pkg/tsdb/graphite/graphite.go index 21db69ec488..a6295292d7e 100644 --- a/pkg/tsdb/graphite/graphite.go +++ b/pkg/tsdb/graphite/graphite.go @@ -13,6 +13,10 @@ import ( "github.com/grafana/grafana/pkg/tsdb" ) +var ( + HttpClient = http.Client{Timeout: time.Duration(10 * time.Second)} +) + type GraphiteExecutor struct { *tsdb.DataSourceInfo } @@ -43,20 +47,27 @@ func (e *GraphiteExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryC glog.Debug("Graphite request", "query", query.Query) } - client := http.Client{Timeout: time.Duration(10 * time.Second)} - req, _ := http.NewRequest(http.MethodPost, e.Url+"/render?", strings.NewReader(params.Encode())) + formData := params.Encode() + glog.Info("Graphite request body", "formdata", formData) + req, err := http.NewRequest(http.MethodPost, e.Url+"/render", strings.NewReader(formData)) + if err != nil { + glog.Info("Failed to create request", "error", err) + result.Error = fmt.Errorf("Failed to create request. error: %v", err) + return result + } + if e.BasicAuth { req.SetBasicAuth(e.BasicAuthUser, e.BasicAuthPassword) } - res, err := client.Do(req) + res, err := HttpClient.Do(req) if err != nil { result.Error = err return result } - defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) + defer res.Body.Close() if err != nil { result.Error = err return result From d73547c0dc165e157adea006db7a539fe8e5e903 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 1 Sep 2016 11:12:35 +0200 Subject: [PATCH 27/54] feat(tsdb): add missing content type and join url using path --- pkg/tsdb/graphite/graphite.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pkg/tsdb/graphite/graphite.go b/pkg/tsdb/graphite/graphite.go index a6295292d7e..793aeefe568 100644 --- a/pkg/tsdb/graphite/graphite.go +++ b/pkg/tsdb/graphite/graphite.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "net/http" "net/url" + "path" "strings" "time" @@ -44,18 +45,20 @@ func (e *GraphiteExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryC for _, query := range queries { params["target"] = []string{query.Query} - glog.Debug("Graphite request", "query", query.Query) } - formData := params.Encode() - glog.Info("Graphite request body", "formdata", formData) - req, err := http.NewRequest(http.MethodPost, e.Url+"/render", strings.NewReader(formData)) + u, _ := url.Parse(e.Url) + u.Path = path.Join(u.Path, "render") + glog.Info("Graphite request body", "formdata", params.Encode()) + + req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(params.Encode())) if err != nil { glog.Info("Failed to create request", "error", err) result.Error = fmt.Errorf("Failed to create request. error: %v", err) return result } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") if e.BasicAuth { req.SetBasicAuth(e.BasicAuthUser, e.BasicAuthPassword) } From 86aea8921498f1aec38defbc8bedd10957060642 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 1 Sep 2016 13:03:15 +0200 Subject: [PATCH 28/54] style(tsdb): extract some methods --- pkg/tsdb/graphite/graphite.go | 76 +++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/pkg/tsdb/graphite/graphite.go b/pkg/tsdb/graphite/graphite.go index 793aeefe568..0247ad0dca8 100644 --- a/pkg/tsdb/graphite/graphite.go +++ b/pkg/tsdb/graphite/graphite.go @@ -36,7 +36,7 @@ func init() { func (e *GraphiteExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryContext) *tsdb.BatchResult { result := &tsdb.BatchResult{} - params := url.Values{ + formData := url.Values{ "from": []string{"-" + formatTimeRange(context.TimeRange.From)}, "until": []string{formatTimeRange(context.TimeRange.To)}, "format": []string{"json"}, @@ -44,52 +44,28 @@ func (e *GraphiteExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryC } for _, query := range queries { - params["target"] = []string{query.Query} + formData["target"] = []string{query.Query} } - u, _ := url.Parse(e.Url) - u.Path = path.Join(u.Path, "render") - glog.Info("Graphite request body", "formdata", params.Encode()) + glog.Info("Graphite request body", "formdata", formData.Encode()) - req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(params.Encode())) + req, err := e.createRequest(formData) if err != nil { - glog.Info("Failed to create request", "error", err) - result.Error = fmt.Errorf("Failed to create request. error: %v", err) + result.Error = err return result } - - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - if e.BasicAuth { - req.SetBasicAuth(e.BasicAuthUser, e.BasicAuthPassword) - } - res, err := HttpClient.Do(req) if err != nil { result.Error = err return result } - body, err := ioutil.ReadAll(res.Body) - defer res.Body.Close() + data, err := e.parseResponse(res) if err != nil { result.Error = err return result } - if res.StatusCode == http.StatusUnauthorized { - glog.Info("Request is Unauthorized", "status", res.Status, "body", string(body)) - result.Error = fmt.Errorf("Request is Unauthorized status: %v body: %s", res.Status, string(body)) - return result - } - - var data []TargetResponseDTO - err = json.Unmarshal(body, &data) - if err != nil { - glog.Info("Failed to unmarshal graphite response", "error", err, "status", res.Status, "body", string(body)) - result.Error = err - return result - } - result.QueryResults = make(map[string]*tsdb.QueryResult) queryRes := &tsdb.QueryResult{} for _, series := range data { @@ -103,6 +79,46 @@ func (e *GraphiteExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryC return result } +func (e *GraphiteExecutor) parseResponse(res *http.Response) ([]TargetResponseDTO, error) { + body, err := ioutil.ReadAll(res.Body) + defer res.Body.Close() + if err != nil { + return nil, err + } + + if res.StatusCode == http.StatusUnauthorized { + glog.Info("Request is Unauthorized", "status", res.Status, "body", string(body)) + return nil, fmt.Errorf("Request is Unauthorized status: %v body: %s", res.Status, string(body)) + } + + var data []TargetResponseDTO + err = json.Unmarshal(body, &data) + if err != nil { + glog.Info("Failed to unmarshal graphite response", "error", err, "status", res.Status, "body", string(body)) + return nil, err + } + + return data, nil +} + +func (e *GraphiteExecutor) createRequest(data url.Values) (*http.Request, error) { + u, _ := url.Parse(e.Url) + u.Path = path.Join(u.Path, "render") + + req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(data.Encode())) + if err != nil { + glog.Info("Failed to create request", "error", err) + return nil, fmt.Errorf("Failed to create request. error: %v", err) + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + if e.BasicAuth { + req.SetBasicAuth(e.BasicAuthUser, e.BasicAuthPassword) + } + + return req, err +} + func formatTimeRange(input string) string { if input == "now" { return input From 1955defc4357c2136240421bb78536870469ad4a Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 1 Sep 2016 17:21:13 +0200 Subject: [PATCH 29/54] fix(api): add missing jsonData field to dto The field is available on the dto so I guess is should be used. Otherwise we should remove the field rather then not setting it. ref #5944 --- pkg/api/datasources.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index 62a83bdaa0b..18b48cd8e29 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -22,7 +22,6 @@ func GetDataSources(c *middleware.Context) { result := make(dtos.DataSourceList, 0) for _, ds := range query.Result { - dsItem := dtos.DataSource{ Id: ds.Id, OrgId: ds.OrgId, @@ -35,6 +34,7 @@ func GetDataSources(c *middleware.Context) { User: ds.User, BasicAuth: ds.BasicAuth, IsDefault: ds.IsDefault, + JsonData: ds.JsonData, } if plugin, exists := plugins.DataSources[ds.Type]; exists { From 3645fb371c1118d4938ddb1c55b4a415fec0e7e9 Mon Sep 17 00:00:00 2001 From: Hiroaki Kobayashi Date: Fri, 2 Sep 2016 15:22:04 +0900 Subject: [PATCH 30/54] fix typo --- public/app/features/dashboard/import/dash_import.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/dashboard/import/dash_import.html b/public/app/features/dashboard/import/dash_import.html index 64a4a91a3c8..245e147dd3d 100644 --- a/public/app/features/dashboard/import/dash_import.html +++ b/public/app/features/dashboard/import/dash_import.html @@ -36,7 +36,7 @@
    - +
    - From 4c5461d4ba7bc5bfcd1a9e41434c18eda1024611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 5 Sep 2016 14:26:08 +0200 Subject: [PATCH 34/54] feat(alerting): alerting scheduling distribution, only distibutes it on seconds for now, not sub second distribution, #5854 --- pkg/services/alerting/models.go | 9 +++++---- pkg/services/alerting/scheduler.go | 29 ++++++++++++++++++++++++----- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/pkg/services/alerting/models.go b/pkg/services/alerting/models.go index e11e1e1aaaf..39e7eb83166 100644 --- a/pkg/services/alerting/models.go +++ b/pkg/services/alerting/models.go @@ -1,10 +1,11 @@ package alerting type Job struct { - Offset int64 - Delay bool - Running bool - Rule *Rule + Offset int64 + OffsetWait bool + Delay bool + Running bool + Rule *Rule } type ResultLogEntry struct { diff --git a/pkg/services/alerting/scheduler.go b/pkg/services/alerting/scheduler.go index ffac7ddb659..6701ad6eb6c 100644 --- a/pkg/services/alerting/scheduler.go +++ b/pkg/services/alerting/scheduler.go @@ -1,6 +1,7 @@ package alerting import ( + "math" "time" "github.com/grafana/grafana/pkg/log" @@ -34,8 +35,8 @@ func (s *SchedulerImpl) Update(rules []*Rule) { } job.Rule = rule - job.Offset = int64(i) - + job.Offset = ((rule.Frequency * 1000) / int64(len(rules))) * int64(i) + job.Offset = int64(math.Floor(float64(job.Offset) / 1000)) jobs[rule.Id] = job } @@ -46,9 +47,27 @@ func (s *SchedulerImpl) Tick(tickTime time.Time, execQueue chan *Job) { now := tickTime.Unix() for _, job := range s.jobs { - if now%job.Rule.Frequency == 0 && job.Running == false { - s.log.Debug("Scheduler: Putting job on to exec queue", "name", job.Rule.Name) - execQueue <- job + if job.Running { + continue + } + + if job.OffsetWait && now%job.Offset == 0 { + job.OffsetWait = false + s.enque(job, execQueue) + continue + } + + if now%job.Rule.Frequency == 0 { + if job.Offset > 0 { + job.OffsetWait = true + } else { + s.enque(job, execQueue) + } } } } + +func (s *SchedulerImpl) enque(job *Job, execQueue chan *Job) { + s.log.Debug("Scheduler: Putting job on to exec queue", "name", job.Rule.Name, "id", job.Rule.Id) + execQueue <- job +} From d11bc57c37d2b7e2f32c04d84258ff91fbee130e Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 5 Sep 2016 14:43:53 +0200 Subject: [PATCH 35/54] feat(notifications): make it possible to send test alert notifications closes #5847 --- pkg/api/alerting.go | 16 ++++ pkg/api/api.go | 1 + pkg/api/dtos/alerting.go | 7 ++ pkg/services/alerting/notifier.go | 5 +- pkg/services/alerting/test_notification.go | 93 +++++++++++++++++++ .../alerting/notification_edit_ctrl.ts | 20 ++++ .../alerting/partials/notification_edit.html | 24 ++++- 7 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 pkg/services/alerting/test_notification.go diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index 80b5108bbad..10492cb9a47 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -214,6 +214,22 @@ func DeleteAlertNotification(c *middleware.Context) Response { return ApiSuccess("Notification deleted") } +//POST /api/alert-notifications/test +func NotificationTest(c *middleware.Context, dto dtos.NotificationTestCommand) Response { + cmd := &alerting.NotificationTestCommand{ + Name: dto.Name, + Type: dto.Type, + Severity: dto.Severity, + Settings: dto.Settings, + } + + if err := bus.Dispatch(cmd); err != nil { + return ApiError(500, "Failed to send alert notifications", err) + } + + return ApiSuccess("Test notification sent") +} + func GetAlertHistory(c *middleware.Context) Response { alertId, err := getAlertIdForRequest(c) if err != nil { diff --git a/pkg/api/api.go b/pkg/api/api.go index 132c609dde4..de3a1de8a65 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -259,6 +259,7 @@ func Register(r *macaron.Macaron) { r.Get("/alert-notifications", wrap(GetAlertNotifications)) r.Group("/alert-notifications", func() { + r.Post("/test", bind(dtos.NotificationTestCommand{}), wrap(NotificationTest)) r.Post("/", bind(m.CreateAlertNotificationCommand{}), wrap(CreateAlertNotification)) r.Put("/:notificationId", bind(m.UpdateAlertNotificationCommand{}), wrap(UpdateAlertNotification)) r.Get("/:notificationId", wrap(GetAlertNotificationById)) diff --git a/pkg/api/dtos/alerting.go b/pkg/api/dtos/alerting.go index d5fbb2c3a9f..474f0a5a048 100644 --- a/pkg/api/dtos/alerting.go +++ b/pkg/api/dtos/alerting.go @@ -63,3 +63,10 @@ type AlertHistory struct { Data *simplejson.Json `json:"data"` } + +type NotificationTestCommand struct { + Name string `json:"name"` + Type string `json:"type"` + Settings *simplejson.Json `json:"settings"` + Severity string `json:"severity"` +} diff --git a/pkg/services/alerting/notifier.go b/pkg/services/alerting/notifier.go index c345594de2c..55090d9772f 100644 --- a/pkg/services/alerting/notifier.go +++ b/pkg/services/alerting/notifier.go @@ -46,6 +46,10 @@ func (n *RootNotifier) Notify(context *EvalContext) { n.log.Error("Failed to upload alert panel image", "error", err) } + n.sendNotifications(notifiers, context) +} + +func (n *RootNotifier) sendNotifications(notifiers []Notifier, context *EvalContext) { for _, notifier := range notifiers { n.log.Info("Sending notification", "firing", context.Firing, "type", notifier.GetType()) go notifier.Notify(context) @@ -53,7 +57,6 @@ func (n *RootNotifier) Notify(context *EvalContext) { } func (n *RootNotifier) uploadImage(context *EvalContext) error { - uploader, _ := imguploader.NewImageUploader() imageUrl, err := context.GetImageUrl() diff --git a/pkg/services/alerting/test_notification.go b/pkg/services/alerting/test_notification.go new file mode 100644 index 00000000000..026dfdd18a5 --- /dev/null +++ b/pkg/services/alerting/test_notification.go @@ -0,0 +1,93 @@ +package alerting + +import ( + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/models" +) + +type NotificationTestCommand struct { + Severity string + Name string + Type string + Settings *simplejson.Json +} + +func init() { + bus.AddHandler("alerting", handleNotificationTestCommand) + +} + +func handleNotificationTestCommand(cmd *NotificationTestCommand) error { + notifier := NewRootNotifier() + + model := &models.AlertNotification{ + Name: cmd.Name, + Type: cmd.Type, + Settings: cmd.Settings, + } + + notifiers, err := notifier.getNotifierFor(model) + + if err != nil { + log.Error2("Failed to create notifier", "error", err.Error()) + return err + } + + severity := models.AlertSeverityType(cmd.Severity) + notifier.sendNotifications([]Notifier{notifiers}, createTestEvalContext(severity)) + + return nil +} + +func createTestEvalContext(severity models.AlertSeverityType) *EvalContext { + state := models.AlertStateOK + firing := false + if severity == models.AlertSeverityCritical { + state = models.AlertStateCritical + firing = true + } + if severity == models.AlertSeverityWarning { + state = models.AlertStateWarning + firing = true + } + + testRule := &Rule{ + DashboardId: 1, + PanelId: 1, + Name: "Test notification", + Message: "Someone is testing the alert notification within grafana.", + State: state, + Severity: severity, + } + + ctx := NewEvalContext(testRule) + ctx.ImagePublicUrl = "http://grafana.org/assets/img/blog/mixed_styles.png" + + ctx.IsTestRun = true + ctx.Firing = firing + ctx.Error = nil + ctx.EvalMatches = evalMatchesBasedOnSeverity(severity) + + return ctx +} + +func evalMatchesBasedOnSeverity(severity models.AlertSeverityType) []*EvalMatch { + matches := make([]*EvalMatch, 0) + if severity == models.AlertSeverityOK { + return matches + } + + matches = append(matches, &EvalMatch{ + Metric: "High value", + Value: 100, + }) + + matches = append(matches, &EvalMatch{ + Metric: "Higher Value", + Value: 200, + }) + + return matches +} diff --git a/public/app/features/alerting/notification_edit_ctrl.ts b/public/app/features/alerting/notification_edit_ctrl.ts index fc33118cb00..99193a9d7d1 100644 --- a/public/app/features/alerting/notification_edit_ctrl.ts +++ b/public/app/features/alerting/notification_edit_ctrl.ts @@ -7,6 +7,8 @@ import config from 'app/core/config'; export class AlertNotificationEditCtrl { model: any; + showTest: boolean = false; + testSeverity: string = "critical"; /** @ngInject */ constructor(private $routeParams, private backendSrv, private $scope, private $location) { @@ -47,6 +49,24 @@ export class AlertNotificationEditCtrl { typeChanged() { this.model.settings = {}; } + + toggleTest() { + this.showTest = !this.showTest; + } + + testNotification() { + var payload = { + name: this.model.name, + type: this.model.type, + settings: this.model.settings, + severity: this.testSeverity + }; + + this.backendSrv.post(`/api/alert-notifications/test`, payload) + .then(res => { + this.$scope.appEvent('alert-succes', ['Test notification sent', '']); + }); + } } coreModule.controller('AlertNotificationEditCtrl', AlertNotificationEditCtrl); diff --git a/public/app/features/alerting/partials/notification_edit.html b/public/app/features/alerting/partials/notification_edit.html index 2faf3e2dfe0..5664d201967 100644 --- a/public/app/features/alerting/partials/notification_edit.html +++ b/public/app/features/alerting/partials/notification_edit.html @@ -60,7 +60,27 @@
-
- +
+
+
+ +
+
+ +
+ +
+ Severity for test notification +
+ +
+
+
+ +
+
From b501590a75d3a2d78ad6d121ec0897711827a78f Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Mon, 5 Sep 2016 17:38:55 +0200 Subject: [PATCH 36/54] fix(.editorconfig): correct spelling of indent_style Unleash the fucking fury --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 831bb8696cc..3701d80b453 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,7 +2,7 @@ root = true [*.go] -indent_style = tabs +indent_style = tab indent_size = 2 charset = utf-8 trim_trailing_whitespace = true From 015423b2332138e914cb91491583ae725bc4e1e6 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 5 Sep 2016 21:13:35 +0200 Subject: [PATCH 37/54] tech(gitignore): ignore vscode folder --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 515c19e7e0b..721a2a71ad4 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ public/css/*.min.css *.swp .idea/ *.iml +.vscode/ /data/* /bin/* @@ -37,4 +38,4 @@ profile.cov .notouch /pkg/cmd/grafana-cli/grafana-cli /pkg/cmd/grafana-server/grafana-server -/examples/*/dist \ No newline at end of file +/examples/*/dist From eb0396ad950c36570d0930a07272f2614b20a941 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 5 Sep 2016 21:35:03 +0200 Subject: [PATCH 38/54] chore(tsdb): remove some logging --- pkg/tsdb/graphite/graphite.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/tsdb/graphite/graphite.go b/pkg/tsdb/graphite/graphite.go index 0247ad0dca8..7191cbd7a63 100644 --- a/pkg/tsdb/graphite/graphite.go +++ b/pkg/tsdb/graphite/graphite.go @@ -47,8 +47,6 @@ func (e *GraphiteExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryC formData["target"] = []string{query.Query} } - glog.Info("Graphite request body", "formdata", formData.Encode()) - req, err := e.createRequest(formData) if err != nil { result.Error = err From c893e5d24183c604bdea8904a9a3091f9e3e3502 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 5 Sep 2016 21:33:05 +0200 Subject: [PATCH 39/54] feat(notifications): add support for default notifications ref #5883 --- pkg/api/alerting.go | 11 ++-- pkg/api/dtos/alerting.go | 11 ++-- pkg/models/alert_notifications.go | 31 +++++----- pkg/services/alerting/notifier.go | 5 +- pkg/services/sqlstore/alert_notification.go | 61 +++++++++++-------- .../sqlstore/alert_notification_test.go | 4 +- pkg/services/sqlstore/migrations/alert_mig.go | 4 ++ .../alerting/notification_edit_ctrl.ts | 3 +- .../alerting/partials/notification_edit.html | 9 +++ .../alerting/partials/notifications_list.html | 7 ++- 10 files changed, 89 insertions(+), 57 deletions(-) diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index 10492cb9a47..83a370499c5 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -157,11 +157,12 @@ func GetAlertNotifications(c *middleware.Context) Response { for _, notification := range query.Result { result = append(result, dtos.AlertNotification{ - Id: notification.Id, - Name: notification.Name, - Type: notification.Type, - Created: notification.Created, - Updated: notification.Updated, + Id: notification.Id, + Name: notification.Name, + Type: notification.Type, + IsDefault: notification.IsDefault, + Created: notification.Created, + Updated: notification.Updated, }) } diff --git a/pkg/api/dtos/alerting.go b/pkg/api/dtos/alerting.go index 474f0a5a048..83b21757d32 100644 --- a/pkg/api/dtos/alerting.go +++ b/pkg/api/dtos/alerting.go @@ -22,11 +22,12 @@ type AlertRule struct { } type AlertNotification struct { - Id int64 `json:"id"` - Name string `json:"name"` - Type string `json:"type"` - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` + Id int64 `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + IsDefault bool `json:"isDefault"` + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` } type AlertTestCommand struct { diff --git a/pkg/models/alert_notifications.go b/pkg/models/alert_notifications.go index 464d6dc88da..1e586b4974e 100644 --- a/pkg/models/alert_notifications.go +++ b/pkg/models/alert_notifications.go @@ -7,29 +7,32 @@ import ( ) type AlertNotification struct { - Id int64 `json:"id"` - OrgId int64 `json:"-"` - Name string `json:"name"` - Type string `json:"type"` - Settings *simplejson.Json `json:"settings"` - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` + Id int64 `json:"id"` + OrgId int64 `json:"-"` + Name string `json:"name"` + Type string `json:"type"` + IsDefault bool `json:"isDefault"` + Settings *simplejson.Json `json:"settings"` + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` } type CreateAlertNotificationCommand struct { - Name string `json:"name" binding:"Required"` - Type string `json:"type" binding:"Required"` - Settings *simplejson.Json `json:"settings"` + Name string `json:"name" binding:"Required"` + Type string `json:"type" binding:"Required"` + IsDefault bool `json:"isDefault"` + Settings *simplejson.Json `json:"settings"` OrgId int64 `json:"-"` Result *AlertNotification } type UpdateAlertNotificationCommand struct { - Id int64 `json:"id" binding:"Required"` - Name string `json:"name" binding:"Required"` - Type string `json:"type" binding:"Required"` - Settings *simplejson.Json `json:"settings" binding:"Required"` + Id int64 `json:"id" binding:"Required"` + Name string `json:"name" binding:"Required"` + Type string `json:"type" binding:"Required"` + IsDefault bool `json:"isDefault"` + Settings *simplejson.Json `json:"settings" binding:"Required"` OrgId int64 `json:"-"` Result *AlertNotification diff --git a/pkg/services/alerting/notifier.go b/pkg/services/alerting/notifier.go index 55090d9772f..b82d4dd3864 100644 --- a/pkg/services/alerting/notifier.go +++ b/pkg/services/alerting/notifier.go @@ -88,11 +88,12 @@ func (n *RootNotifier) uploadImage(context *EvalContext) error { } func (n *RootNotifier) getNotifiers(orgId int64, notificationIds []int64) ([]Notifier, error) { + query := &m.GetAlertNotificationsQuery{OrgId: orgId} + if len(notificationIds) == 0 { - return []Notifier{}, nil + query.Ids = []int64{0} } - query := &m.GetAlertNotificationsQuery{OrgId: orgId, Ids: notificationIds} if err := bus.Dispatch(query); err != nil { return nil, err } diff --git a/pkg/services/sqlstore/alert_notification.go b/pkg/services/sqlstore/alert_notification.go index c5b3a2f5975..d33e6d90151 100644 --- a/pkg/services/sqlstore/alert_notification.go +++ b/pkg/services/sqlstore/alert_notification.go @@ -40,33 +40,36 @@ func getAlertNotificationsInternal(query *m.GetAlertNotificationsQuery, sess *xo params := make([]interface{}, 0) sql.WriteString(`SELECT - alert_notification.id, - alert_notification.org_id, - alert_notification.name, - alert_notification.type, - alert_notification.created, - alert_notification.updated, - alert_notification.settings - FROM alert_notification - `) + alert_notification.id, + alert_notification.org_id, + alert_notification.name, + alert_notification.type, + alert_notification.created, + alert_notification.updated, + alert_notification.settings, + alert_notification.is_default + FROM alert_notification + `) sql.WriteString(` WHERE alert_notification.org_id = ?`) params = append(params, query.OrgId) - if query.Name != "" { - sql.WriteString(` AND alert_notification.name = ?`) - params = append(params, query.Name) - } + if query.Name != "" || query.Id != 0 || len(query.Ids) > 0 { + if query.Name != "" { + sql.WriteString(` AND alert_notification.name = ?`) + params = append(params, query.Name) + } - if query.Id != 0 { - sql.WriteString(` AND alert_notification.id = ?`) - params = append(params, query.Id) - } + if query.Id != 0 { + sql.WriteString(` AND alert_notification.id = ?`) + params = append(params, query.Id) + } - if len(query.Ids) > 0 { - sql.WriteString(` AND alert_notification.id IN (?` + strings.Repeat(",?", len(query.Ids)-1) + ")") - for _, v := range query.Ids { - params = append(params, v) + if len(query.Ids) > 0 { + sql.WriteString(` AND ((alert_notification.is_default = 1) OR alert_notification.id IN (?` + strings.Repeat(",?", len(query.Ids)-1) + "))") + for _, v := range query.Ids { + params = append(params, v) + } } } @@ -93,12 +96,13 @@ func CreateAlertNotificationCommand(cmd *m.CreateAlertNotificationCommand) error } alertNotification := &m.AlertNotification{ - OrgId: cmd.OrgId, - Name: cmd.Name, - Type: cmd.Type, - Settings: cmd.Settings, - Created: time.Now(), - Updated: time.Now(), + OrgId: cmd.OrgId, + Name: cmd.Name, + Type: cmd.Type, + Settings: cmd.Settings, + Created: time.Now(), + Updated: time.Now(), + IsDefault: cmd.IsDefault, } if _, err = sess.Insert(alertNotification); err != nil { @@ -132,6 +136,9 @@ func UpdateAlertNotification(cmd *m.UpdateAlertNotificationCommand) error { current.Settings = cmd.Settings current.Name = cmd.Name current.Type = cmd.Type + current.IsDefault = cmd.IsDefault + + sess.UseBool("is_default") if affected, err := sess.Id(cmd.Id).Update(current); err != nil { return err diff --git a/pkg/services/sqlstore/alert_notification_test.go b/pkg/services/sqlstore/alert_notification_test.go index 4cbcf13a000..f9ecf0e5234 100644 --- a/pkg/services/sqlstore/alert_notification_test.go +++ b/pkg/services/sqlstore/alert_notification_test.go @@ -63,10 +63,12 @@ func TestAlertNotificationSQLAccess(t *testing.T) { cmd1 := m.CreateAlertNotificationCommand{Name: "nagios", Type: "webhook", OrgId: 1, Settings: simplejson.New()} cmd2 := m.CreateAlertNotificationCommand{Name: "slack", Type: "webhook", OrgId: 1, Settings: simplejson.New()} cmd3 := m.CreateAlertNotificationCommand{Name: "ops2", Type: "email", OrgId: 1, Settings: simplejson.New()} + cmd4 := m.CreateAlertNotificationCommand{IsDefault: true, Name: "default", Type: "email", OrgId: 1, Settings: simplejson.New()} So(CreateAlertNotificationCommand(&cmd1), ShouldBeNil) So(CreateAlertNotificationCommand(&cmd2), ShouldBeNil) So(CreateAlertNotificationCommand(&cmd3), ShouldBeNil) + So(CreateAlertNotificationCommand(&cmd4), ShouldBeNil) Convey("search", func() { query := &m.GetAlertNotificationsQuery{ @@ -76,7 +78,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { err := GetAlertNotifications(query) So(err, ShouldBeNil) - So(len(query.Result), ShouldEqual, 2) + So(len(query.Result), ShouldEqual, 3) }) }) }) diff --git a/pkg/services/sqlstore/migrations/alert_mig.go b/pkg/services/sqlstore/migrations/alert_mig.go index 63d61d8b196..b6956be41c7 100644 --- a/pkg/services/sqlstore/migrations/alert_mig.go +++ b/pkg/services/sqlstore/migrations/alert_mig.go @@ -62,5 +62,9 @@ func addAlertMigrations(mg *Migrator) { } mg.AddMigration("create alert_notification table v1", NewAddTableMigration(alert_notification)) + mg.AddMigration("Add column is_default", NewAddColumnMigration(alert_notification, &Column{ + Name: "is_default", Type: DB_Bool, Nullable: false, Default: "0", + })) mg.AddMigration("add index alert_notification org_id & name", NewAddIndexMigration(alert_notification, alert_notification.Indices[0])) + } diff --git a/public/app/features/alerting/notification_edit_ctrl.ts b/public/app/features/alerting/notification_edit_ctrl.ts index 99193a9d7d1..d465a9021a6 100644 --- a/public/app/features/alerting/notification_edit_ctrl.ts +++ b/public/app/features/alerting/notification_edit_ctrl.ts @@ -17,7 +17,8 @@ export class AlertNotificationEditCtrl { } else { this.model = { type: 'email', - settings: {} + settings: {}, + isDefault: false }; } } diff --git a/public/app/features/alerting/partials/notification_edit.html b/public/app/features/alerting/partials/notification_edit.html index 5664d201967..c9f181eacf9 100644 --- a/public/app/features/alerting/partials/notification_edit.html +++ b/public/app/features/alerting/partials/notification_edit.html @@ -25,6 +25,15 @@ +
+ + +
diff --git a/public/app/features/alerting/partials/notifications_list.html b/public/app/features/alerting/partials/notifications_list.html index df3cbfcc65d..8777d6f6a1b 100644 --- a/public/app/features/alerting/partials/notifications_list.html +++ b/public/app/features/alerting/partials/notifications_list.html @@ -10,7 +10,7 @@
- +
@@ -25,7 +25,10 @@ -
Name Type {{notification.type}} + + + default + edit From 2ca7284a56476602f2b3843168370a7cf90fe924 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 6 Sep 2016 08:42:35 +0200 Subject: [PATCH 40/54] tech(notifications): splitt into 3 queries closes #5883 --- pkg/api/alerting.go | 4 +- pkg/models/alert_notifications.go | 12 +++ pkg/services/alerting/notifier.go | 6 +- pkg/services/sqlstore/alert_notification.go | 77 +++++++++++++++---- .../sqlstore/alert_notification_test.go | 19 ++++- 5 files changed, 93 insertions(+), 25 deletions(-) diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index 83a370499c5..37d247a7c8f 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -147,7 +147,7 @@ func DelAlert(c *middleware.Context) Response { } func GetAlertNotifications(c *middleware.Context) Response { - query := &models.GetAlertNotificationsQuery{OrgId: c.OrgId} + query := &models.GetAllAlertNotificationsQuery{OrgId: c.OrgId} if err := bus.Dispatch(query); err != nil { return ApiError(500, "Failed to get alert notifications", err) @@ -179,7 +179,7 @@ func GetAlertNotificationById(c *middleware.Context) Response { return ApiError(500, "Failed to get alert notifications", err) } - return Json(200, query.Result[0]) + return Json(200, query.Result) } func CreateAlertNotification(c *middleware.Context, cmd models.CreateAlertNotificationCommand) Response { diff --git a/pkg/models/alert_notifications.go b/pkg/models/alert_notifications.go index 1e586b4974e..87b515f370c 100644 --- a/pkg/models/alert_notifications.go +++ b/pkg/models/alert_notifications.go @@ -46,8 +46,20 @@ type DeleteAlertNotificationCommand struct { type GetAlertNotificationsQuery struct { Name string Id int64 + OrgId int64 + + Result *AlertNotification +} + +type GetAlertNotificationsToSendQuery struct { Ids []int64 OrgId int64 Result []*AlertNotification } + +type GetAllAlertNotificationsQuery struct { + OrgId int64 + + Result []*AlertNotification +} diff --git a/pkg/services/alerting/notifier.go b/pkg/services/alerting/notifier.go index b82d4dd3864..b4b5cd819ff 100644 --- a/pkg/services/alerting/notifier.go +++ b/pkg/services/alerting/notifier.go @@ -88,11 +88,7 @@ func (n *RootNotifier) uploadImage(context *EvalContext) error { } func (n *RootNotifier) getNotifiers(orgId int64, notificationIds []int64) ([]Notifier, error) { - query := &m.GetAlertNotificationsQuery{OrgId: orgId} - - if len(notificationIds) == 0 { - query.Ids = []int64{0} - } + query := &m.GetAlertNotificationsToSendQuery{OrgId: orgId, Ids: notificationIds} if err := bus.Dispatch(query); err != nil { return nil, err diff --git a/pkg/services/sqlstore/alert_notification.go b/pkg/services/sqlstore/alert_notification.go index d33e6d90151..5105ca39eff 100644 --- a/pkg/services/sqlstore/alert_notification.go +++ b/pkg/services/sqlstore/alert_notification.go @@ -16,6 +16,8 @@ func init() { bus.AddHandler("sql", CreateAlertNotificationCommand) bus.AddHandler("sql", UpdateAlertNotification) bus.AddHandler("sql", DeleteAlertNotification) + bus.AddHandler("sql", GetAlertNotificationsToSend) + bus.AddHandler("sql", GetAllAlertNotifications) } func DeleteAlertNotification(cmd *m.DeleteAlertNotificationCommand) error { @@ -32,10 +34,20 @@ func DeleteAlertNotification(cmd *m.DeleteAlertNotificationCommand) error { } func GetAlertNotifications(query *m.GetAlertNotificationsQuery) error { - return getAlertNotificationsInternal(query, x.NewSession()) + return getAlertNotificationInternal(query, x.NewSession()) } -func getAlertNotificationsInternal(query *m.GetAlertNotificationsQuery, sess *xorm.Session) error { +func GetAllAlertNotifications(query *m.GetAllAlertNotificationsQuery) error { + results := make([]*m.AlertNotification, 0) + if err := x.Where("org_id = ?", query.OrgId).Find(&results); err != nil { + return err + } + + query.Result = results + return nil +} + +func GetAlertNotificationsToSend(query *m.GetAlertNotificationsToSendQuery) error { var sql bytes.Buffer params := make([]interface{}, 0) @@ -54,7 +66,44 @@ func getAlertNotificationsInternal(query *m.GetAlertNotificationsQuery, sess *xo sql.WriteString(` WHERE alert_notification.org_id = ?`) params = append(params, query.OrgId) - if query.Name != "" || query.Id != 0 || len(query.Ids) > 0 { + sql.WriteString(` AND ((alert_notification.is_default = 1)`) + if len(query.Ids) > 0 { + sql.WriteString(` OR alert_notification.id IN (?` + strings.Repeat(",?", len(query.Ids)-1) + ")") + for _, v := range query.Ids { + params = append(params, v) + } + } + sql.WriteString(`)`) + + results := make([]*m.AlertNotification, 0) + if err := x.Sql(sql.String(), params...).Find(&results); err != nil { + return err + } + + query.Result = results + return nil +} + +func getAlertNotificationInternal(query *m.GetAlertNotificationsQuery, sess *xorm.Session) error { + var sql bytes.Buffer + params := make([]interface{}, 0) + + sql.WriteString(`SELECT + alert_notification.id, + alert_notification.org_id, + alert_notification.name, + alert_notification.type, + alert_notification.created, + alert_notification.updated, + alert_notification.settings, + alert_notification.is_default + FROM alert_notification + `) + + sql.WriteString(` WHERE alert_notification.org_id = ?`) + params = append(params, query.OrgId) + + if query.Name != "" || query.Id != 0 { if query.Name != "" { sql.WriteString(` AND alert_notification.name = ?`) params = append(params, query.Name) @@ -64,13 +113,6 @@ func getAlertNotificationsInternal(query *m.GetAlertNotificationsQuery, sess *xo sql.WriteString(` AND alert_notification.id = ?`) params = append(params, query.Id) } - - if len(query.Ids) > 0 { - sql.WriteString(` AND ((alert_notification.is_default = 1) OR alert_notification.id IN (?` + strings.Repeat(",?", len(query.Ids)-1) + "))") - for _, v := range query.Ids { - params = append(params, v) - } - } } results := make([]*m.AlertNotification, 0) @@ -78,20 +120,25 @@ func getAlertNotificationsInternal(query *m.GetAlertNotificationsQuery, sess *xo return err } - query.Result = results + if len(results) == 0 { + query.Result = nil + } else { + query.Result = results[0] + } + return nil } func CreateAlertNotificationCommand(cmd *m.CreateAlertNotificationCommand) error { return inTransaction(func(sess *xorm.Session) error { existingQuery := &m.GetAlertNotificationsQuery{OrgId: cmd.OrgId, Name: cmd.Name} - err := getAlertNotificationsInternal(existingQuery, sess) + err := getAlertNotificationInternal(existingQuery, sess) if err != nil { return err } - if len(existingQuery.Result) > 0 { + if existingQuery.Result != nil { return fmt.Errorf("Alert notification name %s already exists", cmd.Name) } @@ -124,11 +171,11 @@ func UpdateAlertNotification(cmd *m.UpdateAlertNotificationCommand) error { // check if name exists sameNameQuery := &m.GetAlertNotificationsQuery{OrgId: cmd.OrgId, Name: cmd.Name} - if err := getAlertNotificationsInternal(sameNameQuery, sess); err != nil { + if err := getAlertNotificationInternal(sameNameQuery, sess); err != nil { return err } - if len(sameNameQuery.Result) > 0 && sameNameQuery.Result[0].Id != current.Id { + if sameNameQuery.Result != nil && sameNameQuery.Result.Id != current.Id { return fmt.Errorf("Alert notification name %s already exists", cmd.Name) } diff --git a/pkg/services/sqlstore/alert_notification_test.go b/pkg/services/sqlstore/alert_notification_test.go index f9ecf0e5234..d37062fb58f 100644 --- a/pkg/services/sqlstore/alert_notification_test.go +++ b/pkg/services/sqlstore/alert_notification_test.go @@ -23,7 +23,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { err := GetAlertNotifications(cmd) fmt.Printf("errror %v", err) So(err, ShouldBeNil) - So(len(cmd.Result), ShouldEqual, 0) + So(cmd.Result, ShouldBeNil) }) Convey("Can save Alert Notification", func() { @@ -65,21 +65,34 @@ func TestAlertNotificationSQLAccess(t *testing.T) { cmd3 := m.CreateAlertNotificationCommand{Name: "ops2", Type: "email", OrgId: 1, Settings: simplejson.New()} cmd4 := m.CreateAlertNotificationCommand{IsDefault: true, Name: "default", Type: "email", OrgId: 1, Settings: simplejson.New()} + otherOrg := m.CreateAlertNotificationCommand{Name: "default", Type: "email", OrgId: 2, Settings: simplejson.New()} + So(CreateAlertNotificationCommand(&cmd1), ShouldBeNil) So(CreateAlertNotificationCommand(&cmd2), ShouldBeNil) So(CreateAlertNotificationCommand(&cmd3), ShouldBeNil) So(CreateAlertNotificationCommand(&cmd4), ShouldBeNil) + So(CreateAlertNotificationCommand(&otherOrg), ShouldBeNil) Convey("search", func() { - query := &m.GetAlertNotificationsQuery{ + query := &m.GetAlertNotificationsToSendQuery{ Ids: []int64{cmd1.Result.Id, cmd2.Result.Id, 112341231}, OrgId: 1, } - err := GetAlertNotifications(query) + err := GetAlertNotificationsToSend(query) So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 3) }) + + Convey("all", func() { + query := &m.GetAllAlertNotificationsQuery{ + OrgId: 1, + } + + err := GetAllAlertNotifications(query) + So(err, ShouldBeNil) + So(len(query.Result), ShouldEqual, 4) + }) }) }) } From 6b17cdbca64c4417154430d0ca1dc4bde7338a50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 6 Sep 2016 09:14:11 +0200 Subject: [PATCH 41/54] feat(alerting): Save As removes alerts from panels, closes #5965 --- .../app/features/alerting/alert_tab_ctrl.ts | 38 +++++++++++-------- .../features/alerting/partials/alert_tab.html | 1 - .../features/dashboard/saveDashboardAsCtrl.js | 8 ++++ 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/public/app/features/alerting/alert_tab_ctrl.ts b/public/app/features/alerting/alert_tab_ctrl.ts index c5ed6187f35..d10265a3ab7 100644 --- a/public/app/features/alerting/alert_tab_ctrl.ts +++ b/public/app/features/alerting/alert_tab_ctrl.ts @@ -74,18 +74,20 @@ export class AlertTabCtrl { this.alertNotifications.push(model); } }); - }).then(() => { - this.backendSrv.get(`/api/alert-history?dashboardId=${this.panelCtrl.dashboard.id}&panelId=${this.panel.id}`).then(res => { - this.alertHistory = _.map(res, ah => { - ah.time = moment(ah.timestamp).format('MMM D, YYYY HH:mm:ss'); - ah.stateModel = alertDef.getStateDisplayModel(ah.newState); + }); + } - ah.metrics = _.map(ah.data, ev=> { - return ev.Metric + "=" + ev.Value; - }).join(', '); + getAlertHistory() { + this.backendSrv.get(`/api/alert-history?dashboardId=${this.panelCtrl.dashboard.id}&panelId=${this.panel.id}`).then(res => { + this.alertHistory = _.map(res, ah => { + ah.time = moment(ah.timestamp).format('MMM D, YYYY HH:mm:ss'); + ah.stateModel = alertDef.getStateDisplayModel(ah.newState); - return ah; - }); + ah.metrics = _.map(ah.data, ev=> { + return ev.Metric + "=" + ev.Value; + }).join(', '); + + return ah; }); }); } @@ -125,7 +127,11 @@ export class AlertTabCtrl { } initModel() { - var alert = this.alert = this.panel.alert = this.panel.alert || {}; + var alert = this.alert = this.panel.alert = this.panel.alert || {enabled: false}; + + if (!this.alert.enabled) { + return; + } alert.conditions = alert.conditions || []; if (alert.conditions.length === 0) { @@ -145,11 +151,9 @@ export class AlertTabCtrl { return memo; }, []); - if (this.alert.enabled) { - this.panelCtrl.editingThresholds = true; - } - ThresholdMapper.alertToGraphThresholds(this.panel); + + this.panelCtrl.editingThresholds = true; this.panelCtrl.render(); } @@ -173,6 +177,10 @@ export class AlertTabCtrl { } validateModel() { + if (!this.alert.enabled) { + return; + } + let firstTarget; var fixed = false; let foundTarget = null; diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index e755496b732..85919ef8a63 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -65,7 +65,6 @@ -
+ From 22805ce56bd2bc89bb23a05d83c925771e46b2b3 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 6 Sep 2016 14:42:34 +0200 Subject: [PATCH 44/54] fix(alert_tab): fix broken alert history --- public/app/features/alerting/alert_tab_ctrl.ts | 7 +++++++ public/app/features/alerting/partials/alert_tab.html | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/public/app/features/alerting/alert_tab_ctrl.ts b/public/app/features/alerting/alert_tab_ctrl.ts index d10265a3ab7..c0cb0936f62 100644 --- a/public/app/features/alerting/alert_tab_ctrl.ts +++ b/public/app/features/alerting/alert_tab_ctrl.ts @@ -106,6 +106,13 @@ export class AlertTabCtrl { })); } + changeTabIndex(newTabIndex) { + this.subTabIndex = newTabIndex; + + if (this.subTabIndex === 2) { + this.getAlertHistory(); + } + } notificationAdded() { var model = _.findWhere(this.notifications, {name: this.addNotificationSegment.value}); diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index 85919ef8a63..db2fa978869 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -2,15 +2,15 @@