Merge pull request #108 from rashidkpc/master

Trends panel
pull/7/head
Rashid Khan 12 years ago
commit 0dd0cfeb2e
  1. 20
      common/css/bootstrap.dark.min.css
  2. 8
      common/css/main.css
  3. 2
      config.js
  4. 79
      js/services.js
  5. 4
      panels/histogram/editor.html
  6. 2
      panels/histogram/module.js
  7. 6
      panels/hits/editor.html
  8. 11
      panels/hits/module.js
  9. 8
      panels/map/module.js
  10. 3
      panels/pie/module.js
  11. 95
      panels/timepicker/module.js
  12. 64
      panels/trends/editor.html
  13. 10
      panels/trends/module.html
  14. 207
      panels/trends/module.js

@ -226,7 +226,7 @@ body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 21px;
color: #c8c8c8;
color: #ddd;
background-color: #272b30;
}
@ -1096,7 +1096,7 @@ input[type="color"],
margin-bottom: 10.5px;
font-size: 14px;
line-height: 21px;
color: #c8c8c8;
color: #fff;
vertical-align: middle;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
@ -1129,8 +1129,8 @@ input[type="search"],
input[type="tel"],
input[type="color"],
.uneditable-input {
background-color: #222;
border: 1px solid #333;
background-color: #666;
border: 1px solid #666;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
@ -1197,8 +1197,8 @@ input[type="file"] {
select {
width: 220px;
background-color: #222;
border: 1px solid #333;
background-color: #666;
border: 1px solid #666;
}
select[multiple],
@ -5201,8 +5201,8 @@ input[type="submit"].btn.btn-mini {
top: 10%;
left: 50%;
z-index: 1050;
width: 560px;
margin-left: -280px;
width: 760px;
margin-left: -380px;
background-color: #ffffff;
border: 1px solid #999;
border: 1px solid rgba(0, 0, 0, 0.3);
@ -5228,7 +5228,7 @@ input[type="submit"].btn.btn-mini {
}
.modal.fade.in {
top: 10%;
top: 10px;
}
.modal-header {
@ -5247,7 +5247,7 @@ input[type="submit"].btn.btn-mini {
.modal-body {
position: relative;
max-height: 400px;
max-height: 500px;
padding: 15px;
overflow-y: auto;
}

@ -131,6 +131,14 @@
font-weight: bold;
}
.normal {
font-weight: normal;
}
.light {
font-weight: 200;
}
.input-append label {
font-size: inherit !important;
}

@ -22,6 +22,6 @@ var config = new Settings(
kibana_index: "kibana-int",
modules: ['histogram','map','pie','table','stringquery','sort',
'timepicker','text','fields','hits','dashcontrol',
'column','derivequeries'],
'column','derivequeries','trends'],
}
);

@ -83,6 +83,85 @@ angular.module('kibana.services', [])
return fields;
})
.service('kbnIndex',function($http) {
// returns a promise containing an array of all indices matching the index
// pattern that exist in a given range
this.indices = function(from,to,pattern,interval) {
var possible = [];
_.each(expand_range(fake_utc(from),fake_utc(to),interval),function(d){
possible.push(d.format(pattern));
});
return all_indices().then(function(p) {
var indices = _.intersection(possible,p);
indices.reverse();
return indices
})
};
// returns a promise containing an array of all indices in an elasticsearch
// cluster
function all_indices() {
var something = $http({
url: config.elasticsearch + "/_aliases",
method: "GET"
}).error(function(data, status, headers, config) {
// Handle error condition somehow?
});
return something.then(function(p) {
var indices = [];
_.each(p.data, function(v,k) {
indices.push(k)
});
return indices;
});
}
// this is stupid, but there is otherwise no good way to ensure that when
// I extract the date from an object that I'm get the UTC date. Stupid js.
// I die a little inside every time I call this function.
// Update: I just read this again. I died a little more inside.
// Update2: More death.
function fake_utc(date) {
date = moment(date).clone().toDate()
return moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000));
}
// Create an array of date objects by a given interval
function expand_range(start, end, interval) {
if(_.contains(['hour','day','week','month','year'],interval)) {
var range;
start = moment(start).clone();
range = [];
while (start.isBefore(end)) {
range.push(start.clone());
switch (interval) {
case 'hour':
start.add('hours',1)
break
case 'day':
start.add('days',1)
break
case 'week':
start.add('weeks',1)
break
case 'month':
start.add('months',1)
break
case 'year':
start.add('years',1)
break
}
}
range.push(moment(end).clone());
return range;
} else {
return false;
}
}
})
.service('timer', function($timeout) {
// This service really just tracks a list of $timeout promises to give us a
// method for cancelling them all when we need to

@ -25,7 +25,7 @@
<label class="small">Query</label>
<form class="input-append" style="margin-bottom: 0px">
<input type="text" placeholder="New Query" style="width:80%" ng-model="newquery">
<button class="btn" ng-click="add_query(newlabel,newquery);newlabel='';newquery=''"><i class="icon-plus"></i></button>
<button class="btn" ng-click="add_query(newlabel,newquery);newlabel='';newquery='';set_refresh(true)"><i class="icon-plus"></i></button>
</form>
</div>
<div class="span1">
@ -34,7 +34,7 @@
<div class="row-fluid" ng-repeat="q in panel.query">
<div class="span3">
<form style="margin-bottom: 0px">
<input type="text" style="width:70%" ng-model="q.label">
<input type="text" style="width:70%" ng-model="q.label" ng-change="set_refresh(true)">
</form>
</div>
<div class="span8">

@ -315,7 +315,7 @@ angular.module('kibana.histogram', [])
color: '#ccc'
},
grid: {
backgroundColor: '#272b30',
backgroundColor: null,
borderWidth: 0,
borderColor: '#eee',
color: "#eee",

@ -38,7 +38,7 @@
<form class="input-append" style="margin-bottom: 0px">
<label class="small">Query</label>
<input type="text" placeholder="New Query" style="width:80%" ng-model="newquery">
<button class="btn" ng-click="add_query(newlabel,newquery);newlabel='';newquery=''"><i class="icon-plus"></i></button>
<button class="btn" ng-click="add_query(newlabel,newquery);newlabel='';newquery='';set_refresh(true)"><i class="icon-plus"></i></button>
</form>
</div>
<div class="span1">
@ -47,12 +47,12 @@
<div class="row-fluid" ng-repeat="q in panel.query">
<div class="span3">
<form style="margin-bottom: 0px">
<input type="text" style="width:70%" ng-model="q.label">
<input type="text" style="width:70%" ng-model="q.label" ng-change="set_refresh(true)">
</form>
</div>
<div class="span8">
<form class="input-append" style="margin-bottom: 0px">
<input type="text" style="width:80%" ng-model="q.query">
<input type="text" style="width:80%" ng-model="q.query" ng-change="set_refresh(true)">
<button class="btn" ng-click="get_data()"><i class="icon-search"></i></button>
</form>
</div>

@ -141,6 +141,17 @@ angular.module('kibana.hits', [])
$scope.get_data();
}
$scope.set_refresh = function (state) {
$scope.refresh = state;
}
$scope.close_edit = function() {
if($scope.refresh)
$scope.get_data();
$scope.refresh = false;
$scope.$emit('render');
}
function set_time(time) {
$scope.time = time;
$scope.index = _.isUndefined(time.index) ? $scope.index : time.index

@ -145,7 +145,7 @@ angular.module('kibana.map', [])
map: scope.panel.map,
regionStyle: {initial: {fill: '#8c8c8c'}},
zoomOnScroll: false,
backgroundColor: '#272b30',
backgroundColor: null,
series: {
regions: [{
values: scope.data,
@ -157,9 +157,9 @@ angular.module('kibana.map', [])
$('.jvectormap-label').css({
"position" : "absolute",
"display" : "none",
'color' : "#c8c8c8",
'padding' : '10px',
'font-size': '11pt',
'color' : "#c8c8c8",
'padding' : '10px',
'font-size' : '11pt',
'font-weight' : 200,
'background-color': '#1f1f1f',
'border-radius': '5px'

@ -246,14 +246,13 @@ angular.module('kibana.pie', [])
},
label: label,
stroke: {
color: '#272b30',
width: 0
}
}
},
//grid: { hoverable: true, clickable: true },
grid: {
backgroundColor: '#272b30',
backgroundColor: null,
hoverable: true,
clickable: true
},

@ -28,7 +28,7 @@
*/
angular.module('kibana.timepicker', [])
.controller('timepicker', function($scope, eventBus, $timeout, timer, $http) {
.controller('timepicker', function($scope, eventBus, $timeout, timer, $http, kbnIndex) {
// Set and populate defaults
var _d = {
@ -89,7 +89,7 @@ angular.module('kibana.timepicker', [])
// request one be sent by broadcasting a 'get_time' with its _id to its group
// This panel can handle multiple groups
eventBus.register($scope,"get_time", function(event,id) {
eventBus.broadcast($scope.$id,id,'time',unmoment($scope.time))
eventBus.broadcast($scope.$id,id,'time',compile_time($scope.time))
});
// In case some other panel broadcasts a time, set us to an absolute range
@ -208,13 +208,17 @@ angular.module('kibana.timepicker', [])
// Get indices for the time period, then broadcast time range and index list
// in a single object. Not sure if I like this.
if($scope.panel.index_interval !== 'none') {
indices($scope.time.from,$scope.time.to).then(function (p) {
kbnIndex.indices($scope.time.from,
$scope.time.to,
$scope.panel.index,
$scope.panel.index_interval
).then(function (p) {
$scope.time.index = p;
eventBus.broadcast($scope.$id,$scope.panel.group,'time',unmoment($scope.time))
eventBus.broadcast($scope.$id,$scope.panel.group,'time',compile_time($scope.time))
});
} else {
$scope.time.index = [$scope.panel.index];
eventBus.broadcast($scope.$id,$scope.panel.group,'time',unmoment($scope.time))
eventBus.broadcast($scope.$id,$scope.panel.group,'time',compile_time($scope.time))
}
// Update panel's string representation of the time object.Don't update if
@ -233,10 +237,12 @@ angular.module('kibana.timepicker', [])
// Prefer to pass around Date() objects in the EventBus since interacting with
// moment objects in libraries that are expecting Date()s can be tricky
function unmoment(time) {
function compile_time(time) {
time = _.clone(time)
time.from = time.from.toDate()
time.to = time.to.toDate()
time.interval = $scope.panel.index_interval
time.pattern = $scope.panel.index
return time;
}
@ -254,81 +260,4 @@ angular.module('kibana.timepicker', [])
}
}
// returns a promise containing an array of all indices matching the index
// pattern that exist in a given range
function indices(from,to) {
var possible = [];
_.each(expand_range(fake_utc(from),fake_utc(to),$scope.panel.index_interval),function(d){
possible.push(d.format($scope.panel.index));
});
return all_indices().then(function(p) {
var indices = _.intersection(possible,p);
indices.reverse();
return indices.length == 0 ? [$scope.panel.defaultindex] : indices;
})
};
// returns a promise containing an array of all indices in an elasticsearch
// cluster
function all_indices() {
var something = $http({
url: config.elasticsearch + "/_aliases",
method: "GET"
}).error(function(data, status, headers, config) {
$scope.error = status;
});
return something.then(function(p) {
var indices = [];
_.each(p.data, function(v,k) {
indices.push(k)
});
return indices;
});
}
// this is stupid, but there is otherwise no good way to ensure that when
// I extract the date from an object that I'm get the UTC date. Stupid js.
// I die a little inside every time I call this function.
// Update: I just read this again. I died a little more inside.
// Update2: More death.
function fake_utc(date) {
date = date.clone().toDate()
return moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000));
}
// Create an array of date objects by a given interval
function expand_range(start, end, interval) {
if(_.contains(['hour','day','week','month','year'],interval)) {
var range;
start = start.clone();
range = [];
while (start.isBefore(end)) {
range.push(start.clone());
switch (interval) {
case 'hour':
start.add('hours',1)
break
case 'day':
start.add('days',1)
break
case 'week':
start.add('weeks',1)
break
case 'month':
start.add('months',1)
break
case 'year':
start.add('years',1)
break
}
}
range.push(end.clone());
return range;
} else {
return false;
}
}
})

@ -0,0 +1,64 @@
<div>
<div class="row-fluid">
<div class="span12">
The trends panel will give you a percentage representation of how your query
has moved in your current timespan compared a specified amount of time ago. For
example, if the time is 1:10pm, your time picker was set to "Last 10m", and the
"Time Ago" parameter was set to '1h', the panel would show how much the query
results have changed since 12:00-12:10pm
</div>
</div>
<h4>Settings</h4>
<div class="row-fluid">
<div class="span3" ng-hide='panel.auto_int'>
<label class="small">Use Elasticsearch date math format here (eg 1m, 5m, 1d, 2w, 1y)</label>
</div>
<div class="span3">
<label class="small">Time Ago</label>
<input type="text" class="input-small" ng-model="panel.ago">
</div>
<div class="span2">
<label class="small">Font Size</label>
<select class="input-small" ng-model="panel.style['font-size']" ng-options="f for f in ['7pt','8pt','9pt','10pt','12pt','14pt','16pt','18pt','20pt','24pt','28pt','32pt','36pt','42pt','48pt','52pt','60pt','72pt']"></select></span>
</div>
<div class="span3">
<label class="small" >List Format</label>
<select class="input-small" ng-model="panel.arrangement" ng-options="f for f in ['horizontal','vertical']"></select></span>
</div>
</div>
<h5>Queries</h5>
<div class="row-fluid">
<div class="span3">
<form style="margin-bottom: 0px">
<label class="small">Label</label>
<input type="text" placeholder="New Label" style="width:70%" ng-model="newlabel">
</form>
</div>
<div class="span8">
<form class="input-append" style="margin-bottom: 0px">
<label class="small">Query</label>
<input type="text" placeholder="New Query" style="width:80%" ng-model="newquery">
<button class="btn" ng-click="add_query(newlabel,newquery);newlabel='';newquery='';set_refresh(true)"><i class="icon-plus"></i></button>
</form>
</div>
<div class="span1">
</div>
</div>
<div class="row-fluid" ng-repeat="q in panel.query">
<div class="span3">
<form style="margin-bottom: 0px">
<input type="text" style="width:70%" ng-model="q.label" ng-change="set_refresh(true)">
</form>
</div>
<div class="span8">
<form class="input-append" style="margin-bottom: 0px">
<input type="text" style="width:80%" ng-model="q.query" ng-change="set_refresh(true)">
<button class="btn" ng-click="get_data()"><i class="icon-search"></i></button>
</form>
</div>
<div class="span1">
<i class="icon-remove pointer" ng-click="remove_query(q)"></i>
</div>
</div>
</div>

@ -0,0 +1,10 @@
<kibana-panel ng-controller='trends' ng-init="init()">
<div ng-style="panel.style" style="line-height:{{panel.style['font-size']}};display:inline-block;padding-right: 5px;" ng-repeat="query in data">
<span ng-class="{'text-success': query.hits.new >= query.hits.old, 'text-error': query.hits.old > query.hits.new}" class='strong'>
<i class='large' ng-class="{'icon-caret-up': query.hits.new >= query.hits.old, 'icon-caret-down': query.hits.old > query.hits.new}"></i> {{query.percent}}%
</span>
<span class="tiny pointer light" bs-tooltip="'Then: '+query.hits.old+', Now: '+query.hits.new">({{query.label}})</span>
<br ng-show="panel.arrangement == 'vertical'">
</div>
</kibana-panel>

@ -0,0 +1,207 @@
/*
## Hits
A variety of representations of the hits a query matches
### Parameters
* query :: An array of queries. No labels here, just an array of strings. Maybe
there should be labels. Probably.
* style :: A hash of css styles
* arrangement :: How should I arrange the query results? 'horizontal' or 'vertical'
* ago :: Date math formatted time to look back
### Group Events
#### Sends
* get_time :: On panel initialization get time range to query
#### Receives
* time :: An object containing the time range to use and the index(es) to query
* query :: An Array of queries, even if its only one
*/
angular.module('kibana.trends', [])
.controller('trends', function($scope, eventBus, kbnIndex) {
// Set and populate defaults
var _d = {
query : ["*"],
group : "default",
style : { "font-size": '14pt'},
ago : '1d',
arrangement : 'vertical',
}
_.defaults($scope.panel,_d)
$scope.init = function () {
$scope.hits = 0;
eventBus.register($scope,'time', function(event,time){
set_time(time)
});
eventBus.register($scope,'query', function(event, query) {
$scope.panel.query = _.map(query,function(q) {
return {query: q, label: q};
})
$scope.get_data();
});
// Now that we're all setup, request the time from our group
eventBus.broadcast($scope.$id,$scope.panel.group,'get_time')
}
$scope.get_data = function(segment,query_id) {
delete $scope.panel.error
$scope.panel.loading = true;
// Make sure we have everything for the request to complete
if(_.isUndefined($scope.index) || _.isUndefined($scope.time))
return
$scope.old_time = {
from : new Date($scope.time.from.getTime() - interval_to_seconds($scope.panel.ago)*1000),
to : new Date($scope.time.to.getTime() - interval_to_seconds($scope.panel.ago)*1000)
}
var _segment = _.isUndefined(segment) ? 0 : segment
var request = $scope.ejs.Request();
// Build the question part of the query
var queries = [];
_.each($scope.panel.query, function(v) {
queries.push($scope.ejs.FilteredQuery(
ejs.QueryStringQuery(v.query || '*'),
ejs.RangeFilter($scope.time.field)
.from($scope.time.from)
.to($scope.time.to))
)
});
// Build the facet part
_.each(queries, function(v) {
request = request
.facet($scope.ejs.QueryFacet("new"+_.indexOf(queries,v))
.query(v)
).size(0)
})
var queries = [];
_.each($scope.panel.query, function(v) {
queries.push($scope.ejs.FilteredQuery(
ejs.QueryStringQuery(v.query || '*'),
ejs.RangeFilter($scope.time.field)
.from($scope.old_time.from)
.to($scope.old_time.to))
)
});
// Build the facet part
_.each(queries, function(v) {
request = request
.facet($scope.ejs.QueryFacet("old"+_.indexOf(queries,v))
.query(v)
).size(0)
})
// TODO: Spy for hits panel
//$scope.populate_modal(request);
// If we're on the first segment we need to get our indices
if (_segment == 0) {
kbnIndex.indices(
$scope.old_time.from,
$scope.old_time.to,
$scope.time.pattern,
$scope.time.interval
).then(function (p) {
$scope.index = _.union(p,$scope.index);
process_results(request.indices($scope.index[_segment]).doSearch());
});
} else {
process_results(request.indices($scope.index[_segment]).doSearch());
}
// Populate scope when we have results
function process_results(results) {
results.then(function(results) {
$scope.panel.loading = false;
if(_segment == 0) {
$scope.hits = {};
$scope.data = [];
query_id = $scope.query_id = new Date().getTime();
}
// Check for error and abort if found
if(!(_.isUndefined(results.error))) {
$scope.panel.error = $scope.parse_error(results.error);
return;
}
if($scope.query_id === query_id) {
var i = 0;
_.each($scope.panel.query, function(k) {
var n = results.facets['new'+i].count
var o = results.facets['old'+i].count
var hits = {
new : _.isUndefined($scope.data[i]) || _segment == 0 ? n : $scope.data[i].hits.new+n,
old : _.isUndefined($scope.data[i]) || _segment == 0 ? o : $scope.data[i].hits.old+o
}
$scope.hits.new += n;
$scope.hits.old += o;
var percent = Math.round(percentage(hits.old,hits.new)*100)/100
// Create series
$scope.data[i] = {
label: $scope.panel.query[i].label || "query"+(parseInt(i)+1),
hits: {
new : hits.new,
old : hits.old
},
percent: _.isNull(percent) ? 0 : percent
};
i++;
});
$scope.$emit('render');
if(_segment < $scope.index.length-1)
$scope.get_data(_segment+1,query_id)
}
});
}
}
function percentage(x,y) {
return 100*(y-x)/x
}
$scope.remove_query = function(q) {
$scope.panel.query = _.without($scope.panel.query,q);
$scope.get_data();
}
$scope.add_query = function(label,query) {
$scope.panel.query.unshift({
query: query,
label: label,
});
$scope.get_data();
}
$scope.set_refresh = function (state) {
$scope.refresh = state;
}
$scope.close_edit = function() {
if($scope.refresh)
$scope.get_data();
$scope.refresh = false;
$scope.$emit('render');
}
function set_time(time) {
$scope.time = time;
$scope.index = time.index || $scope.index
$scope.get_data();
}
})
Loading…
Cancel
Save