Pārlūkot izejas kodu

Added trend panel, moved index calculation to service, made chart backgrounds transparent

Rashid Khan 12 gadi atpakaļ
vecāks
revīzija
bf456c864e

+ 10 - 10
common/css/bootstrap.dark.min.css

@@ -226,7 +226,7 @@ body {
   font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
   font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
   font-size: 14px;
   font-size: 14px;
   line-height: 21px;
   line-height: 21px;
-  color: #c8c8c8;
+  color: #ddd;
   background-color: #272b30;
   background-color: #272b30;
 }
 }
 
 
@@ -1096,7 +1096,7 @@ input[type="color"],
   margin-bottom: 10.5px;
   margin-bottom: 10.5px;
   font-size: 14px;
   font-size: 14px;
   line-height: 21px;
   line-height: 21px;
-  color: #c8c8c8;
+  color: #fff;
   vertical-align: middle;
   vertical-align: middle;
   -webkit-border-radius: 4px;
   -webkit-border-radius: 4px;
      -moz-border-radius: 4px;
      -moz-border-radius: 4px;
@@ -1129,8 +1129,8 @@ input[type="search"],
 input[type="tel"],
 input[type="tel"],
 input[type="color"],
 input[type="color"],
 .uneditable-input {
 .uneditable-input {
-  background-color: #222;
-  border: 1px solid #333;
+  background-color: #666;
+  border: 1px solid #666;
   -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
   -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
      -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
      -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
           box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
           box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
@@ -1197,8 +1197,8 @@ input[type="file"] {
 
 
 select {
 select {
   width: 220px;
   width: 220px;
-  background-color: #222;
-  border: 1px solid #333;
+  background-color: #666;
+  border: 1px solid #666;
 }
 }
 
 
 select[multiple],
 select[multiple],
@@ -5201,8 +5201,8 @@ input[type="submit"].btn.btn-mini {
   top: 10%;
   top: 10%;
   left: 50%;
   left: 50%;
   z-index: 1050;
   z-index: 1050;
-  width: 560px;
-  margin-left: -280px;
+  width: 760px;
+  margin-left: -380px;
   background-color: #ffffff;
   background-color: #ffffff;
   border: 1px solid #999;
   border: 1px solid #999;
   border: 1px solid rgba(0, 0, 0, 0.3);
   border: 1px solid rgba(0, 0, 0, 0.3);
@@ -5228,7 +5228,7 @@ input[type="submit"].btn.btn-mini {
 }
 }
 
 
 .modal.fade.in {
 .modal.fade.in {
-  top: 10%;
+  top: 10px;
 }
 }
 
 
 .modal-header {
 .modal-header {
@@ -5247,7 +5247,7 @@ input[type="submit"].btn.btn-mini {
 
 
 .modal-body {
 .modal-body {
   position: relative;
   position: relative;
-  max-height: 400px;
+  max-height: 500px;
   padding: 15px;
   padding: 15px;
   overflow-y: auto;
   overflow-y: auto;
 }
 }

+ 8 - 0
common/css/main.css

@@ -131,6 +131,14 @@
   font-weight: bold;
   font-weight: bold;
 }
 }
 
 
+.normal {
+  font-weight: normal;
+}
+
+.light {
+  font-weight: 200;
+}
+
 .input-append label {
 .input-append label {
   font-size: inherit !important;
   font-size: inherit !important;
 }
 }

+ 1 - 1
config.js

@@ -22,6 +22,6 @@ var config = new Settings(
   kibana_index:     "kibana-int", 
   kibana_index:     "kibana-int", 
   modules:          ['histogram','map','pie','table','stringquery','sort',
   modules:          ['histogram','map','pie','table','stringquery','sort',
                     'timepicker','text','fields','hits','dashcontrol',
                     'timepicker','text','fields','hits','dashcontrol',
-                    'column','derivequeries'],
+                    'column','derivequeries','trends'],
   }
   }
 );
 );

+ 79 - 0
js/services.js

@@ -83,6 +83,85 @@ angular.module('kibana.services', [])
   return fields;
   return fields;
 
 
 })
 })
