From 3a06457c5d888551a564c2325ad60ad85a22987f Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Fri, 26 Jul 2013 14:35:27 -0700 Subject: [PATCH 1/2] Added terms panel, deprecated pie panel --- config.js | 3 +- panels/pie/module.js | 11 +- panels/terms/editor.html | 50 +++++++ panels/terms/module.html | 56 ++++++++ panels/terms/module.js | 302 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 415 insertions(+), 7 deletions(-) create mode 100644 panels/terms/editor.html create mode 100644 panels/terms/module.html create mode 100644 panels/terms/module.js diff --git a/config.js b/config.js index 30d70cd988c23..8d694bd880287 100644 --- a/config.js +++ b/config.js @@ -22,6 +22,7 @@ var config = new Settings( kibana_index: "kibana-int", modules: ['histogram','map','pie','table','filtering', 'timepicker','text','fields','hits','dashcontrol', - 'column','derivequeries','trends','bettermap','query'], + 'column','derivequeries','trends','bettermap','query', + 'terms'], } ); diff --git a/panels/pie/module.js b/panels/pie/module.js index 467fe9a4faa43..e2d660c3282be 100644 --- a/panels/pie/module.js +++ b/panels/pie/module.js @@ -25,10 +25,10 @@ angular.module('kibana.pie', []) .controller('pie', function($scope, $rootScope, querySrv, dashboard, filterSrv) { $scope.panelMeta = { - status : "Deprecating Soon", + status : "Deprecated", 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"+ - " replaced with a panel that can represent a terms facet in a variety of ways." + " point this at not_analyzed fields for that reason. This panel is going away soon, it has"+ + " been replaced by the terms panel. Please use that one instead." }; // Set and populate defaults @@ -247,11 +247,10 @@ angular.module('kibana.pie', []) colors: querySrv.colors }; - // Populate element + // Populate legend if(elem.is(":visible")){ scripts.wait(function(){ - scope.plot = $.plot(elem, scope.data, pie); - scope.legend = scope.plot.getData(); + scope.legend = $.plot(elem, scope.data, pie).getData(); if(!scope.$$phase) { scope.$apply(); } diff --git a/panels/terms/editor.html b/panels/terms/editor.html new file mode 100644 index 0000000000000..3568e529fc361 --- /dev/null +++ b/panels/terms/editor.html @@ -0,0 +1,50 @@ +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
diff --git a/panels/terms/module.html b/panels/terms/module.html new file mode 100644 index 0000000000000..9cb6848dc4494 --- /dev/null +++ b/panels/terms/module.html @@ -0,0 +1,56 @@ + + + +
+ + + + + +
{{term.label}}{{term.data[0][1]}}
+ + +
+ {{term.label}} ({{term.data[0][1]}}) +

+ +
+ + +
+ +
+ +
+ + + + + +
{{term.label}}{{term.data[0][1]}}
+ + +
+ {{term.label}} ({{term.data[0][1]}}) +

+ +
+ + + + + + + + + + + +
Term Count Action
{{term.label}}{{term.data[0][1]}} + + + + +
+ +
\ No newline at end of file diff --git a/panels/terms/module.js b/panels/terms/module.js new file mode 100644 index 0000000000000..2d791866e6593 --- /dev/null +++ b/panels/terms/module.js @@ -0,0 +1,302 @@ +/*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 '
'+ + label+'
'+Math.round(series.percent)+'%
'; + }; + + 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') : $('
'); + //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, + "
"+item.series.label+ + " ("+value.toFixed(0)+")"); + } else { + $("#pie-tooltip").remove(); + } + }); + + } + }; +}); From 9e218671c165836d2b2530a2c82ce67c804e1163 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Fri, 26 Jul 2013 14:43:39 -0700 Subject: [PATCH 2/2] Fixed link --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3bdc9cc8cc2cf..e57fe84b11cfb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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. 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 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 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. +