Преглед изворни кода

Merge branch 'query_part_refactor' into alerting

Conflicts:
	pkg/services/sqlstore/migrations/alert_mig.go
	pkg/services/sqlstore/migrations/heartbeat_mig.go
Torkel Ödegaard пре 9 година
родитељ
комит
9f29c54170

+ 1 - 0
pkg/services/alerting/result_handler.go

@@ -39,6 +39,7 @@ func (handler *DefaultResultHandler) Handle(ctx *EvalContext) {
 	}
 	}
 
 
 	countSeverity(ctx.Rule.Severity)
 	countSeverity(ctx.Rule.Severity)
+
 	if ctx.Rule.State != oldState {
 	if ctx.Rule.State != oldState {
 		handler.log.Info("New state change", "alertId", ctx.Rule.Id, "newState", ctx.Rule.State, "oldState", oldState)
 		handler.log.Info("New state change", "alertId", ctx.Rule.Id, "newState", ctx.Rule.State, "oldState", oldState)
 
 

+ 11 - 12
pkg/services/annotations/annotations.go

@@ -27,18 +27,17 @@ const (
 )
 )
 
 
 type Item struct {
 type Item struct {
-	Id          int64     `json:"id"`
-	OrgId       int64     `json:"orgId"`
-	PanelLinkId string    `json:"panelLinkId"`
-	Type        ItemType  `json:"type"`
-	Title       string    `json:"title"`
-	Text        string    `json:"text"`
-	Metric      string    `json:"metric"`
-	AlertId     int64     `json:"alertId"`
-	UserId      int64     `json:"userId"`
-	PrevState   string    `json:"prevState"`
-	NewState    string    `json:"newState"`
-	Timestamp   time.Time `json:"timestamp"`
+	Id        int64     `json:"id"`
+	OrgId     int64     `json:"orgId"`
+	Type      ItemType  `json:"type"`
+	Title     string    `json:"title"`
+	Text      string    `json:"text"`
+	Metric    string    `json:"metric"`
+	AlertId   int64     `json:"alertId"`
+	UserId    int64     `json:"userId"`
+	PrevState string    `json:"prevState"`
+	NewState  string    `json:"newState"`
+	Timestamp time.Time `json:"timestamp"`
 
 
 	Data *simplejson.Json `json:"data"`
 	Data *simplejson.Json `json:"data"`
 }
 }

+ 0 - 1
pkg/services/sqlstore/migrations/alert_mig.go

@@ -62,6 +62,5 @@ func addAlertMigrations(mg *Migrator) {
 	}
 	}
 
 
 	mg.AddMigration("create alert_notification table v1", NewAddTableMigration(alert_notification))
 	mg.AddMigration("create alert_notification table v1", NewAddTableMigration(alert_notification))
-
 	mg.AddMigration("add index alert_notification org_id & name", NewAddIndexMigration(alert_notification, alert_notification.Indices[0]))
 	mg.AddMigration("add index alert_notification org_id & name", NewAddIndexMigration(alert_notification, alert_notification.Indices[0]))
 }
 }

+ 1 - 4
pkg/services/sqlstore/migrations/annotation_mig.go

