Merge branch 'alert_ui_take2' of github.com:grafana/grafana into alert_ui_take2

Conflicts:
	public/app/plugins/panel/graph/alert_tab_ctrl.ts
pull/5622/head
Torkel Ödegaard 9 years ago
commit c18017381b
  1. 2
      pkg/middleware/middleware_test.go
  2. 41
      pkg/models/alerts.go
  3. 2
      pkg/models/alerts_state.go
  4. 4
      pkg/models/alerts_test.go
  5. 72
      pkg/services/alerting/dashboard_parser.go
  6. 17
      pkg/services/alerting/executor.go
  7. 41
      pkg/services/alerting/executor_test.go
  8. 44
      pkg/services/alerting/models.go
  9. 10
      pkg/services/alerting/rule_reader.go
  10. 8
      pkg/services/alerting/scheduler.go
  11. 20
      pkg/services/sqlstore/alert_rule.go
  12. 2
      pkg/services/sqlstore/alert_rule_changes.go
  13. 2
      pkg/services/sqlstore/alert_rule_changes_test.go
  14. 55
      pkg/services/sqlstore/alert_rule_parser_test.go
  15. 14
      pkg/services/sqlstore/alert_rule_test.go
  16. 2
      pkg/services/sqlstore/alert_state.go
  17. 2
      pkg/services/sqlstore/alert_state_test.go
  18. 399
      pkg/services/sqlstore/dashboard_parser_test.go
  19. 9
      pkg/tsdb/graphite/graphite.go
  20. 4
      public/app/plugins/panel/graph/alert_tab_ctrl.ts

@ -191,9 +191,7 @@ func TestMiddlewareContext(t *testing.T) {
}
})
var createUserCmd *m.CreateUserCommand
bus.AddHandler("test", func(cmd *m.CreateUserCommand) error {
createUserCmd = cmd
cmd.Result = m.User{Id: 33}
return nil
})

