Commit 86c83d79 by Rashid Khan

Merge branch 'annotations'

parents 8a4b8cf1 b1aef03b
If you have a bugfix or new feature that you would like to contribute to Kibana, please find or open an issue about it first. Talk about what you would like to do. It may be that somebody is already working on it, or that there are particular issues that you should know about before implementing the change. If you have a bugfix or new feature that you would like to contribute to Kibana, please **find or open an issue about it before you start working on it.** Talk about what you would like to do. It may be that somebody is already working on it, or that there are particular issues that you should know about before implementing the change.
We enjoy working with contributors to get their code accepted. There are many approaches to fixing a problem and it is important to find the best approach before writing too much code. We enjoy working with contributors to get their code accepted. There are many approaches to fixing a problem and it is important to find the best approach before writing too much code.
......
...@@ -32,6 +32,7 @@ require.config({ ...@@ -32,6 +32,7 @@ require.config({
'jquery.flot': '../vendor/jquery/jquery.flot', 'jquery.flot': '../vendor/jquery/jquery.flot',
'jquery.flot.pie': '../vendor/jquery/jquery.flot.pie', 'jquery.flot.pie': '../vendor/jquery/jquery.flot.pie',
'jquery.flot.events': '../vendor/jquery/jquery.flot.events',
'jquery.flot.selection': '../vendor/jquery/jquery.flot.selection', 'jquery.flot.selection': '../vendor/jquery/jquery.flot.selection',
'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack', 'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack',
'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent', 'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
...@@ -66,6 +67,7 @@ require.config({ ...@@ -66,6 +67,7 @@ require.config({
'jquery-ui': ['jquery'], 'jquery-ui': ['jquery'],
'jquery.flot': ['jquery'], 'jquery.flot': ['jquery'],
'jquery.flot.pie': ['jquery', 'jquery.flot'], 'jquery.flot.pie': ['jquery', 'jquery.flot'],
'jquery.flot.events': ['jquery', 'jquery.flot'],
'jquery.flot.selection':['jquery', 'jquery.flot'], 'jquery.flot.selection':['jquery', 'jquery.flot'],
'jquery.flot.stack': ['jquery', 'jquery.flot'], 'jquery.flot.stack': ['jquery', 'jquery.flot'],
'jquery.flot.stackpercent':['jquery', 'jquery.flot'], 'jquery.flot.stackpercent':['jquery', 'jquery.flot'],
......
...@@ -37,7 +37,7 @@ define([ ...@@ -37,7 +37,7 @@ define([
'./timeSeries', './timeSeries',
'jquery.flot', 'jquery.flot',
'jquery.flot.pie', 'jquery.flot.events',
'jquery.flot.selection', 'jquery.flot.selection',
'jquery.flot.time', 'jquery.flot.time',
'jquery.flot.stack', 'jquery.flot.stack',
...@@ -67,7 +67,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { ...@@ -67,7 +67,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
}, },
{ {
title:'Queries', title:'Queries',
src:'app/partials/querySelect.html' src:'app/panels/histogram/queriesEditor.html'
}, },
], ],
status : "Stable", status : "Stable",
...@@ -84,6 +84,13 @@ function (angular, app, $, _, kbn, moment, timeSeries) { ...@@ -84,6 +84,13 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
mode : 'all', mode : 'all',
ids : [] ids : []
}, },
annotate : {
enable : false,
query : "*",
size : 20,
field : '@message',
sort : ['_score','desc']
},
value_field : null, value_field : null,
auto_int : true, auto_int : true,
resolution : 100, resolution : 100,
...@@ -121,6 +128,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { ...@@ -121,6 +128,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
_.defaults($scope.panel,_d); _.defaults($scope.panel,_d);
_.defaults($scope.panel.tooltip,_d.tooltip); _.defaults($scope.panel.tooltip,_d.tooltip);
_.defaults($scope.panel.annotate,_d.annotate);
_.defaults($scope.panel.grid,_d.grid); _.defaults($scope.panel.grid,_d.grid);
...@@ -190,6 +198,16 @@ function (angular, app, $, _, kbn, moment, timeSeries) { ...@@ -190,6 +198,16 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
* this call is made recursively for more segments * this call is made recursively for more segments
*/ */
$scope.get_data = function(segment, query_id) { $scope.get_data = function(segment, query_id) {
var
_range,
_interval,
request,
queries,
results;
$scope.panel.annotate.enable = true;
$scope.panel.annotate.ids = [1];
if (_.isUndefined(segment)) { if (_.isUndefined(segment)) {
segment = 0; segment = 0;
} }
...@@ -199,8 +217,8 @@ function (angular, app, $, _, kbn, moment, timeSeries) { ...@@ -199,8 +217,8 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
if(dashboard.indices.length === 0) { if(dashboard.indices.length === 0) {
return; return;
} }
var _range = $scope.get_time_range(); _range = $scope.get_time_range();
var _interval = $scope.get_interval(_range); _interval = $scope.get_interval(_range);
if ($scope.panel.auto_int) { if ($scope.panel.auto_int) {
$scope.panel.interval = kbn.secondsToHms( $scope.panel.interval = kbn.secondsToHms(
...@@ -208,11 +226,11 @@ function (angular, app, $, _, kbn, moment, timeSeries) { ...@@ -208,11 +226,11 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
} }
$scope.panelMeta.loading = true; $scope.panelMeta.loading = true;
var request = $scope.ejs.Request().indices(dashboard.indices[segment]); request = $scope.ejs.Request().indices(dashboard.indices[segment]);
$scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries); $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
var queries = querySrv.getQueryObjs($scope.panel.queries.ids); queries = querySrv.getQueryObjs($scope.panel.queries.ids);
// Build the query // Build the query
_.each(queries, function(q) { _.each(queries, function(q) {
...@@ -224,7 +242,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { ...@@ -224,7 +242,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
var facet = $scope.ejs.DateHistogramFacet(q.id); var facet = $scope.ejs.DateHistogramFacet(q.id);
if($scope.panel.mode === 'count') { if($scope.panel.mode === 'count') {
facet = facet.field($scope.panel.time_field); facet = facet.field($scope.panel.time_field).global(true);
} else { } else {
if(_.isNull($scope.panel.value_field)) { if(_.isNull($scope.panel.value_field)) {
$scope.panel.error = "In " + $scope.panel.mode + " mode a field must be specified"; $scope.panel.error = "In " + $scope.panel.mode + " mode a field must be specified";
...@@ -233,14 +251,30 @@ function (angular, app, $, _, kbn, moment, timeSeries) { ...@@ -233,14 +251,30 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
facet = facet.keyField($scope.panel.time_field).valueField($scope.panel.value_field); facet = facet.keyField($scope.panel.time_field).valueField($scope.panel.value_field);
} }
facet = facet.interval(_interval).facetFilter($scope.ejs.QueryFilter(query)); facet = facet.interval(_interval).facetFilter($scope.ejs.QueryFilter(query));
request = request.facet(facet).size(0); request = request.facet(facet)
.size($scope.panel.annotate.enable ? $scope.panel.annotate.size : 0);
}); });
if($scope.panel.annotate.enable) {
var query = $scope.ejs.FilteredQuery(
$scope.ejs.QueryStringQuery($scope.panel.annotate.query || '*'),
filterSrv.getBoolFilter(filterSrv.idsByType('time'))
);
request = request.query(query);
// This is a hack proposed by @boaz to work around the fact that we can't get
// to field data values directly, and we need timestamps as normalized longs
request = request.sort([
$scope.ejs.Sort($scope.panel.annotate.sort[0]).order($scope.panel.annotate.sort[1]),
$scope.ejs.Sort($scope.panel.time_field).desc()
]);
}
// Populate the inspector panel // Populate the inspector panel
$scope.populate_modal(request); $scope.populate_modal(request);
// Then run it // Then run it
var results = request.doSearch(); results = request.doSearch();
// Populate scope when we have results // Populate scope when we have results
results.then(function(results) { results.then(function(results) {
...@@ -249,6 +283,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { ...@@ -249,6 +283,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
if(segment === 0) { if(segment === 0) {
$scope.hits = 0; $scope.hits = 0;
$scope.data = []; $scope.data = [];
$scope.annotations = [];
query_id = $scope.query_id = new Date().getTime(); query_id = $scope.query_id = new Date().getTime();
} }
...@@ -298,6 +333,30 @@ function (angular, app, $, _, kbn, moment, timeSeries) { ...@@ -298,6 +333,30 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
i++; i++;
}); });
if($scope.panel.annotate.enable) {
$scope.annotations = $scope.annotations.concat(_.map(results.hits.hits, function(hit) {
var _p = _.omit(hit,'_source','sort','_score');
var _h = _.extend(kbn.flatten_json(hit._source),_p);
return {
min: hit.sort[1],
max: hit.sort[1],
eventType: "annotation",
title: null,
description: "<small><i class='icon-tag icon-flip-vertical'></i> "+
_h[$scope.panel.annotate.field]+"</small><br>"+
moment(hit.sort[1]).format('YYYY-MM-DD HH:mm:ss'),
score: hit.sort[0]
};
}));
// Sort the data
$scope.annotations = _.sortBy($scope.annotations, function(v){
// Sort in reverse
return v.score*($scope.panel.annotate.sort[1] === 'desc' ? -1 : 1);
});
// And slice to the right size
$scope.annotations = $scope.annotations.slice(0,$scope.panel.annotate.size);
}
// Tell the histogram directive to render. // Tell the histogram directive to render.
$scope.$emit('render'); $scope.$emit('render');
...@@ -429,7 +488,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { ...@@ -429,7 +488,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
bars: { bars: {
show: scope.panel.bars, show: scope.panel.bars,
fill: 1, fill: 1,
barWidth: barwidth/1.8, barWidth: barwidth/1.5,
zero: false, zero: false,
lineWidth: 0 lineWidth: 0
}, },
...@@ -464,6 +523,25 @@ function (angular, app, $, _, kbn, moment, timeSeries) { ...@@ -464,6 +523,25 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
} }
}; };
if(scope.panel.annotate.enable) {
options.events = {
levels: 1,
data: scope.annotations,
types: {
'annotation': {
level: 1,
icon: {
icon: "icon-tag icon-flip-vertical",
size: 20,
color: "#222",
outline: "#bbb"
}
}
}
//xaxis: int // the x axis to attach events to
};
}
if(scope.panel.interactive) { if(scope.panel.interactive) {
options.selection = { mode: "x", color: '#666' }; options.selection = { mode: "x", color: '#666' };
} }
...@@ -503,13 +581,13 @@ function (angular, app, $, _, kbn, moment, timeSeries) { ...@@ -503,13 +581,13 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
function time_format(interval) { function time_format(interval) {
var _int = kbn.interval_to_seconds(interval); var _int = kbn.interval_to_seconds(interval);
if(_int >= 2628000) { if(_int >= 2628000) {
return "%m/%y"; return "%Y-%m";
} }
if(_int >= 86400) { if(_int >= 86400) {
return "%m/%d/%y"; return "%Y-%m-%d";
} }
if(_int >= 60) { if(_int >= 60) {
return "%H:%M<br>%m/%d"; return "%H:%M<br>%m-%d";
} }
return "%H:%M:%S"; return "%H:%M:%S";
...@@ -534,7 +612,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { ...@@ -534,7 +612,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
} }
$tooltip $tooltip
.html( .html(
group + value + " @ " + moment(item.datapoint[0]).format('MM/DD HH:mm:ss') group + value + " @ " + moment(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss')
) )
.place_tt(pos.pageX, pos.pageY); .place_tt(pos.pageX, pos.pageY);
} else { } else {
......
<h4>Charted</h4>
<div ng-include src="'app/partials/querySelect.html'"></div>
<div class="editor-row">
<h4>Markers</h4>
<div class="small">
Here you can specify a query to be plotted on your chart as a marker. Hovering over a marker will display the field you specify below. If more documents are found than the limit you set, they will be scored by Elasticsearch and events that best match your query will be displayed.
</div>
<style>
.querySelect .query {
margin-right: 5px;
}
.querySelect .selected {
border: 3px solid;
}
.querySelect .unselected {
border: 0px solid;
}
</style>
<p>
<div class="editor-option">
<label class="small">Enable</label>
<input type="checkbox" ng-change="set_refresh(true)" ng-model="panel.annotate.enable" ng-checked="panel.annotate.enable">
</div>
<div class="editor-option" ng-show="panel.annotate.enable">
<label class="small">Marker Query</label>
<input type="text" ng-change="set_refresh(true)" class="input-large" ng-model="panel.annotate.query"/>
</div>
<div class="editor-option" ng-show="panel.annotate.enable">
<label class="small">Tooltip field</label>
<input type="text" class="input-small" ng-model="panel.annotate.field" bs-typeahead="fields.list"/>
</div>
<div class="editor-option" ng-show="panel.annotate.enable">
<label class="small">Limit <tip>Max markers on the chart</tip></label>
<input type="number" class="input-mini" ng-model="panel.annotate.size" ng-change="set_refresh(true)"/>
</div>
<div class="editor-option" ng-show="panel.annotate.enable">
<label class="small">Sort <tip>Determine the most relevant markers using this field</tip></label>
<input type="text" class="input-small" bs-typeahead="fields.list" ng-model="panel.annotate.sort[0]" ng-change="set_refresh(true)" />
<i ng-click="panel.annotate.sort[1] = _.toggle(panel.annotate.sort[1],'desc','asc');set_refresh(true)" ng-class="{'icon-chevron-up': panel.annotate.sort[1] == 'asc','icon-chevron-down': panel.annotate.sort[1] == 'desc'}"></i>
</div>
</div>
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -256,7 +256,9 @@ a { ...@@ -256,7 +256,9 @@ a {
.boolean {color:lighten(@warningText, 5%)} .boolean {color:lighten(@warningText, 5%)}
.key {color:lighten(@errorText, 5%)} .key {color:lighten(@errorText, 5%)}
.typeahead { z-index: 1051; } .modal-body {
overflow-y:visible;
}
.btn-active { .btn-active {
background-color: #E6E6E6; background-color: #E6E6E6;
......
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