+.service('kbnIndex',function($http) {
+    // returns a promise containing an array of all indices matching the index
+  // pattern that exist in a given range
+  this.indices = function(from,to,pattern,interval) {
+    var possible = [];
+    _.each(expand_range(fake_utc(from),fake_utc(to),interval),function(d){
+      possible.push(d.format(pattern));
+    });
+
+    return all_indices().then(function(p) {
+      var indices = _.intersection(possible,p);
+      indices.reverse();
+      return indices
+    })
+  };
+
+  // returns a promise containing an array of all indices in an elasticsearch
+  // cluster
+  function all_indices() {
+    var something = $http({
+      url: config.elasticsearch + "/_aliases",
+      method: "GET"
+    }).error(function(data, status, headers, config) {
+      // Handle error condition somehow?
+    });
+
+    return something.then(function(p) {
+      var indices = [];
+      _.each(p.data, function(v,k) {
+        indices.push(k)
+      });
+      return indices;
+    });
+  }
+
+  // this is stupid, but there is otherwise no good way to ensure that when
+  // I extract the date from an object that I'm get the UTC date. Stupid js.
+  // I die a little inside every time I call this function.
+  // Update: I just read this again. I died a little more inside.
+  // Update2: More death.
+  function fake_utc(date) {
+    date = moment(date).clone().toDate()
+    return moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000));
+  }
+
+  // Create an array of date objects by a given interval
+  function expand_range(start, end, interval) {
+    if(_.contains(['hour','day','week','month','year'],interval)) {
+      var range;
+      start = moment(start).clone();
+      range = [];
+      while (start.isBefore(end)) {
+        range.push(start.clone());
+        switch (interval) {
+        case 'hour':
+          start.add('hours',1)
+          break
+        case 'day':
+          start.add('days',1)
+          break
+        case 'week':
+          start.add('weeks',1)
+          break
+        case 'month':
+          start.add('months',1)
+          break
+        case 'year':
+          start.add('years',1)
+          break
+        }
+      }
+      range.push(moment(end).clone());
+      return range;
+    } else {
+      return false;
+    }
+  }
+})
+
 .service('timer', function($timeout) {
 .service('timer', function($timeout) {
   // This service really just tracks a list of $timeout promises to give us a
   // This service really just tracks a list of $timeout promises to give us a
   // method for cancelling them all when we need to
   // method for cancelling them all when we need to

+ 2 - 2
panels/histogram/editor.html

@@ -25,7 +25,7 @@
       <label class="small">Query</label>
       <label class="small">Query</label>
       <form class="input-append" style="margin-bottom: 0px">
       <form class="input-append" style="margin-bottom: 0px">
         <input type="text" placeholder="New Query" style="width:80%" ng-model="newquery">
         <input type="text" placeholder="New Query" style="width:80%" ng-model="newquery">
-        <button class="btn" ng-click="add_query(newlabel,newquery);newlabel='';newquery=''"><i class="icon-plus"></i></button>
+        <button class="btn" ng-click="add_query(newlabel,newquery);newlabel='';newquery='';set_refresh(true)"><i class="icon-plus"></i></button>
       </form>
       </form>
     </div>
     </div>
     <div class="span1">
     <div class="span1">
@@ -34,7 +34,7 @@
   <div class="row-fluid" ng-repeat="q in panel.query">        
   <div class="row-fluid" ng-repeat="q in panel.query">        
     <div class="span3">
     <div class="span3">
       <form style="margin-bottom: 0px">
       <form style="margin-bottom: 0px">
-        <input type="text" style="width:70%" ng-model="q.label">
+        <input type="text" style="width:70%" ng-model="q.label" ng-change="set_refresh(true)">
       </form>
       </form>
     </div>
     </div>
     <div class="span8">
     <div class="span8">

+ 1 - 1
panels/histogram/module.js

@@ -315,7 +315,7 @@ angular.module('kibana.histogram', [])
                 color: '#ccc'
                 color: '#ccc'
               },
               },
               grid: {
               grid: {
-                backgroundColor: '#272b30',
+                backgroundColor: null,
                 borderWidth: 0,
                 borderWidth: 0,
                 borderColor: '#eee',
                 borderColor: '#eee',
                 color: "#eee",
                 color: "#eee",

+ 3 - 3
panels/hits/editor.html

@@ -38,7 +38,7 @@
       <form class="input-append" style="margin-bottom: 0px">
       <form class="input-append" style="margin-bottom: 0px">
         <label class="small">Query</label>
         <label class="small">Query</label>
         <input type="text" placeholder="New Query" style="width:80%" ng-model="newquery">
         <input type="text" placeholder="New Query" style="width:80%" ng-model="newquery">
-        <button class="btn" ng-click="add_query(newlabel,newquery);newlabel='';newquery=''"><i class="icon-plus"></i></button>
+        <button class="btn" ng-click="add_query(newlabel,newquery);newlabel='';newquery='';set_refresh(true)"><i class="icon-plus"></i></button>
       </form>
       </form>
     </div>
     </div>
     <div class="span1">
     <div class="span1">
@@ -47,12 +47,12 @@
   <div class="row-fluid" ng-repeat="q in panel.query">        
   <div class="row-fluid" ng-repeat="q in panel.query">        
     <div class="span3">
     <div class="span3">
       <form style="margin-bottom: 0px">
       <form style="margin-bottom: 0px">
-        <input type="text" style="width:70%" ng-model="q.label">
+        <input type="text" style="width:70%" ng-model="q.label" ng-change="set_refresh(true)">
       </form>
       </form>
     </div>
     </div>
     <div class="span8">
     <div class="span8">
       <form class="input-append" style="margin-bottom: 0px">
       <form class="input-append" style="margin-bottom: 0px">
-        <input type="text" style="width:80%" ng-model="q.query">
+        <input type="text" style="width:80%" ng-model="q.query" ng-change="set_refresh(true)">
         <button class="btn" ng-click="get_data()"><i class="icon-search"></i></button>
         <button class="btn" ng-click="get_data()"><i class="icon-search"></i></button>
       </form>
       </form>
     </div>
     </div>

+ 11 - 0
panels/hits/module.js

@@ -141,6 +141,17 @@ angular.module('kibana.hits', [])
     $scope.get_data();
     $scope.get_data();
   }
   }
 
 
+  $scope.set_refresh = function (state) { 
+    $scope.refresh = state; 
+  }
+
+  $scope.close_edit = function() {
+    if($scope.refresh)
+      $scope.get_data();
+    $scope.refresh =  false;
+    $scope.$emit('render');
+  }
+
   function set_time(time) {
   function set_time(time) {
     $scope.time = time;
     $scope.time = time;
     $scope.index = _.isUndefined(time.index) ? $scope.index : time.index
     $scope.index = _.isUndefined(time.index) ? $scope.index : time.index

+ 4 - 4
panels/map/module.js

@@ -145,7 +145,7 @@ angular.module('kibana.map', [])
             map: scope.panel.map,
             map: scope.panel.map,
             regionStyle: {initial: {fill: '#8c8c8c'}},
             regionStyle: {initial: {fill: '#8c8c8c'}},
             zoomOnScroll: false,
             zoomOnScroll: false,
-            backgroundColor: '#272b30',
+            backgroundColor: null,
             series: {
             series: {
               regions: [{
               regions: [{
                 values: scope.data,
                 values: scope.data,
@@ -157,9 +157,9 @@ angular.module('kibana.map', [])
               $('.jvectormap-label').css({
               $('.jvectormap-label').css({
                 "position"    : "absolute",
                 "position"    : "absolute",
                 "display"     : "none",
                 "display"     : "none",
-                'color'   : "#c8c8c8",
-                'padding' : '10px',
-                'font-size': '11pt',
+                'color'       : "#c8c8c8",
+                'padding'     : '10px',
+                'font-size'   : '11pt',
                 'font-weight' : 200,
                 'font-weight' : 200,
                 'background-color': '#1f1f1f',
                 'background-color': '#1f1f1f',
                 'border-radius': '5px'
                 'border-radius': '5px'

+ 1 - 2
panels/pie/module.js

@@ -246,14 +246,13 @@ angular.module('kibana.pie', [])
               },
               },
               label: label,
               label: label,
               stroke: {
               stroke: {
-                color: '#272b30',
                 width: 0
                 width: 0
               }
               }
             }
             }
           },
           },
           //grid: { hoverable: true, clickable: true },
           //grid: { hoverable: true, clickable: true },
           grid:   { 
           grid:   { 
-            backgroundColor: '#272b30',
+            backgroundColor: null,
             hoverable: true, 
             hoverable: true, 
             clickable: true 
             clickable: true 
           },
           },

+ 12 - 83
panels/timepicker/module.js

@@ -28,7 +28,7 @@
 */
 */
 
 
 angular.module('kibana.timepicker', [])
 angular.module('kibana.timepicker', [])
-.controller('timepicker', function($scope, eventBus, $timeout, timer, $http) {
+.controller('timepicker', function($scope, eventBus, $timeout, timer, $http, kbnIndex) {
 
 
   // Set and populate defaults
   // Set and populate defaults
   var _d = {
   var _d = {
@@ -89,7 +89,7 @@ angular.module('kibana.timepicker', [])
     // request one be sent by broadcasting a 'get_time' with its _id to its group
     // request one be sent by broadcasting a 'get_time' with its _id to its group
     // This panel can handle multiple groups
     // This panel can handle multiple groups
     eventBus.register($scope,"get_time", function(event,id) {
     eventBus.register($scope,"get_time", function(event,id) {
-      eventBus.broadcast($scope.$id,id,'time',unmoment($scope.time))
+      eventBus.broadcast($scope.$id,id,'time',compile_time($scope.time))
     });
     });
 
 
     // In case some other panel broadcasts a time, set us to an absolute range
     // In case some other panel broadcasts a time, set us to an absolute range
@@ -208,13 +208,17 @@ angular.module('kibana.timepicker', [])
     // Get indices for the time period, then broadcast time range and index list
     // Get indices for the time period, then broadcast time range and index list
     // in a single object. Not sure if I like this.
     // in a single object. Not sure if I like this.
     if($scope.panel.index_interval !== 'none') {
     if($scope.panel.index_interval !== 'none') {
-      indices($scope.time.from,$scope.time.to).then(function (p) {
+      kbnIndex.indices($scope.time.from,
+        $scope.time.to,
+        $scope.panel.index,
+        $scope.panel.index_interval
+      ).then(function (p) {
         $scope.time.index = p;
         $scope.time.index = p;
-        eventBus.broadcast($scope.$id,$scope.panel.group,'time',unmoment($scope.time))
+        eventBus.broadcast($scope.$id,$scope.panel.group,'time',compile_time($scope.time))
       });
       });
     } else {
     } else {
       $scope.time.index = [$scope.panel.index];
       $scope.time.index = [$scope.panel.index];
-      eventBus.broadcast($scope.$id,$scope.panel.group,'time',unmoment($scope.time))
+      eventBus.broadcast($scope.$id,$scope.panel.group,'time',compile_time($scope.time))
     }
     }
 
 
     // Update panel's string representation of the time object.Don't update if
     // Update panel's string representation of the time object.Don't update if
@@ -233,10 +237,12 @@ angular.module('kibana.timepicker', [])
 
 
   // Prefer to pass around Date() objects in the EventBus since interacting with
   // Prefer to pass around Date() objects in the EventBus since interacting with
   // moment objects in libraries that are expecting Date()s can be tricky
   // moment objects in libraries that are expecting Date()s can be tricky
-  function unmoment(time) {
+  function compile_time(time) {
     time = _.clone(time)
     time = _.clone(time)
     time.from = time.from.toDate()
     time.from = time.from.toDate()
     time.to   = time.to.toDate()
     time.to   = time.to.toDate()
+    time.interval = $scope.panel.index_interval
+    time.pattern = $scope.panel.index 
     return time;
     return time;
   }
   }
 
 
@@ -254,81 +260,4 @@ angular.module('kibana.timepicker', [])
     }
     }
   }
   }
 
 
-  // returns a promise containing an array of all indices matching the index
-  // pattern that exist in a given range
-  function indices(from,to) {
-    var possible = [];
-    _.each(expand_range(fake_utc(from),fake_utc(to),$scope.panel.index_interval),function(d){
-      possible.push(d.format($scope.panel.index));
-    });
-
-    return all_indices().then(function(p) {
-      var indices = _.intersection(possible,p);
-      indices.reverse();
-      return indices.length == 0 ? [$scope.panel.defaultindex] : indices;
-    })
-  };
-
-  // returns a promise containing an array of all indices in an elasticsearch
-  // cluster
-  function all_indices() {
-    var something = $http({
-      url: config.elasticsearch + "/_aliases",
-      method: "GET"
-    }).error(function(data, status, headers, config) {
-      $scope.error = status;
-    });
-
-    return something.then(function(p) {
-      var indices = [];
-      _.each(p.data, function(v,k) {
-        indices.push(k)
-      });
-      return indices;
-    });
-  }
-
-  // this is stupid, but there is otherwise no good way to ensure that when
-  // I extract the date from an object that I'm get the UTC date. Stupid js.
-  // I die a little inside every time I call this function.
-  // Update: I just read this again. I died a little more inside.
-  // Update2: More death.
-  function fake_utc(date) {
-    date = date.clone().toDate()
-    return moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000));
-  }
-
-  // Create an array of date objects by a given interval
-  function expand_range(start, end, interval) {
-    if(_.contains(['hour','day','week','month','year'],interval)) {
-      var range;
-      start = start.clone();
-      range = [];
-      while (start.isBefore(end)) {
-        range.push(start.clone());
-        switch (interval) {
-        case 'hour':
-          start.add('hours',1)
-          break
-        case 'day':
-          start.add('days',1)
-          break
-        case 'week':
-          start.add('weeks',1)
-          break
-        case 'month':
-          start.add('months',1)
-          break
-        case 'year':
-          start.add('years',1)
-          break
-        }
-      }
-      range.push(end.clone());
-      return range;
-    } else {
-      return false;
-    }
-  }
-
 })
 })

+ 64 - 0
panels/trends/editor.html

@@ -0,0 +1,64 @@
+<div>
+  <div class="row-fluid">
+    <div class="span12">
+      The trends panel will give you a percentage representation of how your query
+      has moved in your current timespan compared a specified amount of time ago. For
+      example, if the time is 1:10pm, your time picker was set to "Last 10m", and the 
+      "Time Ago" parameter was set to '1h', the panel would show how much the query 
+      results have changed since 12:00-12:10pm
+    </div>
+  </div>
+  <h4>Settings</h4>
+  <div class="row-fluid">
+    <div class="span3" ng-hide='panel.auto_int'> 
+      <label class="small">Use Elasticsearch date math format here (eg 1m, 5m, 1d, 2w, 1y)</label>
+    </div>
+    <div class="span3">
+       <label class="small">Time Ago</label>
+        <input type="text" class="input-small" ng-model="panel.ago">
+    </div>
+    <div class="span2">
+      <label class="small">Font Size</label> 
+      <select class="input-small" 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="span3">
+      <label class="small" >List Format</label> 
+      <select class="input-small" ng-model="panel.arrangement" ng-options="f for f in ['horizontal','vertical']"></select></span>
+    </div>
+  </div>
+
+  <h5>Queries</h5>    
+  <div class="row-fluid">
+    <div class="span3">
+      <form style="margin-bottom: 0px">
+       <label class="small">Label</label>
+        <input type="text" placeholder="New Label" style="width:70%" ng-model="newlabel">
+      </form>
+    </div>
+    <div class="span8">
+      <form class="input-append" style="margin-bottom: 0px">
+        <label class="small">Query</label>
+        <input type="text" placeholder="New Query" style="width:80%" ng-model="newquery">
+        <button class="btn" ng-click="add_query(newlabel,newquery);newlabel='';newquery='';set_refresh(true)"><i class="icon-plus"></i></button>
+      </form>
+    </div>
+    <div class="span1">
+    </div>
+  </div>
+  <div class="row-fluid" ng-repeat="q in panel.query">        
+    <div class="span3">
+      <form style="margin-bottom: 0px">
+        <input type="text" style="width:70%" ng-model="q.label" ng-change="set_refresh(true)">
+      </form>
+    </div>
+    <div class="span8">
+      <form class="input-append" style="margin-bottom: 0px">
+        <input type="text" style="width:80%" ng-model="q.query" ng-change="set_refresh(true)">
+        <button class="btn" ng-click="get_data()"><i class="icon-search"></i></button>
+      </form>
+    </div>
+    <div class="span1">
+      <i class="icon-remove pointer" ng-click="remove_query(q)"></i>
+    </div>
+  </div>
+</div>

+ 10 - 0
panels/trends/module.html

@@ -0,0 +1,10 @@
+<kibana-panel ng-controller='trends' ng-init="init()">
+
+  <div ng-style="panel.style" style="line-height:{{panel.style['font-size']}};display:inline-block;padding-right: 5px;" ng-repeat="query in data">
+    <span ng-class="{'text-success': query.hits.new >= query.hits.old, 'text-error': query.hits.old > query.hits.new}" class='strong'>
+      <i class='large' ng-class="{'icon-caret-up': query.hits.new >= query.hits.old, 'icon-caret-down': query.hits.old > query.hits.new}"></i> {{query.percent}}% 
+    </span>
+    <span class="tiny pointer light" bs-tooltip="'Then: '+query.hits.old+', Now: '+query.hits.new">({{query.label}})</span>
+    <br ng-show="panel.arrangement == 'vertical'">
+  </div>
+</kibana-panel>         

+ 207 - 0
panels/trends/module.js

@@ -0,0 +1,207 @@
+/*
+
+  ## Hits
+
+  A variety of representations of the hits a query matches
+
+  ### Parameters
+  * query ::  An array of queries. No labels here, just an array of strings. Maybe
+              there should be labels. Probably. 
+  * style :: A hash of css styles
+  * arrangement :: How should I arrange the query results? 'horizontal' or 'vertical'
+  * ago :: Date math formatted time to look back
+  ### Group Events
+  #### Sends
+  * get_time :: On panel initialization get time range to query
+  #### Receives
+  * time :: An object containing the time range to use and the index(es) to query
+  * query :: An Array of queries, even if its only one
+
+*/
+angular.module('kibana.trends', [])
+.controller('trends', function($scope, eventBus, kbnIndex) {
+
+  // Set and populate defaults
+  var _d = {
+    query   : ["*"],
+    group   : "default",
+    style   : { "font-size": '14pt'},
+    ago     : '1d',
+    arrangement : 'vertical',
+  }
+  _.defaults($scope.panel,_d)
+
+  $scope.init = function () {
+    $scope.hits = 0;
+    eventBus.register($scope,'time', function(event,time){
+      set_time(time)
+    });
+    eventBus.register($scope,'query', function(event, query) {
+      $scope.panel.query = _.map(query,function(q) {
+        return {query: q, label: q};
+      })
+      $scope.get_data();
+    });
+    // Now that we're all setup, request the time from our group
+    eventBus.broadcast($scope.$id,$scope.panel.group,'get_time')
+  }
+
+  $scope.get_data = function(segment,query_id) {
+    delete $scope.panel.error
+    $scope.panel.loading = true;
+
+    // Make sure we have everything for the request to complete
+    if(_.isUndefined($scope.index) || _.isUndefined($scope.time))
+      return
+
+    $scope.old_time = {
+      from : new Date($scope.time.from.getTime() - interval_to_seconds($scope.panel.ago)*1000),
+      to   : new Date($scope.time.to.getTime() - interval_to_seconds($scope.panel.ago)*1000)
+    }
+
+    var _segment = _.isUndefined(segment) ? 0 : segment
+    var request = $scope.ejs.Request();
+
+    // Build the question part of the query
+    var queries = [];
+    _.each($scope.panel.query, function(v) {
+      queries.push($scope.ejs.FilteredQuery(
+        ejs.QueryStringQuery(v.query || '*'),
+        ejs.RangeFilter($scope.time.field)
+          .from($scope.time.from)
+          .to($scope.time.to))
+      )
+    });
+
+    // Build the facet part
+    _.each(queries, function(v) {
+      request = request
+        .facet($scope.ejs.QueryFacet("new"+_.indexOf(queries,v))
+          .query(v)
+        ).size(0)
+    })
+
+    var queries = [];
+    _.each($scope.panel.query, function(v) {
+      queries.push($scope.ejs.FilteredQuery(
+        ejs.QueryStringQuery(v.query || '*'),
+        ejs.RangeFilter($scope.time.field)
+          .from($scope.old_time.from)
+          .to($scope.old_time.to))
+      )
+    });
+
+    // Build the facet part
+    _.each(queries, function(v) {
+      request = request
+        .facet($scope.ejs.QueryFacet("old"+_.indexOf(queries,v))
+          .query(v)
+        ).size(0)
+    })
+
+    // TODO: Spy for hits panel
+    //$scope.populate_modal(request);
+
+    // If we're on the first segment we need to get our indices
+    if (_segment == 0) {
+      kbnIndex.indices(
+        $scope.old_time.from,
+        $scope.old_time.to,
+        $scope.time.pattern,
+        $scope.time.interval
+      ).then(function (p) {
+        $scope.index = _.union(p,$scope.index);
+        process_results(request.indices($scope.index[_segment]).doSearch());
+      });
+    } else {
+      process_results(request.indices($scope.index[_segment]).doSearch());
+    }
+
+    // Populate scope when we have results
+    function process_results(results) { 
+      results.then(function(results) {
+
+        $scope.panel.loading = false;
+        if(_segment == 0) {
+          $scope.hits = {};
+          $scope.data = [];
+          query_id = $scope.query_id = new Date().getTime();
+        }
+        
+        // Check for error and abort if found
+        if(!(_.isUndefined(results.error))) {
+          $scope.panel.error = $scope.parse_error(results.error);
+          return;
+        }
+        if($scope.query_id === query_id) {
+          var i = 0;
+          _.each($scope.panel.query, function(k) {
+            var n = results.facets['new'+i].count
+            var o = results.facets['old'+i].count
+
+            var hits = {
+              new : _.isUndefined($scope.data[i]) || _segment == 0 ? n : $scope.data[i].hits.new+n,        
+              old : _.isUndefined($scope.data[i]) || _segment == 0 ? o : $scope.data[i].hits.old+o
+            }
+            
+            $scope.hits.new += n;
+            $scope.hits.old += o;
+
+            var percent = Math.round(percentage(hits.old,hits.new)*100)/100
+            // Create series
+            $scope.data[i] = { 
+              label: $scope.panel.query[i].label || "query"+(parseInt(i)+1), 
+              hits: {
+                new : hits.new,
+                old : hits.old
+              },
+              percent: _.isNull(percent) ? 0 : percent
+            };
+
+            i++;
+          });
+          $scope.$emit('render');
+          if(_segment < $scope.index.length-1) 
+            $scope.get_data(_segment+1,query_id)
+          
+        }
+      });
+    }
+
+  }
+
+  function percentage(x,y) {
+    return 100*(y-x)/x
+  }
+
+  $scope.remove_query = function(q) {
+    $scope.panel.query = _.without($scope.panel.query,q);
+    $scope.get_data();
+  }
+
+  $scope.add_query = function(label,query) {
+    $scope.panel.query.unshift({
+      query: query,
+      label: label, 
+    });
+    $scope.get_data();
+  }
+
+  $scope.set_refresh = function (state) { 
+    $scope.refresh = state; 
+  }
+
+  $scope.close_edit = function() {
+    if($scope.refresh)
+      $scope.get_data();
+    $scope.refresh =  false;
+    $scope.$emit('render');
+  }
+
+  function set_time(time) {
+    $scope.time = time;
+    $scope.index = time.index || $scope.index
+    $scope.get_data();
+  }
+
+})