panels.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. /*jshint globalstrict:true */
  2. /*global angular:true */
  3. 'use strict';
  4. /* NOTE: This is very much a preview, many things will change. In fact, this
  5. file will probably go away
  6. */
  7. /*
  8. METAPARAMETERS
  9. If you're implementing a panel, these are used by default. You need not handle
  10. them in your directive.
  11. span: The grid is made up of N rows, however there are only 12 columns. Span
  12. is a number, 1-12
  13. type: This is the name of your directive.
  14. */
  15. /*
  16. Histogram
  17. Draw a histogram of a single query
  18. NOTE: This will likely be renamed or get a setting that allows for non-time
  19. based keys. It may also be updated to allow multiple stacked or unstacked
  20. queries.
  21. query: query to execute
  22. interval: Bucket size in the standard Nunit (eg 1d, 5m, 30s) Attempts to auto
  23. scale itself based on timespan
  24. color: line/bar color.
  25. show: array of what to show, (eg ['bars','lines','points'])
  26. */
  27. /*
  28. Piequery
  29. Use a query facets to compare counts of for different queries, then show them
  30. on a pie chart
  31. queries: An array of queries
  32. donut: Make a hole in the middle?
  33. tilt: Tilt the pie in a 3dish way
  34. legend: Show it or not?
  35. colors: An array of colors to use for slices. These map 1-to-1 with the #
  36. of queries in your queries array
  37. */
  38. /* Pieterms
  39. Use a terms facet to calculate the most popular terms for a field
  40. query: Query to perform the facet on
  41. size: Limit to this many terms
  42. exclude: An array of terms to exclude from the results
  43. donut: Make a hole in the middle?
  44. tilt: Tilt the pie in a 3dish way
  45. legend: Show it or not?
  46. */
  47. /* Stackedquery
  48. Use date histograms to assemble stacked bar or line charts representing
  49. multple queries over time
  50. queries: An array of queries
  51. interval: Bucket size in the standard Nunit (eg 1d, 5m, 30s) Attempts to auto
  52. scale itself based on timespan
  53. colors: An array of colors to use for slices. These map 1-to-1 with the #
  54. of queries in your queries array
  55. show: array of what to show, (eg ['bars','lines','points'])
  56. */
  57. angular.module('kibana-dash.panels', [])
  58. .directive('histogram', function() {
  59. return {
  60. restrict: 'A',
  61. link: function(scope, elem, attrs) {
  62. // Specify defaults for ALL directives
  63. var _d = {
  64. query : "*",
  65. interval: secondsToHms(calculate_interval(scope.from,scope.to,40,0)/1000),
  66. color : "#27508C",
  67. show : ['bars']
  68. }
  69. // Set ready flag and fill parameters (REQUIRED IN EVERY PANEL)
  70. scope.$watch(function () {
  71. return (attrs.params && scope.index) ? true : false;
  72. }, function (ready) {
  73. scope.ready = ready;
  74. if(ready) {
  75. scope.params = JSON.parse(attrs.params);
  76. _.each(_d, function(v, k) {
  77. scope.params[k] = _.isUndefined(scope.params[k])
  78. ? _d[k] : scope.params[k];
  79. });
  80. }
  81. });
  82. // Also get the data if time frame changes.
  83. // (REQUIRED IN EVERY PANEL)
  84. scope.$watch(function() {
  85. return angular.toJson([scope.from, scope.to, scope.ready])
  86. }, function(){
  87. if(scope.ready)
  88. if (_.isUndefined(attrs.params.interval))
  89. scope.params.interval = secondsToHms(
  90. calculate_interval(scope.from,scope.to,50,0)/1000),
  91. get_data(scope,elem,attrs);
  92. });
  93. // Re-rending the panel if it is resized,
  94. scope.$watch('data', function() {
  95. render_panel(scope,elem,attrs);
  96. });
  97. // Or if the model changes
  98. angular.element(window).bind('resize', function(){
  99. render_panel(scope,elem,attrs);
  100. });
  101. // Function for getting data
  102. function get_data(scope,elem,attrs) {
  103. var params = scope.params;
  104. var ejs = scope.ejs;
  105. var request = ejs.Request().indices(scope.index);
  106. // Build the question part of the query
  107. var query = ejs.FilteredQuery(
  108. ejs.QueryStringQuery(params.query || '*'),
  109. ejs.RangeFilter(config.timefield)
  110. .from(scope.from)
  111. .to(scope.to)
  112. .cache(false)
  113. );
  114. // Then the insert into facet and make the request
  115. var results = request
  116. .facet(ejs.DateHistogramFacet('histogram')
  117. .field(config.timefield)
  118. .interval(params.interval)
  119. .facetFilter(ejs.QueryFilter(query))
  120. )
  121. .doSearch();
  122. // Populate scope when we have results
  123. results.then(function(results) {
  124. scope.hits = results.hits.total;
  125. scope.data = results.facets.histogram.entries;
  126. });
  127. }
  128. // Function for rendering panel
  129. function render_panel(scope,elem,attrs) {
  130. // Parse our params object
  131. var params = scope.params;
  132. // Determine format
  133. var show = _.isUndefined(params.show) ? {
  134. bars: true, lines: false, points: false
  135. } : {
  136. lines: _.indexOf(params.show,'lines') < 0 ? false : true,
  137. bars: _.indexOf(params.show,'bars') < 0 ? false : true,
  138. points: _.indexOf(params.show,'points') < 0 ? false : true,
  139. }
  140. // Push null values at beginning and end of timeframe
  141. scope.graph = [
  142. [scope.from.getTime(), null],[scope.to.getTime(), null]];
  143. // Create FLOT value array
  144. _.each(scope.data, function(v, k) {
  145. scope.graph.push([v['time'],v['count']])
  146. });
  147. // Set barwidth based on specified interval
  148. var barwidth = interval_to_seconds(params.interval)*1000
  149. // Populate element
  150. $.plot(elem, [{
  151. label: _.isUndefined(params.label) ? params.query: params.label,
  152. data: scope.graph
  153. }], {
  154. legend: {
  155. position: "nw",
  156. labelFormatter: function(label, series) {
  157. return '<span class="legend">' + label + ' / ' + params.interval
  158. + '</span>';
  159. }
  160. },
  161. series: {
  162. lines: { show: show.lines, fill: false },
  163. bars: { show: show.bars, fill: 1, barWidth: barwidth/1.8 },
  164. points: { show: show.points },
  165. color: params.color,
  166. shadowSize: 1
  167. },
  168. yaxis: { min: 0, color: "#000" },
  169. xaxis: {
  170. mode: "time",
  171. timeformat: "%H:%M:%S<br>%m-%d",
  172. label: "Datetime",
  173. color: "#000",
  174. },
  175. grid: {
  176. backgroundColor: '#fff',
  177. borderWidth: 0,
  178. borderColor: '#eee',
  179. color: "#eee",
  180. hoverable: true,
  181. }
  182. });
  183. //elem.show();
  184. }
  185. }
  186. };
  187. })
  188. .directive('pieterms', function() {
  189. return {
  190. restrict: 'A',
  191. link: function(scope, elem, attrs) {
  192. // Specify defaults for ALL directives
  193. var _d = {
  194. size : 5,
  195. query : "*",
  196. exclude : [],
  197. donut : false,
  198. tilt : false,
  199. legend : true,
  200. }
  201. // Set ready flag and fill parameters (REQUIRED IN EVERY PANEL)
  202. scope.$watch(function () {
  203. return (attrs.params && scope.index) ? true : false;
  204. }, function (ready) {
  205. scope.ready = ready;
  206. if(ready) {
  207. scope.params = JSON.parse(attrs.params);
  208. _.each(_d, function(v, k) {
  209. scope.params[k] = _.isUndefined(scope.params[k])
  210. ? _d[k] : scope.params[k];
  211. });
  212. }
  213. });
  214. // Also get the data if time frame changes.
  215. // (REQUIRED IN EVERY PANEL)
  216. scope.$watch(function() {
  217. return angular.toJson([scope.from, scope.to, scope.ready])
  218. }, function(){
  219. if(scope.ready)
  220. get_data(scope,elem,attrs);
  221. });
  222. // Re-rending the panel if it is resized,
  223. scope.$watch('data', function() {
  224. render_panel(scope,elem,attrs);
  225. });
  226. // Or if the model changes
  227. angular.element(window).bind('resize', function(){
  228. render_panel(scope,elem,attrs);
  229. });
  230. // Function for getting data
  231. function get_data(scope,elem,attrs) {
  232. var params = scope.params;
  233. var ejs = scope.ejs;
  234. var request = ejs.Request().indices(scope.index);
  235. // Build the question part of the query
  236. var query = ejs.FilteredQuery(
  237. ejs.QueryStringQuery(params.query || '*'),
  238. ejs.RangeFilter(config.timefield)
  239. .from(scope.from)
  240. .to(scope.to)
  241. .cache(false)
  242. );
  243. // Then the insert into facet and make the request
  244. var results = request
  245. .facet(ejs.TermsFacet('termpie')
  246. .field(params.field)
  247. .size(params['size'])
  248. .exclude(params.exclude)
  249. .facetFilter(ejs.QueryFilter(query))
  250. )
  251. .doSearch();
  252. // Populate scope when we have results
  253. results.then(function(results) {
  254. scope.hits = results.hits.total;
  255. scope.data = results.facets.termpie.terms;
  256. });
  257. }
  258. // Function for rendering panel
  259. function render_panel(scope,elem,attrs) {
  260. // Parse our params object
  261. var params = scope.params;
  262. // Create graph array
  263. scope.graph = [];
  264. _.each(scope.data, function(v, k) {
  265. if(!_.isUndefined(params.only) && _.indexOf(params.only,v['term']) < 0)
  266. return
  267. var point = {
  268. label : v['term'],
  269. data : v['count']
  270. }
  271. if(!_.isUndefined(params.colors))
  272. point.color = params.colors[_.indexOf(params.only,v['term'])]
  273. scope.graph.push(point)
  274. });
  275. var pie = {
  276. series: {
  277. pie: {
  278. innerRadius: params.donut ? 0.4 : 0,
  279. tilt: params.tilt ? 0.45 : 1,
  280. radius: 1,
  281. show: true,
  282. combine: {
  283. color: '#999',
  284. label: 'The Rest'
  285. },
  286. label: {
  287. show: true,
  288. radius: 2/3,
  289. formatter: function(label, series){
  290. return '<div style="font-size:8pt;text-align:center;padding:2px;color:white;">'+
  291. label+'<br/>'+Math.round(series.percent)+'%</div>';
  292. },
  293. threshold: 0.1
  294. }
  295. }
  296. },
  297. //grid: { hoverable: true, clickable: true },
  298. legend: { show: params.legend }
  299. };
  300. // Populate element
  301. $.plot(elem, scope.graph, pie);
  302. //elem.show();
  303. }
  304. }
  305. };
  306. })
  307. .directive('piequery', function() {
  308. return {
  309. restrict: 'A',
  310. link: function(scope, elem, attrs) {
  311. // Specify defaults for ALL directives
  312. var _d = {
  313. queries : ["*"],
  314. donut : false,
  315. tilt : false,
  316. legend : true,
  317. }
  318. // Set ready flag and fill parameters (REQUIRED IN EVERY PANEL)
  319. scope.$watch(function () {
  320. return (attrs.params && scope.index) ? true : false;
  321. }, function (ready) {
  322. scope.ready = ready;
  323. if(ready) {
  324. scope.params = JSON.parse(attrs.params);
  325. _.each(_d, function(v, k) {
  326. scope.params[k] = _.isUndefined(scope.params[k])
  327. ? _d[k] : scope.params[k];
  328. });
  329. }
  330. });
  331. // Also get the data if time frame changes.
  332. // (REQUIRED IN EVERY PANEL)
  333. scope.$watch(function() {
  334. return angular.toJson([scope.from, scope.to, scope.ready])
  335. }, function(){
  336. if(scope.ready)
  337. get_data(scope,elem,attrs);
  338. });
  339. // Re-rending the panel if it is resized,
  340. scope.$watch('data', function() {
  341. render_panel(scope,elem,attrs);
  342. });
  343. // Or if the model changes
  344. angular.element(window).bind('resize', function(){
  345. render_panel(scope,elem,attrs);
  346. });
  347. // Function for getting data
  348. function get_data(scope,elem,attrs) {
  349. var params = scope.params;
  350. var ejs = scope.ejs;
  351. var request = ejs.Request().indices(scope.index);
  352. var queries = [];
  353. // Build the question part of the query
  354. _.each(params.queries, function(v) {
  355. queries.push(ejs.FilteredQuery(
  356. ejs.QueryStringQuery(v || '*'),
  357. ejs.RangeFilter(config.timefield)
  358. .from(scope.from)
  359. .to(scope.to)
  360. .cache(false))
  361. )
  362. });
  363. _.each(queries, function(v) {
  364. request = request.facet(ejs.QueryFacet(_.indexOf(queries,v))
  365. .query(v)
  366. .facetFilter(ejs.QueryFilter(v))
  367. )
  368. })
  369. // Then the insert into facet and make the request
  370. var results = request.doSearch();
  371. // Populate scope when we have results
  372. results.then(function(results) {
  373. scope.hits = results.hits.total;
  374. scope.data = results.facets;
  375. });
  376. }
  377. // Function for rendering panel
  378. function render_panel(scope,elem,attrs) {
  379. // Parse our params object
  380. var params = scope.params;
  381. // Create graph array
  382. scope.graph = [];
  383. _.each(scope.data, function(v, k) {
  384. var point = {
  385. label : params.queries[k],
  386. data : v['count']
  387. }
  388. if(!_.isUndefined(params.colors))
  389. point.color = params.colors[k%params.colors.length];
  390. scope.graph.push(point)
  391. });
  392. // Populate element
  393. $.plot(elem, scope.graph, {
  394. series: {
  395. pie: {
  396. innerRadius: params.donut ? 0.4 : 0,
  397. tilt: params.tilt ? 0.45 : 1,
  398. radius: 1,
  399. show: true,
  400. combine: {
  401. color: '#999',
  402. label: 'The Rest'
  403. },
  404. label: {
  405. show: true,
  406. radius: 2/3,
  407. formatter: function(label, series){
  408. return '<div style="font-size:8pt;text-align:center;padding:2px;color:white;">'+
  409. label+'<br/>'+Math.round(series.percent)+'%</div>';
  410. },
  411. threshold: 0.1
  412. }
  413. }
  414. },
  415. //grid: { hoverable: true, clickable: true },
  416. legend: { show: params.legend }
  417. });
  418. //elem.show();
  419. }
  420. }
  421. };
  422. })
  423. .directive('stackedquery', function() {
  424. return {
  425. restrict: 'A',
  426. link: function(scope, elem, attrs) {
  427. // Specify defaults for ALL directives
  428. var _d = {
  429. queries : ["*"],
  430. interval: secondsToHms(calculate_interval(scope.from,scope.to,40,0)/1000),
  431. colors : ["#BF3030","#1D7373","#86B32D","#A98A21","#411F73"],
  432. show : ['bars']
  433. }
  434. // Set ready flag and fill parameters (REQUIRED IN EVERY PANEL)
  435. scope.$watch(function () {
  436. return (attrs.params && scope.index) ? true : false;
  437. }, function (ready) {
  438. scope.ready = ready;
  439. if(ready) {
  440. scope.params = JSON.parse(attrs.params);
  441. _.each(_d, function(v, k) {
  442. scope.params[k] = _.isUndefined(scope.params[k])
  443. ? _d[k] : scope.params[k];
  444. });
  445. }
  446. });
  447. // Also get the data if time frame changes.
  448. // (REQUIRED IN EVERY PANEL)
  449. scope.$watch(function() {
  450. return angular.toJson([scope.from, scope.to, scope.ready])
  451. }, function(){
  452. if(scope.ready)
  453. if (_.isUndefined(attrs.params.interval))
  454. scope.params.interval = secondsToHms(
  455. calculate_interval(scope.from,scope.to,50,0)/1000),
  456. get_data(scope,elem,attrs);
  457. });
  458. // Re-rending the panel if it is resized,
  459. scope.$watch('data', function() {
  460. render_panel(scope,elem,attrs);
  461. });
  462. // Or if the model changes
  463. angular.element(window).bind('resize', function(){
  464. render_panel(scope,elem,attrs);
  465. });
  466. // Function for getting data
  467. function get_data(scope,elem,attrs) {
  468. var params = scope.params;
  469. var ejs = scope.ejs;
  470. var request = ejs.Request().indices(scope.index);
  471. // Build the question part of the query
  472. var queries = [];
  473. _.each(params.queries, function(v) {
  474. queries.push(ejs.FilteredQuery(
  475. ejs.QueryStringQuery(v || '*'),
  476. ejs.RangeFilter(config.timefield)
  477. .from(scope.from)
  478. .to(scope.to)
  479. .cache(false))
  480. )
  481. });
  482. // Build the facet part
  483. _.each(queries, function(v) {
  484. request = request
  485. .facet(ejs.DateHistogramFacet(_.indexOf(queries,v))
  486. .field(config.timefield)
  487. .interval(params.interval)
  488. .facetFilter(ejs.QueryFilter(v))
  489. )
  490. })
  491. // Then run it
  492. var results = request.doSearch();
  493. // Populate scope when we have results
  494. results.then(function(results) {
  495. scope.hits = results.hits.total;
  496. scope.data = results.facets;
  497. });
  498. }
  499. // Function for rendering panel
  500. function render_panel(scope,elem,attrs) {
  501. // Parse our params object
  502. var params = scope.params;
  503. // Determine format
  504. var show = _.isUndefined(params.show) ? {
  505. bars: true, lines: false, points: false, fill: false
  506. } : {
  507. lines: _.indexOf(params.show,'lines') < 0 ? false : true,
  508. bars: _.indexOf(params.show,'bars') < 0 ? false : true,
  509. points: _.indexOf(params.show,'points') < 0 ? false : true,
  510. fill: _.indexOf(params.show,'fill') < 0 ? false : true
  511. }
  512. scope.graph = [];
  513. // Push null values at beginning and end of timeframe
  514. _.each(scope.data, function(v, k) {
  515. var series = {};
  516. var data = [[scope.from.getTime(), null]];
  517. _.each(v.entries, function(v, k) {
  518. data.push([v['time'],v['count']])
  519. });
  520. data.push([scope.to.getTime(), null])
  521. series.data = {
  522. label: params.queries[k],
  523. data: data,
  524. color: params.colors[k%params.colors.length]
  525. };
  526. scope.graph.push(series.data)
  527. });
  528. // Set barwidth based on specified interval
  529. var barwidth = interval_to_seconds(params.interval)*1000
  530. // Populate element
  531. $.plot(elem, scope.graph, {
  532. legend: {
  533. position: "nw",
  534. labelFormatter: function(label, series) {
  535. return '<span class="legend">' + label + ' / ' + params.interval
  536. + '</span>';
  537. }
  538. },
  539. series: {
  540. stack: 0,
  541. lines: { show: show.lines, fill: show.fill },
  542. bars: { show: show.bars, fill: 1, barWidth: barwidth/1.8 },
  543. points: { show: show.points },
  544. color: params.color,
  545. shadowSize: 1
  546. },
  547. yaxis: { min: 0, color: "#000" },
  548. xaxis: {
  549. mode: "time",
  550. timeformat: "%H:%M:%S<br>%m-%d",
  551. label: "Datetime",
  552. color: "#000",
  553. },
  554. grid: {
  555. backgroundColor: '#fff',
  556. borderWidth: 0,
  557. borderColor: '#eee',
  558. color: "#eee",
  559. hoverable: true,
  560. }
  561. });
  562. //elem.show();
  563. }
  564. }
  565. };
  566. });