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

Conflicts:
	public/app/plugins/panel/graph/alert_tab_ctrl.ts
This commit is contained in:
Torkel Ödegaard 2016-06-10 14:52:17 +02:00
commit c18017381b
20 changed files with 295 additions and 463 deletions

View File

@ -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
})

View File

@ -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,25 +21,30 @@ 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.
return result
@ -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 {

View File

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

View File

@ -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",

View File

@ -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
alert.Expression = alerting
alertRule := &AlertRule{}
_, err := ConvetAlertModelToAlertRule(alert)
ParseAlertRulesFromAlertModel(alert, alertRule)
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")
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(),
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(),
}
return nil
if model.Query.Query == "" {
return nil, fmt.Errorf("missing query.query")
}
if model.Query.DatasourceId == 0 {
return nil, fmt.Errorf("missing query.datasourceId")
}
return model, nil
}

View File

@ -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,

View File

@ -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: ">"}}
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}}),
}
timeSeries := []*tsdb.TimeSeries{
tsdb.NewTimeSeries("test1", [][2]float64{{1, 0}, {11, 0}}),
}
result := executor.evaluateRule(rule, timeSeries)
So(result.State, ShouldEqual, alertstates.Critical)
})
result := executor.evaluateRule(rule, timeSeries)
So(result.State, ShouldEqual, alertstates.Critical)
})
*/
})
Convey("muliple time series", func() {

View File

@ -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
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
}
Warning Level
Critical Level
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
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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, ">")
})
})
}

View File

@ -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,

View File

@ -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")

View File

@ -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,

View File

@ -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)
AddDataSource(&m.AddDataSourceCommand{
Name: "graphite2",
OrgId: 1,
Type: m.DS_INFLUXDB,
Access: m.DS_ACCESS_DIRECT,
Url: "http://test",
IsDefault: false,
Database: "site",
})
AddDataSource(&m.AddDataSourceCommand{
Name: "InfluxDB",
OrgId: 1,
Type: m.DS_GRAPHITE,
Access: m.DS_ACCESS_DIRECT,
Url: "http://test",
IsDefault: true,
})
alerts := alerting.ParseAlertsFromDashboard(cmd)
Convey("all properties have been set", func() {
So(alerts, ShouldNotBeEmpty)
So(len(alerts), ShouldEqual, 2)
for _, v := range alerts {
So(v.DashboardId, ShouldEqual, 1)
So(v.PanelId, ShouldNotEqual, 0)
So(v.Name, ShouldNotBeEmpty)
So(v.Description, ShouldNotBeEmpty)
expr := simplejson.NewFromAny(v.Expression)
So(expr.Get("valueQuery").Get("query").MustString(), ShouldNotEqual, "")
So(expr.Get("valueQuery").Get("datsourceId").MustInt64(), ShouldNotEqual, 0)
dashboardJSON, _ := simplejson.NewJson([]byte(json))
cmd := &m.SaveDashboardCommand{
Dashboard: dashboardJSON,
UserId: 1,
OrgId: 1,
Overwrite: true,
Result: &m.Dashboard{
Id: 1,
},
}
InitTestDB(t)
AddDataSource(&m.AddDataSourceCommand{
Name: "graphite2",
OrgId: 1,
Type: m.DS_INFLUXDB,
Access: m.DS_ACCESS_DIRECT,
Url: "http://test",
IsDefault: false,
Database: "site",
})
AddDataSource(&m.AddDataSourceCommand{
Name: "InfluxDB",
OrgId: 1,
Type: m.DS_GRAPHITE,
Access: m.DS_ACCESS_DIRECT,
Url: "http://test",
IsDefault: true,
})
alerts := alerting.ParseAlertsFromDashboard(cmd)
Convey("all properties have been set", func() {
So(alerts, ShouldNotBeEmpty)
So(len(alerts), ShouldEqual, 2)
for _, v := range alerts {
So(v.DashboardId, ShouldEqual, 1)
So(v.PanelId, ShouldNotEqual, 0)
So(v.Name, ShouldNotBeEmpty)
So(v.Description, ShouldNotBeEmpty)
}
})
})
})
}

View File

@ -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)
}

View File

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