@@ -12,7 +12,6 @@ func addAnnotationMig(mg *Migrator) {
 			{Name: "org_id", Type: DB_BigInt, Nullable: false},
 			{Name: "org_id", Type: DB_BigInt, Nullable: false},
 			{Name: "alert_id", Type: DB_BigInt, Nullable: true},
 			{Name: "alert_id", Type: DB_BigInt, Nullable: true},
 			{Name: "user_id", Type: DB_BigInt, Nullable: true},
 			{Name: "user_id", Type: DB_BigInt, Nullable: true},
-			{Name: "panel_link_id", Type: DB_NVarchar, Length: 32, Nullable: false},
 			{Name: "type", Type: DB_NVarchar, Length: 25, Nullable: false},
 			{Name: "type", Type: DB_NVarchar, Length: 25, Nullable: false},
 			{Name: "title", Type: DB_Text, Nullable: false},
 			{Name: "title", Type: DB_Text, Nullable: false},
 			{Name: "text", Type: DB_Text, Nullable: false},
 			{Name: "text", Type: DB_Text, Nullable: false},
@@ -25,7 +24,6 @@ func addAnnotationMig(mg *Migrator) {
 		Indices: []*Index{
 		Indices: []*Index{
 			{Cols: []string{"org_id", "alert_id"}, Type: IndexType},
 			{Cols: []string{"org_id", "alert_id"}, Type: IndexType},
 			{Cols: []string{"org_id", "type"}, Type: IndexType},
 			{Cols: []string{"org_id", "type"}, Type: IndexType},
-			{Cols: []string{"org_id", "panel_link_id"}, Type: IndexType},
 			{Cols: []string{"timestamp"}, Type: IndexType},
 			{Cols: []string{"timestamp"}, Type: IndexType},
 		},
 		},
 	}
 	}
@@ -35,6 +33,5 @@ func addAnnotationMig(mg *Migrator) {
 	// create indices
 	// create indices
 	mg.AddMigration("add index annotation org_id & alert_id ", NewAddIndexMigration(table, table.Indices[0]))
 	mg.AddMigration("add index annotation org_id & alert_id ", NewAddIndexMigration(table, table.Indices[0]))
 	mg.AddMigration("add index annotation org_id & type", NewAddIndexMigration(table, table.Indices[1]))
 	mg.AddMigration("add index annotation org_id & type", NewAddIndexMigration(table, table.Indices[1]))
-	mg.AddMigration("add index annotation org_id & panel_link_id ", NewAddIndexMigration(table, table.Indices[2]))
-	mg.AddMigration("add index annotation timestamp", NewAddIndexMigration(table, table.Indices[3]))
+	mg.AddMigration("add index annotation timestamp", NewAddIndexMigration(table, table.Indices[2]))
 }
 }

+ 23 - 28
public/app/core/components/query_part/query_part_editor.ts

@@ -5,33 +5,34 @@ import $ from 'jquery';
 import coreModule from 'app/core/core_module';
 import coreModule from 'app/core/core_module';
 
 
 var template = `
 var template = `
-<div class="tight-form-func-controls">
-  <span class="pointer fa fa-remove" ng-click="removeActionInternal()"></span>
-</div>
-
-<a ng-click="toggleControls()" class="query-part-name">{{part.def.type}}</a>
+<div class="dropdown cascade-open">
+<a ng-click="showActionsMenu()" class="query-part-name pointer dropdown-toggle" data-toggle="dropdown">{{part.def.type}}</a>
 <span>(</span><span class="query-part-parameters"></span><span>)</span>
 <span>(</span><span class="query-part-parameters"></span><span>)</span>