@ -6,7 +6,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
)
type AlertRule struct {
type AlertRuleModel struct {
Id int64
OrgId int64
DashboardId int64
@ -21,24 +21,29 @@ type AlertRule struct {
Expression *simplejson.Json
}
func (alertRule *AlertRule) ValidToSave() bool {
return true
func (this AlertRuleModel) TableName() string {
return "alert_rule"
}
func (this *AlertRule) ContainsUpdates(other *AlertRule) bool {
result := false
func (alertRule *AlertRuleModel) ValidToSave() bool {
return alertRule.DashboardId != 0
}
func (this *AlertRuleModel) ContainsUpdates(other *AlertRuleModel) bool {
result := false
result = result || this.Name != other.Name
result = result || this.Description != other.Description
json1, err1 := this.Expression.MarshalJSON()
json2, err2 := other.Expression.MarshalJSON()
if this.Expression != nil && other.Expression != nil {
json1, err1 := this.Expression.Encode()
json2, err2 := other.Expression.Encode()
if err1 != nil || err2 != nil {
return false
}
if err1 != nil || err2 != nil {
return false
}
result = result || string(json1) != string(json2)
result = result || string(json1) != string(json2)
}
//don't compare .State! That would be insane.
@ -78,7 +83,7 @@ type SaveAlertsCommand struct {
UserId int64
OrgId int64
Alerts []*AlertRule
Alerts []*AlertRuleModel
}
type DeleteAlertCommand struct {
@ -92,23 +97,17 @@ type GetAlertsQuery struct {
DashboardId int64
PanelId int64
Result []*AlertRule
Result []*AlertRuleModel
}
type GetAllAlertsQuery struct {
Result []*AlertRule
}
type GetAlertsForExecutionQuery struct {
Timestamp int64
Result []*AlertRule
Result []*AlertRuleModel
}
type GetAlertByIdQuery struct {
Id int64
Result *AlertRule
Result *AlertRuleModel
}
type GetAlertChangesQuery struct {

@ -31,7 +31,7 @@ type UpdateAlertStateCommand struct {
NewState string `json:"newState" binding:"Required"`
Info string `json:"info"`
Result *AlertRule
Result *AlertRuleModel
}
// Queries

@ -13,13 +13,13 @@ func TestAlertingModelTest(t *testing.T) {
json1, _ := simplejson.NewJson([]byte(`{ "field": "value" }`))
json2, _ := simplejson.NewJson([]byte(`{ "field": "value" }`))
rule1 := &AlertRule{
rule1 := &AlertRuleModel{
Expression: json1,
Name: "Namn",
Description: "Description",
}
rule2 := &AlertRule{
rule2 := &AlertRuleModel{
Expression: json2,
Name: "Namn",
Description: "Description",

@ -1,14 +1,16 @@
package alerting
import (
"fmt"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
)
func ParseAlertsFromDashboard(cmd *m.SaveDashboardCommand) []*m.AlertRule {
alerts := make([]*m.AlertRule, 0)
func ParseAlertsFromDashboard(cmd *m.SaveDashboardCommand) []*m.AlertRuleModel {
alerts := make([]*m.AlertRuleModel, 0)
for _, rowObj := range cmd.Dashboard.Get("rows").MustArray() {
row := simplejson.NewFromAny(rowObj)
@ -17,7 +19,7 @@ func ParseAlertsFromDashboard(cmd *m.SaveDashboardCommand) []*m.AlertRule {
panel := simplejson.NewFromAny(panelObj)
alerting := panel.Get("alerting")
alert := &m.AlertRule{
alert := &m.AlertRuleModel{
DashboardId: cmd.Result.Id,
OrgId: cmd.Result.OrgId,
PanelId: panel.Get("id").MustInt64(),
@ -28,9 +30,8 @@ func ParseAlertsFromDashboard(cmd *m.SaveDashboardCommand) []*m.AlertRule {
log.Info("Alertrule: %v", alert.Name)
expression := alerting
valueQuery := expression.Get("valueQuery")
valueQueryRef := valueQuery.Get("queryRefId").MustString()
valueQuery := alerting.Get("query")
valueQueryRef := valueQuery.Get("refId").MustString()
for _, targetsObj := range panel.Get("targets").MustArray() {
target := simplejson.NewFromAny(targetsObj)
@ -47,7 +48,7 @@ func ParseAlertsFromDashboard(cmd *m.SaveDashboardCommand) []*m.AlertRule {
if err := bus.Dispatch(query); err == nil {
for _, ds := range query.Result {
if ds.IsDefault {
valueQuery.Set("datasourceId", ds.Id)
alerting.SetPath([]string{"query", "datasourceId"}, ds.Id)
}
}
}
@ -57,59 +58,72 @@ func ParseAlertsFromDashboard(cmd *m.SaveDashboardCommand) []*m.AlertRule {
OrgId: cmd.OrgId,
}
bus.Dispatch(query)
valueQuery.Set("datasourceId", query.Result.Id)
alerting.SetPath([]string{"query", "datasourceId"}, query.Result.Id)
}
targetQuery := target.Get("target").MustString()
if targetQuery != "" {
valueQuery.Set("query", targetQuery)
alerting.SetPath([]string{"query", "query"}, targetQuery)
}
}
}
expression.Set("valueQuery", valueQuery)
alert.Expression = expression
alertRule := &AlertRule{}
alert.Expression = alerting
ParseAlertRulesFromAlertModel(alert, alertRule)
_, err := ConvetAlertModelToAlertRule(alert)
if alert.ValidToSave() && alertRule.IsValid() {
if err == nil && alert.ValidToSave() {
alerts = append(alerts, alert)
} else {
log.Error2("Failed to parse model from expression", "error", err)
}
}
}
return alerts
}
func (rule *AlertRule) IsValid() bool {
return rule.ValueQuery.Query != ""
}
func ConvetAlertModelToAlertRule(ruleDef *m.AlertRuleModel) (*AlertRule, error) {
model := &AlertRule{}
model.Id = ruleDef.Id
model.OrgId = ruleDef.OrgId
model.Name = ruleDef.Name
model.Description = ruleDef.Description
model.State = ruleDef.State
func ParseAlertRulesFromAlertModel(ruleDef *m.AlertRule, model *AlertRule) error {
critical := ruleDef.Expression.Get("critical")
model.Critical = Level{
Operator: critical.Get("operator").MustString(),
Operator: critical.Get("op").MustString(),
Level: critical.Get("level").MustFloat64(),
}
warning := ruleDef.Expression.Get("warning")
model.Warning = Level{
Operator: warning.Get("operator").MustString(),
Operator: warning.Get("op").MustString(),
Level: warning.Get("level").MustFloat64(),
}
model.Frequency = ruleDef.Expression.Get("frequency").MustInt64()
model.Transform = ruleDef.Expression.Get("transform").Get("type").MustString()
model.TransformParams = *ruleDef.Expression.Get("transform")
query := ruleDef.Expression.Get("query")
model.Query = AlertQuery{
Query: query.Get("query").MustString(),
DatasourceId: query.Get("datasourceId").MustInt64(),
From: query.Get("from").MustString(),
To: query.Get("to").MustString(),
Aggregator: query.Get("agg").MustString(),
}
if model.Query.Query == "" {
return nil, fmt.Errorf("missing query.query")
}
valueQuery := ruleDef.Expression.Get("valueQuery")
model.ValueQuery = AlertQuery{
Query: valueQuery.Get("query").MustString(),
DatasourceId: valueQuery.Get("datasourceId").MustInt64(),
From: valueQuery.Get("From").MustInt64(),
Until: valueQuery.Get("until").MustInt64(),
Aggregator: valueQuery.Get("aggregator").MustString(),
if model.Query.DatasourceId == 0 {
return nil, fmt.Errorf("missing query.datasourceId")
}
return nil
return model, nil
}

@ -2,7 +2,6 @@ package alerting
import (
"fmt"
"strconv"
"math"
@ -14,7 +13,6 @@ import (
)
var (
resultLogFmt = "Alerting: executor %s %1.2f %s %1.2f : %v"
descriptionFmt = "Actual value: %1.2f for %s"
)
@ -102,7 +100,7 @@ func (e *ExecutorImpl) Execute(job *AlertJob, resultQueue chan *AlertResult) {
func (e *ExecutorImpl) executeQuery(job *AlertJob) (tsdb.TimeSeriesSlice, error) {
getDsInfo := &m.GetDataSourceByIdQuery{
Id: 1,
Id: job.Rule.Query.DatasourceId,
OrgId: job.Rule.OrgId,
}
@ -130,15 +128,16 @@ func (e *ExecutorImpl) executeQuery(job *AlertJob) (tsdb.TimeSeriesSlice, error)
}
func (e *ExecutorImpl) GetRequestForAlertRule(rule *AlertRule, datasource *m.DataSource) *tsdb.Request {
log.Debug2("GetRequest", "query", rule.Query.Query, "from", rule.Query.From, "datasourceId", datasource.Id)
req := &tsdb.Request{
TimeRange: tsdb.TimeRange{
From: "-" + strconv.Itoa(int(rule.ValueQuery.From)) + "s",
To: "now",
From: "-" + rule.Query.From,
To: rule.Query.To,
},
Queries: []*tsdb.Query{
{
RefId: "A",
Query: "apps.fakesite.*.counters.requests.count",
Query: rule.Query.Query,
DataSource: &tsdb.DataSourceInfo{
Id: datasource.Id,
Name: datasource.Name,
@ -156,7 +155,7 @@ func (e *ExecutorImpl) evaluateRule(rule *AlertRule, series tsdb.TimeSeriesSlice
e.log.Debug("Evaluating Alerting Rule", "seriesCount", len(series), "ruleName", rule.Name)
for _, serie := range series {
log.Debug("Evaluating series", "series", serie.Name)
e.log.Debug("Evaluating series", "series", serie.Name)
if aggregator["avg"] == nil {
continue
@ -166,7 +165,7 @@ func (e *ExecutorImpl) evaluateRule(rule *AlertRule, series tsdb.TimeSeriesSlice
var critOperartor = operators[rule.Critical.Operator]
var critResult = critOperartor(aggValue, rule.Critical.Level)
log.Trace(resultLogFmt, "Crit", serie.Name, aggValue, rule.Critical.Operator, rule.Critical.Level, critResult)
e.log.Debug("Alert execution Crit", "name", serie.Name, "aggValue", aggValue, "operator", rule.Critical.Operator, "level", rule.Critical.Level, "result", critResult)
if critResult {
return &AlertResult{
State: alertstates.Critical,
@ -177,7 +176,7 @@ func (e *ExecutorImpl) evaluateRule(rule *AlertRule, series tsdb.TimeSeriesSlice
var warnOperartor = operators[rule.Warning.Operator]
var warnResult = warnOperartor(aggValue, rule.Warning.Level)
log.Trace(resultLogFmt, "Warn", serie.Name, aggValue, rule.Warning.Operator, rule.Warning.Level, warnResult)
e.log.Debug("Alert execution Warn", "name", serie.Name, "aggValue", aggValue, "operator", rule.Warning.Operator, "level", rule.Warning.Level, "result", warnResult)
if warnResult {
return &AlertResult{
State: alertstates.Warn,

@ -35,16 +35,18 @@ func TestAlertingExecutor(t *testing.T) {
So(result.State, ShouldEqual, alertstates.Critical)
})
Convey("Show return critical since sum is above 10", func() {
rule := &AlertRule{Critical: Level{Level: 10, Operator: ">"}}
/*
Convey("Show return critical since sum is above 10", func() {
rule := &AlertRule{Critical: Level{Level: 10, Operator: ">"}}
timeSeries := []*tsdb.TimeSeries{
tsdb.NewTimeSeries("test1", [][2]float64{{9, 0}, {9, 0}}),
}
timeSeries := []*tsdb.TimeSeries{
tsdb.NewTimeSeries("test1", [][2]float64{{9, 0}, {9, 0}}),
}
result := executor.evaluateRule(rule, timeSeries)
So(result.State, ShouldEqual, alertstates.Critical)
})
result := executor.evaluateRule(rule, timeSeries)
So(result.State, ShouldEqual, alertstates.Critical)
})
*/
Convey("Show return ok since avg is below 10", func() {
rule := &AlertRule{Critical: Level{Level: 10, Operator: ">"}}
@ -67,17 +69,18 @@ func TestAlertingExecutor(t *testing.T) {
result := executor.evaluateRule(rule, timeSeries)
So(result.State, ShouldEqual, alertstates.Ok)
})
Convey("Show return ok since max is above 10", func() {
rule := &AlertRule{Critical: Level{Level: 10, Operator: ">"}}
timeSeries := []*tsdb.TimeSeries{
tsdb.NewTimeSeries("test1", [][2]float64{{1, 0}, {11, 0}}),
}
result := executor.evaluateRule(rule, timeSeries)
So(result.State, ShouldEqual, alertstates.Critical)
})
/*
Convey("Show return ok since max is above 10", func() {
rule := &AlertRule{Critical: Level{Level: 10, Operator: ">"}}
timeSeries := []*tsdb.TimeSeries{
tsdb.NewTimeSeries("test1", [][2]float64{{1, 0}, {11, 0}}),
}
result := executor.evaluateRule(rule, timeSeries)
So(result.State, ShouldEqual, alertstates.Critical)
})
*/
})
Convey("muliple time series", func() {

@ -1,5 +1,10 @@
package alerting
import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
)
type AlertJob struct {
Offset int64
Delay bool
@ -18,26 +23,23 @@ type AlertResult struct {
}
type AlertRule struct {
Id int64
OrgId int64
DashboardId int64
PanelId int64
//WarnLevel float64
//CritLevel float64
//WarnOperator string
//CritOperator string
Frequency int64
Name string
Description string
State string
Warning Level
Critical Level
Id int64
OrgId int64
DashboardId int64
PanelId int64
Frequency int64
Name string
Description string
State string
Warning Level
Critical Level
Query AlertQuery
Transform string
TransformParams simplejson.Json
}
ValueQuery AlertQuery
EvalFunc string
EvalQuery AlertQuery
EvalParam string
type Transformer interface {
Transform(tsdb tsdb.TimeSeriesSlice) float64
}
type Level struct {
@ -49,6 +51,6 @@ type AlertQuery struct {
Query string
DatasourceId int64
Aggregator string
From int64
Until int64
From string
To string
}

@ -49,15 +49,7 @@ func (arr *AlertRuleReader) Fetch() []*AlertRule {
res := make([]*AlertRule, len(cmd.Result))
for i, ruleDef := range cmd.Result {
model := &AlertRule{}
model.Id = ruleDef.Id
model.OrgId = ruleDef.OrgId
model.Name = ruleDef.Name
model.Description = ruleDef.Description
model.State = ruleDef.State
ParseAlertRulesFromAlertModel(ruleDef, model)
model, _ := ConvetAlertModelToAlertRule(ruleDef)
res[i] = model
}

@ -8,16 +8,18 @@ import (
type SchedulerImpl struct {
jobs map[int64]*AlertJob
log log.Logger
}
func NewScheduler() Scheduler {
return &SchedulerImpl{
jobs: make(map[int64]*AlertJob, 0),
log: log.New("alerting.scheduler"),
}
}
func (s *SchedulerImpl) Update(rules []*AlertRule) {
log.Debug("Scheduler: Update()")
s.log.Debug("Scheduler: Update")
jobs := make(map[int64]*AlertJob, 0)
@ -38,7 +40,7 @@ func (s *SchedulerImpl) Update(rules []*AlertRule) {
jobs[rule.Id] = job
}
log.Debug("Scheduler: Selected %d jobs", len(jobs))
s.log.Debug("Scheduler: Selected new jobs", "job count", len(jobs))
s.jobs = jobs
}
@ -47,7 +49,7 @@ func (s *SchedulerImpl) Tick(tickTime time.Time, execQueue chan *AlertJob) {
for _, job := range s.jobs {
if now%job.Rule.Frequency == 0 && job.Running == false {
log.Trace("Scheduler: Putting job on to exec queue: %s", job.Rule.Name)
s.log.Debug("Scheduler: Putting job on to exec queue", "name", job.Rule.Name)
execQueue <- job
}
}

@ -64,7 +64,7 @@ func HeartBeat(query *m.HeartBeatCommand) error {
*/
func GetAlertById(query *m.GetAlertByIdQuery) error {
alert := m.AlertRule{}
alert := m.AlertRuleModel{}
has, err := x.Id(query.Id).Get(&alert)
if !has {
return fmt.Errorf("could not find alert")
@ -78,7 +78,7 @@ func GetAlertById(query *m.GetAlertByIdQuery) error {
}
func GetAllAlertQueryHandler(query *m.GetAllAlertsQuery) error {
var alerts []*m.AlertRule
var alerts []*m.AlertRuleModel
err := x.Sql("select * from alert_rule").Find(&alerts)
if err != nil {
return err
@ -131,7 +131,7 @@ func HandleAlertsQuery(query *m.GetAlertsQuery) error {
sql.WriteString(")")
}
alerts := make([]*m.AlertRule, 0)
alerts := make([]*m.AlertRuleModel, 0)
if err := x.Sql(sql.String(), params...).Find(&alerts); err != nil {
return err
}
@ -141,7 +141,7 @@ func HandleAlertsQuery(query *m.GetAlertsQuery) error {
}
func DeleteAlertDefinition(dashboardId int64, sess *xorm.Session) error {
alerts := make([]*m.AlertRule, 0)
alerts := make([]*m.AlertRuleModel, 0)
sess.Where("dashboard_id = ?", dashboardId).Find(&alerts)
for _, alert := range alerts {
@ -172,10 +172,10 @@ func SaveAlerts(cmd *m.SaveAlertsCommand) error {
})
}
func upsertAlerts(alerts []*m.AlertRule, posted []*m.AlertRule, sess *xorm.Session) error {
func upsertAlerts(alerts []*m.AlertRuleModel, posted []*m.AlertRuleModel, sess *xorm.Session) error {
for _, alert := range posted {
update := false
var alertToUpdate *m.AlertRule
var alertToUpdate *m.AlertRuleModel
for _, k := range alerts {
if alert.PanelId == k.PanelId {
@ -212,7 +212,7 @@ func upsertAlerts(alerts []*m.AlertRule, posted []*m.AlertRule, sess *xorm.Sessi
return nil
}
func deleteMissingAlerts(alerts []*m.AlertRule, posted []*m.AlertRule, sess *xorm.Session) error {
func deleteMissingAlerts(alerts []*m.AlertRuleModel, posted []*m.AlertRuleModel, sess *xorm.Session) error {
for _, missingAlert := range alerts {
missing := true
@ -238,12 +238,12 @@ func deleteMissingAlerts(alerts []*m.AlertRule, posted []*m.AlertRule, sess *xor
return nil
}
func GetAlertsByDashboardId2(dashboardId int64, sess *xorm.Session) ([]*m.AlertRule, error) {
alerts := make([]*m.AlertRule, 0)
func GetAlertsByDashboardId2(dashboardId int64, sess *xorm.Session) ([]*m.AlertRuleModel, error) {
alerts := make([]*m.AlertRuleModel, 0)
err := sess.Where("dashboard_id = ?", dashboardId).Find(&alerts)
if err != nil {
return []*m.AlertRule{}, err
return []*m.AlertRuleModel{}, err
}
return alerts, nil

@ -48,7 +48,7 @@ func GetAlertRuleChanges(query *m.GetAlertChangesQuery) error {
return nil
}
func SaveAlertChange(change string, alert *m.AlertRule, sess *xorm.Session) error {
func SaveAlertChange(change string, alert *m.AlertRuleModel, sess *xorm.Session) error {
_, err := sess.Insert(&m.AlertRuleChange{
OrgId: alert.OrgId,
Type: change,

@ -20,7 +20,7 @@ func TestAlertRuleChangesDataAccess(t *testing.T) {
var err error
Convey("When dashboard is removed", func() {
items := []*m.AlertRule{
items := []*m.AlertRuleModel{
{
PanelId: 1,
DashboardId: testDash.Id,

@ -0,0 +1,55 @@
package sqlstore
import (
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
. "github.com/smartystreets/goconvey/convey"
)
func TestAlertRuleModelParsing(t *testing.T) {
Convey("Parsing alertRule from expression", t, func() {
alertRuleDAO := &m.AlertRuleModel{}
json, _ := simplejson.NewJson([]byte(`
{
"frequency": 10,
"warning": {
"op": ">",
"level": 10
},
"critical": {
"op": ">",
"level": 20
},
"query": {
"refId": "A",
"from": "5m",
"to": "now",
"datasourceId": 1,
"query": "aliasByNode(statsd.fakesite.counters.session_start.*.count, 4)"
},
"transform": {
"type": "aggregation",
"method": "avg"
}
}`))
alertRuleDAO.Name = "Test"
alertRuleDAO.Expression = json
rule, _ := alerting.ConvetAlertModelToAlertRule(alertRuleDAO)
Convey("Confirm that all properties are set", func() {
So(rule.Query.Query, ShouldEqual, "aliasByNode(statsd.fakesite.counters.session_start.*.count, 4)")
So(rule.Query.From, ShouldEqual, "5m")
So(rule.Query.To, ShouldEqual, "now")
So(rule.Query.DatasourceId, ShouldEqual, 1)
So(rule.Warning.Level, ShouldEqual, 10)
So(rule.Warning.Operator, ShouldEqual, ">")
So(rule.Critical.Level, ShouldEqual, 20)
So(rule.Critical.Operator, ShouldEqual, ">")
})
})
}

@ -3,6 +3,7 @@ package sqlstore
import (
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey"
)
@ -13,13 +14,14 @@ func TestAlertingDataAccess(t *testing.T) {
testDash := insertTestDashboard("dashboard with alerts", 1, "alert")
items := []*m.AlertRule{
items := []*m.AlertRuleModel{
{
PanelId: 1,
DashboardId: testDash.Id,
OrgId: testDash.OrgId,
Name: "Alerting title",
Description: "Alerting description",
Expression: simplejson.New(),
},
}
@ -54,8 +56,7 @@ func TestAlertingDataAccess(t *testing.T) {
Convey("Alerts with same dashboard id and panel id should update", func() {
modifiedItems := items
modifiedItems[0].Name = "New name"
//modifiedItems[0].State = "ALERT"
modifiedItems[0].Name = "Name"
modifiedCmd := m.SaveAlertsCommand{
DashboardId: testDash.Id,
@ -95,24 +96,27 @@ func TestAlertingDataAccess(t *testing.T) {
})
Convey("Multiple alerts per dashboard", func() {
multipleItems := []*m.AlertRule{
multipleItems := []*m.AlertRuleModel{
{
DashboardId: testDash.Id,
PanelId: 1,
Name: "1",
OrgId: 1,
Expression: simplejson.New(),
},
{
DashboardId: testDash.Id,
PanelId: 2,
Name: "2",
OrgId: 1,
Expression: simplejson.New(),
},
{
DashboardId: testDash.Id,
PanelId: 3,
Name: "3",
OrgId: 1,
Expression: simplejson.New(),
},
}
@ -157,7 +161,7 @@ func TestAlertingDataAccess(t *testing.T) {
})
Convey("When dashboard is removed", func() {
items := []*m.AlertRule{
items := []*m.AlertRuleModel{
{
PanelId: 1,
DashboardId: testDash.Id,

@ -19,7 +19,7 @@ func SetNewAlertState(cmd *m.UpdateAlertStateCommand) error {
return fmt.Errorf("new state is invalid")
}
alert := m.AlertRule{}
alert := m.AlertRuleModel{}
has, err := sess.Id(cmd.AlertId).Get(&alert)
if !has {
return fmt.Errorf("Could not find alert")

@ -13,7 +13,7 @@ func TestAlertingStateAccess(t *testing.T) {
testDash := insertTestDashboard("dashboard with alerts", 1, "alert")
items := []*m.AlertRule{
items := []*m.AlertRuleModel{
{
PanelId: 1,
DashboardId: testDash.Id,

@ -9,34 +9,25 @@ import (
. "github.com/smartystreets/goconvey/convey"
)
func TestAlertModel(t *testing.T) {
func TestAlertModelParsing(t *testing.T) {
Convey("Parsing alerts from dashboard", t, func() {
json := `{
Convey("Parsing alert info from json", t, func() {
Convey("Parsing and validating alerts from dashboards", func() {
json := `{
"id": 57,
"title": "Graphite 4",
"originalTitle": "Graphite 4",
"tags": [
"graphite"
],
"style": "dark",
"timezone": "browser",
"editable": true,
"hideControls": false,
"sharedCrosshair": false,
"rows": [
{
"collapse": false,
"editable": true,
"height": "250px",
"panels": [
{
"title": "Active desktop users",
"error": false,
"span": 6,
"editable": true,
"type": "graph",
"isNew": true,
"id": 3,
"targets": [
{
@ -45,111 +36,31 @@ func TestAlertModel(t *testing.T) {
}
],
"datasource": null,
"renderer": "flot",
"yaxes": [
{
"label": null,
"show": true,
"logBase": 1,
"min": null,
"max": null,
"format": "short"
},
{
"label": null,
"show": true,
"logBase": 1,
"min": null,
"max": null,
"format": "short"
}
],
"xaxis": {
"show": true
},
"grid": {
"threshold1": null,
"threshold2": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"lines": true,
"fill": 1,
"linewidth": 2,
"points": false,
"pointradius": 5,
"bars": false,
"stack": false,
"percentage": false,
"legend": {
"show": true,
"values": false,
"min": false,
"max": false,
"current": false,
"total": false,
"avg": false
},
"nullPointMode": "connected",
"steppedLine": false,
"tooltip": {
"value_type": "cumulative",
"shared": true,
"msResolution": false
},
"timeFrom": null,
"timeShift": null,
"aliasColors": {},
"seriesOverrides": [],
"alerting": {
"frequency": 10,
"warning": {
"op": ">",
"level": 10
},
"name": "Alerting Panel Title alert",
"description": "description",
"critical": {
"op": ">",
"level": 20
"level": 20,
"op": ">"
},
"function": "static",
"valueQuery": {
"queryRefId": "A",
"frequency": 10,
"query": {
"from": "5m",
"to": "now",
"agg": "avg",
"params": [
"#A",
"5m",
"now",
"avg"
]
"refId": "A",
"to": "now"
},
"evalQuery": {
"queryRefId": "A",
"from": "5m",
"to": "now",
"agg": "avg",
"params": [
"#A",
"5m",
"now",
"avg"
]
"transform": {
"method": "avg",
"name": "aggregation"
},
"evalStringParam1": "",
"name": "Alerting Panel Title alert"
},
"links": []
"warning": {
"level": 10,
"op": ">"
}
}
},
{
"title": "Active mobile users",
"error": false,
"span": 6,
"editable": true,
"type": "graph",
"isNew": true,
"id": 4,
"targets": [
{
@ -158,103 +69,28 @@ func TestAlertModel(t *testing.T) {
}
],
"datasource": "graphite2",
"renderer": "flot",
"yaxes": [
{
"label": null,
"show": true,
"logBase": 1,
"min": null,
"max": null,
"format": "short"
},
{
"label": null,
"show": true,
"logBase": 1,
"min": null,
"max": null,
"format": "short"
}
],
"xaxis": {
"show": true
},
"grid": {
"threshold1": null,
"threshold2": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"lines": true,
"fill": 1,
"linewidth": 2,
"points": false,
"pointradius": 5,
"bars": false,
"stack": false,
"percentage": false,
"legend": {
"show": true,
"values": false,
"min": false,
"max": false,
"current": false,
"total": false,
"avg": false
},
"nullPointMode": "connected",
"steppedLine": false,
"tooltip": {
"value_type": "cumulative",
"shared": true,
"msResolution": false
},
"timeFrom": null,
"timeShift": null,
"aliasColors": {
"mobile": "#EAB839"
},
"seriesOverrides": [],
"alerting": {
"frequency": 10,
"warning": {
"op": ">",
"level": 10
},
"name": "Alerting Panel Title alert",
"description": "description",
"critical": {
"op": ">",
"level": 20
"level": 20,
"op": ">"
},
"function": "static",
"valueQuery": {
"queryRefId": "A",
"frequency": 10,
"query": {
"from": "5m",
"to": "now",
"agg": "avg",
"params": [
"#A",
"5m",
"now",
"avg"
]
"refId": "A",
"to": "now"
},
"evalQuery": {
"queryRefId": "A",
"from": "5m",
"to": "now",
"agg": "avg",
"params": [
"#A",
"5m",
"now",
"avg"
]
"transform": {
"method": "avg",
"name": "aggregation"
},
"evalStringParam1": "",
"name": "Alerting Panel Title alert"
},
"links": []
"warning": {
"level": 10,
"op": ">"
}
}
}
],
"title": "Row"
@ -265,41 +101,8 @@ func TestAlertModel(t *testing.T) {
"height": "250px",
"panels": [
{
"columns": [],
"datasource": "InfluxDB",
"editable": true,
"error": false,
"fontSize": "100%",
"id": 2,
"isNew": true,
"pageSize": null,
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"span": 6,
"styles": [
{
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"pattern": "Time",
"type": "date"
},
{
"colorMode": null,
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "number",
"unit": "short"
}
],
"targets": [
{
"dsType": "influxdb",
@ -342,104 +145,60 @@ func TestAlertModel(t *testing.T) {
],
"title": "Broken influxdb panel",
"transform": "table",
"type": "table",
"links": []
"type": "table"
}
],
"title": "New row"
}
],
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {
"now": true,
"nowDelay": "5m",
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d",
"7d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"templating": {
"list": []
},
"annotations": {
"list": []
},
"schemaVersion": 12,
"version": 16,
"links": []
}`
dashboardJson, _ := simplejson.NewJson([]byte(json))
cmd := &m.SaveDashboardCommand{
Dashboard: dashboardJson,
UserId: 1,
OrgId: 1,
Overwrite: true,
Result: &m.Dashboard{
Id: 1,
},
}
]
InitTestDB(t)
}`
dashboardJSON, _ := simplejson.NewJson([]byte(json))
cmd := &m.SaveDashboardCommand{
Dashboard: dashboardJSON,
UserId: 1,
OrgId: 1,
Overwrite: true,
Result: &m.Dashboard{
Id: 1,
},
}
AddDataSource(&m.AddDataSourceCommand{
Name: "graphite2",
OrgId: 1,
Type: m.DS_INFLUXDB,
Access: m.DS_ACCESS_DIRECT,
Url: "http://test",
IsDefault: false,
Database: "site",
})
InitTestDB(t)
AddDataSource(&m.AddDataSourceCommand{
Name: "InfluxDB",
OrgId: 1,
Type: m.DS_GRAPHITE,
Access: m.DS_ACCESS_DIRECT,
Url: "http://test",
IsDefault: true,
})
AddDataSource(&m.AddDataSourceCommand{
Name: "graphite2",
OrgId: 1,
Type: m.DS_INFLUXDB,
Access: m.DS_ACCESS_DIRECT,
Url: "http://test",
IsDefault: false,
Database: "site",
})
alerts := alerting.ParseAlertsFromDashboard(cmd)
AddDataSource(&m.AddDataSourceCommand{
Name: "InfluxDB",
OrgId: 1,
Type: m.DS_GRAPHITE,
Access: m.DS_ACCESS_DIRECT,
Url: "http://test",
IsDefault: true,
})
Convey("all properties have been set", func() {
So(alerts, ShouldNotBeEmpty)
So(len(alerts), ShouldEqual, 2)
alerts := alerting.ParseAlertsFromDashboard(cmd)
for _, v := range alerts {
So(v.DashboardId, ShouldEqual, 1)
So(v.PanelId, ShouldNotEqual, 0)
Convey("all properties have been set", func() {
So(alerts, ShouldNotBeEmpty)
So(len(alerts), ShouldEqual, 2)
So(v.Name, ShouldNotBeEmpty)
So(v.Description, ShouldNotBeEmpty)
for _, v := range alerts {
So(v.DashboardId, ShouldEqual, 1)
So(v.PanelId, ShouldNotEqual, 0)
expr := simplejson.NewFromAny(v.Expression)
So(expr.Get("valueQuery").Get("query").MustString(), ShouldNotEqual, "")
So(expr.Get("valueQuery").Get("datsourceId").MustInt64(), ShouldNotEqual, 0)
}
So(v.Name, ShouldNotBeEmpty)
So(v.Description, ShouldNotBeEmpty)
}
})
})
})
}

@ -5,6 +5,7 @@ import (
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
"github.com/grafana/grafana/pkg/log"
@ -30,7 +31,7 @@ func (e *GraphiteExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryC
result := &tsdb.BatchResult{}
params := url.Values{
"from": []string{context.TimeRange.From},
"from": []string{formatTimeRange(context.TimeRange.From)},
"until": []string{context.TimeRange.To},
"format": []string{"json"},
"maxDataPoints": []string{"500"},
@ -59,7 +60,7 @@ func (e *GraphiteExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryC
var data []TargetResponseDTO
err = json.Unmarshal(body, &data)
if err != nil {
glog.Info("Failed to unmarshal graphite response", "error", err)
glog.Info("Failed to unmarshal graphite response", "error", err, "body", string(body))
result.Error = err
return result
}
@ -76,3 +77,7 @@ func (e *GraphiteExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryC
result.QueryResults["A"] = queryRes
return result
}
func formatTimeRange(input string) string {
return strings.Replace(strings.Replace(input, "m", "min", -1), "M", "mon", -1)
}

@ -60,8 +60,8 @@ export class AlertTabCtrl {
},
transform: {
type: 'aggregation',
method: 'avg',
},
method: 'avg'
}
};
/** @ngInject */

Loading…
Cancel
Save