Commit f8de0c76 by Rashid Khan

Merge pull request #284 from rashidkpc/master

Added terms panel & fixed CLA link
parents 6090f684 9e218671
...@@ -5,7 +5,7 @@ We enjoy working with contributors to get their code accepted. There are many ap ...@@ -5,7 +5,7 @@ We enjoy working with contributors to get their code accepted. There are many ap
The process for contributing to any of the Elasticsearch repositories is similar. The process for contributing to any of the Elasticsearch repositories is similar.
1. Sign the contributor license agreement 1. Sign the contributor license agreement
Please make sure you have signed the [http://www.elasticsearch.org/contributor-agreement/](Contributor License Agreement). We are not asking you to assign copyright to us, but to give us the right to distribute your code without restriction. We ask this of all contributors in order to assure our users of the origin and continuing existence of the code. You only need to sign the CLA once. Please make sure you have signed the [Contributor License Agreement](http://www.elasticsearch.org/contributor-agreement/). We are not asking you to assign copyright to us, but to give us the right to distribute your code without restriction. We ask this of all contributors in order to assure our users of the origin and continuing existence of the code. You only need to sign the CLA once.
2. Rebase your changes 2. Rebase your changes
Update your local repository with the most recent code from the main Kibana repository, and rebase your branch on top of the latest master branch. We prefer your changes to be squashed into a single commit. Update your local repository with the most recent code from the main Kibana repository, and rebase your branch on top of the latest master branch. We prefer your changes to be squashed into a single commit.
...@@ -14,3 +14,4 @@ Update your local repository with the most recent code from the main Kibana repo ...@@ -14,3 +14,4 @@ Update your local repository with the most recent code from the main Kibana repo
Push your local changes to your forked copy of the repository and submit a pull request. In the pull request, describe what your changes do and mention the number of the issue where discussion has taken place, eg “Closes #123″. Push your local changes to your forked copy of the repository and submit a pull request. In the pull request, describe what your changes do and mention the number of the issue where discussion has taken place, eg “Closes #123″.
Then sit back and wait. There will probably be discussion about the pull request and, if any changes are needed, we would love to work with you to get your pull request merged into Kibana. Then sit back and wait. There will probably be discussion about the pull request and, if any changes are needed, we would love to work with you to get your pull request merged into Kibana.
...@@ -22,6 +22,7 @@ var config = new Settings( ...@@ -22,6 +22,7 @@ var config = new Settings(
kibana_index: "kibana-int", kibana_index: "kibana-int",
modules: ['histogram','map','pie','table','filtering', modules: ['histogram','map','pie','table','filtering',
'timepicker','text','fields','hits','dashcontrol', 'timepicker','text','fields','hits','dashcontrol',
'column','derivequeries','trends','bettermap','query'], 'column','derivequeries','trends','bettermap','query',
'terms'],
} }
); );
...@@ -25,10 +25,10 @@ angular.module('kibana.pie', []) ...@@ -25,10 +25,10 @@ angular.module('kibana.pie', [])
.controller('pie', function($scope, $rootScope, querySrv, dashboard, filterSrv) { .controller('pie', function($scope, $rootScope, querySrv, dashboard, filterSrv) {
$scope.panelMeta = { $scope.panelMeta = {
status : "Deprecating Soon", status : "Deprecated",
description : "Uses an Elasticsearch terms facet to create a pie chart. You should really only"+ description : "Uses an Elasticsearch terms facet to create a pie chart. You should really only"+
" point this at not_analyzed fields for that reason. This panel is going away soon, to be"+ " point this at not_analyzed fields for that reason. This panel is going away soon, it has"+
" replaced with a panel that can represent a terms facet in a variety of ways." " <strong>been replaced by the terms panel</strong>. Please use that one instead."
}; };
// Set and populate defaults // Set and populate defaults
...@@ -247,11 +247,10 @@ angular.module('kibana.pie', []) ...@@ -247,11 +247,10 @@ angular.module('kibana.pie', [])
colors: querySrv.colors colors: querySrv.colors
}; };
// Populate element // Populate legend
if(elem.is(":visible")){ if(elem.is(":visible")){
scripts.wait(function(){ scripts.wait(function(){
scope.plot = $.plot(elem, scope.data, pie); scope.legend = $.plot(elem, scope.data, pie).getData();
scope.legend = scope.plot.getData();
if(!scope.$$phase) { if(!scope.$$phase) {
scope.$apply(); scope.$apply();
} }
......
<div>
<div class="row-fluid">
<div class="span2">
<label class="small">Field</label>
<input type="text" class="input-small" bs-typeahead="fields.list" ng-model="panel.field" ng-change="set_refresh(true)">
</div>
<div class="span2">
<label class="small">Length</label>
<input class="input-small" type="number" ng-model="panel.size" ng-change="set_refresh(true)">
</div>
<div class="span6">
<label class="small">Exclude Terms(s) (comma seperated)</label>
<input array-join type="text" ng-model='panel.exclude'></input>
</div>
</div>
<div class="row-fluid">
<div class="span2">
<label class="small">Style</label>
<select class="input-small" ng-model="panel.chart" ng-options="f for f in ['bar','pie','table']"></select></span>
</div>
<div class="span2" ng-show="panel.chart == 'table'">
<label class="small">Font Size</label>
<select class="input-mini" 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="span2" ng-show="panel.chart == 'bar' || panel.chart == 'pie'">
<label class="small">Legend</label>
<select class="input-small" ng-model="panel.counter_pos" ng-options="f for f in ['above','below','none']"></select></span>
</div>
<div class="span3" ng-show="panel.chart != 'table' && panel.counter_pos != 'none'">
<label class="small" >Legend Format</label>
<select class="input-small" ng-model="panel.arrangement" ng-options="f for f in ['horizontal','vertical']"></select></span>
</div>
<div class="span1">
<label class="small">Missing</label><input type="checkbox" ng-model="panel.missing" ng-checked="panel.missing">
</div>
<div class="span1">
<label class="small">Other</label><input type="checkbox" ng-model="panel.other" ng-checked="panel.other">
</div>
<div class="span1" ng-show='panel.chart == "pie"'>
<label class="small">Donut</label><input type="checkbox" ng-model="panel.donut" ng-checked="panel.donut">
</div>
<div class="span1" ng-show='panel.chart == "pie"'>
<label class="small">Tilt</label><input type="checkbox" ng-model="panel.tilt" ng-checked="panel.tilt">
</div>
<div class="span1" ng-show='panel.chart == "pie"'>
<label class="small">Labels</label><input type="checkbox" ng-model="panel.labels" ng-checked="panel.labels">
</div>
</div>
<div class="row-fluid" ng-include="'partials/querySelect.html'"></div>
</div>
<kibana-panel ng-controller='terms' ng-init="init()">
<!-- START Pie or bar chart -->
<div ng-show="panel.counter_pos == 'above' && (panel.chart == 'bar' || panel.chart == 'pie')" id='{{$id}}-legend'>
<!-- vertical legend above -->
<table class="small" ng-show="panel.arrangement == 'vertical'">
<tr ng-repeat="term in legend">
<td><i class="icon-circle" ng-style="{color:term.color}"></i></td> <td style="padding-right:10px;padding-left:10px;">{{term.label}}</td><td>{{term.data[0][1]}}</td>
</tr>
</table>
<!-- horizontal legend above -->
<div class="small" ng-show="panel.arrangement == 'horizontal'" ng-repeat="term in legend" style="float:left;padding-left: 10px;">
<span><i class="icon-circle" ng-style="{color:term.color}"></i> {{term.label}} ({{term.data[0][1]}}) </span>
</div><br>
</div>
<!-- keep legend from over lapping -->
<div style="clear:both"></div>
<div ng-show="panel.chart == 'pie' || panel.chart == 'bar'" terms-chart params="{{panel}}" style="position:relative"></div>
<div ng-show="panel.counter_pos == 'below' && (panel.chart == 'bar' || panel.chart == 'pie')" id='{{$id}}-legend'>
<!-- vertical legend below -->
<table class="small" ng-show="panel.arrangement == 'vertical'">
<tr ng-repeat="term in legend">
<td><i class="icon-circle" ng-style="{color:term.color}"></i></i></td> <td style="padding-right:10px;padding-left:10px;">{{term.label}}</td><td>{{term.data[0][1]}}</td>
</tr>
</table>
<!-- horizontal legend below -->
<div class="small" ng-show="panel.arrangement == 'horizontal'" ng-repeat="term in legend" style="float:left;padding-left: 10px;">
<span><i class="icon-circle" ng-style="{color:term.color}"></i></span> {{term.label}} ({{term.data[0][1]}}) </span>
</div><br>
</div>
<!-- END Pie or Bar chart -->
<table ng-style="panel.style" class="table table-striped table-condensed" ng-show="panel.chart == 'table'">
<thead>
<th>Term</th> <th>Count</th> <th>Action</th>
</thead>
<tr ng-repeat="term in data" ng-show="showMeta(term)">
<td>{{term.label}}</td>
<td>{{term.data[0][1]}}</td>
<td>
<span ng-hide="term.meta == 'other'">
<i class='icon-search pointer' ng-click="build_search(term)"></i>
<i class='icon-ban-circle pointer' ng-click="build_search(term,true)"></i>
</span>
</td>
</tr>
</table>
</kibana-panel>
\ No newline at end of file
/*jshint globalstrict:true */
/*global angular:true */
/*
## Terms
### Parameters
* style :: A hash of css styles
* size :: top N
* arrangement :: How should I arrange the query results? 'horizontal' or 'vertical'
* chart :: Show a chart? 'none', 'bar', 'pie'
* donut :: Only applies to 'pie' charts. Punches a hole in the chart for some reason
* tilt :: Only 'pie' charts. Janky 3D effect. Looks terrible 90% of the time.
* lables :: Only 'pie' charts. Labels on the pie?
*/
'use strict';
angular.module('kibana.terms', [])
.controller('terms', function($scope, querySrv, dashboard, filterSrv) {
$scope.panelMeta = {
status : "Beta",
description : "Displays the results of an elasticsearch facet as a pie chart, bar chart, or a "+
"table"
};
// Set and populate defaults
var _d = {
queries : {
mode : 'all',
ids : []
},
field : '_type',
exclude : [],
missing : true,
other : true,
size : 10,
style : { "font-size": '10pt'},
donut : false,
tilt : false,
labels : true,
arrangement : 'horizontal',
chart : 'bar',
counter_pos : 'above'
};
_.defaults($scope.panel,_d);
$scope.init = function () {
$scope.hits = 0;
$scope.$on('refresh',function(){
$scope.get_data();
});
$scope.get_data();
};
$scope.get_data = function(segment,query_id) {
// Make sure we have everything for the request to complete
if(dashboard.indices.length === 0) {
return;
}
$scope.panel.loading = true;
var request,
results,
boolQuery;
request = $scope.ejs.Request().indices(dashboard.indices);
$scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
// This could probably be changed to a BoolFilter
boolQuery = $scope.ejs.BoolQuery();
_.each($scope.panel.queries.ids,function(id) {
boolQuery = boolQuery.should(querySrv.getEjsObj(id));
});
// Terms mode
request = request
.facet($scope.ejs.TermsFacet('terms')
.field($scope.panel.field)
.size($scope.panel.size)
.exclude($scope.panel.exclude)
.facetFilter($scope.ejs.QueryFilter(
$scope.ejs.FilteredQuery(
boolQuery,
filterSrv.getBoolFilter(filterSrv.ids)
)))).size(0);
//$scope.populate_modal(request);
results = request.doSearch();
// Populate scope when we have results
results.then(function(results) {
var k = 0;
$scope.panel.loading = false;
$scope.hits = results.hits.total;
$scope.data = [];
_.each(results.facets.terms.terms, function(v) {
var slice = { label : v.term, data : [[k,v.count]], actions: true};
$scope.data.push(slice);
k = k + 1;
});
$scope.data.push({label:'Missing field',
data:[[k,results.facets.terms.missing]],meta:"missing",color:'#aaa',opacity:0});
$scope.data.push({label:'Other values',
data:[[k+1,results.facets.terms.other]],meta:"other",color:'#444'});
$scope.$emit('render');
});
};
$scope.build_search = function(term,negate) {
if(_.isUndefined(term.meta)) {
filterSrv.set({type:'terms',field:$scope.panel.field,value:term.label,
mandate:(negate ? 'mustNot':'must')});
} else if(term.meta === 'missing') {
filterSrv.set({type:'exists',field:$scope.panel.field,
mandate:(negate ? 'must':'mustNot')});
} else {
return;
}
dashboard.refresh();
};
$scope.set_refresh = function (state) {
$scope.refresh = state;
};
$scope.close_edit = function() {
if($scope.refresh) {
$scope.get_data();
}
$scope.refresh = false;
$scope.$emit('render');
};
$scope.showMeta = function(term) {
if(_.isUndefined(term.meta)) {
return true;
}
if(term.meta === 'other' && !$scope.panel.other) {
return false;
}
if(term.meta === 'missing' && !$scope.panel.missing) {
return false;
}
return true;
};
}).directive('termsChart', function(querySrv, filterSrv, dashboard) {
return {
restrict: 'A',
link: function(scope, elem, attrs, ctrl) {
// Receive render events
scope.$on('render',function(){
render_panel();
});
// Re-render if the window is resized
angular.element(window).bind('resize', function(){
render_panel();
});
// Function for rendering panel
function render_panel() {
var plot, chartData;
var scripts = $LAB.script("common/lib/panels/jquery.flot.js").wait()
.script("common/lib/panels/jquery.flot.pie.js");
// IE doesn't work without this
elem.css({height:scope.panel.height||scope.row.height});
// Make a clone we can operate on.
chartData = _.clone(scope.data);
chartData = scope.panel.missing ? chartData :
_.without(chartData,_.findWhere(chartData,{meta:'missing'}));
chartData = scope.panel.other ? chartData :
_.without(chartData,_.findWhere(chartData,{meta:'other'}));
// Populate element.
scripts.wait(function(){
// Populate element
try {
// Add plot to scope so we can build out own legend
if(scope.panel.chart === 'bar') {
plot = $.plot(elem, chartData, {
legend: { show: false },
series: {
lines: { show: false, },
bars: { show: true, fill: 1, barWidth: 0.8, horizontal: false },
shadowSize: 1
},
yaxis: { show: true, min: 0, color: "#c8c8c8" },
xaxis: { show: false },
grid: {
borderWidth: 0,
borderColor: '#eee',
color: "#eee",
hoverable: true,
clickable: true
},
colors: querySrv.colors
});
}
if(scope.panel.chart === 'pie') {
var labelFormat = function(label, series){
return '<div ng-click="build_search(panel.field,\''+label+'\')'+
' "style="font-size:8pt;text-align:center;padding:2px;color:white;">'+
label+'<br/>'+Math.round(series.percent)+'%</div>';
};
plot = $.plot(elem, chartData, {
legend: { show: false },
series: {
pie: {
innerRadius: scope.panel.donut ? 0.4 : 0,
tilt: scope.panel.tilt ? 0.45 : 1,
radius: 1,
show: true,
combine: {
color: '#999',
label: 'The Rest'
},
stroke: {
width: 0
},
label: {
show: scope.panel.labels,
radius: 2/3,
formatter: labelFormat,
threshold: 0.1
}
}
},
//grid: { hoverable: true, clickable: true },
grid: { hoverable: true, clickable: true },
colors: querySrv.colors
});
}
// Populate legend
if(elem.is(":visible")){
scripts.wait(function(){
scope.legend = plot.getData();
if(!scope.$$phase) {
scope.$apply();
}
});
}
} catch(e) {
elem.text(e);
}
});
}
function tt(x, y, contents) {
var tooltip = $('#pie-tooltip').length ?
$('#pie-tooltip') : $('<div id="pie-tooltip"></div>');
//var tooltip = $('#pie-tooltip')
tooltip.html(contents).css({
position: 'absolute',
top : y + 5,
left : x + 5,
color : "#c8c8c8",
padding : '10px',
'font-size': '11pt',
'font-weight' : 200,
'background-color': '#1f1f1f',
'border-radius': '5px',
}).appendTo("body");
}
elem.bind("plotclick", function (event, pos, object) {
if(object) {
scope.build_search(scope.data[object.seriesIndex]);
}
});
elem.bind("plothover", function (event, pos, item) {
if (item) {
var value = scope.panel.chart === 'bar' ?
item.datapoint[1] : item.datapoint[1][0][1];
tt(pos.pageX, pos.pageY,
"<div style='vertical-align:middle;border-radius:10px;display:inline-block;background:"+
item.series.color+";height:20px;width:20px'></div> "+item.series.label+
" ("+value.toFixed(0)+")");
} else {
$("#pie-tooltip").remove();
}
});
}
};
});
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