+<ul class="dropdown-menu">
+  <li ng-repeat="action in partActions">
+    <a ng-click="triggerPartAction(action)">{{action.text}}</a>
+  </li>
+</ul>
 `;
 `;
 
 
   /** @ngInject */
   /** @ngInject */
 export function queryPartEditorDirective($compile, templateSrv) {
 export function queryPartEditorDirective($compile, templateSrv) {
 
 
-  var paramTemplate = '<input type="text" style="display:none"' +
-    ' class="input-mini tight-form-func-param"></input>';
+  var paramTemplate = '<input type="text" class="hide input-mini tight-form-func-param"></input>';
+
   return {
   return {
     restrict: 'E',
     restrict: 'E',
     template: template,
     template: template,
     scope: {
     scope: {
       part: "=",
       part: "=",
-      removeAction: "&",
-      partUpdated: "&",
-      getOptions: "&",
+      handleEvent: "&",
     },
     },
     link: function postLink($scope, elem) {
     link: function postLink($scope, elem) {
       var part = $scope.part;
       var part = $scope.part;
       var partDef = part.def;
       var partDef = part.def;
       var $paramsContainer = elem.find('.query-part-parameters');
       var $paramsContainer = elem.find('.query-part-parameters');
-      var $controlsContainer = elem.find('.tight-form-func-controls');
+
+      $scope.partActions = [];
 
 
       function clickFuncParam(paramIndex) {
       function clickFuncParam(paramIndex) {
         /*jshint validthis:true */
         /*jshint validthis:true */
@@ -63,7 +64,9 @@ export function queryPartEditorDirective($compile, templateSrv) {
           $link.html(templateSrv.highlightVariablesAsHtml(newValue));
           $link.html(templateSrv.highlightVariablesAsHtml(newValue));
 
 
           part.updateParam($input.val(), paramIndex);
           part.updateParam($input.val(), paramIndex);
-          $scope.$apply($scope.partUpdated);
+          $scope.$apply(() => {
+            $scope.handleEvent({$event: {name: 'part-param-changed'}});
+          });
         }
         }
 
 
         $input.hide();
         $input.hide();
@@ -91,7 +94,7 @@ export function queryPartEditorDirective($compile, templateSrv) {
           if (param.options) { return param.options; }
           if (param.options) { return param.options; }
 
 
           $scope.$apply(function() {
           $scope.$apply(function() {
-            $scope.getOptions().then(function(result) {
+            $scope.handleEvent({$event: {name: 'get-param-options'}}).then(function(result) {
               var dynamicOptions = _.map(result, function(op) { return op.value; });
               var dynamicOptions = _.map(result, function(op) { return op.value; });
               callback(dynamicOptions);
               callback(dynamicOptions);
             });
             });
@@ -124,24 +127,16 @@ export function queryPartEditorDirective($compile, templateSrv) {
         };
         };
       }
       }
 
 
-      $scope.toggleControls = function() {
-        var targetDiv = elem.closest('.tight-form');
-
-        if (elem.hasClass('show-function-controls')) {
-          elem.removeClass('show-function-controls');
-          targetDiv.removeClass('has-open-function');
-          $controlsContainer.hide();
-          return;
+      $scope.showActionsMenu = function() {
+        if ($scope.partActions.length === 0) {
+          $scope.handleEvent({$event: {name: 'get-part-actions'}}).then(res => {
+            $scope.partActions = res;
+          });
         }
         }
-
-        elem.addClass('show-function-controls');
-        targetDiv.addClass('has-open-function');
-        $controlsContainer.show();
       };
       };
 
 
-      $scope.removeActionInternal = function() {
-        $scope.toggleControls();
-        $scope.removeAction();
+      $scope.triggerPartAction = function(action) {
+        $scope.handleEvent({$event: {name: 'action', action: action}});
       };
       };
 
 
       function addElementsAndCompile() {
       function addElementsAndCompile() {

+ 15 - 7
public/app/features/alerting/alert_def.ts

@@ -25,12 +25,6 @@ var alertQueryDef = new QueryPartDef({
   defaultParams: ['#A', '5m', 'now', 'avg']
   defaultParams: ['#A', '5m', 'now', 'avg']
 });
 });
 
 
-var reducerAvgDef = new QueryPartDef({
-  type: 'avg',
-  params: [],
-  defaultParams: []
-});
-
 var conditionTypes = [
 var conditionTypes = [
   {text: 'Query', value: 'query'},
   {text: 'Query', value: 'query'},
 ];
 ];
@@ -43,6 +37,19 @@ var evalFunctions = [
   {text: 'HAS NO VALUE' , value: 'no_value'}
   {text: 'HAS NO VALUE' , value: 'no_value'}
 ];
 ];
 
 
+var reducerTypes = [
+  {text: 'avg()', value: 'avg'},
+  {text: 'min()', value: 'min'},
+  {text: 'max()', value: 'max'},
+  {text: 'sum()' , value: 'sum'},
+  {text: 'count()', value: 'count'},
+];
+
+function createReducerPart(model) {
+  var def = new QueryPartDef({type: model.type, defaultParams: []});
+  return new QueryPart(model, def);
+}
+
 var severityLevels = [
 var severityLevels = [
   {text: 'Critical', value: 'critical'},
   {text: 'Critical', value: 'critical'},
   {text: 'Warning', value: 'warning'},
   {text: 'Warning', value: 'warning'},
@@ -50,9 +57,10 @@ var severityLevels = [
 
 
 export default {
 export default {
   alertQueryDef: alertQueryDef,
   alertQueryDef: alertQueryDef,
-  reducerAvgDef: reducerAvgDef,
   getSeverityIconClass: getSeverityIconClass,
   getSeverityIconClass: getSeverityIconClass,
   conditionTypes: conditionTypes,
   conditionTypes: conditionTypes,
   evalFunctions: evalFunctions,
   evalFunctions: evalFunctions,
   severityLevels: severityLevels,
   severityLevels: severityLevels,
+  reducerTypes: reducerTypes,
+  createReducerPart: createReducerPart,
 };
 };

+ 31 - 4
public/app/features/alerting/alert_tab_ctrl.ts

@@ -21,7 +21,7 @@ export class AlertTabCtrl {
   alertNotifications;
   alertNotifications;
 
 
   /** @ngInject */
   /** @ngInject */
-  constructor(private $scope, private $timeout, private backendSrv, private dashboardSrv, private uiSegmentSrv) {
+  constructor(private $scope, private $timeout, private backendSrv, private dashboardSrv, private uiSegmentSrv, private $q) {
     this.panelCtrl = $scope.ctrl;
     this.panelCtrl = $scope.ctrl;
     this.panel = this.panelCtrl.panel;
     this.panel = this.panelCtrl.panel;
     this.$scope.ctrl = this;
     this.$scope.ctrl = this;
@@ -148,19 +148,46 @@ export class AlertTabCtrl {
     var cm: any = {source: source, type: source.type};
     var cm: any = {source: source, type: source.type};
 
 
     cm.queryPart = new QueryPart(source.query, alertDef.alertQueryDef);
     cm.queryPart = new QueryPart(source.query, alertDef.alertQueryDef);
-    cm.reducerPart = new QueryPart({params: []}, alertDef.reducerAvgDef);
+    cm.reducerPart = alertDef.createReducerPart(source.reducer);
     cm.evaluator = source.evaluator;
     cm.evaluator = source.evaluator;
 
 
     return cm;
     return cm;
   }
   }
 
 
-  queryPartUpdated(conditionModel) {
+  handleQueryPartEvent(conditionModel, evt) {
+    switch (evt.name) {
+      case "action-remove-part": {
+        break;
+      }
+      case "get-part-actions": {
+        return this.$q.when([]);
+      }
+    }
+  }
+
+  handleReducerPartEvent(conditionModel, evt) {
+    switch (evt.name) {
+      case "action": {
+        conditionModel.source.reducer.type = evt.action.value;
+        conditionModel.reducerPart = alertDef.createReducerPart(conditionModel.source.reducer);
+        break;
+      }
+      case "get-part-actions": {
+        var result = [];
+        for (var type of alertDef.reducerTypes) {
+          if (type.value !== conditionModel.source.reducer.type) {
+            result.push(type);
+          }
+        }
+        return this.$q.when(result);
+      }
+    }
   }
   }
 
 
   addCondition(type) {
   addCondition(type) {
     var condition = this.buildDefaultCondition();
     var condition = this.buildDefaultCondition();
     // add to persited model
     // add to persited model
-    this.alert.conditions.push(condition);
+    this.panelCtrl.conditions.push(condition);
     // add to view model
     // add to view model
     this.conditionModels.push(this.buildConditionModel(condition));
     this.conditionModels.push(this.buildConditionModel(condition));
   }
   }

+ 2 - 2
public/app/features/alerting/partials/alert_tab.html

@@ -49,12 +49,12 @@
 						<span class="gf-form-label query-keyword width-5" ng-if="$index===0">WHEN</span>
 						<span class="gf-form-label query-keyword width-5" ng-if="$index===0">WHEN</span>
 					</div>
 					</div>
 					<div class="gf-form">
 					<div class="gf-form">
-						<query-part-editor class="gf-form-label query-part" part="conditionModel.queryPart" part-updated="ctrl.queryPartUpdated(conditionModel)">
+						<query-part-editor class="gf-form-label query-part" part="conditionModel.queryPart" handle-event="ctrl.handleQueryPartEvent(conditionModel, $event)">
 						</query-part-editor>
 						</query-part-editor>
 					</div>
 					</div>
 					<div class="gf-form">
 					<div class="gf-form">
 						<span class="gf-form-label">Reducer</span>
 						<span class="gf-form-label">Reducer</span>
-						<query-part-editor class="gf-form-label query-part" part="conditionModel.reducerPart" part-updated="ctrl.reducerPartUpdated(conditionModel)">
+						<query-part-editor class="gf-form-label query-part" part="conditionModel.reducerPart" handle-event="ctrl.handleReducerPartEvent(conditionModel, $event)">
 						</query-part-editor>
 						</query-part-editor>
 					</div>
 					</div>
 					<div class="gf-form">
 					<div class="gf-form">

+ 4 - 11
public/app/plugins/datasource/influxdb/partials/query.editor.html

@@ -35,12 +35,7 @@
 			</div>
 			</div>
 
 
 			<div class="gf-form" ng-repeat="part in selectParts">
 			<div class="gf-form" ng-repeat="part in selectParts">
-				<query-part-editor
-														class="gf-form-label query-part"
-														part="part"
-														remove-action="ctrl.removeSelectPart(selectParts, part)"
-														part-updated="ctrl.selectPartUpdated(selectParts, part)"
-														get-options="ctrl.getPartOptions(part)">
+				<query-part-editor class="gf-form-label query-part" part="part" handle-event="ctrl.handleSelectPartEvent(selectParts, part, $event)">
 				</query-part-editor>
 				</query-part-editor>
 			</div>
 			</div>
 
 
@@ -62,11 +57,9 @@
 					<span>GROUP BY</span>
 					<span>GROUP BY</span>
 				</label>
 				</label>
 
 
-				<query-part-editor
-								ng-repeat="part in ctrl.queryModel.groupByParts"
-								part="part"
-								class="gf-form-label query-part"
-								remove-action="ctrl.removeGroupByPart(part, $index)" part-updated="ctrl.refresh();" get-options="ctrl.getPartOptions(part)">
+				<query-part-editor  ng-repeat="part in ctrl.queryModel.groupByParts"
+                            part="part" class="gf-form-label query-part"
+                            handle-event="ctrl.handleGroupByPartEvent(part, $index, $event)">
 				</query-part-editor>
 				</query-part-editor>
 			</div>
 			</div>
 
 

+ 42 - 32
public/app/plugins/datasource/influxdb/query_ctrl.ts

@@ -20,7 +20,6 @@ export class InfluxQueryCtrl extends QueryCtrl {
   measurementSegment: any;
   measurementSegment: any;
   removeTagFilterSegment: any;
   removeTagFilterSegment: any;
 
 
-
   /** @ngInject **/
   /** @ngInject **/
   constructor($scope, $injector, private templateSrv, private $q, private uiSegmentSrv) {
   constructor($scope, $injector, private templateSrv, private $q, private uiSegmentSrv) {
     super($scope, $injector);
     super($scope, $injector);
@@ -106,23 +105,55 @@ export class InfluxQueryCtrl extends QueryCtrl {
     this.panelCtrl.refresh();
     this.panelCtrl.refresh();
   }
   }
 
 
-  removeGroupByPart(part, index) {
-    this.queryModel.removeGroupByPart(part, index);
-    this.panelCtrl.refresh();
-  }
-
   addSelectPart(selectParts, cat, subitem) {
   addSelectPart(selectParts, cat, subitem) {
     this.queryModel.addSelectPart(selectParts, subitem.value);
     this.queryModel.addSelectPart(selectParts, subitem.value);
     this.panelCtrl.refresh();
     this.panelCtrl.refresh();
   }
   }
 
 
-  removeSelectPart(selectParts, part) {
-    this.queryModel.removeSelectPart(selectParts, part);
-    this.panelCtrl.refresh();
+  handleSelectPartEvent(selectParts, part, evt) {
+    switch (evt.name) {
+      case "get-param-options": {
+        var fieldsQuery = this.queryBuilder.buildExploreQuery('FIELDS');
+        return this.datasource.metricFindQuery(fieldsQuery)
+        .then(this.transformToSegments(true))
+        .catch(this.handleQueryError.bind(this));
+      }
+      case "part-param-changed": {
+        this.panelCtrl.refresh();
+        break;
+      }
+      case "action": {
+        this.queryModel.removeSelectPart(selectParts, part);
+        this.panelCtrl.refresh();
+        break;
+      }
+      case "get-part-actions": {
+        return this.$q.when([{text: 'Remove', value: 'remove-part'}]);
+      }
+    }
   }
   }
 
 
-  selectPartUpdated() {
-    this.panelCtrl.refresh();
+  handleGroupByPartEvent(part, index, evt) {
+    switch (evt.name) {
+      case "get-param-options": {
+        var tagsQuery = this.queryBuilder.buildExploreQuery('TAG_KEYS');
+        return this.datasource.metricFindQuery(tagsQuery)
+        .then(this.transformToSegments(true))
+        .catch(this.handleQueryError.bind(this));
+      }
+      case "part-param-changed": {
+        this.panelCtrl.refresh();
+        break;
+      }
+      case "action": {
+        this.queryModel.removeGroupByPart(part, index);
+        this.panelCtrl.refresh();
+        break;
+      }
+      case "get-part-actions": {
+        return this.$q.when([{text: 'Remove', value: 'remove-part'}]);
+      }
+    }
   }
   }
 
 
   fixTagSegments() {
   fixTagSegments() {
@@ -167,21 +198,6 @@ export class InfluxQueryCtrl extends QueryCtrl {
       .catch(this.handleQueryError.bind(this));
       .catch(this.handleQueryError.bind(this));
   }
   }
 
 
-  getPartOptions(part) {
-    if (part.def.type === 'field') {
-      var fieldsQuery = this.queryBuilder.buildExploreQuery('FIELDS');
-      return this.datasource.metricFindQuery(fieldsQuery)
-      .then(this.transformToSegments(true))
-      .catch(this.handleQueryError.bind(this));
-    }
-    if (part.def.type === 'tag') {
-      var tagsQuery = this.queryBuilder.buildExploreQuery('TAG_KEYS');
-      return this.datasource.metricFindQuery(tagsQuery)
-      .then(this.transformToSegments(true))
-      .catch(this.handleQueryError.bind(true));
-    }
-  }
-
   handleQueryError(err) {
   handleQueryError(err) {
     this.error = err.message || 'Failed to issue metric query';
     this.error = err.message || 'Failed to issue metric query';
     return [];
     return [];
@@ -243,11 +259,6 @@ export class InfluxQueryCtrl extends QueryCtrl {
     .catch(this.handleQueryError);
     .catch(this.handleQueryError);
   }
   }
 
 
-  setFill(fill) {
-    this.target.fill = fill;
-    this.panelCtrl.refresh();
-  }
-
   tagSegmentUpdated(segment, index) {
   tagSegmentUpdated(segment, index) {
     this.tagSegments[index] = segment;
     this.tagSegments[index] = segment;
 
 
@@ -323,4 +334,3 @@ export class InfluxQueryCtrl extends QueryCtrl {
     return this.queryModel.render(false);
     return this.queryModel.render(false);
   }
   }
 }
 }
-

+ 6 - 0
public/sass/components/_dropdown.scss

@@ -140,6 +140,12 @@
   & > .dropdown-menu {
   & > .dropdown-menu {
     display: block;
     display: block;
   }
   }
+
+  &.cascade-open {
+    .dropdown-menu {
+      display: block;
+    }
+  }
 }
 }
 
 
 // Backdrop to catch body clicks on mobile, etc.
 // Backdrop to catch body clicks on mobile, etc.