module.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. /*
  2. ## Pie
  3. This panel is probably going away. For now its has 2 modes:
  4. * terms: Run a terms facet on the query. You're gonna have a bad (ES crashing) day
  5. if you run in this mode on a high cardinality field
  6. * goal: Compare the query to this number and display the percentage that the query
  7. represents
  8. ### Parameters
  9. * query :: An object with 3 possible parameters depends on the mode:
  10. ** field: Fields to run a terms facet on. Only does anything in terms mode
  11. ** query: A string of the query to run
  12. ** goal: How many to shoot for, only does anything in goal mode
  13. * exclude :: In terms mode, ignore these terms
  14. * donut :: Drill a big hole in the pie
  15. * tilt :: A janky 3D representation of the pie. Looks terrible 90% of the time.
  16. * legend :: Show the legend?
  17. * labels :: Label the slices of the pie?
  18. * mode :: 'terms' or 'goal'
  19. * default_field :: LOL wat? A dumb fail over field if for some reason the query object
  20. doesn't have a field
  21. * spyable :: Show the 'eye' icon that displays the last ES query for this panel
  22. */
  23. angular.module('kibana.pie', [])
  24. .controller('pie', function($scope, $rootScope, query, dashboard, filterSrv) {
  25. // Set and populate defaults
  26. var _d = {
  27. status : "Deprecating Soon",
  28. query : { field:"_type", goal: 100},
  29. size : 10,
  30. exclude : [],
  31. donut : false,
  32. tilt : false,
  33. legend : "above",
  34. labels : true,
  35. mode : "terms",
  36. group : "default",
  37. default_field : 'DEFAULT',
  38. spyable : true,
  39. }
  40. _.defaults($scope.panel,_d)
  41. $scope.init = function() {
  42. $scope.$on('refresh',function(){$scope.get_data()})
  43. $scope.get_data();
  44. }
  45. $scope.set_mode = function(mode) {
  46. switch(mode)
  47. {
  48. case 'terms':
  49. $scope.panel.query = {field:"_all"};
  50. break;
  51. case 'goal':
  52. $scope.panel.query = {goal:100};
  53. break;
  54. }
  55. }
  56. $scope.set_refresh = function (state) {
  57. $scope.refresh = state;
  58. }
  59. $scope.close_edit = function() {
  60. if($scope.refresh)
  61. $scope.get_data();
  62. $scope.refresh = false;
  63. $scope.$emit('render');
  64. }
  65. $scope.get_data = function() {
  66. // Make sure we have everything for the request to complete
  67. if(dashboard.indices.length == 0) {
  68. return
  69. }
  70. $scope.panel.loading = true;
  71. var request = $scope.ejs.Request().indices(dashboard.indices);
  72. // This could probably be changed to a BoolFilter
  73. var boolQuery = ejs.BoolQuery();
  74. _.each(query.list,function(q) {
  75. boolQuery = boolQuery.should(ejs.QueryStringQuery(q.query || '*'))
  76. })
  77. // Terms mode
  78. if ($scope.panel.mode == "terms") {
  79. request = request
  80. .facet(ejs.TermsFacet('pie')
  81. .field($scope.panel.query.field || $scope.panel.default_field)
  82. .size($scope.panel['size'])
  83. .exclude($scope.panel.exclude)
  84. .facetFilter(ejs.QueryFilter(
  85. ejs.FilteredQuery(
  86. boolQuery,
  87. filterSrv.getBoolFilter(filterSrv.ids)
  88. )))).size(0)
  89. $scope.populate_modal(request);
  90. var results = request.doSearch();
  91. // Populate scope when we have results
  92. results.then(function(results) {
  93. $scope.panel.loading = false;
  94. $scope.hits = results.hits.total;
  95. $scope.data = [];
  96. var k = 0;
  97. _.each(results.facets.pie.terms, function(v) {
  98. var slice = { label : v.term, data : v.count };
  99. $scope.data.push();
  100. if(!(_.isUndefined($scope.panel.colors))
  101. && _.isArray($scope.panel.colors)
  102. && $scope.panel.colors.length > 0) {
  103. slice.color = $scope.panel.colors[k%$scope.panel.colors.length];
  104. }
  105. $scope.data.push(slice)
  106. k = k + 1;
  107. });
  108. $scope.$emit('render');
  109. });
  110. // Goal mode
  111. } else {
  112. request = request
  113. .query(boolQuery)
  114. .filter(filterSrv.getBoolFilter(filterSrv.ids))
  115. .size(0)
  116. $scope.populate_modal(request);
  117. var results = request.doSearch();
  118. results.then(function(results) {
  119. $scope.panel.loading = false;
  120. var complete = results.hits.total;
  121. var remaining = $scope.panel.query.goal - complete;
  122. $scope.data = [
  123. { label : 'Complete', data : complete, color: '#BF6730' },
  124. { data : remaining, color: '#e2d0c4'}]
  125. $scope.$emit('render');
  126. });
  127. }
  128. }
  129. // I really don't like this function, too much dom manip. Break out into directive?
  130. $scope.populate_modal = function(request) {
  131. $scope.modal = {
  132. title: "Inspector",
  133. body : "<h5>Last Elasticsearch Query</h5><pre>"+
  134. 'curl -XGET '+config.elasticsearch+'/'+dashboard.indices+"/_search?pretty -d'\n"+
  135. angular.toJson(JSON.parse(request.toString()),true)+
  136. "'</pre>",
  137. }
  138. }
  139. })
  140. .directive('pie', function(query, filterSrv, dashboard) {
  141. return {
  142. restrict: 'A',
  143. link: function(scope, elem, attrs) {
  144. elem.html('<center><img src="common/img/load_big.gif"></center>')
  145. // Receive render events
  146. scope.$on('render',function(){
  147. render_panel();
  148. });
  149. // Or if the window is resized
  150. angular.element(window).bind('resize', function(){
  151. render_panel();
  152. });
  153. // Function for rendering panel
  154. function render_panel() {
  155. var scripts = $LAB.script("common/lib/panels/jquery.flot.js").wait()
  156. .script("common/lib/panels/jquery.flot.pie.js")
  157. if(scope.panel.mode === 'goal')
  158. var label = {
  159. show: scope.panel.labels,
  160. radius: 0,
  161. formatter: function(label, series){
  162. var font = parseInt(scope.row.height.replace('px',''))/8 + String('px')
  163. if(!(_.isUndefined(label)))
  164. return '<div style="font-size:'+font+';font-weight:bold;text-align:center;padding:2px;color:#fff;">'+
  165. Math.round(series.percent)+'%</div>';
  166. else
  167. return ''
  168. },
  169. }
  170. else
  171. var label = {
  172. show: scope.panel.labels,
  173. radius: 2/3,
  174. formatter: function(label, series){
  175. return '<div "style="font-size:8pt;text-align:center;padding:2px;color:white;">'+
  176. series.info.alias+'<br/>'+Math.round(series.percent)+'%</div>';
  177. },
  178. threshold: 0.1
  179. }
  180. var pie = {
  181. series: {
  182. pie: {
  183. innerRadius: scope.panel.donut ? 0.45 : 0,
  184. tilt: scope.panel.tilt ? 0.45 : 1,
  185. radius: 1,
  186. show: true,
  187. combine: {
  188. color: '#999',
  189. label: 'The Rest'
  190. },
  191. label: label,
  192. stroke: {
  193. width: 0
  194. }
  195. }
  196. },
  197. //grid: { hoverable: true, clickable: true },
  198. grid: {
  199. backgroundColor: null,
  200. hoverable: true,
  201. clickable: true
  202. },
  203. legend: { show: false },
  204. colors: query.colors
  205. };
  206. // Populate element
  207. if(elem.is(":visible")){
  208. scripts.wait(function(){
  209. scope.plot = $.plot(elem, scope.data, pie);
  210. });
  211. }
  212. }
  213. function tt(x, y, contents) {
  214. var tooltip = $('#pie-tooltip').length ?
  215. $('#pie-tooltip') : $('<div id="pie-tooltip"></div>');
  216. tooltip.html(contents).css({
  217. position: 'absolute',
  218. top : y + 10,
  219. left : x + 10,
  220. color : "#c8c8c8",
  221. padding : '10px',
  222. 'font-size': '11pt',
  223. 'font-weight' : 200,
  224. 'background-color': '#1f1f1f',
  225. 'border-radius': '5px',
  226. }).appendTo("body");
  227. }
  228. elem.bind("plotclick", function (event, pos, object) {
  229. if (!object) {
  230. return;
  231. }
  232. if(scope.panel.mode === 'terms') {
  233. filterSrv.set({type:'terms',field:scope.panel.query.field,value:object.series.label})
  234. dashboard.refresh();
  235. }
  236. });
  237. elem.bind("plothover", function (event, pos, item) {
  238. if (item) {
  239. var percent = parseFloat(item.series.percent).toFixed(1) + "%";
  240. tt(pos.pageX, pos.pageY, "<div style='vertical-align:middle;display:inline-block;background:"+item.series.color+";height:15px;width:15px;border-radius:10px;'></div> " +
  241. (item.series.label||"")+ " " + percent);
  242. } else {
  243. $("#pie-tooltip").remove();
  244. }
  245. });
  246. }
  247. };
  248. })