module.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /*
  2. ## Better maps
  3. ### Parameters
  4. * size :: How many results to show, more results = slower
  5. * field :: field containing a 2 element array in the format [lon,lat]
  6. * tooltip :: field to extract the tool tip value from
  7. * spyable :: Show the 'eye' icon that reveals the last ES query
  8. */
  9. define([
  10. 'angular',
  11. 'app',
  12. 'underscore',
  13. './leaflet/leaflet-src',
  14. 'require',
  15. 'css!./module.css',
  16. 'css!./leaflet/leaflet.css',
  17. 'css!./leaflet/plugins.css'
  18. ],
  19. function (angular, app, _, L, localRequire) {
  20. 'use strict';
  21. var module = angular.module('kibana.panels.bettermap', []);
  22. app.useModule(module);
  23. module.controller('bettermap', function($scope, querySrv, dashboard, filterSrv) {
  24. $scope.panelMeta = {
  25. editorTabs : [
  26. {
  27. title: 'Queries',
  28. src: 'app/partials/querySelect.html'
  29. }
  30. ],
  31. modals : [
  32. {
  33. description: "Inspect",
  34. icon: "icon-info-sign",
  35. partial: "app/partials/inspector.html",
  36. show: $scope.panel.spyable
  37. }
  38. ],
  39. status : "Experimental",
  40. description : "Displays geo points in clustered groups on a map. The cavaet for this panel is"+
  41. " that, for better or worse, it does NOT use the terms facet and it <b>does</b> query "+
  42. "sequentially. This however means that it transfers more data and is generally heavier to"+
  43. " compute, while showing less actual data. If you have a time filter, it will attempt to"+
  44. " show to most recent points in your search, up to your defined limit"
  45. };
  46. // Set and populate defaults
  47. var _d = {
  48. queries : {
  49. mode : 'all',
  50. ids : []
  51. },
  52. size : 1000,
  53. spyable : true,
  54. tooltip : "_id",
  55. field : null
  56. };
  57. _.defaults($scope.panel,_d);
  58. $scope.requireContext = localRequire;
  59. // inorder to use relative paths in require calls, require needs a context to run. Without
  60. // setting this property the paths would be relative to the app not this context/file.
  61. $scope.init = function() {
  62. $scope.$on('refresh',function(){
  63. $scope.get_data();
  64. });
  65. $scope.get_data();
  66. };
  67. $scope.get_data = function(segment,query_id) {
  68. $scope.require(['./leaflet/plugins'], function () {
  69. $scope.panel.error = false;
  70. // Make sure we have everything for the request to complete
  71. if(dashboard.indices.length === 0) {
  72. return;
  73. }
  74. if(_.isUndefined($scope.panel.field)) {
  75. $scope.panel.error = "Please select a field that contains geo point in [lon,lat] format";
  76. return;
  77. }
  78. // Determine the field to sort on
  79. var timeField = _.uniq(_.pluck(filterSrv.getByType('time'),'field'));
  80. if(timeField.length > 1) {
  81. $scope.panel.error = "Time field must be consistent amongst time filters";
  82. } else if(timeField.length === 0) {
  83. timeField = null;
  84. } else {
  85. timeField = timeField[0];
  86. }
  87. var _segment = _.isUndefined(segment) ? 0 : segment;
  88. $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
  89. var queries = querySrv.getQueryObjs($scope.panel.queries.ids);
  90. var boolQuery = $scope.ejs.BoolQuery();
  91. _.each(queries,function(q) {
  92. boolQuery = boolQuery.should(querySrv.toEjsObj(q));
  93. });
  94. var request = $scope.ejs.Request().indices(dashboard.indices[_segment])
  95. .query($scope.ejs.FilteredQuery(
  96. boolQuery,
  97. filterSrv.getBoolFilter(filterSrv.ids).must($scope.ejs.ExistsFilter($scope.panel.field))
  98. ))
  99. .fields([$scope.panel.field,$scope.panel.tooltip])
  100. .size($scope.panel.size);
  101. if(!_.isNull(timeField)) {
  102. request = request.sort(timeField,'desc');
  103. }
  104. $scope.populate_modal(request);
  105. var results = request.doSearch();
  106. // Populate scope when we have results
  107. results.then(function(results) {
  108. $scope.panelMeta.loading = false;
  109. if(_segment === 0) {
  110. $scope.hits = 0;
  111. $scope.data = [];
  112. query_id = $scope.query_id = new Date().getTime();
  113. }
  114. // Check for error and abort if found
  115. if(!(_.isUndefined(results.error))) {
  116. $scope.panel.error = $scope.parse_error(results.error);
  117. return;
  118. }
  119. // Check that we're still on the same query, if not stop
  120. if($scope.query_id === query_id) {
  121. // Keep only what we need for the set
  122. $scope.data = $scope.data.slice(0,$scope.panel.size).concat(_.map(results.hits.hits, function(hit) {
  123. return {
  124. coordinates : new L.LatLng(hit.fields[$scope.panel.field][1],hit.fields[$scope.panel.field][0]),
  125. tooltip : hit.fields[$scope.panel.tooltip]
  126. };
  127. }));
  128. } else {
  129. return;
  130. }
  131. $scope.$emit('draw');
  132. // Get $size results then stop querying
  133. if($scope.data.length < $scope.panel.size && _segment+1 < dashboard.indices.length) {
  134. $scope.get_data(_segment+1,$scope.query_id);
  135. }
  136. });
  137. });
  138. };
  139. $scope.populate_modal = function(request) {
  140. $scope.inspector = angular.toJson(JSON.parse(request.toString()),true);
  141. };
  142. });
  143. module.directive('bettermap', function() {
  144. return {
  145. restrict: 'A',
  146. link: function(scope, elem, attrs) {
  147. elem.html('<center><img src="img/load_big.gif"></center>');
  148. // Receive render events
  149. scope.$on('draw',function(){
  150. render_panel();
  151. });
  152. scope.$on('render', function(){
  153. if(!_.isUndefined(map)) {
  154. map.invalidateSize();
  155. map.getPanes();
  156. }
  157. });
  158. var map, layerGroup;
  159. function render_panel() {
  160. scope.require(['./leaflet/plugins'], function () {
  161. scope.panelMeta.loading = false;
  162. L.Icon.Default.imagePath = 'app/panels/bettermap/leaflet/images';
  163. if(_.isUndefined(map)) {
  164. map = L.map(attrs.id, {
  165. scrollWheelZoom: false,
  166. center: [40, -86],
  167. zoom: 10
  168. });
  169. L.tileLayer('http://{s}.tile.cloudmade.com/57cbb6ca8cac418dbb1a402586df4528/22677/256/{z}/{x}/{y}.png', {
  170. maxZoom: 18,
  171. minZoom: 2
  172. }).addTo(map);
  173. layerGroup = new L.MarkerClusterGroup({maxClusterRadius:30});
  174. } else {
  175. layerGroup.clearLayers();
  176. }
  177. var markerList = [];
  178. _.each(scope.data, function(p) {
  179. if(!_.isUndefined(p.tooltip) && p.tooltip !== '') {
  180. markerList.push(L.marker(p.coordinates).bindLabel(p.tooltip));
  181. } else {
  182. markerList.push(L.marker(p.coordinates));
  183. }
  184. });
  185. layerGroup.addLayers(markerList);
  186. layerGroup.addTo(map);
  187. map.fitBounds(_.pluck(scope.data,'coordinates'));
  188. });
  189. }
  190. }
  191. };
  192. });
  193. });