feat(alerting): more model changes

This commit is contained in:
Torkel Ödegaard 2016-06-11 10:54:24 +02:00
parent a362984c57
commit 382f396247
9 changed files with 97 additions and 98 deletions

View File

@ -18,20 +18,20 @@ func init() {
}
func updateDashboardAlerts(cmd *UpdateDashboardAlertsCommand) error {
saveRulesCmd := m.SaveAlertsCommand{
saveAlerts := m.SaveAlertsCommand{
OrgId: cmd.OrgId,
UserId: cmd.UserId,
}
extractor := NewAlertRuleExtractor(cmd.Dashboard, cmd.OrgId)
extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId)
rules, err := extractor.GetRuleModels()
alerts, err := extractor.GetRuleModels()
if err != nil {
return err
}
saveRulesCmd.Alerts = rules
if bus.Dispatch(&saveRulesCmd); err != nil {
saveAlerts.Alerts = alerts
if bus.Dispatch(&saveAlerts); err != nil {
return err
}

View File

@ -9,21 +9,21 @@ import (
m "github.com/grafana/grafana/pkg/models"
)
type AlertRuleExtractor struct {
type DashAlertExtractor struct {
Dash *m.Dashboard
OrgId int64
log log.Logger
}
func NewAlertRuleExtractor(dash *m.Dashboard, orgId int64) *AlertRuleExtractor {
return &AlertRuleExtractor{
func NewDashAlertExtractor(dash *m.Dashboard, orgId int64) *DashAlertExtractor {
return &DashAlertExtractor{
Dash: dash,
OrgId: orgId,
log: log.New("alerting.extractor"),
}
}
func (e *AlertRuleExtractor) lookupDatasourceId(dsName string) (int64, error) {
func (e *DashAlertExtractor) lookupDatasourceId(dsName string) (int64, error) {
if dsName == "" {
query := &m.GetDataSourcesQuery{OrgId: e.OrgId}
if err := bus.Dispatch(query); err != nil {
@ -47,36 +47,36 @@ func (e *AlertRuleExtractor) lookupDatasourceId(dsName string) (int64, error) {
return 0, errors.New("Could not find datasource id for " + dsName)
}
func (e *AlertRuleExtractor) GetRuleModels() (m.AlertRules, error) {
func (e *DashAlertExtractor) GetRuleModels() ([]*m.Alert, error) {
rules := make(m.AlertRules, 0)
alerts := make([]*m.Alert, 0)
for _, rowObj := range e.Dash.Data.Get("rows").MustArray() {
row := simplejson.NewFromAny(rowObj)
for _, panelObj := range row.Get("panels").MustArray() {
panel := simplejson.NewFromAny(panelObj)
jsonRule := panel.Get("alerting")
jsonAlert := panel.Get("alert")
// check if marked for deletion
deleted := jsonRule.Get("deleted").MustBool()
deleted := jsonAlert.Get("deleted").MustBool()
if deleted {
e.log.Info("Deleted alert rule found")
continue
}
ruleModel := &m.Alert{
alert := &m.Alert{
DashboardId: e.Dash.Id,
OrgId: e.OrgId,
PanelId: panel.Get("id").MustInt64(),
Id: jsonRule.Get("id").MustInt64(),
Name: jsonRule.Get("name").MustString(),
Scheduler: jsonRule.Get("scheduler").MustInt64(),
Enabled: jsonRule.Get("enabled").MustBool(),
Description: jsonRule.Get("description").MustString(),
Id: jsonAlert.Get("id").MustInt64(),
Name: jsonAlert.Get("name").MustString(),
Scheduler: jsonAlert.Get("scheduler").MustInt64(),
Enabled: jsonAlert.Get("enabled").MustBool(),
Description: jsonAlert.Get("description").MustString(),
}
valueQuery := jsonRule.Get("query")
valueQuery := jsonAlert.Get("query")
valueQueryRef := valueQuery.Get("refId").MustString()
for _, targetsObj := range panel.Get("targets").MustArray() {
target := simplejson.NewFromAny(targetsObj)
@ -97,24 +97,24 @@ func (e *AlertRuleExtractor) GetRuleModels() (m.AlertRules, error) {
targetQuery := target.Get("target").MustString()
if targetQuery != "" {
jsonRule.SetPath([]string{"query", "query"}, targetQuery)
jsonAlert.SetPath([]string{"query", "query"}, targetQuery)
}
}
}
ruleModel.Expression = jsonRule
alert.Expression = jsonAlert
// validate
_, err := NewAlertRuleFromDBModel(ruleModel)
if err == nil && ruleModel.ValidToSave() {
rules = append(rules, ruleModel)
_, err := NewAlertRuleFromDBModel(alert)
if err == nil && alert.ValidToSave() {
alerts = append(alerts, alert)
} else {
e.log.Error("Failed to extract alert rules from dashboard", "error", err)
return nil, errors.New("Failed to extract alert rules from dashboard")
e.log.Error("Failed to extract alerts from dashboard", "error", err)
return nil, errors.New("Failed to extract alerts from dashboard")
}
}
}
return rules, nil
return alerts, nil
}

View File

@ -36,7 +36,7 @@ func TestAlertRuleExtraction(t *testing.T) {
}
],
"datasource": null,
"alerting": {
"alert": {
"name": "name1",
"description": "desc1",
"scheduler": 1,
@ -71,7 +71,7 @@ func TestAlertRuleExtraction(t *testing.T) {
}
],
"datasource": "graphite2",
"alerting": {
"alert": {
"name": "name2",
"description": "desc2",
"scheduler": 0,
@ -150,7 +150,7 @@ func TestAlertRuleExtraction(t *testing.T) {
"title": "Broken influxdb panel",
"transform": "table",
"type": "table",
"alerting": {
"alert": {
"deleted": true
}
}
@ -164,7 +164,7 @@ func TestAlertRuleExtraction(t *testing.T) {
So(err, ShouldBeNil)
dash := m.NewDashboardFromJson(dashJson)
extractor := NewAlertRuleExtractor(dash, 1)
extractor := NewDashAlertExtractor(dash, 1)
// mock data
defaultDs := &m.DataSource{Id: 12, OrgId: 2, Name: "I am default", IsDefault: true}

View File

@ -79,7 +79,7 @@ func GetAlertById(query *m.GetAlertByIdQuery) error {
func GetAllAlertQueryHandler(query *m.GetAllAlertsQuery) error {
var alerts []*m.Alert
err := x.Sql("select * from alert_rule").Find(&alerts)
err := x.Sql("select * from alert").Find(&alerts)
if err != nil {
return err
}
@ -90,7 +90,7 @@ func GetAllAlertQueryHandler(query *m.GetAllAlertsQuery) error {
func DeleteAlertById(cmd *m.DeleteAlertCommand) error {
return inTransaction(func(sess *xorm.Session) error {
if _, err := sess.Exec("DELETE FROM alert_rule WHERE id = ?", cmd.AlertId); err != nil {
if _, err := sess.Exec("DELETE FROM alert WHERE id = ?", cmd.AlertId); err != nil {
return err
}
@ -103,7 +103,7 @@ func HandleAlertsQuery(query *m.GetAlertsQuery) error {
params := make([]interface{}, 0)
sql.WriteString(`SELECT *
from alert_rule
from alert
`)
sql.WriteString(`WHERE org_id = ?`)
@ -141,15 +141,17 @@ func HandleAlertsQuery(query *m.GetAlertsQuery) error {
}
func DeleteAlertDefinition(dashboardId int64, sess *xorm.Session) error {
alerts := make(m.Alerts, 0)
alerts := make([]*m.Alert, 0)
sess.Where("dashboard_id = ?", dashboardId).Find(&alerts)
for _, alert := range alerts {
_, err := sess.Exec("DELETE FROM alert_rule WHERE id = ? ", alert.Id)
_, err := sess.Exec("DELETE FROM alert WHERE id = ? ", alert.Id)
if err != nil {
return err
}
sqlog.Debug("Alert deleted (due to dashboard deletion)", "name", alert.Name, "id", alert.Id)
if err := SaveAlertChange("DELETED", alert, sess); err != nil {
return err
}
@ -194,6 +196,7 @@ func upsertAlerts(alerts []*m.Alert, posted []*m.Alert, sess *xorm.Session) erro
return err
}
sqlog.Debug("Alert updated", "name", alert.Name, "id", alert.Id)
SaveAlertChange("UPDATED", alert, sess)
}
@ -205,6 +208,8 @@ func upsertAlerts(alerts []*m.Alert, posted []*m.Alert, sess *xorm.Session) erro
if err != nil {
return err
}
sqlog.Debug("Alert inserted", "name", alert.Name, "id", alert.Id)
SaveAlertChange("CREATED", alert, sess)
}
}
@ -223,11 +228,13 @@ func deleteMissingAlerts(alerts []*m.Alert, posted []*m.Alert, sess *xorm.Sessio
}
if missing {
_, err := sess.Exec("DELETE FROM alert_rule WHERE id = ?", missingAlert.Id)
_, err := sess.Exec("DELETE FROM alert WHERE id = ?", missingAlert.Id)
if err != nil {
return err
}
sqlog.Debug("Alert deleted", "name", missingAlert.Name, "id", missingAlert.Id)
err = SaveAlertChange("DELETED", missingAlert, sess)
if err != nil {
return err

View File

@ -39,7 +39,7 @@ func GetAlertRuleChanges(query *m.GetAlertChangesQuery) error {
params = append(params, query.Limit)
}
alertChanges := make([]*m.AlertRuleChange, 0)
alertChanges := make([]*m.AlertChange, 0)
if err := x.Sql(sql.String(), params...).Find(&alertChanges); err != nil {
return err
}
@ -49,7 +49,7 @@ func GetAlertRuleChanges(query *m.GetAlertChangesQuery) error {
}
func SaveAlertChange(change string, alert *m.Alert, sess *xorm.Session) error {
_, err := sess.Insert(&m.AlertRuleChange{
_, err := sess.Insert(&m.AlertChange{
OrgId: alert.OrgId,
Type: change,
Created: time.Now(),

View File

@ -7,7 +7,7 @@ import (
func addAlertMigrations(mg *Migrator) {
alertV1 := Table{
Name: "alert_rule",
Name: "alert",
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "dashboard_id", Type: DB_BigInt, Nullable: false},
@ -26,10 +26,10 @@ func addAlertMigrations(mg *Migrator) {
}
// create table
mg.AddMigration("create alert_rule table v2", NewAddTableMigration(alertV1))
mg.AddMigration("create alert table v1", NewAddTableMigration(alertV1))
alert_changes := Table{
Name: "alert_rule_change",
Name: "alert_change",
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "alert_id", Type: DB_BigInt, Nullable: false},
@ -39,7 +39,7 @@ func addAlertMigrations(mg *Migrator) {
},
}
mg.AddMigration("create alert_rules_updates table v1", NewAddTableMigration(alert_changes))
mg.AddMigration("create alert_change table v1", NewAddTableMigration(alert_changes))
alert_state_log := Table{
Name: "alert_state",

View File

@ -107,7 +107,7 @@ func (mg *Migrator) Start() error {
}
func (mg *Migrator) exec(m Migration) error {
log.Info("Executing migration", "id", m.Id())
mg.Logger.Info("Executing migration", "id", m.Id())
err := mg.inTransaction(func(sess *xorm.Session) error {

View File

@ -22,7 +22,6 @@ var alertQueryDef = new QueryPartDef({
export class AlertTabCtrl {
panel: any;
panelCtrl: any;
alerting: any;
metricTargets = [{ refId: '- select query -' } ];
schedulers = [{text: 'Grafana', value: 1}, {text: 'External', value: 0}];
transforms = [
@ -36,7 +35,7 @@ export class AlertTabCtrl {
},
];
aggregators = ['avg', 'sum', 'min', 'max', 'last'];
rule: any;
alert: any;
query: any;
queryParams: any;
transformDef: any;
@ -71,33 +70,37 @@ export class AlertTabCtrl {
$scope.ctrl = this;
this.metricTargets = this.panel.targets.map(val => val);
this.rule = this.panel.alerting = this.panel.alerting || {};
this.initAlertModel();
}
initAlertModel() {
this.alert = this.panel.alert = this.panel.alert || {};
// set defaults
_.defaults(this.rule, this.defaultValues);
_.defaults(this.alert, this.defaultValues);
var defaultName = (this.panelCtrl.dashboard.title + ' ' + this.panel.title + ' alert');
this.rule.name = this.rule.name || defaultName;
this.rule.description = this.rule.description || defaultName;
this.rule.queryRef = this.panel.alerting.queryRef || this.metricTargets[0].refId;
this.alert.name = this.alert.name || defaultName;
this.alert.description = this.alert.description || defaultName;
// great temp working model
this.queryParams = {
params: [
this.rule.query.refId,
this.rule.query.from,
this.rule.query.to
this.alert.query.refId,
this.alert.query.from,
this.alert.query.to
]
};
// init the query part components model
this.query = new QueryPart(this.queryParams, alertQueryDef);
this.convertThresholdsToAlertThresholds();
this.transformDef = _.findWhere(this.transforms, {type: this.rule.transform.type});
this.transformDef = _.findWhere(this.transforms, {type: this.alert.transform.type});
}
queryUpdated() {
this.rule.query = {
this.alert.query = {
refId: this.query.params[0],
from: this.query.params[1],
to: this.query.params[2],
@ -106,16 +109,16 @@ export class AlertTabCtrl {
transformChanged() {
// clear model
this.rule.transform = {type: this.rule.transform.type};
this.transformDef = _.findWhere(this.transforms, {type: this.rule.transform.type});
this.alert.transform = {type: this.alert.transform.type};
this.transformDef = _.findWhere(this.transforms, {type: this.alert.transform.type});
switch (this.rule.transform.type) {
switch (this.alert.transform.type) {
case 'aggregation': {
this.rule.transform.method = 'avg';
this.alert.transform.method = 'avg';
break;
}
case "forecast": {
this.rule.transform.timespan = '7d';
this.alert.transform.timespan = '7d';
break;
}
}
@ -124,45 +127,34 @@ export class AlertTabCtrl {
convertThresholdsToAlertThresholds() {
if (this.panel.grid
&& this.panel.grid.threshold1
&& this.rule.warnLevel === undefined
&& this.alert.warnLevel === undefined
) {
this.rule.warning.op = '>';
this.rule.warning.level = this.panel.grid.threshold1;
this.alert.warning.op = '>';
this.alert.warning.level = this.panel.grid.threshold1;
}
if (this.panel.grid
&& this.panel.grid.threshold2
&& this.rule.critical.level === undefined
&& this.alert.critical.level === undefined
) {
this.rule.critical.op = '>';
this.rule.critical.level = this.panel.grid.threshold2;
this.alert.critical.op = '>';
this.alert.critical.level = this.panel.grid.threshold2;
}
}
delete() {
this.rule = this.panel.alerting = this.defaultValues;
this.rule.deleted = true;
this.alert = this.panel.alert = {};
this.alert.deleted = true;
this.initAlertModel();
}
enable() {
delete this.rule.deleted;
this.rule.enabled = true;
delete this.alert.deleted;
this.alert.enabled = true;
}
disable() {
this.rule.enabled = false;
}
thresholdsUpdated() {
if (this.panel.alerting.warnLevel) {
this.panel.grid.threshold1 = parseInt(this.panel.alerting.warnLevel);
}
if (this.panel.alerting.critLevel) {
this.panel.grid.threshold2 = parseInt(this.panel.alerting.critLevel);
}
this.panelCtrl.render();
this.alert.enabled = false;
}
}

View File

@ -13,7 +13,7 @@
<span class="gf-form-label">Transform using</span>
<div class="gf-form-select-wrapper">
<select class="gf-form-input"
ng-model="ctrl.rule.transform.type"
ng-model="ctrl.alert.transform.type"
ng-options="f.type as f.text for f in ctrl.transforms"
ng-change="ctrl.transformChanged()"
>
@ -24,14 +24,14 @@
<span class="gf-form-label">Method</span>
<div class="gf-form-select-wrapper">
<select class="gf-form-input"
ng-model="ctrl.rule.transform.method"
ng-model="ctrl.alert.transform.method"
ng-options="f for f in ctrl.aggregators">
</select>
</div>
</div>
<div class="gf-form" ng-if="ctrl.transformDef.type === 'forecast'">
<span class="gf-form-label">Timespan</span>
<input class="gf-form-input max-width-5" type="text" ng-model="ctrl.rule.transform.timespan" ng-change="ctrl.ruleUpdated()"></input>
<input class="gf-form-input max-width-5" type="text" ng-model="ctrl.alert.transform.timespan" ng-change="ctrl.ruleUpdated()"></input>
</div>
</div>
</div>
@ -44,16 +44,16 @@
<i class="icon-gf icon-gf-warn alert-icon-warn"></i>
Warn if
</span>
<metric-segment-model property="ctrl.rule.warning.op" options="ctrl.levelOpList" custom="false" css-class="query-segment-operator"></metric-segment-model>
<input class="gf-form-input max-width-7" type="number" ng-model="ctrl.rule.warnLevel" ng-change="ctrl.thresholdsUpdated()"></input>
<metric-segment-model property="ctrl.alert.warning.op" options="ctrl.levelOpList" custom="false" css-class="query-segment-operator"></metric-segment-model>
<input class="gf-form-input max-width-7" type="number" ng-model="ctrl.alert.warnLevel" ng-change="ctrl.thresholdsUpdated()"></input>
</div>
<div class="gf-form">
<span class="gf-form-label">
<i class="icon-gf icon-gf-warn alert-icon-critical"></i>
Critcal if
</span>
<metric-segment-model property="ctrl.rule.critical.op" options="ctrl.levelOpList" custom="false" css-class="query-segment-operator"></metric-segment-model>
<input class="gf-form-input max-width-7" type="number" ng-model="ctrl.rule.critLevel" ng-change="ctrl.thresholdsUpdated()"></input>
<metric-segment-model property="ctrl.alert.critical.op" options="ctrl.levelOpList" custom="false" css-class="query-segment-operator"></metric-segment-model>
<input class="gf-form-input max-width-7" type="number" ng-model="ctrl.alert.critLevel" ng-change="ctrl.thresholdsUpdated()"></input>
</div>
</div>
</div>
@ -67,14 +67,14 @@
<span class="gf-form-label">Scheduler</span>
<div class="gf-form-select-wrapper">
<select class="gf-form-input"
ng-model="ctrl.rule.scheduler"
ng-model="ctrl.alert.scheduler"
ng-options="f.value as f.text for f in ctrl.schedulers">
</select>
</div>
</div>
<div class="gf-form">
<span class="gf-form-label">Evaluate every</span>
<input class="gf-form-input max-width-7" type="text" ng-model="ctrl.rule.frequency"></input>
<input class="gf-form-input max-width-7" type="text" ng-model="ctrl.alert.frequency"></input>
</div>
</div>
</div>
@ -83,7 +83,7 @@
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label">Groups</span>
<bootstrap-tagsinput ng-model="ctrl.rule.notify" tagclass="label label-tag" placeholder="add tags">
<bootstrap-tagsinput ng-model="ctrl.alert.notify" tagclass="label label-tag" placeholder="add tags">
</bootstrap-tagsinput>
</div>
</div>
@ -109,8 +109,8 @@
<div class="editor-row">
<div class="gf-form-button-row">
<button class="btn btn-danger" ng-click="ctrl.delete()" ng-show="ctrl.rule.enabled">Delete</button>
<button class="btn btn-success" ng-click="ctrl.enable()" ng-hide="ctrl.rule.enabled">Enable</button>
<button class="btn btn-secondary" ng-click="ctrl.disable()" ng-show="ctrl.rule.enabled">Disable</button>
<button class="btn btn-danger" ng-click="ctrl.delete()" ng-show="ctrl.alert.enabled">Delete</button>
<button class="btn btn-success" ng-click="ctrl.enable()" ng-hide="ctrl.alert.enabled">Enable</button>
<button class="btn btn-secondary" ng-click="ctrl.disable()" ng-show="ctrl.alert.enabled">Disable</button>
</div>
</div>