diff --git a/pkg/services/alerting/result_handler.go b/pkg/services/alerting/result_handler.go
index eeb4f57c1ec..b0830726cb0 100644
--- a/pkg/services/alerting/result_handler.go
+++ b/pkg/services/alerting/result_handler.go
@@ -39,6 +39,7 @@ func (handler *DefaultResultHandler) Handle(ctx *EvalContext) {
}
countSeverity(ctx.Rule.Severity)
+
if ctx.Rule.State != oldState {
handler.log.Info("New state change", "alertId", ctx.Rule.Id, "newState", ctx.Rule.State, "oldState", oldState)
diff --git a/pkg/services/annotations/annotations.go b/pkg/services/annotations/annotations.go
index 005651630a0..fd8c8ebdb9a 100644
--- a/pkg/services/annotations/annotations.go
+++ b/pkg/services/annotations/annotations.go
@@ -27,18 +27,17 @@ const (
)
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"`
}
diff --git a/pkg/services/sqlstore/migrations/alert_mig.go b/pkg/services/sqlstore/migrations/alert_mig.go
index 336d290cb62..342c2282933 100644
--- a/pkg/services/sqlstore/migrations/alert_mig.go
+++ b/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("add index alert_notification org_id & name", NewAddIndexMigration(alert_notification, alert_notification.Indices[0]))
}
diff --git a/pkg/services/sqlstore/migrations/annotation_mig.go b/pkg/services/sqlstore/migrations/annotation_mig.go
index 64b5f948d66..af8d1cf0a03 100644
--- a/pkg/services/sqlstore/migrations/annotation_mig.go
+++ b/pkg/services/sqlstore/migrations/annotation_mig.go
@@ -12,7 +12,6 @@ func addAnnotationMig(mg *Migrator) {
{Name: "org_id", Type: DB_BigInt, Nullable: false},
{Name: "alert_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: "title", Type: DB_Text, Nullable: false},
{Name: "text", Type: DB_Text, Nullable: false},
@@ -25,7 +24,6 @@ func addAnnotationMig(mg *Migrator) {
Indices: []*Index{
{Cols: []string{"org_id", "alert_id"}, Type: IndexType},
{Cols: []string{"org_id", "type"}, Type: IndexType},
- {Cols: []string{"org_id", "panel_link_id"}, Type: IndexType},
{Cols: []string{"timestamp"}, Type: IndexType},
},
}
@@ -35,6 +33,5 @@ func addAnnotationMig(mg *Migrator) {
// create indices
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 & 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]))
}
diff --git a/public/app/core/components/query_part/query_part_editor.ts b/public/app/core/components/query_part/query_part_editor.ts
index f9122ee283b..7bc309d1bd2 100644
--- a/public/app/core/components/query_part/query_part_editor.ts
+++ b/public/app/core/components/query_part/query_part_editor.ts
@@ -5,33 +5,34 @@ import $ from 'jquery';
import coreModule from 'app/core/core_module';
var template = `
-
-
-
-
-{{part.def.type}}
+
+
{{part.def.type}}
()
+
`;
/** @ngInject */
export function queryPartEditorDirective($compile, templateSrv) {
- var paramTemplate = '
';
+ var paramTemplate = '
';
+
return {
restrict: 'E',
template: template,
scope: {
part: "=",
- removeAction: "&",
- partUpdated: "&",
- getOptions: "&",
+ handleEvent: "&",
},
link: function postLink($scope, elem) {
var part = $scope.part;
var partDef = part.def;
var $paramsContainer = elem.find('.query-part-parameters');
- var $controlsContainer = elem.find('.tight-form-func-controls');
+
+ $scope.partActions = [];
function clickFuncParam(paramIndex) {
/*jshint validthis:true */
@@ -63,7 +64,9 @@ export function queryPartEditorDirective($compile, templateSrv) {
$link.html(templateSrv.highlightVariablesAsHtml(newValue));
part.updateParam($input.val(), paramIndex);
- $scope.$apply($scope.partUpdated);
+ $scope.$apply(() => {
+ $scope.handleEvent({$event: {name: 'part-param-changed'}});
+ });
}
$input.hide();
@@ -91,7 +94,7 @@ export function queryPartEditorDirective($compile, templateSrv) {
if (param.options) { return param.options; }
$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; });
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() {
diff --git a/public/app/features/alerting/alert_def.ts b/public/app/features/alerting/alert_def.ts
index 84c1bc7cb05..69f639d04c4 100644
--- a/public/app/features/alerting/alert_def.ts
+++ b/public/app/features/alerting/alert_def.ts
@@ -25,12 +25,6 @@ var alertQueryDef = new QueryPartDef({
defaultParams: ['#A', '5m', 'now', 'avg']
});
-var reducerAvgDef = new QueryPartDef({
- type: 'avg',
- params: [],
- defaultParams: []
-});
-
var conditionTypes = [
{text: 'Query', value: 'query'},
];
@@ -43,6 +37,19 @@ var evalFunctions = [
{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 = [
{text: 'Critical', value: 'critical'},
{text: 'Warning', value: 'warning'},
@@ -50,9 +57,10 @@ var severityLevels = [
export default {
alertQueryDef: alertQueryDef,
- reducerAvgDef: reducerAvgDef,
getSeverityIconClass: getSeverityIconClass,
conditionTypes: conditionTypes,
evalFunctions: evalFunctions,
severityLevels: severityLevels,
+ reducerTypes: reducerTypes,
+ createReducerPart: createReducerPart,
};
diff --git a/public/app/features/alerting/alert_tab_ctrl.ts b/public/app/features/alerting/alert_tab_ctrl.ts
index 217f67124ef..0dd228e7040 100644
--- a/public/app/features/alerting/alert_tab_ctrl.ts
+++ b/public/app/features/alerting/alert_tab_ctrl.ts
@@ -21,7 +21,7 @@ export class AlertTabCtrl {
alertNotifications;
/** @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.panel = this.panelCtrl.panel;
this.$scope.ctrl = this;
@@ -148,19 +148,46 @@ export class AlertTabCtrl {
var cm: any = {source: source, type: source.type};
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;
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) {
var condition = this.buildDefaultCondition();
// add to persited model
- this.alert.conditions.push(condition);
+ this.panelCtrl.conditions.push(condition);
// add to view model
this.conditionModels.push(this.buildConditionModel(condition));
}
diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html
index e9f7e2aa556..43ac9abaad4 100644
--- a/public/app/features/alerting/partials/alert_tab.html
+++ b/public/app/features/alerting/partials/alert_tab.html
@@ -49,12 +49,12 @@
WHEN
-
+
Reducer
-
+
diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html
index 68b8ee60d98..df5c326b962 100644
--- a/public/app/plugins/datasource/influxdb/partials/query.editor.html
+++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html
@@ -35,12 +35,7 @@
-
+
@@ -62,11 +57,9 @@
GROUP BY
-
+
diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.ts b/public/app/plugins/datasource/influxdb/query_ctrl.ts
index 4cc07e9a64b..aad613b8d5f 100644
--- a/public/app/plugins/datasource/influxdb/query_ctrl.ts
+++ b/public/app/plugins/datasource/influxdb/query_ctrl.ts
@@ -20,7 +20,6 @@ export class InfluxQueryCtrl extends QueryCtrl {
measurementSegment: any;
removeTagFilterSegment: any;
-
/** @ngInject **/
constructor($scope, $injector, private templateSrv, private $q, private uiSegmentSrv) {
super($scope, $injector);
@@ -106,23 +105,55 @@ export class InfluxQueryCtrl extends QueryCtrl {
this.panelCtrl.refresh();
}
- removeGroupByPart(part, index) {
- this.queryModel.removeGroupByPart(part, index);
- this.panelCtrl.refresh();
- }
-
addSelectPart(selectParts, cat, subitem) {
this.queryModel.addSelectPart(selectParts, subitem.value);
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() {
@@ -167,21 +198,6 @@ export class InfluxQueryCtrl extends QueryCtrl {
.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) {
this.error = err.message || 'Failed to issue metric query';
return [];
@@ -243,11 +259,6 @@ export class InfluxQueryCtrl extends QueryCtrl {
.catch(this.handleQueryError);
}
- setFill(fill) {
- this.target.fill = fill;
- this.panelCtrl.refresh();
- }
-
tagSegmentUpdated(segment, index) {
this.tagSegments[index] = segment;
@@ -323,4 +334,3 @@ export class InfluxQueryCtrl extends QueryCtrl {
return this.queryModel.render(false);
}
}
-
diff --git a/public/sass/components/_dropdown.scss b/public/sass/components/_dropdown.scss
index 3934fa69640..142910033b0 100644
--- a/public/sass/components/_dropdown.scss
+++ b/public/sass/components/_dropdown.scss
@@ -140,6 +140,12 @@
& > .dropdown-menu {
display: block;
}
+
+ &.cascade-open {
+ .dropdown-menu {
+ display: block;
+ }
+ }
}
// Backdrop to catch body clicks on mobile, etc.