Commit c85bb476 by Torkel Ödegaard

Merge branch 'alerting_definitions' into alert_ui_take2

parents 071c16b7 dee5f582
......@@ -10,6 +10,7 @@
* **InfluxDB**: Add spread function, closes [#5211](https://github.com/grafana/grafana/issues/5211)
* **Scripts**: Use restart instead of start for deb package script, closes [#5282](https://github.com/grafana/grafana/pull/5282)
* **Logging**: Moved to structured logging lib, and moved to component specific level filters via config file, closes [#4590](https://github.com/grafana/grafana/issues/4590)
* **Search**: Add search limit query parameter, closes [#5292](https://github.com/grafana/grafana/pull/5292)
## Breaking changes
* **Logging** : Changed default logging output format (now structured into message, and key value pairs, with logger key acting as component). You can also no change in config to json log ouput.
......
......@@ -27,6 +27,10 @@ type AlertRule struct {
Updated time.Time `json:"updated"`
}
func (alertRule *AlertRule) ValidToSave() bool {
return alertRule.Query != "" && alertRule.Frequency != 0 && alertRule.QueryRange != 0 && alertRule.Name != ""
}
func (this *AlertRule) Equals(other *AlertRule) bool {
result := false
......@@ -52,6 +56,13 @@ type AlertingClusterInfo struct {
UptimePosition int
}
type HeartBeat struct {
Id int64
ServerId string
Updated time.Time
Created time.Time
}
type HeartBeatCommand struct {
ServerId string
......
......@@ -65,7 +65,7 @@ func ParseAlertsFromDashboard(cmd *m.SaveDashboardCommand) []*m.AlertRule {
alert.DatasourceId = query.Result.Id
}
if alert.Query != "" {
if alert.ValidToSave() {
alerts = append(alerts, alert)
}
}
......
......@@ -64,6 +64,7 @@ func (arr *AlertRuleReader) Fetch() []*AlertRule {
model.Description = ruleDef.Description
model.Aggregator = ruleDef.Aggregator
model.State = ruleDef.State
model.QueryRange = ruleDef.QueryRange
res[i] = model
}
......
......@@ -44,6 +44,7 @@ func searchHandler(query *Query) error {
IsStarred: query.IsStarred,
OrgId: query.OrgId,
DashboardIds: query.DashboardIds,
Limit: query.Limit,
}
if err := bus.Dispatch(&dashQuery); err != nil {
......
......@@ -42,6 +42,7 @@ type FindPersistedDashboardsQuery struct {
UserId int64
IsStarred bool
DashboardIds []int
Limit int
Result HitList
}
......@@ -17,8 +17,52 @@ func init() {
bus.AddHandler("sql", GetAlertById)
bus.AddHandler("sql", DeleteAlertById)
bus.AddHandler("sql", GetAllAlertQueryHandler)
//bus.AddHandler("sql", HeartBeat)
}
/*
func HeartBeat(query *m.HeartBeatCommand) error {
return inTransaction(func(sess *xorm.Session) error {
now := time.Now().Sub(0, 0, 0, 5)
activeTime := time.Now().Sub(0, 0, 0, 5)
ownHeartbeats := make([]m.HeartBeat, 0)
err := x.Where("server_id = ?", query.ServerId).Find(&ownHeartbeats)
if err != nil {
return err
}
if (len(ownHeartbeats)) > 0 && ownHeartbeats[0].Updated > activeTime {
//update
x.Insert(&m.HeartBeat{ServerId: query.ServerId, Created: now, Updated: now})
} else {
thisServer := ownHeartbeats[0]
thisServer.Updated = now
x.Id(thisServer.Id).Update(&thisServer)
}
activeServers := make([]m.HeartBeat, 0)
err = x.Where("server_id = ? and updated > ", query.ServerId, now.String()).OrderBy("id").Find(&activeServers)
if err != nil {
return err
}
for i, pos := range activeServers {
if pos.ServerId == query.ServerId {
query.Result = &m.AlertingClusterInfo{
ClusterSize: len(activeServers),
UptimePosition: i,
}
return nil
}
}
return nil
})
}
*/
func GetAlertById(query *m.GetAlertByIdQuery) error {
alert := m.AlertRule{}
has, err := x.Id(query.Id).Get(&alert)
......
......@@ -123,6 +123,11 @@ type DashboardSearchProjection struct {
}
func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
limit := query.Limit
if limit == 0 {
limit = 1000
}
var sql bytes.Buffer
params := make([]interface{}, 0)
......@@ -165,7 +170,8 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
params = append(params, "%"+query.Title+"%")
}
sql.WriteString(fmt.Sprintf(" ORDER BY dashboard.title ASC LIMIT 1000"))
sql.WriteString(fmt.Sprintf(" ORDER BY dashboard.title ASC LIMIT ?"))
params = append(params, limit)
var res []DashboardSearchProjection
......
///<reference path="../../../headers/common.d.ts" />
import _ from 'lodash';
export class QueryPartDef {
type: string;
params: any[];
defaultParams: any[];
renderer: any;
category: any;
addStrategy: any;
constructor(options: any) {
this.type = options.type;
this.params = options.params;
this.defaultParams = options.defaultParams;
this.renderer = options.renderer;
this.category = options.category;
this.addStrategy = options.addStrategy;
}
}
export class QueryPart {
part: any;
def: QueryPartDef;
params: any[];
text: string;
constructor(part: any, def: any) {
this.part = part;
this.def = def;
if (!this.def) {
throw {message: 'Could not find query part ' + part.type};
}
part.params = part.params || _.clone(this.def.defaultParams);
this.params = part.params;
this.updateText();
}
render(innerExpr: string) {
return this.def.renderer(this, innerExpr);
}
hasMultipleParamsInString (strValue, index) {
if (strValue.indexOf(',') === -1) {
return false;
}
return this.def.params[index + 1] && this.def.params[index + 1].optional;
}
updateParam (strValue, index) {
// handle optional parameters
// if string contains ',' and next param is optional, split and update both
if (this.hasMultipleParamsInString(strValue, index)) {
_.each(strValue.split(','), function(partVal: string, idx) {
this.updateParam(partVal.trim(), idx);
}, this);
return;
}
if (strValue === '' && this.def.params[index].optional) {
this.params.splice(index, 1);
} else {
this.params[index] = strValue;
}
this.part.params = this.params;
this.updateText();
}
updateText() {
if (this.params.length === 0) {
this.text = this.def.type + '()';
return;
}
var text = this.def.type + '(';
text += this.params.join(', ');
text += ')';
this.text = text;
}
}
export function functionRenderer(part, innerExpr) {
var str = part.def.type + '(';
var parameters = _.map(part.params, (value, index) => {
var paramType = part.def.params[index];
if (paramType.type === 'time') {
if (value === 'auto') {
value = '$interval';
}
}
if (paramType.quote === 'single') {
return "'" + value + "'";
} else if (paramType.quote === 'double') {
return '"' + value + '"';
}
return value;
});
if (innerExpr) {
parameters.unshift(innerExpr);
}
return str + parameters.join(', ') + ')';
}
export function suffixRenderer(part, innerExpr) {
return innerExpr + ' ' + part.params[0];
}
export function identityRenderer(part, innerExpr) {
return part.params[0];
}
export function quotedIdentityRenderer(part, innerExpr) {
return '"' + part.params[0] + '"';
}
......@@ -33,6 +33,7 @@ import {Emitter} from './utils/emitter';
import {layoutSelector} from './components/layout_selector/layout_selector';
import {switchDirective} from './components/switch';
import {dashboardSelector} from './components/dashboard_selector';
import {queryPartEditorDirective} from './components/query_part/query_part_editor';
import 'app/core/controllers/all';
import 'app/core/services/all';
import 'app/core/routes/routes';
......@@ -56,4 +57,5 @@ export {
Emitter,
appEvents,
dashboardSelector,
queryPartEditorDirective,
};
......@@ -51,7 +51,7 @@
<span class="btn btn-primary btn-mini" ng-show="org.orgId === contextSrv.user.orgId">
Current
</span>
<a ng-click="setUsingOrg(org)" class="btn btn-inverse btn-mini" ng-show="org.orgId !== contextSrv.user.orgId">
<a ng-click="ctrl.setUsingOrg(org)" class="btn btn-inverse btn-mini" ng-show="org.orgId !== contextSrv.user.orgId">
Select
</a>
</td>
......
......@@ -35,13 +35,13 @@
</div>
<div class="gf-form" ng-repeat="part in selectParts">
<influx-query-part-editor
<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)">
</influx-query-part-editor>
</query-part-editor>
</div>
<div class="gf-form">
......@@ -62,12 +62,12 @@
<span>GROUP BY</span>
</label>
<influx-query-part-editor
<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)">
</influx-query-part-editor>
</query-part-editor>
</div>
<div class="gf-form">
......
<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><span>(</span><span class="query-part-parameters"></span><span>)</span>
///<reference path="../../../headers/common.d.ts" />
import './query_part_editor';
import './query_part_editor';
import angular from 'angular';
import _ from 'lodash';
import InfluxQueryBuilder from './query_builder';
......
///<reference path="../../../headers/common.d.ts" />
import _ from 'lodash';
import {
QueryPartDef,
QueryPart,
functionRenderer,
suffixRenderer,
identityRenderer,
quotedIdentityRenderer,
} from 'app/core/components/query_part/query_part';
var index = [];
var categories = {
......@@ -12,71 +20,26 @@ var categories = {
Fields: [],
};
var groupByTimeFunctions = [];
class QueryPartDef {
type: string;
params: any[];
defaultParams: any[];
renderer: any;
category: any;
addStrategy: any;
constructor(options: any) {
this.type = options.type;
this.params = options.params;
this.defaultParams = options.defaultParams;
this.renderer = options.renderer;
this.category = options.category;
this.addStrategy = options.addStrategy;
}
static register(options: any) {
index[options.type] = new QueryPartDef(options);
options.category.push(index[options.type]);
function createPart(part): any {
var def = index[part.type];
if (!def) {
throw {message: 'Could not find query part ' + part.type};
}
}
function functionRenderer(part, innerExpr) {
var str = part.def.type + '(';
var parameters = _.map(part.params, (value, index) => {
var paramType = part.def.params[index];
if (paramType.type === 'time') {
if (value === 'auto') {
value = '$interval';
}
}
if (paramType.quote === 'single') {
return "'" + value + "'";
} else if (paramType.quote === 'double') {
return '"' + value + '"';
}
return value;
});
return new QueryPart(part, def);
};
if (innerExpr) {
parameters.unshift(innerExpr);
}
return str + parameters.join(', ') + ')';
function register(options: any) {
index[options.type] = new QueryPartDef(options);
options.category.push(index[options.type]);
}
var groupByTimeFunctions = [];
function aliasRenderer(part, innerExpr) {
return innerExpr + ' AS ' + '"' + part.params[0] + '"';
}
function suffixRenderer(part, innerExpr) {
return innerExpr + ' ' + part.params[0];
}
function identityRenderer(part, innerExpr) {
return part.params[0];
}
function quotedIdentityRenderer(part, innerExpr) {
return '"' + part.params[0] + '"';
}
function fieldRenderer(part, innerExpr) {
if (part.params[0] === '*') {
return '*';
......@@ -149,13 +112,13 @@ function addAliasStrategy(selectParts, partModel) {
function addFieldStrategy(selectParts, partModel, query) {
// copy all parts
var parts = _.map(selectParts, function(part: any) {
return new QueryPart({type: part.def.type, params: _.clone(part.params)});
return createPart({type: part.def.type, params: _.clone(part.params)});
});
query.selectModels.push(parts);
}
QueryPartDef.register({
register({
type: 'field',
addStrategy: addFieldStrategy,
category: categories.Fields,
......@@ -165,7 +128,7 @@ QueryPartDef.register({
});
// Aggregations
QueryPartDef.register({
register({
type: 'count',
addStrategy: replaceAggregationAddStrategy,
category: categories.Aggregations,
......@@ -174,7 +137,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'distinct',
addStrategy: replaceAggregationAddStrategy,
category: categories.Aggregations,
......@@ -183,7 +146,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'integral',
addStrategy: replaceAggregationAddStrategy,
category: categories.Aggregations,
......@@ -192,7 +155,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'mean',
addStrategy: replaceAggregationAddStrategy,
category: categories.Aggregations,
......@@ -201,7 +164,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'median',
addStrategy: replaceAggregationAddStrategy,
category: categories.Aggregations,
......@@ -210,7 +173,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'sum',
addStrategy: replaceAggregationAddStrategy,
category: categories.Aggregations,
......@@ -221,7 +184,7 @@ QueryPartDef.register({
// transformations
QueryPartDef.register({
register({
type: 'derivative',
addStrategy: addTransformationStrategy,
category: categories.Transformations,
......@@ -230,7 +193,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'spread',
addStrategy: addTransformationStrategy,
category: categories.Transformations,
......@@ -239,7 +202,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'non_negative_derivative',
addStrategy: addTransformationStrategy,
category: categories.Transformations,
......@@ -248,7 +211,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'difference',
addStrategy: addTransformationStrategy,
category: categories.Transformations,
......@@ -257,7 +220,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'moving_average',
addStrategy: addTransformationStrategy,
category: categories.Transformations,
......@@ -266,7 +229,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'stddev',
addStrategy: addTransformationStrategy,
category: categories.Transformations,
......@@ -275,7 +238,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'time',
category: groupByTimeFunctions,
params: [{ name: "interval", type: "time", options: ['auto', '1s', '10s', '1m', '5m', '10m', '15m', '1h'] }],
......@@ -283,7 +246,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'fill',
category: groupByTimeFunctions,
params: [{ name: "fill", type: "string", options: ['none', 'null', '0', 'previous'] }],
......@@ -292,7 +255,7 @@ QueryPartDef.register({
});
// Selectors
QueryPartDef.register({
register({
type: 'bottom',
addStrategy: replaceAggregationAddStrategy,
category: categories.Selectors,
......@@ -301,7 +264,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'first',
addStrategy: replaceAggregationAddStrategy,
category: categories.Selectors,
......@@ -310,7 +273,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'last',
addStrategy: replaceAggregationAddStrategy,
category: categories.Selectors,
......@@ -319,7 +282,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'max',
addStrategy: replaceAggregationAddStrategy,
category: categories.Selectors,
......@@ -328,7 +291,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'min',
addStrategy: replaceAggregationAddStrategy,
category: categories.Selectors,
......@@ -337,7 +300,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'percentile',
addStrategy: replaceAggregationAddStrategy,
category: categories.Selectors,
......@@ -346,7 +309,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'top',
addStrategy: replaceAggregationAddStrategy,
category: categories.Selectors,
......@@ -355,7 +318,7 @@ QueryPartDef.register({
renderer: functionRenderer,
});
QueryPartDef.register({
register({
type: 'tag',
category: groupByTimeFunctions,
params: [{name: 'tag', type: 'string', dynamicLookup: true}],
......@@ -363,7 +326,7 @@ QueryPartDef.register({
renderer: fieldRenderer,
});
QueryPartDef.register({
register({
type: 'math',
addStrategy: addMathStrategy,
category: categories.Math,
......@@ -372,7 +335,7 @@ QueryPartDef.register({
renderer: suffixRenderer,
});
QueryPartDef.register({
register({
type: 'alias',
addStrategy: addAliasStrategy,
category: categories.Aliasing,
......@@ -382,74 +345,9 @@ QueryPartDef.register({
renderer: aliasRenderer,
});
class QueryPart {
part: any;
def: QueryPartDef;
params: any[];
text: string;
constructor(part: any) {
this.part = part;
this.def = index[part.type];
if (!this.def) {
throw {message: 'Could not find query part ' + part.type};
}
part.params = part.params || _.clone(this.def.defaultParams);
this.params = part.params;
this.updateText();
}
render(innerExpr: string) {
return this.def.renderer(this, innerExpr);
}
hasMultipleParamsInString (strValue, index) {
if (strValue.indexOf(',') === -1) {
return false;
}
return this.def.params[index + 1] && this.def.params[index + 1].optional;
}
updateParam (strValue, index) {
// handle optional parameters
// if string contains ',' and next param is optional, split and update both
if (this.hasMultipleParamsInString(strValue, index)) {
_.each(strValue.split(','), function(partVal: string, idx) {
this.updateParam(partVal.trim(), idx);
}, this);
return;
}
if (strValue === '' && this.def.params[index].optional) {
this.params.splice(index, 1);
} else {
this.params[index] = strValue;
}
this.part.params = this.params;
this.updateText();
}
updateText() {
if (this.params.length === 0) {
this.text = this.def.type + '()';
return;
}
var text = this.def.type + '(';
text += this.params.join(', ');
text += ')';
this.text = text;
}
}
export default {
create: function(part): any {
return new QueryPart(part);
},
create: createPart,
getCategories: function() {
return categories;
}
......
......@@ -403,10 +403,7 @@ function (angular, _, dateMath) {
} else {
return _.findIndex(options.targets, function(target) {
if (target.filters && target.filters.length > 0) {
return target.metric === metricData.metric &&
_.all(target.filters, function(filter) {
return filter.tagk === interpolatedTagValue === "*";
});
return target.metric === metricData.metric;
} else {
return target.metric === metricData.metric &&
_.all(target.tags, function(tagV, tagK) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment