From 11cc06c49549ebae5d97cc2fd32f735378d15e97 Mon Sep 17 00:00:00 2001 From: Andrew T Date: Tue, 1 Apr 2014 13:01:45 +0700 Subject: [PATCH 01/23] Add dummy module to show how to create a custom module. --- src/app/panels/dummy/editor.html | 5 ++ src/app/panels/dummy/module.html | 3 + src/app/panels/dummy/module.js | 101 +++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 src/app/panels/dummy/editor.html create mode 100644 src/app/panels/dummy/module.html create mode 100644 src/app/panels/dummy/module.js diff --git a/src/app/panels/dummy/editor.html b/src/app/panels/dummy/editor.html new file mode 100644 index 000000000..2e1333d8d --- /dev/null +++ b/src/app/panels/dummy/editor.html @@ -0,0 +1,5 @@ +
+
+ Do Something Here. +
+
\ No newline at end of file diff --git a/src/app/panels/dummy/module.html b/src/app/panels/dummy/module.html new file mode 100644 index 000000000..3dcd744d7 --- /dev/null +++ b/src/app/panels/dummy/module.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/src/app/panels/dummy/module.js b/src/app/panels/dummy/module.js new file mode 100644 index 000000000..f7247de7b --- /dev/null +++ b/src/app/panels/dummy/module.js @@ -0,0 +1,101 @@ +/* + ## Dummy module + * For demonstartion on how to create a custom Banana module. +*/ +define([ + 'angular', + 'app', + 'underscore', + 'jquery' +], +function (angular, app, _, $) { + 'use strict'; + + var module = angular.module('kibana.panels.dummy', []); + app.useModule(module); + + module.controller('dummy', function($scope, dashboard, querySrv, filterSrv) { + $scope.panelMeta = { + status : "Beta", + description : "Dummy module for demonstartion" + }; + + var _d = { + foo : 'bar' + }; + + // Set panel's default values + _.defaults($scope.panel,_d); + + $scope.init = function() { + $scope.$on('refresh',function(){ + $scope.get_data(); + }); + $scope.get_data(); + }; + + $scope.get_data = function() { + + $scope.panelMeta.loading = true; + + var request, results; + // Set Solr server + $scope.sjs.client.server(dashboard.current.solr.server + dashboard.current.solr.core_name); + + request = $scope.sjs.Request(); + + // Construct Solr query + // ... + // $scope.panel.queries.query = 'q=*:*'; + + // Then assign it to request + // request = request.setQuery($scope.panel.queries.query); + + // Execute the search and get results + // results = request.doSearch(); + + // Populate scope when we have results + // results.then(function(results) { + // Parse the results and store in $scope.data + // Then emit 'render' event at the end + // $scope.$emit('render'); + // }); + + }; + + $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.render = function() { + $scope.$emit('render'); + }; + + module.directive('dummyTag', function() { + return { + restrict: 'E', + link: function(scope, element) { + scope.$on('render',function(){ + render_panel(); + }); + + // Function for rendering panel + function render_panel() { + element.html('Hello Dummy!'); + } + + render_panel(); + } + }; + }); + + }); +}); \ No newline at end of file From 125693588ff512c9b310d155e2f88805d72feb62 Mon Sep 17 00:00:00 2001 From: Andrew T Date: Wed, 2 Apr 2014 16:35:35 +0700 Subject: [PATCH 02/23] Edit README to announce v1.1. Edit dashboards/default.json and guided.json with pointers on how to get started. Add silklogo.jpg to src/img --- README.md | 34 +++-- src/app/dashboards/default.json | 75 ++++++++-- src/app/dashboards/guided.json | 242 ++++++++++++++++++++++---------- src/img/silklogo.jpg | Bin 0 -> 20901 bytes 4 files changed, 251 insertions(+), 100 deletions(-) create mode 100644 src/img/silklogo.jpg diff --git a/README.md b/README.md index a2b9e2cb6..3faab83d3 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,24 @@ __NOTE__: You have reached the Banana repository. The Banana project is a fork of Kibana 3, which can be found at [http://three.kibana.org](http://three.kibana.org) -The goal is to port the code to work with Apache Solr as a backend storage. +The goal is to port the code to work with time series data stored in Apache Solr. ## IMPORTANT -Pull the repo from the "release" branch; version 1.0 is tagged as banana-1.0. +Pull the repo from the "release" branch; version 1.1 will be tagged as banana-1.1. + +## Banana 1.1 is here! + +We have added a number of exciting new features and fixed key issues, including: +* You can now add a Filtering panel that supports global filter queries (fq's). Now, if you click on a facet in the terms panel, the results will be filtered for that particular value. +* The terms, histogram and table modules allow you to specify a panel-specific filter query (within the Query Tab while configuring the panel) allowing greater flexibility in designing dashboards. +* The inspector icon on these panels shows the Solr query, which is very useful for debugging dashboards. +* The Histogram module allows you to plot values in addition to counts. It also allows you to group values by another field. This would be useful if for example you plot CPU utilization over time and want to group by hostname. +* The sort operation in the Table module is now fixed and works correctly on single-valued fields. +* We have refactored the code to enable easier addition of new modules and fixes to existing modules. + +### Changes to your dashboards +If you created dashboards for Banana 1.0, you did not have a global filtering panel. In some cases, these filter values can be implicitly set to defaults that may lead to strange search results. We recommend updating your old dashboards by adding a filtering panel. A good way to do it visually is to put the filtering panel on its own row and hide it when it is not needed. ## Overview @@ -34,14 +47,14 @@ Browse to http://localhost:8983/solr/banana/src/index.html#/dashboard If your Solr port is different, edit banana/src/config.js and enter the port you are using. -If you have not created the data collections and ingested data into Solr, you will see an error message saying "No Index found at .." Go to the Solr Output Plug-in for LogStash Page (https://github.com/LucidWorks/solrlogmanager) to learn how to import data into your Solr instance using LogStash +If you have not created the data collections and ingested data into Solr, you will see an error message saying "Collection not found at .." Go to the Solr Output Plug-in for LogStash Page (https://github.com/LucidWorks/solrlogmanager) to learn how to import data into your Solr instance using LogStash -If you want to save and load dashboards from Solr, copy either solr-4.4.0/kibana-int or solr-4.5.0/kibana-int directories (as appropriate) into $SOLR_HOME/example/solr in order to setup the required core and restart Solr. +If you want to save and load dashboards from Solr, copy either solr-4.4.0/kibana-int (for Solr 4.4) or solr-4.5.0/kibana-int (for Solr 4.5 and above) directories (as appropriate) into $SOLR_HOME/example/solr in order to setup the required core. Then restart Solr, and if it is not loaded already, load the core from Solr Admin (if you are using Solr Cloud, you will need to upload the #### QuickStart for Complete SLK Stack -We have packaged Solr, the open Source LogStash with Solr Output Plug-in and Banana, along with example collections and dashboards to make it easy for you to get started. The package is available here (Link to be added). Unzip the package and: +We have packaged Solr, the open Source LogStash with Solr Output Plug-in and Banana, along with example collections and dashboards to make it easy for you to get started. The package is available here at http://www.lucidworks.com/lucidworks-silk/. Unzip the package and: 1. Run Solr cd slk4.7.0/solr-4.7.0/example-logs @@ -52,11 +65,11 @@ Browse to http://localhost:8983/banana You will see example dashboards which you can use as a starting point for your applications. Once again, if you choose to run Solr on a different port, please edit the config.js file. -THAT'S IT! +THAT'S IT! Now you have a baseline that you can use to build your own applications. #### Running from a war file -Pull the repo from the "release" branch; version 1.0 is tagged as banana1.0. Run "ant" from within the banana directory to build the war file. +Pull the repo from the "release" branch; version 1.1 will be tagged as banana-1.1. Run "ant" from within the banana directory to build the war file. cd $BANANA_REPO_HOME ant @@ -93,14 +106,15 @@ __A__: The simplest solution is to use a nginx reverse proxy (See for example ht _Q_: The timestamp field in my data is different from "event_timestamp". How do I modify my dashboards to account for this? _A_: By default, Banana assumes that the time field is "event_timestamp" If your time field is different you will just need to manually change it in ALL panels. We are reviewing this design (of allowing different time fields for different panels) and may move to one time field for all panels, based on user feedback. +_Q_ : Can I use banana for non-time series data? +_A_: Currently, we are focused on time series data and the dashboards will nto work correctly without a time picker and without configuring the time field in the terms, histogram and table panles. However, in many cases you can use the following workaround, if you are at least storing a timestamp indicating the indexing time of the document: +Create a time picker panel in its own row and move the row to the bottom. Select the time field and then set the date range to be "Since 01/01/2000" (or starting even earlier if necessary). Use the same time field in the other terms panels, and you can get beautiful visualizations of facets. + ### Support Banana preserves most of the features of Kibana (from which it is forked). If you have any questions, please contact Andrew Thanalertvisuti (andrew.thanalertvisuti@lucidworks.com) or Ravi Krishnamurthy (ravi.krishnamurthy@lucidworks.com). -Introduction videos on Kibana can be found at [http://three.kibana.org/about.html](http://three.kibana.org/about.html) - - ###Trademarks Kibana is a trademark of Elasticsearch BV diff --git a/src/app/dashboards/default.json b/src/app/dashboards/default.json index 519b2e048..8f190a44d 100755 --- a/src/app/dashboards/default.json +++ b/src/app/dashboards/default.json @@ -1,5 +1,5 @@ { - "title": "Banana for Solr", + "title": "Welcome Syslog Demo", "services": { "query": { "idQueue": [ @@ -7,7 +7,7 @@ ], "list": { "0": { - "query": "*:*", + "query": "error", "alias": "", "color": "#7EB26D", "id": 0, @@ -25,8 +25,8 @@ ], "list": { "0": { - "from": "2014-02-06T20:42:49.790Z", - "to": "2014-02-07T20:42:49.790Z", + "from": "2014-04-01T07:47:30.228Z", + "to": "2014-04-02T07:47:30.228Z", "field": "event_timestamp", "type": "time", "mandate": "must", @@ -88,9 +88,50 @@ "loadingEditor": false, "query": "*:*", "pinned": true, - "history": [], + "history": [ + "error" + ], "remember": 10, - "title": "Search" + "title": "Search", + "def_type": "" + } + ] + }, + { + "title": "", + "height": "100px", + "editable": true, + "collapse": false, + "collapsable": true, + "panels": [ + { + "error": false, + "span": 12, + "editable": true, + "type": "text", + "loadingEditor": false, + "status": "Stable", + "mode": "markdown", + "content": "This is the demonstration dashboard for Banana, a port of Kibana for Solr. This points to a collection called logstash\\_logs. If you followed the QuickStart instructions provided by the Solr Output Writer for LogStash, you will see example data here. \n\nTo build your own dashboards, first get time series data into a collection. Then you can begin with this dashboard and configure it to fit your needs.", + "style": {}, + "title": "Welcome to Banana" + } + ] + }, + { + "title": "Filter", + "height": "150px", + "editable": true, + "collapse": true, + "collapsable": true, + "panels": [ + { + "error": false, + "span": 12, + "editable": true, + "type": "filtering", + "loadingEditor": false, + "title": "Filter" } ] }, @@ -134,7 +175,7 @@ "ids": [ 0 ], - "query": "q=*:*", + "query": "q=error&df=message&df=host&df=path&df=type&wt=json&rows=0&fq=event_timestamp:[2014-04-01T07:47:30.228Z%20TO%202014-04-02T07:47:30.228Z]&facet=true&facet.range=event_timestamp&facet.range.start=2014-04-01T07:47:30.228Z/DAY&facet.range.end=2014-04-02T07:47:30.228Z%2B1DAY/DAY&facet.range.gap=%2B10MINUTE", "custom": "" }, "title": "System Log Messages", @@ -157,7 +198,9 @@ "tooltip": { "value_type": "cumulative", "query_as_alias": false - } + }, + "group_field": null, + "error": false }, { "error": false, @@ -170,7 +213,7 @@ "ids": [ 0 ], - "query": "q=*:*", + "query": "q=error&df=message&df=host&df=path&df=type&wt=json&rows=0&fq=event_timestamp:[2014-04-01T07:47:30.228Z%20TO%202014-04-02T07:47:30.228Z]&facet=true&facet.field=syslog_program&facet.range=event_timestamp&facet.range.start=2014-04-01T07:47:30.228Z&facet.range.end=2014-04-02T07:47:30.228Z&facet.range.gap=%2B1DAY&facet.limit=10", "custom": "" }, "field": "syslog_program", @@ -189,7 +232,8 @@ "chart": "pie", "counter_pos": "none", "spyable": true, - "title": "Programs" + "title": "Programs", + "time_field": "event_timestamp" } ] }, @@ -209,7 +253,7 @@ "default" ], "type": "table", - "size": 100, + "size": 10, "pages": 100, "offset": 0, "sort": [ @@ -235,13 +279,14 @@ "ids": [ 0 ], - "query": "q=*:*", + "query": "q=error&df=message&df=host&df=path&df=type&wt=json&rows=1000&fq=event_timestamp:[2014-04-01T07:47:30.228Z%20TO%202014-04-02T07:47:30.228Z]&sort=event_timestamp desc", "custom": "" }, "field_list": true, "status": "Stable", "trimFactor": 300, - "normTimes": true + "normTimes": true, + "time_field": "event_timestamp" } ] } @@ -268,5 +313,9 @@ "load_elasticsearch_size": 20, "load_local": true, "hide": false + }, + "solr": { + "server": "http://localhost:8983/solr/", + "core_name": "logstash_logs" } } \ No newline at end of file diff --git a/src/app/dashboards/guided.json b/src/app/dashboards/guided.json index 7bc0d23da..76a2aa21f 100755 --- a/src/app/dashboards/guided.json +++ b/src/app/dashboards/guided.json @@ -1,5 +1,5 @@ { - "title": "Your Basic Dashboard", + "title": "Basic Dashboard With Pointers", "services": { "query": { "idQueue": [ @@ -24,17 +24,29 @@ }, "filter": { "idQueue": [ - 0, 1, 2 ], - "list": {}, - "ids": [] + "list": { + "0": { + "from": "2014-04-02T05:07:22.216Z", + "to": "2014-04-02T05:22:22.216Z", + "field": "event_timestamp", + "type": "time", + "mandate": "must", + "active": true, + "alias": "", + "id": 0 + } + }, + "ids": [ + 0 + ] } }, "rows": [ { - "title": "Options", + "title": "Overview", "height": "50px", "editable": true, "collapse": false, @@ -42,7 +54,19 @@ "panels": [ { "error": false, - "span": 8, + "span": 6, + "editable": true, + "type": "text", + "loadingEditor": false, + "status": "Stable", + "mode": "markdown", + "content": "You may be seeing a message that says Solr is not reachable or that the collection is not found. Click on _Configure Dashboard_ (cog icon) on the top right and set the Solr server and collection. By default, we have configured the dashboard to point to server _http://localhost:8983_ and collection _collection1_. You can also choose how many rows you want to have in the dashboard. \n\nTo configure what panels appear in a specific row, click on the _Configure Row_ (cog icon) at the far left of the row.\n\nEach panel can be configured by clicking on _Configure_ (cog icon) near the top right of the panel (just to the left of the panel type). The span of the panel determines its width; each row has width 12, and each panel can take up all or part of this span.\n\nAfter reading these _text panels_ (which are useful for presenting information about a dashboard), you can delete them by clicking on the \"x\" towards the top right of the panel.", + "style": {}, + "title": "Configure Dashboard" + }, + { + "error": false, + "span": 6, "editable": true, "group": [ "default" @@ -50,22 +74,55 @@ "type": "text", "status": "Stable", "mode": "markdown", - "content": "If you have a field with a timestamp in it, you might want to add a 'timepicker' panel here. Click the plus sign over to the right to do so. You can also remove these information text panels there by clicking the 'x' icon above", + "content": "In the row below, we have put in a _time picker_ and a _search bar._ Banana is primarily designed for time series data, and we expect every dashboard to have a time picker. You will also almost always have a search bar for user searches. \n\nBelow that there is a hidden row that contains a _filtering_ module, which is used to configure global filter queries. You will almost certainly want to have one. Clicking on any facet in the terms module will filter results by that value. You can modify each filter once it is created; you can change the value and/or choose between \"must\", \"must not\" and \"either.\"\n\nClick on the right-facing triangle to the far left of the row to _Expand Row_. You can click on the upward-facing triangle on any row in order to _Hide Row._", "style": {}, - "title": "Have a timestamp somewhere?" + "title": "Timestamps, Queries and Filters" } ] }, { - "title": "Query", + "title": "Query and Time Window", "height": "50px", "editable": true, "collapse": false, "collapsable": true, "panels": [ + { + "error": "", + "span": 6, + "editable": true, + "type": "timepicker", + "loadingEditor": false, + "status": "Stable", + "mode": "relative", + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d", + "90d", + "1y", + "5y" + ], + "timespan": "15m", + "timefield": "event_timestamp", + "timeformat": "", + "refresh": { + "enable": false, + "interval": 30, + "min": 3 + }, + "filter_id": 0, + "title": "Time Window" + }, { "error": false, - "span": 5, + "span": 6, "editable": true, "group": [ "default" @@ -78,21 +135,8 @@ "remember": 10, "pinned": true, "query": "*", - "title": "Search" - }, - { - "error": false, - "span": 7, - "editable": true, - "group": [ - "default" - ], - "type": "text", - "status": "Stable", - "mode": "markdown", - "content": "See the *Filters* bar to the below? Click it to expand the filters row. Right now there are none. click on one of the icons in the document types list to filter down to only that document type", - "style": {}, - "title": "About filters" + "title": "Search", + "def_type": "" } ] }, @@ -115,47 +159,95 @@ ] }, { - "title": "Graph", - "height": "250px", + "title": "Facets, Histogram and Table", + "height": "150px", "editable": true, "collapse": false, "collapsable": true, "panels": [ { "error": false, - "span": 3, + "span": 12, "editable": true, "group": [ "default" ], - "type": "terms", + "type": "text", + "status": "Stable", + "mode": "markdown", + "content": "Without knowing about your data, I cannot fully configure the panels I have provided below. However, I have provided you with some starting points, assuming that your time field is \"event_timestamp\" and that there is a field called \"message\" that you wish to facet on in order to view the top terms that appear in the \"message\" field and their frequency. If there is no data, these panels will be empty, and may give an error.\n\nThe _histogram_ panel allows you to plot either _counts_ or a specific field's (integer) _values_ across time. If you go to _Configure_, the panel allows you to set the type of chart and what variable is plotted (if choosing the _values_ option). Moreover, when plotting values, you can specify a _group by_ field which will produce multiple charts. You can modify the time window for the entire page from within the histogram panel.\n\nThe _terms_ panel is great for visualizing facets--as pie charts, bar charts or tables. Clicking on a term will create a global filter query restricting the result set (across all panels within the page) to the field value that is selected. You could have many such panels depending on the number of fields you choose to facet on.\n\nThe *table* panel at the bottom provides you a detailed view of search results. It has attempted to list your fields to the left; select a few to view them in the table. You can sort by any field. Click on a particular row to expand the resulting document that was returned.", + "style": {}, + "title": "Facets, Histogram and Table" + } + ] + }, + { + "title": "Graph", + "height": "250px", + "editable": true, + "collapse": false, + "collapsable": true, + "panels": [ + { + "span": 6, + "editable": true, + "type": "histogram", + "loadingEditor": false, + "mode": "count", + "time_field": "event_timestamp", "queries": { "mode": "all", "ids": [ 0 - ] + ], + "query": "q=*&df=message&df=host&df=path&df=type&wt=json&rows=0&fq=event_timestamp:[2014-04-02T05:07:22.000Z%20TO%202014-04-02T05:22:22.000Z]&facet=true&facet.range=event_timestamp&facet.range.start=2014-04-02T05:07:22.000Z/DAY&facet.range.end=2014-04-02T05:22:22.000Z%2B1DAY/DAY&facet.range.gap=%2B10SECOND", + "custom": "" }, - "field": "_type", - "exclude": [], - "missing": true, - "other": true, - "size": 100, - "order": "count", - "style": { - "font-size": "10pt" + "max_rows": 100000, + "value_field": null, + "group_field": null, + "auto_int": true, + "resolution": 100, + "interval": "10s", + "intervals": [ + "auto", + "1s", + "1m", + "5m", + "10m", + "30m", + "1h", + "3h", + "12h", + "1d", + "1w", + "1M", + "1y" + ], + "fill": 0, + "linewidth": 3, + "timezone": "browser", + "spyable": true, + "zoomlinks": true, + "bars": true, + "stack": true, + "points": false, + "lines": false, + "legend": true, + "x-axis": true, + "y-axis": true, + "percentage": false, + "interactive": true, + "options": true, + "tooltip": { + "value_type": "cumulative", + "query_as_alias": false }, - "donut": false, - "tilt": false, - "labels": true, - "arrangement": "horizontal", - "chart": "pie", - "counter_pos": "none", - "title": "Document types", - "spyable": true + "title": "Event Counts" }, { "error": false, - "span": 3, + "span": 6, "editable": true, "group": [ "default" @@ -165,39 +257,27 @@ "mode": "all", "ids": [ 0 - ] + ], + "query": "q=*&df=message&df=host&df=path&df=type&wt=json&rows=0&fq=event_timestamp:[2014-04-02T05:07:22.000Z%20TO%202014-04-02T05:22:22.000Z]&facet=true&facet.field=message&facet.range=event_timestamp&facet.range.start=2014-04-02T05:07:22.000Z&facet.range.end=2014-04-02T05:22:22.000Z&facet.range.gap=%2B1DAY&facet.limit=100" }, - "field": "_type", + "field": "message", "exclude": [], - "missing": true, - "other": true, - "size": 10, + "missing": false, + "other": false, + "size": 100, "order": "count", "style": { "font-size": "10pt" }, - "donut": false, - "tilt": false, - "labels": true, + "donut": true, + "tilt": true, + "labels": false, "arrangement": "horizontal", - "chart": "table", - "counter_pos": "above", + "chart": "pie", + "counter_pos": "none", + "title": "Message Terms", "spyable": true, - "title": "Document Types" - }, - { - "error": false, - "span": 6, - "editable": true, - "group": [ - "default" - ], - "type": "text", - "status": "Stable", - "mode": "markdown", - "content": "It's the best I can do without knowing much about your data! I've tried to pick some sane defaults for you. The two *terms* panels to the left of this *text* panel show a breakdown of your document type. \n\nKibana is currently configured to point at the special Elasticsearch *_all* index. You can change that by clicking on the cog icon in the navigation bar at the top. You can also add rows from that dialog. You can edit individual panels by click on the cog icon on the panel you want to edit\n\nThe *table* panel below has attempted to list your fields to the left, select a few to view them in the table. To add more panels, of different types, click the cog on the row label to the far left", - "style": {}, - "title": "The most generic dashboard ever" + "time_field": "event_timestamp" } ] }, @@ -220,14 +300,16 @@ "pages": 5, "offset": 0, "sort": [ - "_id", + "id", "desc" ], "style": { "font-size": "9pt" }, "overflow": "min-height", - "fields": [], + "fields": [ + "message" + ], "highlight": [], "sortable": true, "header": true, @@ -237,13 +319,15 @@ "mode": "all", "ids": [ 0 - ] + ], + "query": "q=*&df=message&df=host&df=path&df=type&wt=json&rows=500&fq=event_timestamp:[2014-04-02T05:07:22.000Z%20TO%202014-04-02T05:22:22.000Z]&sort=id desc" }, "field_list": true, "status": "Stable", "trimFactor": 300, "normTimes": true, - "title": "Documents" + "title": "Events", + "time_field": "event_timestamp" } ] } @@ -254,7 +338,7 @@ "pattern": "[logstash-]YYYY.MM.DD", "default": "_all" }, - "style": "dark", + "style": "light", "failover": false, "panel_hints": true, "loader": { @@ -270,5 +354,9 @@ "load_elasticsearch_size": 20, "load_local": true, "hide": false + }, + "solr": { + "server": "http://localhost:8983/solr/", + "core_name": "collection1" } } \ No newline at end of file diff --git a/src/img/silklogo.jpg b/src/img/silklogo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5c5c06835b9b9c64af3b875385b3c05ce0bf57fc GIT binary patch literal 20901 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 OD4+%JNNA#fKmXYbl0+H+ literal 0 HcmV?d00001 From e2c0a4da8e1807d39aa7beffbed3e7d393b74604 Mon Sep 17 00:00:00 2001 From: Andrew T Date: Fri, 4 Apr 2014 17:00:18 +0700 Subject: [PATCH 03/23] Add map module. --- src/app/panels/map/editor.html | 28 +++++----- src/app/panels/map/module.js | 93 ++++++++++++++++++++++++++++++---- src/config.js | 7 +-- 3 files changed, 102 insertions(+), 26 deletions(-) diff --git a/src/app/panels/map/editor.html b/src/app/panels/map/editor.html index 95f2912e3..a3bfc481f 100755 --- a/src/app/panels/map/editor.html +++ b/src/app/panels/map/editor.html @@ -1,15 +1,15 @@ -
-
-
-
Field 2 letter country or state code
- -
-
-
-
Max Maximum countries to plot
- -
-
Map
- -
+
+
+
+
Field 2 letter country or state code
+ +
+
+
Max Maximum countries to plot
+ +
+
Map
+ +
+
diff --git a/src/app/panels/map/module.js b/src/app/panels/map/module.js index 8d1d23739..1884d47c2 100755 --- a/src/app/panels/map/module.js +++ b/src/app/panels/map/module.js @@ -8,7 +8,7 @@ element array, jquerymap will generate shades between these colors * size :: How big to make the facet. Higher = more countries * exclude :: Exlude the array of counties - * spyable :: Show the 'eye' icon that reveals the last ES query + * spyable :: Show the 'eye' icon that reveals the last Solr query * index_limit :: This does nothing yet. Eventually will limit the query to the first N indices @@ -19,7 +19,6 @@ define([ 'app', 'underscore', 'jquery', - 'config', './lib/jquery.jvectormap.min' ], function (angular, app, _, $) { @@ -44,14 +43,16 @@ function (angular, app, _, $) { status : "Stable", description : "Displays a map of shaded regions using a field containing a 2 letter country "+ ", or US state, code. Regions with more hit are shaded darker. Node that this does use the"+ - " Elasticsearch terms facet, so it is important that you set it to the correct field." + " Solr terms facet, so it is important that you set it to the correct field." }; // Set and populate defaults var _d = { queries : { mode : 'all', - ids : [] + ids : [], + query : '*:*', + custom : '' }, map : "world", colors : ['#A0E2E2', '#265656'], @@ -67,6 +68,18 @@ function (angular, app, _, $) { $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'); + }; + $scope.get_data = function() { // Make sure we have everything for the request to complete @@ -77,7 +90,7 @@ function (angular, app, _, $) { var request; - request = $scope.ejs.Request().indices(dashboard.indices); + request = $scope.sjs.Request().indices(dashboard.indices); $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries); // This could probably be changed to a BoolFilter @@ -100,16 +113,78 @@ function (angular, app, _, $) { $scope.populate_modal(request); + // Build Solr query + var start_time = new Date(filterSrv.list[0].from).toISOString(); + var end_time = new Date(filterSrv.list[0].to).toISOString(); + // Get time field from filterSrv, is the time field always at list[0]? + var time_field = filterSrv.list[0].field; + var fq = '&fq=' + time_field + ':[' + start_time + '%20TO%20' + end_time + ']'; // Get timefield from filterSrv + var df = '&df=message'; + var wt_json = '&wt=json'; + var rows_limit = '&rows=0'; // for map module, we don't display results from row, but we use facets. + // var facet_gap = '%2B1DAY'; + var facet = '&facet=true' + + '&facet.field=' + $scope.panel.field + + '&facet.limit=' + $scope.panel.size; + var filter_fq = ''; + var filter_either = []; + + // Apply filters to the query + _.each(filterSrv.list, function(v,k) { + // Skip the timestamp filter because it's already applied to the query using fq param. + // timestamp filter should be in k = 0 + if (k > 0 && v.field != time_field && v.active) { + if (DEBUG) { console.log('terms: k=',k,' v=',v); } + if (v.mandate == 'must') { + filter_fq = filter_fq + '&fq=' + v.field + ':"' + v.value + '"'; + } else if (v.mandate == 'mustNot') { + filter_fq = filter_fq + '&fq=-' + v.field + ':"' + v.value + '"'; + } else if (v.mandate == 'either') { + filter_either.push(v.field + ':"' + v.value + '"'); + } + } + }); + // parse filter_either array values, if exists + if (filter_either.length > 0) { + filter_fq = filter_fq + '&fq=(' + filter_either.join(' OR ') + ')'; + } + + // Set the panel's query + $scope.panel.queries.query = 'q=' + querySrv.list[0].query + df + wt_json + fq + rows_limit + facet + filter_fq; + + // Set the additional custom query + if ($scope.panel.queries.custom != null) { + // request = request.customQuery($scope.panel.queries.custom); + request = request.setQuery($scope.panel.queries.query + $scope.panel.queries.custom); + } else { + request = request.setQuery($scope.panel.queries.query); + } + + console.debug('map: $scope.panel=',$scope.panel); + var results = request.doSearch(); // Populate scope when we have results results.then(function(results) { $scope.panelMeta.loading = false; - $scope.hits = results.hits.total; + // $scope.hits = results.hits.total; + $scope.hits = results.response.numFound; $scope.data = {}; - _.each(results.facets.map.terms, function(v) { - $scope.data[v.term.toUpperCase()] = v.count; - }); + // _.each(results.facets.map.terms, function(v) { + // $scope.data[v.term.toUpperCase()] = v.count; + // }); + console.debug('map: results=',results); + + var terms = results.facet_counts.facet_fields[$scope.panel.field]; + + if ($scope.hits > 0) { + for (var i=0; i < terms.length; i += 2) { + if (terms[i+1] > 0) { + $scope.data[terms[i]] = terms[i+1]; + } + }; + } + $scope.$emit('render'); }); }; diff --git a/src/config.js b/src/config.js index 0ca02c53c..aa628b3c1 100755 --- a/src/config.js +++ b/src/config.js @@ -39,10 +39,10 @@ function (Settings) { */ panel_names: [ 'histogram', - // 'map', // TODO + 'map', // 'pie', // Deprecated, use terms panel instead 'table', - 'filtering', // TODO + 'filtering', 'timepicker', 'text', // 'fields', // Deprecated, table panel now integrates a field selector @@ -53,7 +53,8 @@ function (Settings) { // 'trends', // TODO // 'bettermap', // TODO 'query', - 'terms' + 'terms', + // 'dummy' // Dummy module for testing ] }); }); From 673f10812435e9e7ba832b361f1cd7ec5776e6f8 Mon Sep 17 00:00:00 2001 From: Andrew T Date: Thu, 10 Apr 2014 11:46:09 +0700 Subject: [PATCH 04/23] Fix filtering bugs in map, table, terms, and histogram modules. Optimize code by moving functions to filterSrv. Remove some hard coded. --- src/app/panels/histogram/module.js | 61 ++++++++++--------- src/app/panels/map/module.js | 79 ++++++++++++++++--------- src/app/panels/table/module.js | 60 +++++++++++-------- src/app/panels/terms/module.js | 60 ++++++++++--------- src/app/services/filterSrv.js | 94 ++++++++++++++++++++++++++++++ 5 files changed, 246 insertions(+), 108 deletions(-) diff --git a/src/app/panels/histogram/module.js b/src/app/panels/histogram/module.js index 766ff6ff9..8624e8f2e 100755 --- a/src/app/panels/histogram/module.js +++ b/src/app/panels/histogram/module.js @@ -44,7 +44,6 @@ define([ 'jquery.flot.stackpercent' ], function (angular, app, $, _, kbn, moment, timeSeries) { - 'use strict'; var DEBUG = true; // DEBUG mode @@ -82,7 +81,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { queries : { mode : 'all', ids : [], - query : 'q=*:*', + query : '*:*', custom : '' }, max_rows : 100000, // maximum number of rows returned from Solr @@ -247,41 +246,47 @@ function (angular, app, $, _, kbn, moment, timeSeries) { // Build Solr query // TODO: Validate dashboard.current.services.filter.list[0], what if it is not the timestamp field? // This will cause error. - var start_time = new Date(dashboard.current.services.filter.list[0].from).toISOString(); - var end_time = new Date(dashboard.current.services.filter.list[0].to).toISOString(); - var fq = '&fq=' + $scope.panel.time_field + ':[' + start_time + '%20TO%20' + end_time + ']'; - var df = '&df=message&df=host&df=path&df=type'; + // var start_time = new Date(dashboard.current.services.filter.list[0].from).toISOString(); + // var end_time = new Date(dashboard.current.services.filter.list[0].to).toISOString(); + // var fq = '&fq=' + $scope.panel.time_field + ':[' + start_time + '%20TO%20' + end_time + ']'; + // var df = '&df=message&df=host&df=path&df=type'; + + var fq = '&' + filterSrv.getSolrFq(); + var time_field = filterSrv.timeField(); + var start_time = filterSrv.getStartTime(); + var end_time = filterSrv.getEndTime(); + var wt_json = '&wt=json'; var rows_limit = '&rows=0'; // for histogram, we do not need the actual response doc, so set rows=0 var facet_gap = $scope.sjs.convertFacetGap($scope.panel.interval); var facet = '&facet=true' + - '&facet.range=' + $scope.panel.time_field + + '&facet.range=' + time_field + '&facet.range.start=' + start_time + '/DAY' + '&facet.range.end=' + end_time + '%2B1DAY/DAY' + '&facet.range.gap=' + facet_gap; - var filter_fq = ''; - var filter_either = []; + // var filter_fq = ''; + // var filter_either = []; var values_mode_query = ''; // Apply filters to the query - _.each(dashboard.current.services.filter.list, function(v,k) { - // Skip the timestamp filter because it's already applied to the query using fq param. - // timestamp filter should be in k = 0 - if (k > 0 && v.field != $scope.panel.time_field && v.active) { - if (DEBUG) { console.log('terms: k=',k,' v=',v); } - if (v.mandate == 'must') { - filter_fq = filter_fq + '&fq=' + v.field + ':"' + v.value + '"'; - } else if (v.mandate == 'mustNot') { - filter_fq = filter_fq + '&fq=-' + v.field + ':"' + v.value + '"'; - } else if (v.mandate == 'either') { - filter_either.push(v.field + ':"' + v.value + '"'); - } - } - }); - // parse filter_either array values, if exists - if (filter_either.length > 0) { - filter_fq = filter_fq + '&fq=(' + filter_either.join(' OR ') + ')'; - } + // _.each(dashboard.current.services.filter.list, function(v,k) { + // // Skip the timestamp filter because it's already applied to the query using fq param. + // // timestamp filter should be in k = 0 + // if (k > 0 && v.field != $scope.panel.time_field && v.active) { + // if (DEBUG) { console.log('terms: k=',k,' v=',v); } + // if (v.mandate == 'must') { + // filter_fq = filter_fq + '&fq=' + v.field + ':"' + v.value + '"'; + // } else if (v.mandate == 'mustNot') { + // filter_fq = filter_fq + '&fq=-' + v.field + ':"' + v.value + '"'; + // } else if (v.mandate == 'either') { + // filter_either.push(v.field + ':"' + v.value + '"'); + // } + // } + // }); + // // parse filter_either array values, if exists + // if (filter_either.length > 0) { + // filter_fq = filter_fq + '&fq=(' + filter_either.join(' OR ') + ')'; + // } // For mode = value if($scope.panel.mode === 'values') { @@ -305,7 +310,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { } // Set the panel's query - $scope.panel.queries.query = 'q=' + dashboard.current.services.query.list[0].query + df + wt_json + rows_limit + fq + facet + filter_fq + values_mode_query; + $scope.panel.queries.query = 'q=' + querySrv.list[0].query + wt_json + rows_limit + fq + facet + values_mode_query; // Set the additional custom query if ($scope.panel.queries.custom != null) { diff --git a/src/app/panels/map/module.js b/src/app/panels/map/module.js index 1884d47c2..837fea1b3 100755 --- a/src/app/panels/map/module.js +++ b/src/app/panels/map/module.js @@ -24,6 +24,8 @@ define([ function (angular, app, _, $) { 'use strict'; + var DEBUG = true; // DEBUG mode + var module = angular.module('kibana.panels.map', []); app.useModule(module); @@ -114,11 +116,13 @@ function (angular, app, _, $) { $scope.populate_modal(request); // Build Solr query - var start_time = new Date(filterSrv.list[0].from).toISOString(); - var end_time = new Date(filterSrv.list[0].to).toISOString(); + // var start_time = new Date(filterSrv.list[0].from).toISOString(); + // var end_time = new Date(filterSrv.list[0].to).toISOString(); // Get time field from filterSrv, is the time field always at list[0]? - var time_field = filterSrv.list[0].field; - var fq = '&fq=' + time_field + ':[' + start_time + '%20TO%20' + end_time + ']'; // Get timefield from filterSrv + // var time_field = filterSrv.list[0].field; + // var fq = '&fq=' + time_field + ':[' + start_time + '%20TO%20' + end_time + ']'; // Get timefield from filterSrv + + var fq = '&' + filterSrv.getSolrFq(); var df = '&df=message'; var wt_json = '&wt=json'; var rows_limit = '&rows=0'; // for map module, we don't display results from row, but we use facets. @@ -126,31 +130,32 @@ function (angular, app, _, $) { var facet = '&facet=true' + '&facet.field=' + $scope.panel.field + '&facet.limit=' + $scope.panel.size; - var filter_fq = ''; - var filter_either = []; + // var filter_fq = ''; + // var filter_either = []; // Apply filters to the query - _.each(filterSrv.list, function(v,k) { - // Skip the timestamp filter because it's already applied to the query using fq param. - // timestamp filter should be in k = 0 - if (k > 0 && v.field != time_field && v.active) { - if (DEBUG) { console.log('terms: k=',k,' v=',v); } - if (v.mandate == 'must') { - filter_fq = filter_fq + '&fq=' + v.field + ':"' + v.value + '"'; - } else if (v.mandate == 'mustNot') { - filter_fq = filter_fq + '&fq=-' + v.field + ':"' + v.value + '"'; - } else if (v.mandate == 'either') { - filter_either.push(v.field + ':"' + v.value + '"'); - } - } - }); - // parse filter_either array values, if exists - if (filter_either.length > 0) { - filter_fq = filter_fq + '&fq=(' + filter_either.join(' OR ') + ')'; - } + // _.each(filterSrv.list, function(v,k) { + // // Skip the timestamp filter because it's already applied to the query using fq param. + // // timestamp filter should be in k = 0 + // if (k > 0 && v.field != time_field && v.active) { + // if (DEBUG) { console.log('terms: k=',k,' v=',v); } + // if (v.mandate == 'must') { + // filter_fq = filter_fq + '&fq=' + v.field + ':"' + v.value + '"'; + // } else if (v.mandate == 'mustNot') { + // filter_fq = filter_fq + '&fq=-' + v.field + ':"' + v.value + '"'; + // } else if (v.mandate == 'either') { + // filter_either.push(v.field + ':"' + v.value + '"'); + // } + // } + // }); + // // parse filter_either array values, if exists + // if (filter_either.length > 0) { + // filter_fq = filter_fq + '&fq=(' + filter_either.join(' OR ') + ')'; + // } // Set the panel's query - $scope.panel.queries.query = 'q=' + querySrv.list[0].query + df + wt_json + fq + rows_limit + facet + filter_fq; + // $scope.panel.queries.query = 'q=' + querySrv.list[0].query + df + wt_json + fq + rows_limit + facet + filter_fq; + $scope.panel.queries.query = 'q=' + querySrv.list[0].query + df + wt_json + fq + rows_limit + facet; // Set the additional custom query if ($scope.panel.queries.custom != null) { @@ -168,7 +173,13 @@ function (angular, app, _, $) { results.then(function(results) { $scope.panelMeta.loading = false; // $scope.hits = results.hits.total; - $scope.hits = results.response.numFound; + if (results.response.numFound) { + $scope.hits = results.response.numFound; + } else { + // Undefined numFound + return false; + } + $scope.data = {}; // _.each(results.facets.map.terms, function(v) { // $scope.data[v.term.toUpperCase()] = v.count; @@ -179,8 +190,18 @@ function (angular, app, _, $) { if ($scope.hits > 0) { for (var i=0; i < terms.length; i += 2) { + // Skip states with zero count to make them greyed out in the map. if (terms[i+1] > 0) { - $scope.data[terms[i]] = terms[i+1]; + // if $scope.data[terms] is undefined, assign the value to it + // otherwise, we will add the value. This case can happen when + // the data contains both uppercase and lowercase state letters with + // duplicate states (e.g. CA and ca). By adding the value, the map will + // show correct counts for states with mixed-case letters. + if (!$scope.data[terms[i].toUpperCase()]) { + $scope.data[terms[i].toUpperCase()] = terms[i+1]; + } else { + $scope.data[terms[i].toUpperCase()] += terms[i+1]; + } } }; } @@ -195,7 +216,9 @@ function (angular, app, _, $) { }; $scope.build_search = function(field,value) { - filterSrv.set({type:'querystring',mandate:'must',query:field+":"+value}); + // Set querystring to both uppercase and lowercase state values with double-quote around the value + // to prevent query error from state=OR (Oregon) + filterSrv.set({type:'querystring',mandate:'must',query:field+':"'+value.toUpperCase()+'" OR '+field+':"'+value.toLowerCase()+'"'}); dashboard.refresh(); }; diff --git a/src/app/panels/table/module.js b/src/app/panels/table/module.js index f8eb4a88f..d11602823 100755 --- a/src/app/panels/table/module.js +++ b/src/app/panels/table/module.js @@ -64,7 +64,7 @@ function (angular, app, _, kbn, moment) { queries : { mode : 'all', ids : [], - query : 'q=*:*', + query : '*:*', custom : '' }, size : 100, // Per page @@ -246,36 +246,43 @@ function (angular, app, _, kbn, moment) { // request = request.setQuery(q); // TODO: Validate dashboard.current.services.filter.list[0], what if it is not the timestamp field? // This will cause error. - var start_time = new Date(dashboard.current.services.filter.list[0].from).toISOString(); - var end_time = new Date(dashboard.current.services.filter.list[0].to).toISOString(); - var fq = '&fq=' + $scope.panel.time_field + ':[' + start_time + '%20TO%20' + end_time + ']'; + // var start_time = new Date(filterSrv.list[0].from).toISOString(); + // var end_time = new Date(filterSrv.list[0].to).toISOString(); + // TODO + // Get time field from filterSrv, is the time field always at list[0]? + // var time_field = filterSrv.timeField(); + + // console.debug('table: time_field=',time_field); + + // var fq = '&fq=' + time_field + ':[' + start_time + '%20TO%20' + end_time + ']'; + var fq = '&' + filterSrv.getSolrFq(); var query_size = $scope.panel.size * $scope.panel.pages; var df = '&df=message&df=host&df=path&df=type'; var wt_json = '&wt=json'; var rows_limit; var sorting = ''; - var filter_fq = ''; - var filter_either = []; + // var filter_fq = ''; + // var filter_either = []; // Apply filters to the query - _.each(dashboard.current.services.filter.list, function(v,k) { - // Skip the timestamp filter because it's already applied to the query using fq param. - // timestamp filter should be in k = 0 - if (k > 0 && v.field != $scope.panel.time_field && v.active) { - if (DEBUG) { console.log('terms: k=',k,' v=',v); } - if (v.mandate == 'must') { - filter_fq = filter_fq + '&fq=' + v.field + ':"' + v.value + '"'; - } else if (v.mandate == 'mustNot') { - filter_fq = filter_fq + '&fq=-' + v.field + ':"' + v.value + '"'; - } else if (v.mandate == 'either') { - filter_either.push(v.field + ':"' + v.value + '"'); - } - } - }); - // parse filter_either array values, if exists - if (filter_either.length > 0) { - filter_fq = filter_fq + '&fq=(' + filter_either.join(' OR ') + ')'; - } + // _.each(filterSrv.list, function(v,k) { + // // Skip the timestamp filter because it's already applied to the query using fq param. + // // timestamp filter should be in k = 0 + // if (k > 0 && v.field != time_field && v.active) { + // console.debug('table filter terms: k=',k,' v=',v); + // if (v.mandate == 'must') { + // filter_fq = filter_fq + '&fq=' + v.field + ':"' + v.value + '"'; + // } else if (v.mandate == 'mustNot') { + // filter_fq = filter_fq + '&fq=-' + v.field + ':"' + v.value + '"'; + // } else if (v.mandate == 'either') { + // filter_either.push(v.field + ':"' + v.value + '"'); + // } + // } + // }); + // // parse filter_either array values, if exists + // if (filter_either.length > 0) { + // filter_fq = filter_fq + '&fq=(' + filter_either.join(' OR ') + ')'; + // } if ($scope.panel.sort[0] !== undefined && $scope.panel.sort[1] !== undefined) { sorting = '&sort=' + $scope.panel.sort[0] + ' ' + $scope.panel.sort[1]; @@ -290,8 +297,11 @@ function (angular, app, _, kbn, moment) { // facet_limit = '&facet.limit=10'; } + + // Set the panel's query - $scope.panel.queries.query = 'q=' + dashboard.current.services.query.list[0].query + df + wt_json + rows_limit + fq + sorting + filter_fq; + // $scope.panel.queries.query = 'q=' + querySrv.list[0].query + df + wt_json + rows_limit + fq + sorting + filter_fq; + $scope.panel.queries.query = 'q=' + querySrv.list[0].query + df + wt_json + rows_limit + fq + sorting; // Set the additional custom query if ($scope.panel.queries.custom != null) { diff --git a/src/app/panels/terms/module.js b/src/app/panels/terms/module.js index ab6119108..797867374 100755 --- a/src/app/panels/terms/module.js +++ b/src/app/panels/terms/module.js @@ -48,7 +48,7 @@ function (angular, app, _, $, kbn) { queries : { mode : 'all', ids : [], - query : 'q=*:*', + query : '*:*', custom : '' }, field : 'type', @@ -133,46 +133,52 @@ function (angular, app, _, $, kbn) { // Build Solr query // TODO: Validate dashboard.current.services.filter.list[0], what if it is not the timestamp field? // This will cause error. - var start_time = new Date(dashboard.current.services.filter.list[0].from).toISOString(); - var end_time = new Date(dashboard.current.services.filter.list[0].to).toISOString(); - var fq = '&fq=' + $scope.panel.time_field + ':[' + start_time + '%20TO%20' + end_time + ']'; + // var start_time = new Date(dashboard.current.services.filter.list[0].from).toISOString(); + // var end_time = new Date(dashboard.current.services.filter.list[0].to).toISOString(); + // var fq = '&fq=' + $scope.panel.time_field + ':[' + start_time + '%20TO%20' + end_time + ']'; // var query_size = $scope.panel.size * $scope.panel.pages; - var df = '&df=message&df=host&df=path&df=type'; + + var fq = '&' + filterSrv.getSolrFq(); + var time_field = filterSrv.timeField(); + var start_time = filterSrv.getStartTime(); + var end_time = filterSrv.getEndTime(); + + // var df = '&df=message&df=host&df=path&df=type'; var wt_json = '&wt=json'; var rows_limit = '&rows=0' // for terms, we do not need the actual response doc, so set rows=0 var facet_gap = '%2B1DAY'; var facet = '&facet=true' + '&facet.field=' + $scope.panel.field + - '&facet.range=' + $scope.panel.time_field + + '&facet.range=' + time_field + '&facet.range.start=' + start_time + '&facet.range.end=' + end_time + '&facet.range.gap=' + facet_gap + '&facet.limit=' + $scope.panel.size; - var filter_fq = ''; - var filter_either = []; + // var filter_fq = ''; + // var filter_either = []; // Apply filters to the query - _.each(dashboard.current.services.filter.list, function(v,k) { - // Skip the timestamp filter because it's already applied to the query using fq param. - // timestamp filter should be in k = 0 - if (k > 0 && v.field != $scope.panel.time_field && v.active) { - if (DEBUG) { console.log('terms: k=',k,' v=',v); } - if (v.mandate == 'must') { - filter_fq = filter_fq + '&fq=' + v.field + ':"' + v.value + '"'; - } else if (v.mandate == 'mustNot') { - filter_fq = filter_fq + '&fq=-' + v.field + ':"' + v.value + '"'; - } else if (v.mandate == 'either') { - filter_either.push(v.field + ':"' + v.value + '"'); - } - } - }); - // parse filter_either array values, if exists - if (filter_either.length > 0) { - filter_fq = filter_fq + '&fq=(' + filter_either.join(' OR ') + ')'; - } + // _.each(dashboard.current.services.filter.list, function(v,k) { + // // Skip the timestamp filter because it's already applied to the query using fq param. + // // timestamp filter should be in k = 0 + // if (k > 0 && v.field != $scope.panel.time_field && v.active) { + // if (DEBUG) { console.log('terms: k=',k,' v=',v); } + // if (v.mandate == 'must') { + // filter_fq = filter_fq + '&fq=' + v.field + ':"' + v.value + '"'; + // } else if (v.mandate == 'mustNot') { + // filter_fq = filter_fq + '&fq=-' + v.field + ':"' + v.value + '"'; + // } else if (v.mandate == 'either') { + // filter_either.push(v.field + ':"' + v.value + '"'); + // } + // } + // }); + // // parse filter_either array values, if exists + // if (filter_either.length > 0) { + // filter_fq = filter_fq + '&fq=(' + filter_either.join(' OR ') + ')'; + // } // Set the panel's query - $scope.panel.queries.query = 'q=' + dashboard.current.services.query.list[0].query + df + wt_json + rows_limit + fq + facet + filter_fq; + $scope.panel.queries.query = 'q=' + querySrv.list[0].query + wt_json + rows_limit + fq + facet; // Set the additional custom query if ($scope.panel.queries.custom != null) { diff --git a/src/app/services/filterSrv.js b/src/app/services/filterSrv.js index 80ef79184..928e95086 100755 --- a/src/app/services/filterSrv.js +++ b/src/app/services/filterSrv.js @@ -131,6 +131,100 @@ define([ } }; + // Return fq string for constructing a query to send to Solr. + this.getSolrFq = function () { + var start_time, end_time, time_field; + var filter_fq =''; + var filter_either = []; + + // Loop through the list to find the time field, usually it should be in self.list[0] + _.each(self.list, function(v,k) { + + console.debug('filterSrv: v=',v,' k=',k); + + if (v.type == 'time') { + time_field = v.field; + start_time = new Date(v.from).toISOString(); + end_time = new Date(v.to).toISOString(); + } else if (v.type == 'terms') { + if (v.mandate == 'must') { + filter_fq = filter_fq + '&fq=' + v.field + ':"' + v.value + '"'; + } else if (v.mandate == 'mustNot') { + filter_fq = filter_fq + '&fq=-' + v.field + ':"' + v.value + '"'; + } else if (v.mandate == 'either') { + filter_either.push(v.field + ':"' + v.value + '"'); + } + } else if (v.type == 'field') { + // v.query contains double-quote around it. + if (v.mandate == 'must') { + filter_fq = filter_fq + '&fq=' + v.field + ':' + v.query; + } else if (v.mandate == 'mustNot') { + filter_fq = filter_fq + '&fq=-' + v.field + ':' + v.query; + } else if (v.mandate == 'either') { + filter_either.push(v.field + ':' + v.query); + } + } else if (v.type == 'querystring') { + if (v.mandate == 'must') { + filter_fq = filter_fq + '&fq=' + v.query; + } else if (v.mandate == 'mustNot') { + filter_fq = filter_fq + '&fq=-' + v.query; + } else if (v.mandate == 'either') { + filter_either.push(v.query); + } + } else { + // Unsupport filter type + return false; + } + }); + // Return false for undefined time field + if (!start_time || !end_time || !time_field) { + return false; + } + // parse filter_either array values, if exists + if (filter_either.length > 0) { + filter_fq = filter_fq + '&fq=(' + filter_either.join(' OR ') + ')'; + } + + return 'fq=' + time_field + ':[' + start_time + '%20TO%20' + end_time + ']' + filter_fq; + }; + + // Get start time for Solr query (e.g. facet.range.start) + this.getStartTime = function() { + var start_time; + _.each(self.list, function(v) { + if (v.type == 'time') { + start_time = new Date(v.from).toISOString(); + return; + } + }); + return start_time; + }; + + // Get end time for Solr query (e.g. facet.range.end) + this.getEndTime = function() { + var end_time; + _.each(self.list, function(v) { + if (v.type == 'time') { + end_time = new Date(v.to).toISOString(); + return; + } + }); + return end_time; + }; + + // Get both start and end time in one shot + this.getStartTimeAndEndTime = function() { + var start_time, end_time; + _.each(self.list, function(v) { + if (v.type == 'time') { + start_time = new Date(v.from).toISOString(); + end_time = new Date(v.to).toISOString(); + return; + } + }); + return [start_time, end_time]; + }; + this.getByType = function(type,inactive) { return _.pick(self.list,self.idsByType(type,inactive)); }; From 1373ddd1c94c04a74a0abc754c93fef2e5332173 Mon Sep 17 00:00:00 2001 From: Andrew T Date: Thu, 10 Apr 2014 12:07:39 +0700 Subject: [PATCH 05/23] Fix bugs when getting time field from filterSrv. --- src/app/panels/histogram/module.js | 2 +- src/app/panels/terms/module.js | 2 +- src/app/services/filterSrv.js | 14 +++++++++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/app/panels/histogram/module.js b/src/app/panels/histogram/module.js index 8624e8f2e..82835a125 100755 --- a/src/app/panels/histogram/module.js +++ b/src/app/panels/histogram/module.js @@ -252,7 +252,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { // var df = '&df=message&df=host&df=path&df=type'; var fq = '&' + filterSrv.getSolrFq(); - var time_field = filterSrv.timeField(); + var time_field = filterSrv.getTimeField(); var start_time = filterSrv.getStartTime(); var end_time = filterSrv.getEndTime(); diff --git a/src/app/panels/terms/module.js b/src/app/panels/terms/module.js index 797867374..36e93e4aa 100755 --- a/src/app/panels/terms/module.js +++ b/src/app/panels/terms/module.js @@ -139,7 +139,7 @@ function (angular, app, _, $, kbn) { // var query_size = $scope.panel.size * $scope.panel.pages; var fq = '&' + filterSrv.getSolrFq(); - var time_field = filterSrv.timeField(); + var time_field = filterSrv.getTimeField(); var start_time = filterSrv.getStartTime(); var end_time = filterSrv.getEndTime(); diff --git a/src/app/services/filterSrv.js b/src/app/services/filterSrv.js index 928e95086..106be8aed 100755 --- a/src/app/services/filterSrv.js +++ b/src/app/services/filterSrv.js @@ -132,7 +132,7 @@ define([ }; // Return fq string for constructing a query to send to Solr. - this.getSolrFq = function () { + this.getSolrFq = function() { var start_time, end_time, time_field; var filter_fq =''; var filter_either = []; @@ -188,6 +188,18 @@ define([ return 'fq=' + time_field + ':[' + start_time + '%20TO%20' + end_time + ']' + filter_fq; }; + // Get time field for Solr query + this.getTimeField = function() { + var time_field; + _.each(self.list, function(v) { + if (v.type == 'time') { + time_field = v.field; + return; + } + }); + return time_field; + } + // Get start time for Solr query (e.g. facet.range.start) this.getStartTime = function() { var start_time; From 04ed3614f7c85f7aafd1d2fe5ca82a56f6821a17 Mon Sep 17 00:00:00 2001 From: Andrew T Date: Thu, 10 Apr 2014 17:28:47 +0700 Subject: [PATCH 06/23] Remove all hard coded df from the panel modules. Add defType and df settings to query panel. Optimize code by moving some functions to querySrv. --- src/app/panels/histogram/module.js | 23 ++---------- src/app/panels/map/module.js | 26 ++------------ src/app/panels/query/editor.html | 10 ++++-- src/app/panels/query/module.js | 32 ++++++++--------- src/app/panels/table/module.js | 57 +++--------------------------- src/app/panels/terms/module.js | 25 ++----------- src/app/partials/dasheditor.html | 8 +++++ src/app/partials/inspector.html | 1 + src/app/services/dashboard.js | 4 ++- src/app/services/querySrv.js | 15 +++++++- 10 files changed, 60 insertions(+), 141 deletions(-) diff --git a/src/app/panels/histogram/module.js b/src/app/panels/histogram/module.js index 82835a125..0b1c062bf 100755 --- a/src/app/panels/histogram/module.js +++ b/src/app/panels/histogram/module.js @@ -268,26 +268,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) { // var filter_either = []; var values_mode_query = ''; - // Apply filters to the query - // _.each(dashboard.current.services.filter.list, function(v,k) { - // // Skip the timestamp filter because it's already applied to the query using fq param. - // // timestamp filter should be in k = 0 - // if (k > 0 && v.field != $scope.panel.time_field && v.active) { - // if (DEBUG) { console.log('terms: k=',k,' v=',v); } - // if (v.mandate == 'must') { - // filter_fq = filter_fq + '&fq=' + v.field + ':"' + v.value + '"'; - // } else if (v.mandate == 'mustNot') { - // filter_fq = filter_fq + '&fq=-' + v.field + ':"' + v.value + '"'; - // } else if (v.mandate == 'either') { - // filter_either.push(v.field + ':"' + v.value + '"'); - // } - // } - // }); - // // parse filter_either array values, if exists - // if (filter_either.length > 0) { - // filter_fq = filter_fq + '&fq=(' + filter_either.join(' OR ') + ')'; - // } - // For mode = value if($scope.panel.mode === 'values') { // if (_.isNull($scope.panel.value_field)) { @@ -310,7 +290,8 @@ function (angular, app, $, _, kbn, moment, timeSeries) { } // Set the panel's query - $scope.panel.queries.query = 'q=' + querySrv.list[0].query + wt_json + rows_limit + fq + facet + values_mode_query; + // $scope.panel.queries.query = 'q=' + querySrv.list[0].query + wt_json + rows_limit + fq + facet + values_mode_query; + $scope.panel.queries.query = querySrv.getQuery(0) + wt_json + rows_limit + fq + facet + values_mode_query; // Set the additional custom query if ($scope.panel.queries.custom != null) { diff --git a/src/app/panels/map/module.js b/src/app/panels/map/module.js index 837fea1b3..fb403f188 100755 --- a/src/app/panels/map/module.js +++ b/src/app/panels/map/module.js @@ -123,39 +123,17 @@ function (angular, app, _, $) { // var fq = '&fq=' + time_field + ':[' + start_time + '%20TO%20' + end_time + ']'; // Get timefield from filterSrv var fq = '&' + filterSrv.getSolrFq(); - var df = '&df=message'; + // var df = '&df=message'; var wt_json = '&wt=json'; var rows_limit = '&rows=0'; // for map module, we don't display results from row, but we use facets. // var facet_gap = '%2B1DAY'; var facet = '&facet=true' + '&facet.field=' + $scope.panel.field + '&facet.limit=' + $scope.panel.size; - // var filter_fq = ''; - // var filter_either = []; - - // Apply filters to the query - // _.each(filterSrv.list, function(v,k) { - // // Skip the timestamp filter because it's already applied to the query using fq param. - // // timestamp filter should be in k = 0 - // if (k > 0 && v.field != time_field && v.active) { - // if (DEBUG) { console.log('terms: k=',k,' v=',v); } - // if (v.mandate == 'must') { - // filter_fq = filter_fq + '&fq=' + v.field + ':"' + v.value + '"'; - // } else if (v.mandate == 'mustNot') { - // filter_fq = filter_fq + '&fq=-' + v.field + ':"' + v.value + '"'; - // } else if (v.mandate == 'either') { - // filter_either.push(v.field + ':"' + v.value + '"'); - // } - // } - // }); - // // parse filter_either array values, if exists - // if (filter_either.length > 0) { - // filter_fq = filter_fq + '&fq=(' + filter_either.join(' OR ') + ')'; - // } // Set the panel's query // $scope.panel.queries.query = 'q=' + querySrv.list[0].query + df + wt_json + fq + rows_limit + facet + filter_fq; - $scope.panel.queries.query = 'q=' + querySrv.list[0].query + df + wt_json + fq + rows_limit + facet; + $scope.panel.queries.query = querySrv.getQuery(0) + wt_json + fq + rows_limit + facet; // Set the additional custom query if ($scope.panel.queries.custom != null) { diff --git a/src/app/panels/query/editor.html b/src/app/panels/query/editor.html index 0f178830a..c375e40c3 100755 --- a/src/app/panels/query/editor.html +++ b/src/app/panels/query/editor.html @@ -1,8 +1,12 @@
-
- - +
+
Query Parser (defType)
+ +
+
+
Default Fields
+
\ No newline at end of file diff --git a/src/app/panels/query/module.js b/src/app/panels/query/module.js index e436af5aa..586f47e33 100755 --- a/src/app/panels/query/module.js +++ b/src/app/panels/query/module.js @@ -27,9 +27,9 @@ define([ // Set and populate defaults var _d = { - // query : "*", query : "*:*", - def_type : '', + // defType : 'lucene', + // df : 'df=message', pinned : true, history : [], remember: 10 // max: 100, angular strap can't take a variable for items param @@ -42,16 +42,16 @@ define([ }; $scope.refresh = function() { - _.each($scope.querySrv.list, function (v) { - if ($scope.panel.def_type) { - // If defType is specified, strip off old defType params from the query - // before appending the new defType value. - v.query = remove_deftype(v.query) + '&defType=' + $scope.panel.def_type; - } else { - // strip off defType (in case previously specified) - v.query = remove_deftype(v.query) - } - }); + // _.each($scope.querySrv.list, function (v) { + // if ($scope.panel.def_type) { + // // If defType is specified, strip off old defType params from the query + // // before appending the new defType value. + // v.query = remove_deftype(v.query) + '&defType=' + $scope.panel.def_type; + // } else { + // // strip off defType (in case previously specified) + // v.query = remove_deftype(v.query) + // } + // }); update_history(_.pluck($scope.querySrv.list,'query')); $rootScope.$broadcast('refresh'); }; @@ -78,10 +78,10 @@ define([ } }; - var remove_deftype = function(query) { - // strip off all defType params in the query - return query.replace(/(&defType=\w+)/g,''); - }; + // var remove_deftype = function(query) { + // // strip off all defType params in the query + // return query.replace(/(&defType=\w+)/g,''); + // }; $scope.init(); diff --git a/src/app/panels/table/module.js b/src/app/panels/table/module.js index d11602823..5d91451c7 100755 --- a/src/app/panels/table/module.js +++ b/src/app/panels/table/module.js @@ -198,8 +198,7 @@ function (angular, app, _, kbn, moment) { $scope.segment = _segment; if(DEBUG) { - var dummy = new Date(dashboard.current.services.filter.list[0].from).toISOString(); - console.log('table: Begin of get_data():\n\t$scope=',$scope,'\n\t$scope.panel=',$scope.panel,'\n\t_segment='+_segment,'\n\tdashboard.indices[_segment]=',dashboard.indices[_segment],'\n\tdashboard=',dashboard,'\n\tdashboard.current.services.filter.list[0].from='+ dummy,'\n\tquerySrv=',querySrv,'\n\tfilterSrv=',filterSrv); + console.debug('table: Begin of get_data():\n\t$scope=',$scope,'\n\t$scope.panel=',$scope.panel,'\n\t_segment='+_segment,'\n\tdashboard.indices[_segment]=',dashboard.indices[_segment],'\n\tdashboard=',dashboard,'\n\tquerySrv=',querySrv,'\n\tfilterSrv=',filterSrv); } // Solr @@ -227,62 +226,16 @@ function (angular, app, _, kbn, moment) { $scope.populate_modal(request); - // Create a facet to store and pass on time_field value to request.doSearch() - // var facet = $scope.sjs.RangeFacet('time_facet'); - // facet = facet.field($scope.panel.time_field); - // request = request.facet(facet); - if (DEBUG) { console.log('table:\n\trequest=',request,'\n\trequest.toString()=',request.toString()); } - // TODO: Parse query here and send to request.doSearch() - // declare default Solr params here - // get query - // get from and to time range - // get query.size - // construct the query - // set queryData - // request = request.setQuery(q); - // TODO: Validate dashboard.current.services.filter.list[0], what if it is not the timestamp field? - // This will cause error. - // var start_time = new Date(filterSrv.list[0].from).toISOString(); - // var end_time = new Date(filterSrv.list[0].to).toISOString(); - // TODO - // Get time field from filterSrv, is the time field always at list[0]? - // var time_field = filterSrv.timeField(); - - // console.debug('table: time_field=',time_field); - - // var fq = '&fq=' + time_field + ':[' + start_time + '%20TO%20' + end_time + ']'; var fq = '&' + filterSrv.getSolrFq(); var query_size = $scope.panel.size * $scope.panel.pages; - var df = '&df=message&df=host&df=path&df=type'; + // var df = '&df=message'; var wt_json = '&wt=json'; var rows_limit; var sorting = ''; - // var filter_fq = ''; - // var filter_either = []; - - // Apply filters to the query - // _.each(filterSrv.list, function(v,k) { - // // Skip the timestamp filter because it's already applied to the query using fq param. - // // timestamp filter should be in k = 0 - // if (k > 0 && v.field != time_field && v.active) { - // console.debug('table filter terms: k=',k,' v=',v); - // if (v.mandate == 'must') { - // filter_fq = filter_fq + '&fq=' + v.field + ':"' + v.value + '"'; - // } else if (v.mandate == 'mustNot') { - // filter_fq = filter_fq + '&fq=-' + v.field + ':"' + v.value + '"'; - // } else if (v.mandate == 'either') { - // filter_either.push(v.field + ':"' + v.value + '"'); - // } - // } - // }); - // // parse filter_either array values, if exists - // if (filter_either.length > 0) { - // filter_fq = filter_fq + '&fq=(' + filter_either.join(' OR ') + ')'; - // } if ($scope.panel.sort[0] !== undefined && $scope.panel.sort[1] !== undefined) { sorting = '&sort=' + $scope.panel.sort[0] + ' ' + $scope.panel.sort[1]; @@ -297,11 +250,11 @@ function (angular, app, _, kbn, moment) { // facet_limit = '&facet.limit=10'; } - - // Set the panel's query // $scope.panel.queries.query = 'q=' + querySrv.list[0].query + df + wt_json + rows_limit + fq + sorting + filter_fq; - $scope.panel.queries.query = 'q=' + querySrv.list[0].query + df + wt_json + rows_limit + fq + sorting; + $scope.panel.queries.query = querySrv.getQuery(0) + wt_json + rows_limit + fq + sorting; + + console.debug('table: query=',$scope.panel.queries.query); // Set the additional custom query if ($scope.panel.queries.custom != null) { diff --git a/src/app/panels/terms/module.js b/src/app/panels/terms/module.js index 36e93e4aa..0510451c4 100755 --- a/src/app/panels/terms/module.js +++ b/src/app/panels/terms/module.js @@ -154,31 +154,10 @@ function (angular, app, _, $, kbn) { '&facet.range.end=' + end_time + '&facet.range.gap=' + facet_gap + '&facet.limit=' + $scope.panel.size; - // var filter_fq = ''; - // var filter_either = []; - - // Apply filters to the query - // _.each(dashboard.current.services.filter.list, function(v,k) { - // // Skip the timestamp filter because it's already applied to the query using fq param. - // // timestamp filter should be in k = 0 - // if (k > 0 && v.field != $scope.panel.time_field && v.active) { - // if (DEBUG) { console.log('terms: k=',k,' v=',v); } - // if (v.mandate == 'must') { - // filter_fq = filter_fq + '&fq=' + v.field + ':"' + v.value + '"'; - // } else if (v.mandate == 'mustNot') { - // filter_fq = filter_fq + '&fq=-' + v.field + ':"' + v.value + '"'; - // } else if (v.mandate == 'either') { - // filter_either.push(v.field + ':"' + v.value + '"'); - // } - // } - // }); - // // parse filter_either array values, if exists - // if (filter_either.length > 0) { - // filter_fq = filter_fq + '&fq=(' + filter_either.join(' OR ') + ')'; - // } // Set the panel's query - $scope.panel.queries.query = 'q=' + querySrv.list[0].query + wt_json + rows_limit + fq + facet; + // $scope.panel.queries.query = 'q=' + querySrv.list[0].query + wt_json + rows_limit + fq + facet; + $scope.panel.queries.query = querySrv.getQuery(0) + wt_json + rows_limit + fq + facet; // Set the additional custom query if ($scope.panel.queries.custom != null) { diff --git a/src/app/partials/dasheditor.html b/src/app/partials/dasheditor.html index 9e7e8be32..73fd49325 100755 --- a/src/app/partials/dasheditor.html +++ b/src/app/partials/dasheditor.html @@ -158,6 +158,9 @@
Solr Server
Collection
+
+ diff --git a/src/app/partials/inspector.html b/src/app/partials/inspector.html index 140273d86..c59552d4f 100755 --- a/src/app/partials/inspector.html +++ b/src/app/partials/inspector.html @@ -14,6 +14,7 @@

Parameters of Last Query

-->
Panel Query
{{panel.queries.query}}
+
Additional Custom Query
{{panel.queries.custom}}
   
diff --git a/src/app/services/dashboard.js b/src/app/services/dashboard.js
index 1f7cffa4d..434e69af2 100755
--- a/src/app/services/dashboard.js
+++ b/src/app/services/dashboard.js
@@ -52,7 +52,9 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
       // }
       solr: {
         server: config.solr,
-        core_name: config.solr_core
+        core_name: config.solr_core,
+        // deftype: 'lucene',
+        // df: 'df=message',
       }
     };
 
diff --git a/src/app/services/querySrv.js b/src/app/services/querySrv.js
index da18b4839..0a8242d11 100755
--- a/src/app/services/querySrv.js
+++ b/src/app/services/querySrv.js
@@ -23,7 +23,8 @@ function (angular, _, config) {
       query: '*:*',
       alias: '',
       pin: false,
-      type: 'lucene'
+      type: 'lucene',
+      df: 'df=message'
     };
 
     // For convenience
@@ -114,6 +115,18 @@ function (angular, _, config) {
       }
     };
 
+    // Get query string for Solr with defType param and default fields (df).
+    this.getQuery = function(id) {
+      var solr_q = 'q=' + self.list[id].query;
+      if (self.list[id].type) {
+        solr_q += '&defType=' + self.list[id].type;
+      }
+      if (self.list[id].df) {
+        solr_q += '&' + self.list[id].df;
+      }
+      return  solr_q;
+    }
+
     this.findQuery = function(queryString) {
       return _.findWhere(self.list,{query:queryString});
     };

From 389288791a5001f986accc1675740464c56dc720 Mon Sep 17 00:00:00 2001
From: Andrew T 
Date: Thu, 10 Apr 2014 18:26:00 +0700
Subject: [PATCH 07/23] Fix bug in map module not refreshing when query gives
 no result.

---
 src/app/panels/map/module.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/app/panels/map/module.js b/src/app/panels/map/module.js
index fb403f188..e886a2960 100755
--- a/src/app/panels/map/module.js
+++ b/src/app/panels/map/module.js
@@ -150,6 +150,8 @@ function (angular, app, _, $) {
       // Populate scope when we have results
       results.then(function(results) {
         $scope.panelMeta.loading = false;
+        $scope.data = {}; // empty the data for new results
+
         // $scope.hits = results.hits.total;
         if (results.response.numFound) {
           $scope.hits = results.response.numFound;
@@ -158,7 +160,7 @@ function (angular, app, _, $) {
           return false;
         }
 
-        $scope.data = {};
+        
         // _.each(results.facets.map.terms, function(v) {
         //   $scope.data[v.term.toUpperCase()] = v.count;
         // });

From ed72cb3dfb5942ebbdb0cb4ea32b3d6fda3cd505 Mon Sep 17 00:00:00 2001
From: Andrew T 
Date: Thu, 10 Apr 2014 18:29:50 +0700
Subject: [PATCH 08/23] Fix bug in map module.

---
 src/app/panels/map/module.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/app/panels/map/module.js b/src/app/panels/map/module.js
index e886a2960..6846d99bc 100755
--- a/src/app/panels/map/module.js
+++ b/src/app/panels/map/module.js
@@ -156,7 +156,8 @@ function (angular, app, _, $) {
         if (results.response.numFound) {
           $scope.hits = results.response.numFound;
         } else {
-          // Undefined numFound
+          // Undefined numFound or zero, clear the map.
+          $scope.$emit('render');
           return false;
         }
 

From 631ec47f72149dcd9c103766ce0d593ed4faa127 Mon Sep 17 00:00:00 2001
From: badrit-user 
Date: Wed, 16 Apr 2014 16:33:41 +0200
Subject: [PATCH 09/23] Added bettermap support and replaced ejs with sjs

---
 src/app/dashboards/bettermap.json  | 100 +++++++++++++++++++++++++
 src/app/panels/bettermap/module.js | 114 ++++++++++++++++++++++-------
 2 files changed, 189 insertions(+), 25 deletions(-)
 create mode 100644 src/app/dashboards/bettermap.json

diff --git a/src/app/dashboards/bettermap.json b/src/app/dashboards/bettermap.json
new file mode 100644
index 000000000..d95ba6ae6
--- /dev/null
+++ b/src/app/dashboards/bettermap.json
@@ -0,0 +1,100 @@
+{
+  "title": "Better Map Demo",
+  "services": {
+    "query": {
+      "idQueue": [
+        1
+      ],
+      "list": {
+        "0": {
+          "query": "*:*",
+          "alias": "",
+          "color": "#7EB26D",
+          "id": 0,
+          "pin": false,
+          "type": "lucene"
+        }
+      },
+      "ids": [
+        0
+      ]
+    },
+    "filter": {
+      "idQueue": [
+        1
+      ],
+      "list": {
+        "0": {
+          "from": "2014-02-01T00:00:00Z",
+          "to": "2014-02-28T23:59:47Z",
+          "field": "start_time",
+          "type": "time",
+          "mandate": "must",
+          "active": true,
+          "alias": "",
+          "id": 0
+        }
+      },
+      "ids": [
+        0
+      ]
+    }
+  },
+  "rows": [
+    {
+      "title": "Better Map",
+      "height": "750px",
+      "editable": true,
+      "collapse": false,
+      "collapsable": true,
+      "panels": [
+        {
+          "title": "Better Map Demo",
+          "error": false,
+          "span": 12,
+          "editable": true,
+          "tooltip" : "start_station_id",
+          "group": [
+            "default"
+          ],
+          "latitude_field": "start_station_latitude",
+          "longitude_field": "start_station_longitude",
+          "type": "bettermap",
+          "size": 10000,
+          "time_field": "start_time",
+          "sort": [
+              "start_time",
+              "desc"
+          ]
+        }
+      ]
+    }
+  ],
+  "editable": true,
+  "failover": false,
+  "index": {
+    "interval": "none",
+    "pattern": "[logstash-]YYYY.MM.DD",
+    "default": "NO_TIME_FILTER_OR_INDEX_PATTERN_NOT_MATCHED"
+  },
+  "style": "dark",
+  "panel_hints": true,
+  "loader": {
+    "save_gist": false,
+    "save_elasticsearch": true,
+    "save_local": true,
+    "save_default": true,
+    "save_temp": true,
+    "save_temp_ttl_enable": true,
+    "save_temp_ttl": "30d",
+    "load_gist": true,
+    "load_elasticsearch": true,
+    "load_elasticsearch_size": 20,
+    "load_local": true,
+    "hide": false
+  },
+  "solr": {
+    "server": "http://localhost:8983/solr/",
+    "core_name": "citi-bike-trip"
+  }
+}
diff --git a/src/app/panels/bettermap/module.js b/src/app/panels/bettermap/module.js
index 90ad5a26f..ce215ecad 100755
--- a/src/app/panels/bettermap/module.js
+++ b/src/app/panels/bettermap/module.js
@@ -22,6 +22,8 @@ define([
 function (angular, app, _, L, localRequire) {
   'use strict';
 
+  var DEBUG = true; // DEBUG mode
+
   var module = angular.module('kibana.panels.bettermap', []);
   app.useModule(module);
 
@@ -53,15 +55,17 @@ function (angular, app, _, L, localRequire) {
     var _d = {
       queries     : {
         mode        : 'all',
-        ids         : []
+        ids         : [],
+        query       : '*:*',
+        custom      : ''
       },
       size    : 1000,
       spyable : true,
-      tooltip : "_id",
+//      tooltip : "_id",
       field   : null
     };
 
-    _.defaults($scope.panel,_d);
+    _.defaults($scope.panel, _d);
     $scope.requireContext = localRequire;
 
     // inorder to use relative paths in require calls, require needs a context to run. Without
@@ -90,6 +94,7 @@ function (angular, app, _, L, localRequire) {
 
         // Determine the field to sort on
         var timeField = _.uniq(_.pluck(filterSrv.getByType('time'),'field'));
+
         if(timeField.length > 1) {
           $scope.panel.error = "Time field must be consistent amongst time filters";
         } else if(timeField.length === 0) {
@@ -98,8 +103,14 @@ function (angular, app, _, L, localRequire) {
           timeField = timeField[0];
         }
 
+        // Solr.js
+        $scope.sjs.client.server(dashboard.current.solr.server + dashboard.current.solr.core_name);
+
         var _segment = _.isUndefined(segment) ? 0 : segment;
 
+        var request;
+        request = $scope.sjs.Request().indices(dashboard.indices);
+
         $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
         // This could probably be changed to a BoolFilter
         var boolQuery = $scope.ejs.BoolQuery();
@@ -107,28 +118,76 @@ function (angular, app, _, L, localRequire) {
           boolQuery = boolQuery.should(querySrv.getEjsObj(id));
         });
 
-        var request = $scope.ejs.Request().indices(dashboard.indices[_segment])
-          .query($scope.ejs.FilteredQuery(
-            boolQuery,
-            filterSrv.getBoolFilter(filterSrv.ids).must($scope.ejs.ExistsFilter($scope.panel.field))
-          ))
-          .fields([$scope.panel.field,$scope.panel.tooltip])
-          .size($scope.panel.size);
+        var request = $scope.sjs.Request().indices(dashboard.indices[_segment]);
 
-        if(!_.isNull(timeField)) {
-          request = request.sort(timeField,'desc');
-        }
+        request = request.query(
+        $scope.sjs.FilteredQuery(
+          boolQuery,
+          filterSrv.getBoolFilter(filterSrv.ids)
+        ))
+        .size($scope.panel.size) // Set the size of query result
+        .sort($scope.panel.sort[0], $scope.panel.sort[1]);
 
         $scope.populate_modal(request);
 
-        var results = request.doSearch();
+        if (DEBUG) {
+            console.log('bettermap:\n\trequest=',request,'\n\trequest.toString()=',request.toString());
+        }
+
+      // TODO: Parse query here and send to request.doSearch()
+      // declare default Solr params here
+      // get query
+      // get from and to time range
+      // get query.size
+      // construct the query
+      // set queryData
+      // request = request.setQuery(q);
+      // TODO: Validate dashboard.current.services.filter.list[0], what if it is not the "defined time field of this dataset" field?
+      //       This will cause error.
+
+      var start_time = new Date(dashboard.current.services.filter.list[0].from).toISOString();
+      var end_time = new Date(dashboard.current.services.filter.list[0].to).toISOString();
+
+      var fq = '&fq=' + $scope.panel.time_field + ':[' + start_time + '%20TO%20' + end_time + ']';
+      var query_size = $scope.panel.size;
+      var df = '&df=start_time&df=start_station_id&df=start_station_latitude&df=start_station_longitude';
+      var wt_json = '&wt=json';
+      var rows_limit;
+      var sorting = '';
+      var filter_fq = '';
+      var filter_either = [];
+
+      if ($scope.panel.sort[0] !== undefined && $scope.panel.sort[1] !== undefined) {
+        sorting = '&sort=' + $scope.panel.sort[0] + ' ' + $scope.panel.sort[1];
+      }
 
+      // set the size of query result
+      if (query_size !== undefined && query_size !== 0) {
+        rows_limit = '&rows=' + query_size;
+        // facet_limit = '&facet.limit=' + query_size;
+      } else { // default
+        rows_limit = '&rows=25';
+        // facet_limit = '&facet.limit=10';
+      }
+
+      // Set the panel's query
+      $scope.panel.queries.query = 'q=' + dashboard.current.services.query.list[0].query + df + wt_json + rows_limit + fq + sorting + filter_fq;
+
+      // Set the additional custom query
+      if ($scope.panel.queries.custom != null) {
+        // request = request.customQuery($scope.panel.queries.custom);
+        request = request.setQuery($scope.panel.queries.query + $scope.panel.queries.custom);
+      } else {
+        request = request.setQuery($scope.panel.queries.query);
+      }
+
+      var results = request.doSearch();
         // Populate scope when we have results
+        // Using promises
         results.then(function(results) {
           $scope.panelMeta.loading = false;
 
           if(_segment === 0) {
-            $scope.hits = 0;
             $scope.data = [];
             query_id = $scope.query_id = new Date().getTime();
           }
@@ -143,10 +202,10 @@ function (angular, app, _, L, localRequire) {
           if($scope.query_id === query_id) {
 
             // Keep only what we need for the set
-            $scope.data = $scope.data.slice(0,$scope.panel.size).concat(_.map(results.hits.hits, function(hit) {
+            $scope.data = $scope.data.slice(0,$scope.panel.size).concat(_.map(results.response.docs, function(hit) {
               return {
-                coordinates : new L.LatLng(hit.fields[$scope.panel.field][1],hit.fields[$scope.panel.field][0]),
-                tooltip : hit.fields[$scope.panel.tooltip]
+                coordinates : new L.LatLng(hit[$scope.panel.latitude_field],hit[$scope.panel.longitude_field]),
+                tooltip : hit[$scope.panel.tooltip]
               };
             }));
 
@@ -155,18 +214,17 @@ function (angular, app, _, L, localRequire) {
           }
 
           $scope.$emit('draw');
-
           // Get $size results then stop querying
+          // Searching Solr using Segments
           if($scope.data.length < $scope.panel.size && _segment+1 < dashboard.indices.length) {
-            $scope.get_data(_segment+1,$scope.query_id);
+            $scope.get_data(_segment+1, $scope.query_id);
           }
-
         });
       });
     };
 
     $scope.populate_modal = function(request) {
-      $scope.inspector = angular.toJson(JSON.parse(request.toString()),true);
+      $scope.inspector = angular.toJson(JSON.parse(request.toString()), true);
     };
 
   });
@@ -195,19 +253,25 @@ function (angular, app, _, L, localRequire) {
         function render_panel() {
           scope.require(['./leaflet/plugins'], function () {
             scope.panelMeta.loading = false;
+
             L.Icon.Default.imagePath = 'app/panels/bettermap/leaflet/images';
             if(_.isUndefined(map)) {
               map = L.map(attrs.id, {
-                scrollWheelZoom: false,
+                scrollWheelZoom: true,
                 center: [40, -86],
                 zoom: 10
               });
 
-              L.tileLayer('http://{s}.tile.cloudmade.com/57cbb6ca8cac418dbb1a402586df4528/22677/256/{z}/{x}/{y}.png', {
+              // Add Change to the tile layer url, because it was returning 403 (forbidden)
+              // Forbidden because of API Key in cloudmade, so I used osm for now
+              // osm (open street map) (http://{s}.tile.osm.org/{z}/{x}/{y}.png)
+              // cloud made (http://{s}.tile.cloudmade.com/57cbb6ca8cac418dbb1a402586df4528/22677/256/{z}/{x}/{y}.png)
+              L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
                 maxZoom: 18,
                 minZoom: 2
               }).addTo(map);
-              layerGroup = new L.MarkerClusterGroup({maxClusterRadius:30});
+
+              layerGroup = new L.MarkerClusterGroup({maxClusterRadius:50});
             } else {
               layerGroup.clearLayers();
             }

From 24f1ca26bd5fc35c0557f531e6026e98d32c9085 Mon Sep 17 00:00:00 2001
From: Andrew T 
Date: Wed, 23 Apr 2014 22:42:16 +0700
Subject: [PATCH 10/23] Move UI Settings of query module to dashboard UI
 setting for "Global Query Parameters" (e.g. defType and df params).

---
 src/app/panels/query/editor.html | 11 ++++++--
 src/app/partials/dasheditor.html | 18 ++++++-------
 src/app/services/dashboard.js    | 43 +-------------------------------
 src/app/services/querySrv.js     | 20 +++++++++------
 4 files changed, 32 insertions(+), 60 deletions(-)

diff --git a/src/app/panels/query/editor.html b/src/app/panels/query/editor.html
index c375e40c3..3fc54e345 100755
--- a/src/app/panels/query/editor.html
+++ b/src/app/panels/query/editor.html
@@ -1,5 +1,7 @@
-
+Nothing to Configure Here. + \ No newline at end of file diff --git a/src/app/partials/dasheditor.html b/src/app/partials/dasheditor.html index 73fd49325..871fea1ea 100755 --- a/src/app/partials/dasheditor.html +++ b/src/app/partials/dasheditor.html @@ -153,14 +153,13 @@

Solr Settings

-
Solr Server
+
Solr Server
+
-
Collection
+
Collection
+
-
- + diff --git a/src/app/services/dashboard.js b/src/app/services/dashboard.js index 434e69af2..b408cada8 100755 --- a/src/app/services/dashboard.js +++ b/src/app/services/dashboard.js @@ -53,6 +53,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) { solr: { server: config.solr, core_name: config.solr_core, + global_params: '' // deftype: 'lucene', // df: 'df=message', } @@ -336,41 +337,6 @@ function (angular, $, kbn, _, config, moment, Modernizr) { }); }; - // this.elasticsearch_save = function(type,title,ttl) { - // // Clone object so we can modify it without influencing the existing obejct - // var save = _.clone(self.current); - // var id; - - // // Change title on object clone - // if (type === 'dashboard') { - // id = save.title = _.isUndefined(title) ? self.current.title : title; - // } - - // // Create request with id as title. Rethink this. - // // TODO: - // var request = ejs.Document(config.kibana_index,type,id).source({ - // user: 'guest', - // group: 'guest', - // title: save.title, - // dashboard: angular.toJson(save) - // }); - - // request = type === 'temp' && ttl ? request.ttl(ttl) : request; - - // return request.doIndex( - // // Success - // function(result) { - // if(type === 'dashboard') { - // $location.path('/dashboard/elasticsearch/'+title); - // } - // return result; - // }, - // // Failure - // function() { - // return false; - // } - // ); - // }; // TODO: Rename to solr_save this.elasticsearch_save = function(type,title,ttl) { // Clone object so we can modify it without influencing the existing obejct @@ -382,9 +348,6 @@ function (angular, $, kbn, _, config, moment, Modernizr) { id = save.title = _.isUndefined(title) ? self.current.title : title; } - // DEBUG - // console.log('id for saving dashboard = '+id); - // Create request with id as title. Rethink this. // Use id instead of _id, because it is the default field of Solr schema-less. var request = sjs.Document(config.kibana_index,type,id).source({ @@ -434,10 +397,6 @@ function (angular, $, kbn, _, config, moment, Modernizr) { // Solr this.elasticsearch_list = function(query,count) { - // DEBUG - console.log('LINE 411: query = '+query); - console.log('LINE 412: count = '+count); - // set indices and type sjs.client.server(config.solr + config.kibana_index); var request = sjs.Request().indices(config.kibana_index).types('dashboard'); diff --git a/src/app/services/querySrv.js b/src/app/services/querySrv.js index 0a8242d11..629a9e731 100755 --- a/src/app/services/querySrv.js +++ b/src/app/services/querySrv.js @@ -23,8 +23,8 @@ function (angular, _, config) { query: '*:*', alias: '', pin: false, - type: 'lucene', - df: 'df=message' + // type: 'lucene', + // df: 'df=message' }; // For convenience @@ -118,12 +118,18 @@ function (angular, _, config) { // Get query string for Solr with defType param and default fields (df). this.getQuery = function(id) { var solr_q = 'q=' + self.list[id].query; - if (self.list[id].type) { - solr_q += '&defType=' + self.list[id].type; - } - if (self.list[id].df) { - solr_q += '&' + self.list[id].df; + + // if (self.list[id].type) { + // solr_q += '&defType=' + self.list[id].type; + // } + // if (self.list[id].df) { + // solr_q += '&' + self.list[id].df; + // } + + if (dashboard.current.solr.global_params) { + solr_q += dashboard.current.solr.global_params; } + return solr_q; } From 85b59fca778969bf01a36665d30c777f107326d7 Mon Sep 17 00:00:00 2001 From: Andrew T Date: Thu, 24 Apr 2014 20:06:43 +0700 Subject: [PATCH 11/23] Fix SOL-76: Add Smooth Line mode for histogram module. --- src/app/panels/histogram/editor.html | 14 +++- src/app/panels/histogram/module.js | 95 +++++++++++++--------------- 2 files changed, 56 insertions(+), 53 deletions(-) diff --git a/src/app/panels/histogram/editor.html b/src/app/panels/histogram/editor.html index 109f796c3..193a26898 100755 --- a/src/app/panels/histogram/editor.html +++ b/src/app/panels/histogram/editor.html @@ -32,6 +32,7 @@ --> +
Chart Settings
@@ -45,15 +46,23 @@
Chart Settings
-
+
+ +
+
-
+
+
+ + +
+
@@ -78,6 +87,7 @@
Chart Settings
+
Tooltip Settings
diff --git a/src/app/panels/histogram/module.js b/src/app/panels/histogram/module.js index 0b1c062bf..a8c8bf62d 100755 --- a/src/app/panels/histogram/module.js +++ b/src/app/panels/histogram/module.js @@ -76,7 +76,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) { // Set and populate defaults var _d = { mode : 'count', - // time_field : '@timestamp', time_field : 'event_timestamp', queries : { mode : 'all', @@ -84,8 +83,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { query : '*:*', custom : '' }, - max_rows : 100000, // maximum number of rows returned from Solr - // group_limit : 10000, // maximum number of results to return for each group (group.limit) + max_rows : 100000, // maximum number of rows returned from Solr (also use this for group.limit to simplify UI setting) value_field : null, group_field : null, auto_int : true, @@ -101,6 +99,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { stack : true, points : false, lines : false, + lines_smooth: false, // Enable 'smooth line' mode by removing zero values from the plot. legend : true, 'x-axis' : true, 'y-axis' : true, @@ -115,16 +114,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) { _.defaults($scope.panel,_d); - // var chart_colors = [ - // "#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1 - // "#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2 - // "#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0", //3 - // "#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93", //4 - // "#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7", //5 - // "#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B", //6 - // "#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7" //7 - // ]; - $scope.init = function() { // Hide view options by default $scope.options = false; @@ -212,7 +201,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { $scope.sjs.client.server(dashboard.current.solr.server + dashboard.current.solr.core_name); if (DEBUG) { - console.log('histogram:\n\tdashboard=',dashboard,'\n\t$scope=',$scope,'\n\t$scope.panel=',$scope.panel,'\n\tquerySrv=',querySrv,'\n\tfilterSrv=',filterSrv); + console.debug('histogram:\n\tdashboard=',dashboard,'\n\t$scope=',$scope,'\n\t$scope.panel=',$scope.panel,'\n\tquerySrv=',querySrv,'\n\tfilterSrv=',filterSrv); } var request = $scope.sjs.Request().indices(dashboard.indices[segment]); @@ -244,13 +233,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) { $scope.populate_modal(request); // Build Solr query - // TODO: Validate dashboard.current.services.filter.list[0], what if it is not the timestamp field? - // This will cause error. - // var start_time = new Date(dashboard.current.services.filter.list[0].from).toISOString(); - // var end_time = new Date(dashboard.current.services.filter.list[0].to).toISOString(); - // var fq = '&fq=' + $scope.panel.time_field + ':[' + start_time + '%20TO%20' + end_time + ']'; - // var df = '&df=message&df=host&df=path&df=type'; - var fq = '&' + filterSrv.getSolrFq(); var time_field = filterSrv.getTimeField(); var start_time = filterSrv.getStartTime(); @@ -264,8 +246,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) { '&facet.range.start=' + start_time + '/DAY' + '&facet.range.end=' + end_time + '%2B1DAY/DAY' + '&facet.range.gap=' + facet_gap; - // var filter_fq = ''; - // var filter_either = []; var values_mode_query = ''; // For mode = value @@ -281,16 +261,12 @@ function (angular, app, $, _, kbn, moment, timeSeries) { facet = ''; // if Group By Field is specified - // if ($scope.panel.group_field && $scope.panel.group_limit) { - // values_mode_query += '&group=true&group.field=' + $scope.panel.group_field + '&group.limit=' + $scope.panel.group_limit; - // } if ($scope.panel.group_field) { values_mode_query += '&group=true&group.field=' + $scope.panel.group_field + '&group.limit=' + $scope.panel.max_rows; } } // Set the panel's query - // $scope.panel.queries.query = 'q=' + querySrv.list[0].query + wt_json + rows_limit + fq + facet + values_mode_query; $scope.panel.queries.query = querySrv.getQuery(0) + wt_json + rows_limit + fq + facet + values_mode_query; // Set the additional custom query @@ -331,7 +307,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { results.then(function(results) { if (DEBUG) { - console.log('histogram:\n\trequest='+request+'\n\tresults=',results); + console.debug('histogram:\n\trequest='+request+'\n\tresults=',results); } $scope.panelMeta.loading = false; @@ -354,7 +330,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { var facetIds = [0]; // Need to fix this // Make sure we're still on the same query/queries - // TODO: We probably DON'T NEED THIS + // TODO: We probably DON'T NEED THIS unless we have to support multiple queries in query module. if($scope.query_id === query_id && _.difference(facetIds, $scope.panel.queries.ids).length === 0) { var i = 0, time_series, @@ -363,7 +339,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { _.each($scope.panel.queries.ids, function(id) { // var query_results = results.facets[id]; - if (DEBUG) { console.log('histogram: i='+i+', results=',results,', segment=',segment,', $scope=',$scope); } + if (DEBUG) { console.debug('histogram: i=',i,', segment=',segment,', $scope=',$scope); } // we need to initialize the data variable on the first run, // and when we are working on the first segment of the data. @@ -375,10 +351,10 @@ function (angular, app, $, _, kbn, moment, timeSeries) { fill_style: 'minimal' }); hits = 0; - if (DEBUG) { console.log('\tfirst run: i='+i+', time_series=',time_series); } + if (DEBUG) { console.debug('\tfirst run: i='+i+', time_series=',time_series); } } else { if (DEBUG) { - console.log('\tNot first run: i='+i+', $scope.data[i].time_series=',$scope.data[i].time_series,', hits='+$scope.data[i].hits); + console.debug('\tNot first run: i='+i+', $scope.data[i].time_series=',$scope.data[i].time_series,', hits='+$scope.data[i].hits); } time_series = $scope.data[i].time_series; // Bug fix for wrong event count: @@ -432,18 +408,19 @@ function (angular, app, $, _, kbn, moment, timeSeries) { hits += 1; $scope.hits += 1; } - // TESTING + $scope.data[j] = { - // info: querySrv.list[id], - // Need to define chart info here according to the results, cannot use querySrv.list[id] - info: { - alias: groups[j].groupValue, - color: querySrv.colors[j], - - }, - time_series: group_time_series, - hits: hits + // info: querySrv.list[id], + // Need to define chart info here according to the results, cannot use querySrv.list[id] + info: { + alias: groups[j].groupValue, + color: querySrv.colors[j], + + }, + time_series: group_time_series, + hits: hits }; + } } else { // Group By Field is not specified var entries = results.response.docs; @@ -454,22 +431,20 @@ function (angular, app, $, _, kbn, moment, timeSeries) { hits += 1; $scope.hits += 1; } - // TESTING + $scope.data[i] = { - info: querySrv.list[id], - time_series: time_series, - hits: hits + info: querySrv.list[id], + time_series: time_series, + hits: hits }; } } - - if (DEBUG) { console.log('histogram: time_series=',time_series); } if ($scope.panel.mode !== 'values') { $scope.data[i] = { - info: querySrv.list[id], - time_series: time_series, - hits: hits + info: querySrv.list[id], + time_series: time_series, + hits: hits }; } @@ -651,6 +626,24 @@ function (angular, app, $, _, kbn, moment, timeSeries) { scope.data[i].data = scope.data[i].time_series.getFlotPairs(required_times); } + // ISSUE: SOL-76 + // If 'lines_smooth' is enabled, loop through $scope.data[] and remove zero filled entries. + // Without zero values, the line chart will appear smooth as SiLK ;-) + if (scope.panel.lines_smooth) { + for (var i=0; i < scope.data.length; i++) { + var new_data = []; + for (var j=0; j < scope.data[i].data.length; j++) { + // if value of the timestamp !== 0, then add it to new_data + if (scope.data[i].data[j][1] !== 0) { + new_data.push(scope.data[i].data[j]); + } + } + scope.data[i].data = new_data; + } + } + + console.debug('histogram:\n\tflot options = ',options,'\n\tscope.data = ',scope.data); + scope.plot = $.plot(elem, scope.data, options); } catch(e) { // TODO: Need to fix bug => "Invalid dimensions for plot, width = 0, height = 200" From 7de4afc8e9e4180e2c99d65663b38d0d230c65aa Mon Sep 17 00:00:00 2001 From: Andrew T Date: Thu, 24 Apr 2014 20:07:29 +0700 Subject: [PATCH 12/23] Modify silklogo image. --- src/img/silklogo.jpg | Bin 20901 -> 0 bytes src/img/silklogo.png | Bin 0 -> 4183 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/img/silklogo.jpg create mode 100644 src/img/silklogo.png diff --git a/src/img/silklogo.jpg b/src/img/silklogo.jpg deleted file mode 100644 index 5c5c06835b9b9c64af3b875385b3c05ce0bf57fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20901 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 OD4+%JNNA#fKmXYbl0+H+ diff --git a/src/img/silklogo.png b/src/img/silklogo.png new file mode 100644 index 0000000000000000000000000000000000000000..91c51c159f64b72a97adb0784b3650cff107b348 GIT binary patch literal 4183 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 Date: Thu, 24 Apr 2014 16:57:05 +0200 Subject: [PATCH 13/23] Added lat,lon field to bettermap and enabled filter queries to use filter service --- src/app/dashboards/bettermap.json | 109 +++++++++++++++++++++++---- src/app/panels/bettermap/editor.html | 36 ++++++++- src/app/panels/bettermap/module.js | 83 ++++++++++---------- 3 files changed, 170 insertions(+), 58 deletions(-) diff --git a/src/app/dashboards/bettermap.json b/src/app/dashboards/bettermap.json index d95ba6ae6..ae95831ff 100644 --- a/src/app/dashboards/bettermap.json +++ b/src/app/dashboards/bettermap.json @@ -25,8 +25,8 @@ ], "list": { "0": { - "from": "2014-02-01T00:00:00Z", - "to": "2014-02-28T23:59:47Z", + "from": "2014-01-31T22:00:00.000Z", + "to": "2014-04-23T09:53:36.013Z", "field": "start_time", "type": "time", "mandate": "must", @@ -42,30 +42,109 @@ }, "rows": [ { - "title": "Better Map", - "height": "750px", + "title": "Options", + "height": "50px", "editable": true, "collapse": false, "collapsable": true, "panels": [ { - "title": "Better Map Demo", "error": false, + "span": 4, + "editable": true, + "type": "filtering", + "loadingEditor": false + }, + { + "title": "Set time span", + "error": "", + "span": 4, + "editable": true, + "group": [ + "default" + ], + "type": "timepicker", + "mode": "since", + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ], + "timespan": "30d", + "timefield": "start_time", + "timeformat": "", + "refresh": { + "enable": false, + "interval": 600, + "min": 3 + }, + "filter_id": 0, + "status": "Stable", + "time": { + "from": "02/01/2014 00:00:00", + "to": "04/23/2014 11:53:36" + } + }, + { + "error": false, + "span": 4, + "editable": true, + "type": "query", + "loadingEditor": false, + "query": "*:*", + "pinned": true, + "history": [ + "*:*" + ], + "remember": 10, + "title": "Search", + "def_type": "" + } + ] + }, + { + "title": "BetterMapDemo", + "height": "650px", + "editable": true, + "collapse": false, + "collapsable": true, + "panels": [ + { + "title": "Better Map Demo", "span": 12, "editable": true, - "tooltip" : "start_station_id", + "tooltip": "start_station_id", "group": [ "default" ], - "latitude_field": "start_station_latitude", - "longitude_field": "start_station_longitude", - "type": "bettermap", - "size": 10000, + "field" : "start_station", + "type" : "bettermap", + "size" : 7000, "time_field": "start_time", + "lat_start" : 40, + "lat_end" : 45, + "lon_start" : -80, + "lon_end" : -73, + "dist" : 100, "sort": [ - "start_time", - "desc" - ] + "start_time", + "desc" + ], + "queries": { + "mode": "all", + "ids": [ + 0 + ], + "custom": "" + }, + "spyable": true, + "error": false } ] } @@ -95,6 +174,6 @@ }, "solr": { "server": "http://localhost:8983/solr/", - "core_name": "citi-bike-trip" + "core_name": "bike-trip-geo" } -} +} \ No newline at end of file diff --git a/src/app/panels/bettermap/editor.html b/src/app/panels/bettermap/editor.html index bcbd304d3..7aebfeb45 100755 --- a/src/app/panels/bettermap/editor.html +++ b/src/app/panels/bettermap/editor.html @@ -2,16 +2,46 @@
Coordinate Field geoJSON array! Long,Lat NOT Lat,Long
- +
Tooltip Field
- +
Max Points
- +
+ +
+
+
+
Starting Longitude
+ +
+
+
+
+
Starting Latitude
+ +
+
+
+ +
+
+
+
Ending Longitude
+ +
+
+
+
+
Ending Latitude
+ +
+
+
\ No newline at end of file diff --git a/src/app/panels/bettermap/module.js b/src/app/panels/bettermap/module.js index ce215ecad..3afb206af 100755 --- a/src/app/panels/bettermap/module.js +++ b/src/app/panels/bettermap/module.js @@ -29,12 +29,6 @@ function (angular, app, _, L, localRequire) { module.controller('bettermap', function($scope, querySrv, dashboard, filterSrv) { $scope.panelMeta = { - editorTabs : [ - { - title: 'Queries', - src: 'app/partials/querySelect.html' - } - ], modals : [ { description: "Inspect", @@ -43,6 +37,12 @@ function (angular, app, _, L, localRequire) { show: $scope.panel.spyable } ], + editorTabs : [ + { + title: 'Queries', + src: 'app/partials/querySelect.html' + } + ], status : "Experimental", description : "Displays geo points in clustered groups on a map. The cavaet for this panel is"+ " that, for better or worse, it does NOT use the terms facet and it does query "+ @@ -59,8 +59,12 @@ function (angular, app, _, L, localRequire) { query : '*:*', custom : '' }, - size : 1000, - spyable : true, + size : 1000, + spyable : true, + lat_start: '', + lat_end : '', + lon_start: '', + lon_end: '', // tooltip : "_id", field : null }; @@ -72,11 +76,22 @@ function (angular, app, _, L, localRequire) { // setting this property the paths would be relative to the app not this context/file. $scope.init = function() { - $scope.$on('refresh',function(){ + $scope.$on('refresh',function() { $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.get_data = function(segment,query_id) { $scope.require(['./leaflet/plugins'], function () { @@ -86,23 +101,14 @@ function (angular, app, _, L, localRequire) { if(dashboard.indices.length === 0) { return; } - + + // check if [lon,lat] field is defined + // Used for Now Multi-valued field, Looking forward to support (solr spatial search) if(_.isUndefined($scope.panel.field)) { $scope.panel.error = "Please select a field that contains geo point in [lon,lat] format"; return; } - // Determine the field to sort on - var timeField = _.uniq(_.pluck(filterSrv.getByType('time'),'field')); - - if(timeField.length > 1) { - $scope.panel.error = "Time field must be consistent amongst time filters"; - } else if(timeField.length === 0) { - timeField = null; - } else { - timeField = timeField[0]; - } - // Solr.js $scope.sjs.client.server(dashboard.current.solr.server + dashboard.current.solr.core_name); @@ -113,7 +119,7 @@ function (angular, app, _, L, localRequire) { $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries); // This could probably be changed to a BoolFilter - var boolQuery = $scope.ejs.BoolQuery(); + var boolQuery = $scope.sjs.BoolQuery(); _.each($scope.panel.queries.ids,function(id) { boolQuery = boolQuery.should(querySrv.getEjsObj(id)); }); @@ -125,8 +131,7 @@ function (angular, app, _, L, localRequire) { boolQuery, filterSrv.getBoolFilter(filterSrv.ids) )) - .size($scope.panel.size) // Set the size of query result - .sort($scope.panel.sort[0], $scope.panel.sort[1]); + .size($scope.panel.size); // Set the size of query result $scope.populate_modal(request); @@ -142,40 +147,38 @@ function (angular, app, _, L, localRequire) { // construct the query // set queryData // request = request.setQuery(q); - // TODO: Validate dashboard.current.services.filter.list[0], what if it is not the "defined time field of this dataset" field? + // TODO: Validate dashboard.current.services.filter.list[0], undefined or in-compatible problem // This will cause error. + +// var start_time = new Date(dashboard.current.services.filter.list[0].from).toISOString(); +// var end_time = new Date(dashboard.current.services.filter.list[0].to).toISOString(); +// var fq = '&fq=' + dashboard.current.services.filter.list[0].field + ':[' + start_time + '%20TO%20' + end_time + ']'; - var start_time = new Date(dashboard.current.services.filter.list[0].from).toISOString(); - var end_time = new Date(dashboard.current.services.filter.list[0].to).toISOString(); - - var fq = '&fq=' + $scope.panel.time_field + ':[' + start_time + '%20TO%20' + end_time + ']'; + var fq = '&' + filterSrv.getSolrFq(); var query_size = $scope.panel.size; - var df = '&df=start_time&df=start_station_id&df=start_station_latitude&df=start_station_longitude'; + var df = ''; var wt_json = '&wt=json'; var rows_limit; var sorting = ''; var filter_fq = ''; var filter_either = []; - - if ($scope.panel.sort[0] !== undefined && $scope.panel.sort[1] !== undefined) { - sorting = '&sort=' + $scope.panel.sort[0] + ' ' + $scope.panel.sort[1]; - } - + // set the size of query result if (query_size !== undefined && query_size !== 0) { rows_limit = '&rows=' + query_size; - // facet_limit = '&facet.limit=' + query_size; } else { // default rows_limit = '&rows=25'; - // facet_limit = '&facet.limit=10'; } + + if($scope.panel.lat_start && $scope.panel.lat_end && $scope.panel.lon_start && $scope.panel.lon_end && $scope.panel.field) { + fq += '&fq=' + $scope.panel.field + '_0_coordinate:[' + $scope.panel.lat_start + ' TO ' + $scope.panel.lat_end + '] AND ' + $scope.panel.field + '_1_coordinate:[' + $scope.panel.lon_start + ' TO ' + $scope.panel.lon_end + ']'; + } // Set the panel's query $scope.panel.queries.query = 'q=' + dashboard.current.services.query.list[0].query + df + wt_json + rows_limit + fq + sorting + filter_fq; // Set the additional custom query if ($scope.panel.queries.custom != null) { - // request = request.customQuery($scope.panel.queries.custom); request = request.setQuery($scope.panel.queries.query + $scope.panel.queries.custom); } else { request = request.setQuery($scope.panel.queries.query); @@ -197,14 +200,14 @@ function (angular, app, _, L, localRequire) { $scope.panel.error = $scope.parse_error(results.error); return; } - + // Check that we're still on the same query, if not stop if($scope.query_id === query_id) { // Keep only what we need for the set $scope.data = $scope.data.slice(0,$scope.panel.size).concat(_.map(results.response.docs, function(hit) { return { - coordinates : new L.LatLng(hit[$scope.panel.latitude_field],hit[$scope.panel.longitude_field]), + coordinates : new L.LatLng(hit[$scope.panel.field + '_0_coordinate'],hit[$scope.panel.field + '_1_coordinate']), tooltip : hit[$scope.panel.tooltip] }; })); From f85776b5cb731aad887183d8ebe995b34ce34fdd Mon Sep 17 00:00:00 2001 From: Andrew T Date: Mon, 28 Apr 2014 15:45:38 +0700 Subject: [PATCH 14/23] Cleanup bettermap module. --- src/app/panels/bettermap/module.js | 77 +++++++++++------------------- src/config.js | 2 +- 2 files changed, 30 insertions(+), 49 deletions(-) diff --git a/src/app/panels/bettermap/module.js b/src/app/panels/bettermap/module.js index 3afb206af..c0fb22b81 100755 --- a/src/app/panels/bettermap/module.js +++ b/src/app/panels/bettermap/module.js @@ -136,55 +136,37 @@ function (angular, app, _, L, localRequire) { $scope.populate_modal(request); if (DEBUG) { - console.log('bettermap:\n\trequest=',request,'\n\trequest.toString()=',request.toString()); + console.debug('bettermap:\n\trequest=',request,'\n\trequest.toString()=',request.toString()); } - // TODO: Parse query here and send to request.doSearch() - // declare default Solr params here - // get query - // get from and to time range - // get query.size - // construct the query - // set queryData - // request = request.setQuery(q); - // TODO: Validate dashboard.current.services.filter.list[0], undefined or in-compatible problem - // This will cause error. - -// var start_time = new Date(dashboard.current.services.filter.list[0].from).toISOString(); -// var end_time = new Date(dashboard.current.services.filter.list[0].to).toISOString(); -// var fq = '&fq=' + dashboard.current.services.filter.list[0].field + ':[' + start_time + '%20TO%20' + end_time + ']'; - - var fq = '&' + filterSrv.getSolrFq(); - var query_size = $scope.panel.size; - var df = ''; - var wt_json = '&wt=json'; - var rows_limit; - var sorting = ''; - var filter_fq = ''; - var filter_either = []; - - // set the size of query result - if (query_size !== undefined && query_size !== 0) { - rows_limit = '&rows=' + query_size; - } else { // default - rows_limit = '&rows=25'; - } - - if($scope.panel.lat_start && $scope.panel.lat_end && $scope.panel.lon_start && $scope.panel.lon_end && $scope.panel.field) { - fq += '&fq=' + $scope.panel.field + '_0_coordinate:[' + $scope.panel.lat_start + ' TO ' + $scope.panel.lat_end + '] AND ' + $scope.panel.field + '_1_coordinate:[' + $scope.panel.lon_start + ' TO ' + $scope.panel.lon_end + ']'; - } - - // Set the panel's query - $scope.panel.queries.query = 'q=' + dashboard.current.services.query.list[0].query + df + wt_json + rows_limit + fq + sorting + filter_fq; - - // Set the additional custom query - if ($scope.panel.queries.custom != null) { - request = request.setQuery($scope.panel.queries.query + $scope.panel.queries.custom); - } else { - request = request.setQuery($scope.panel.queries.query); - } + // Build Solr query + var fq = '&' + filterSrv.getSolrFq(); + var query_size = $scope.panel.size; + var wt_json = '&wt=json'; + var rows_limit; + + // set the size of query result + if (query_size !== undefined && query_size !== 0) { + rows_limit = '&rows=' + query_size; + } else { // default + rows_limit = '&rows=25'; + } + + if($scope.panel.lat_start && $scope.panel.lat_end && $scope.panel.lon_start && $scope.panel.lon_end && $scope.panel.field) { + fq += '&fq=' + $scope.panel.field + '_0_coordinate:[' + $scope.panel.lat_start + ' TO ' + $scope.panel.lat_end + '] AND ' + $scope.panel.field + '_1_coordinate:[' + $scope.panel.lon_start + ' TO ' + $scope.panel.lon_end + ']'; + } + + // Set the panel's query + $scope.panel.queries.query = querySrv.getQuery(0) + wt_json + rows_limit + fq; - var results = request.doSearch(); + // Set the additional custom query + if ($scope.panel.queries.custom != null) { + request = request.setQuery($scope.panel.queries.query + $scope.panel.queries.custom); + } else { + request = request.setQuery($scope.panel.queries.query); + } + + var results = request.doSearch(); // Populate scope when we have results // Using promises results.then(function(results) { @@ -203,7 +185,6 @@ function (angular, app, _, L, localRequire) { // Check that we're still on the same query, if not stop if($scope.query_id === query_id) { - // Keep only what we need for the set $scope.data = $scope.data.slice(0,$scope.panel.size).concat(_.map(results.response.docs, function(hit) { return { @@ -296,4 +277,4 @@ function (angular, app, _, L, localRequire) { }; }); -}); +}); \ No newline at end of file diff --git a/src/config.js b/src/config.js index aa628b3c1..8c0bca601 100755 --- a/src/config.js +++ b/src/config.js @@ -51,7 +51,7 @@ function (Settings) { 'column', // 'derivequeries', // TODO // 'trends', // TODO - // 'bettermap', // TODO + 'bettermap', 'query', 'terms', // 'dummy' // Dummy module for testing From 722f5a9296a65e0a259624a94f9c66cd1928075c Mon Sep 17 00:00:00 2001 From: Andrew T Date: Wed, 30 Apr 2014 17:06:21 +0700 Subject: [PATCH 15/23] Major cleanup --- src/app/app.js | 1 - src/app/controllers/dashLoader.js | 14 +---- src/app/panels/histogram/editor.html | 1 + src/app/panels/histogram/module.js | 30 ++------- src/app/panels/map/module.js | 19 +----- src/app/panels/query/editor.html | 20 +----- src/app/panels/query/module.js | 18 ------ src/app/panels/table/module.js | 77 +++++------------------- src/app/panels/terms/module.js | 36 +---------- src/app/partials/inspector.html | 9 +-- src/app/services/dashboard.js | 13 +--- src/app/services/fields.js | 6 -- src/app/services/filterSrv.js | 5 +- src/app/services/kbnIndex.js | 9 +-- src/app/services/querySrv.js | 10 +-- src/config.js | 13 ++-- src/index.html | 5 +- src/vendor/solrjs/solr-angular-client.js | 72 ++-------------------- src/vendor/solrjs/solr.js | 8 +-- 19 files changed, 66 insertions(+), 300 deletions(-) diff --git a/src/app/app.js b/src/app/app.js index 0700038ad..f846e8602 100755 --- a/src/app/app.js +++ b/src/app/app.js @@ -90,7 +90,6 @@ function (angular, $, _, appLevelRequire) { delete $httpProvider.defaults.headers.common["X-Requested-With"]; }]); - // TODO: add ajax-solr ? var apps_deps = [ 'elasticjs.service', diff --git a/src/app/controllers/dashLoader.js b/src/app/controllers/dashLoader.js index 89c755681..fb594f553 100755 --- a/src/app/controllers/dashLoader.js +++ b/src/app/controllers/dashLoader.js @@ -58,13 +58,8 @@ function (angular, _) { ($scope.loader.save_temp_ttl_enable ? ttl : false) ).then( function(result) { - if (DEBUG) { - console.log('result = ',result); - } + if (DEBUG) { console.debug('dashLoader: result = ',result); } - // if(!_.isUndefined(result._id)) { - // alertSrv.set('Dashboard Saved','This dashboard has been saved to Solr as "' + - // result._id + '"','success',5000); // Solr if(!_.isUndefined(result.response.docs[0].id)) { alertSrv.set('Dashboard Saved','This dashboard has been saved to Solr as "' + @@ -102,13 +97,8 @@ function (angular, _) { $scope.elasticsearch_dblist = function(query) { dashboard.elasticsearch_list(query,$scope.loader.load_elasticsearch_size).then( function(result) { - // DEBUG - console.debug("dashLoader : result=",result); + if (DEBUG) { console.debug("dashLoader: result=",result); } - // if(!_.isUndefined(result.hits)) { - // $scope.hits = result.hits.total; - // $scope.elasticsearch.dashboards = result.hits.hits; - // } // Solr if (!_.isUndefined(result.response.docs)) { $scope.hits = result.response.numFound; diff --git a/src/app/panels/histogram/editor.html b/src/app/panels/histogram/editor.html index 193a26898..609dc15d8 100755 --- a/src/app/panels/histogram/editor.html +++ b/src/app/panels/histogram/editor.html @@ -1,6 +1,7 @@
+
diff --git a/src/app/panels/histogram/module.js b/src/app/panels/histogram/module.js index a8c8bf62d..3116356cf 100755 --- a/src/app/panels/histogram/module.js +++ b/src/app/panels/histogram/module.js @@ -200,12 +200,11 @@ function (angular, app, $, _, kbn, moment, timeSeries) { // Solr $scope.sjs.client.server(dashboard.current.solr.server + dashboard.current.solr.core_name); - if (DEBUG) { - console.debug('histogram:\n\tdashboard=',dashboard,'\n\t$scope=',$scope,'\n\t$scope.panel=',$scope.panel,'\n\tquerySrv=',querySrv,'\n\tfilterSrv=',filterSrv); - } + if (DEBUG) { console.debug('histogram:\n\tdashboard=',dashboard,'\n\t$scope=',$scope,'\n\t$scope.panel=',$scope.panel,'\n\tquerySrv=',querySrv,'\n\tfilterSrv=',filterSrv); } var request = $scope.sjs.Request().indices(dashboard.indices[segment]); $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries); + // Build the query _.each($scope.panel.queries.ids, function(id) { var query = $scope.sjs.FilteredQuery( @@ -250,7 +249,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) { // For mode = value if($scope.panel.mode === 'values') { - // if (_.isNull($scope.panel.value_field)) { if (!$scope.panel.value_field) { $scope.panel.error = "In " + $scope.panel.mode + " mode a field must be specified"; return; @@ -271,7 +269,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) { // Set the additional custom query if ($scope.panel.queries.custom != null) { - // request = request.customQuery($scope.panel.queries.custom); request = request.setQuery($scope.panel.queries.query + $scope.panel.queries.custom); } else { request = request.setQuery($scope.panel.queries.query); @@ -305,10 +302,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { // Populate scope when we have results results.then(function(results) { - - if (DEBUG) { - console.debug('histogram:\n\trequest='+request+'\n\tresults=',results); - } + if (DEBUG) { console.debug('histogram:\n\trequest='+request+'\n\tresults=',results); } $scope.panelMeta.loading = false; if(segment === 0) { @@ -319,7 +313,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) { // Check for error and abort if found if(!(_.isUndefined(results.error))) { - // $scope.panel.error = $scope.parse_error(results.error); $scope.panel.error = $scope.parse_error(results.error.msg); return; } @@ -353,9 +346,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { hits = 0; if (DEBUG) { console.debug('\tfirst run: i='+i+', time_series=',time_series); } } else { - if (DEBUG) { - console.debug('\tNot first run: i='+i+', $scope.data[i].time_series=',$scope.data[i].time_series,', hits='+$scope.data[i].hits); - } + if (DEBUG) { console.debug('\tNot first run: i='+i+', $scope.data[i].time_series=',$scope.data[i].time_series,', hits='+$scope.data[i].hits); } time_series = $scope.data[i].time_series; // Bug fix for wrong event count: // Solr don't need to accumulate hits count since it can get total count from facet query. @@ -365,13 +356,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) { $scope.hits = 0; } - // push each entry into the time series, while incrementing counters - // _.each(query_results.entries, function(entry) { - // time_series.addValue(entry.time, entry[$scope.panel.mode]); - // hits += entry.count; // The series level hits counter - // $scope.hits += entry.count; // Entire dataset level hits counter - // }); - // Solr facet counts response is in one big array. // So no need to get each segment like Elasticsearch does. if ($scope.panel.mode === 'count') { @@ -420,7 +404,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) { time_series: group_time_series, hits: hits }; - } } else { // Group By Field is not specified var entries = results.response.docs; @@ -456,10 +439,9 @@ function (angular, app, $, _, kbn, moment, timeSeries) { // Tell the histogram directive to render. $scope.$emit('render'); - // DON'T NEED THIS + // Don't need this for Solr unless we need to support multiple queries. // If we still have segments left, get them // if(segment < dashboard.indices.length-1) { - // if (DEBUG) { console.log('histogram: Still have segments left!'); } // $scope.get_data(segment+1,query_id); // } } @@ -642,7 +624,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { } } - console.debug('histogram:\n\tflot options = ',options,'\n\tscope.data = ',scope.data); + if (DEBUG) { console.debug('histogram:\n\tflot options = ',options,'\n\tscope.data = ',scope.data); } scope.plot = $.plot(elem, scope.data, options); } catch(e) { diff --git a/src/app/panels/map/module.js b/src/app/panels/map/module.js index 6846d99bc..dba96fd43 100755 --- a/src/app/panels/map/module.js +++ b/src/app/panels/map/module.js @@ -116,34 +116,24 @@ function (angular, app, _, $) { $scope.populate_modal(request); // Build Solr query - // var start_time = new Date(filterSrv.list[0].from).toISOString(); - // var end_time = new Date(filterSrv.list[0].to).toISOString(); - // Get time field from filterSrv, is the time field always at list[0]? - // var time_field = filterSrv.list[0].field; - // var fq = '&fq=' + time_field + ':[' + start_time + '%20TO%20' + end_time + ']'; // Get timefield from filterSrv - var fq = '&' + filterSrv.getSolrFq(); - // var df = '&df=message'; var wt_json = '&wt=json'; var rows_limit = '&rows=0'; // for map module, we don't display results from row, but we use facets. - // var facet_gap = '%2B1DAY'; var facet = '&facet=true' + '&facet.field=' + $scope.panel.field + '&facet.limit=' + $scope.panel.size; // Set the panel's query - // $scope.panel.queries.query = 'q=' + querySrv.list[0].query + df + wt_json + fq + rows_limit + facet + filter_fq; $scope.panel.queries.query = querySrv.getQuery(0) + wt_json + fq + rows_limit + facet; // Set the additional custom query if ($scope.panel.queries.custom != null) { - // request = request.customQuery($scope.panel.queries.custom); request = request.setQuery($scope.panel.queries.query + $scope.panel.queries.custom); } else { request = request.setQuery($scope.panel.queries.query); } - console.debug('map: $scope.panel=',$scope.panel); + if (DEBUG) { console.debug('map: $scope.panel=',$scope.panel); } var results = request.doSearch(); @@ -152,7 +142,6 @@ function (angular, app, _, $) { $scope.panelMeta.loading = false; $scope.data = {}; // empty the data for new results - // $scope.hits = results.hits.total; if (results.response.numFound) { $scope.hits = results.response.numFound; } else { @@ -160,12 +149,8 @@ function (angular, app, _, $) { $scope.$emit('render'); return false; } - - // _.each(results.facets.map.terms, function(v) { - // $scope.data[v.term.toUpperCase()] = v.count; - // }); - console.debug('map: results=',results); + if (DEBUG) { console.debug('map: results=',results); } var terms = results.facet_counts.facet_fields[$scope.panel.field]; diff --git a/src/app/panels/query/editor.html b/src/app/panels/query/editor.html index 3fc54e345..bc586bb35 100755 --- a/src/app/panels/query/editor.html +++ b/src/app/panels/query/editor.html @@ -1,19 +1 @@ -Nothing to Configure Here. - \ No newline at end of file +Nothing to Configure Here. \ No newline at end of file diff --git a/src/app/panels/query/module.js b/src/app/panels/query/module.js index 586f47e33..67c75a87a 100755 --- a/src/app/panels/query/module.js +++ b/src/app/panels/query/module.js @@ -28,8 +28,6 @@ define([ // Set and populate defaults var _d = { query : "*:*", - // defType : 'lucene', - // df : 'df=message', pinned : true, history : [], remember: 10 // max: 100, angular strap can't take a variable for items param @@ -42,16 +40,6 @@ define([ }; $scope.refresh = function() { - // _.each($scope.querySrv.list, function (v) { - // if ($scope.panel.def_type) { - // // If defType is specified, strip off old defType params from the query - // // before appending the new defType value. - // v.query = remove_deftype(v.query) + '&defType=' + $scope.panel.def_type; - // } else { - // // strip off defType (in case previously specified) - // v.query = remove_deftype(v.query) - // } - // }); update_history(_.pluck($scope.querySrv.list,'query')); $rootScope.$broadcast('refresh'); }; @@ -78,12 +66,6 @@ define([ } }; - // var remove_deftype = function(query) { - // // strip off all defType params in the query - // return query.replace(/(&defType=\w+)/g,''); - // }; - $scope.init(); - }); }); \ No newline at end of file diff --git a/src/app/panels/table/module.js b/src/app/panels/table/module.js index 5d91451c7..d409338c8 100755 --- a/src/app/panels/table/module.js +++ b/src/app/panels/table/module.js @@ -70,10 +70,7 @@ function (angular, app, _, kbn, moment) { size : 100, // Per page pages : 5, // Pages available offset : 0, - - // sort : ['_score','desc'], sort : ['event_timestamp','desc'], - group : "default", style : {'font-size': '9pt'}, overflow: 'min-height', @@ -93,7 +90,6 @@ function (angular, app, _, kbn, moment) { $scope.init = function () { $scope.Math = Math; // Solr - // $scope.sjs = $scope.sjs || sjsResource(config.solr + config.solr_collection); $scope.sjs = $scope.sjs || sjsResource(dashboard.current.solr.server + dashboard.current.solr.core_name); $scope.$on('refresh',function(){$scope.get_data();}); @@ -197,11 +193,8 @@ function (angular, app, _, kbn, moment) { var _segment = _.isUndefined(segment) ? 0 : segment; $scope.segment = _segment; - if(DEBUG) { - console.debug('table: Begin of get_data():\n\t$scope=',$scope,'\n\t$scope.panel=',$scope.panel,'\n\t_segment='+_segment,'\n\tdashboard.indices[_segment]=',dashboard.indices[_segment],'\n\tdashboard=',dashboard,'\n\tquerySrv=',querySrv,'\n\tfilterSrv=',filterSrv); - } + if (DEBUG) { console.debug('table: Begin of get_data():\n\t$scope=',$scope,'\n\t$scope.panel=',$scope.panel,'\n\t_segment='+_segment,'\n\tdashboard.indices[_segment]=',dashboard.indices[_segment],'\n\tdashboard=',dashboard,'\n\tquerySrv=',querySrv,'\n\tfilterSrv=',filterSrv); } - // Solr $scope.sjs.client.server(dashboard.current.solr.server + dashboard.current.solr.core_name); var request = $scope.sjs.Request().indices(dashboard.indices[_segment]); @@ -226,13 +219,10 @@ function (angular, app, _, kbn, moment) { $scope.populate_modal(request); - if (DEBUG) { - console.log('table:\n\trequest=',request,'\n\trequest.toString()=',request.toString()); - } + if (DEBUG) { console.debug('table:\n\trequest=',request,'\n\trequest.toString()=',request.toString()); } var fq = '&' + filterSrv.getSolrFq(); var query_size = $scope.panel.size * $scope.panel.pages; - // var df = '&df=message'; var wt_json = '&wt=json'; var rows_limit; var sorting = ''; @@ -244,21 +234,17 @@ function (angular, app, _, kbn, moment) { // set the size of query result if (query_size !== undefined && query_size !== 0) { rows_limit = '&rows=' + query_size; - // facet_limit = '&facet.limit=' + query_size; } else { // default rows_limit = '&rows=25'; - // facet_limit = '&facet.limit=10'; } // Set the panel's query - // $scope.panel.queries.query = 'q=' + querySrv.list[0].query + df + wt_json + rows_limit + fq + sorting + filter_fq; $scope.panel.queries.query = querySrv.getQuery(0) + wt_json + rows_limit + fq + sorting; - console.debug('table: query=',$scope.panel.queries.query); + if (DEBUG) { console.debug('table: query=',$scope.panel.queries.query); } // Set the additional custom query if ($scope.panel.queries.custom != null) { - // request = request.customQuery($scope.panel.queries.custom); request = request.setQuery($scope.panel.queries.query + $scope.panel.queries.custom); } else { request = request.setQuery($scope.panel.queries.query); @@ -279,61 +265,31 @@ function (angular, app, _, kbn, moment) { $scope.data = []; } - if(DEBUG) { - console.log('table: results=',results); - console.log('\t_segment='+_segment+', $scope.hits='+$scope.hits+', $scope.data=',$scope.data,', query_id='+query_id+'\n\t$scope.panel',$scope.panel); - } + if(DEBUG) { console.debug('table:\n\tresults=',results,'\n\t_segment=',_segment,', $scope.hits=',$scope.hits,', $scope.data=',$scope.data,', query_id=',query_id,'\n\t$scope.panel',$scope.panel); } // Check for error and abort if found if(!(_.isUndefined(results.error))) { - // $scope.panel.error = $scope.parse_error(results.error); $scope.panel.error = $scope.parse_error(results.error.msg); // There's also results.error.code return; } // Check that we're still on the same query, if not stop if($scope.query_id === query_id) { - // $scope.data= $scope.data.concat(_.map(results.hits.hits, function(hit) { - $scope.data = $scope.data.concat(_.map(results.response.docs, function(hit) { + $scope.data = $scope.data.concat(_.map(results.response.docs, function(hit) { var _h = _.clone(hit); - //_h._source = kbn.flatten_json(hit._source); - //_h.highlight = kbn.flatten_json(hit.highlight||{}); - - _h.kibana = { - // _source : kbn.flatten_json(hit._source), - // highlight : kbn.flatten_json(hit.highlight||{}) - - // _source : kbn.flatten_json(hit_object), - _source : kbn.flatten_json(hit), - highlight : kbn.flatten_json(hit.highlight||{}) - }; - return _h; - })); + _h.kibana = { + _source : kbn.flatten_json(hit), + highlight : kbn.flatten_json(hit.highlight||{}) + }; + + return _h; + })); // Solr does not need to accumulate hits count because it can get total count // from a single faceted query. - // $scope.hits += results.hits.total; $scope.hits = results.response.numFound; - if (DEBUG) { - console.log('\t$scope.hits='+$scope.hits+', $scope.data=',$scope.data); - } - - // NO NEED for sorting here. Solr result is already sorted. - // Sort the data - // $scope.data = _.sortBy($scope.data, function(v){ - // if(!_.isUndefined(v.sort)) { - // return v.sort[0]; - // } else { - // return 0; - // } - // }); - - // We DO NOT need to reverse here because Solr's result is already sorted. - // if($scope.panel.sort[1] === 'desc') { - // if (DEBUG) { console.log('\tREVERSE IT!!'); } - // $scope.data.reverse(); - // } + if (DEBUG) { console.debug('table: $scope.hits=',$scope.hits,', $scope.data=',$scope.data); } // Keep only what we need for the set $scope.data = $scope.data.slice(0,$scope.panel.size * $scope.panel.pages); @@ -349,9 +305,7 @@ function (angular, app, _, kbn, moment) { _segment+1 < dashboard.indices.length) { $scope.get_data(_segment+1,$scope.query_id); - if (DEBUG) { - console.log('\tnot sorting in reverse chrono order!'); - } + if (DEBUG) { console.debug('\tnot sorting in reverse chrono order!'); } } }); @@ -392,7 +346,6 @@ function (angular, app, _, kbn, moment) { return obj; }; - }); // This also escapes some xml sequences @@ -420,8 +373,6 @@ function (angular, app, _, kbn, moment) { }; }); - - module.filter('tableJson', function() { var json; return function(text,prettyLevel) { diff --git a/src/app/panels/terms/module.js b/src/app/panels/terms/module.js index 0510451c4..7b248ae6e 100755 --- a/src/app/panels/terms/module.js +++ b/src/app/panels/terms/module.js @@ -93,9 +93,7 @@ function (angular, app, _, $, kbn) { //Solr $scope.sjs.client.server(dashboard.current.solr.server + dashboard.current.solr.core_name); - if (DEBUG) { - console.log('terms:\n\tdashboard',dashboard,'\n\tquerySrv=',querySrv,'\n\tfilterSrv=',filterSrv); - } + if (DEBUG) { console.debug('terms:\n\tdashboard',dashboard,'\n\tquerySrv=',querySrv,'\n\tfilterSrv=',filterSrv); } request = $scope.sjs.Request().indices(dashboard.indices); @@ -119,31 +117,14 @@ function (angular, app, _, $, kbn) { filterSrv.getBoolFilter(filterSrv.ids) )))).size(0); - // Use request.size to limit the facet query (is this a good idea?) - request = request.size($scope.panel.size); - // Populate the inspector panel $scope.inspector = angular.toJson(JSON.parse(request.toString()),true); - // Create a facet to store and pass on time_field value to request.doSearch() - var facet = $scope.sjs.RangeFacet('time_facet'); - facet = facet.field($scope.panel.time_field); - request = request.facet(facet); - // Build Solr query - // TODO: Validate dashboard.current.services.filter.list[0], what if it is not the timestamp field? - // This will cause error. - // var start_time = new Date(dashboard.current.services.filter.list[0].from).toISOString(); - // var end_time = new Date(dashboard.current.services.filter.list[0].to).toISOString(); - // var fq = '&fq=' + $scope.panel.time_field + ':[' + start_time + '%20TO%20' + end_time + ']'; - // var query_size = $scope.panel.size * $scope.panel.pages; - var fq = '&' + filterSrv.getSolrFq(); var time_field = filterSrv.getTimeField(); var start_time = filterSrv.getStartTime(); var end_time = filterSrv.getEndTime(); - - // var df = '&df=message&df=host&df=path&df=type'; var wt_json = '&wt=json'; var rows_limit = '&rows=0' // for terms, we do not need the actual response doc, so set rows=0 var facet_gap = '%2B1DAY'; @@ -156,12 +137,10 @@ function (angular, app, _, $, kbn) { '&facet.limit=' + $scope.panel.size; // Set the panel's query - // $scope.panel.queries.query = 'q=' + querySrv.list[0].query + wt_json + rows_limit + fq + facet; $scope.panel.queries.query = querySrv.getQuery(0) + wt_json + rows_limit + fq + facet; // Set the additional custom query if ($scope.panel.queries.custom != null) { - // request = request.customQuery($scope.panel.queries.custom); request = request.setQuery($scope.panel.queries.query + $scope.panel.queries.custom); } else { request = request.setQuery($scope.panel.queries.query); @@ -171,21 +150,14 @@ function (angular, app, _, $, kbn) { // Populate scope when we have results results.then(function(results) { - if (DEBUG) { - console.log('terms: results=',results); - } + if (DEBUG) { console.debug('terms: results=',results); } var k = 0; $scope.panelMeta.loading = false; - // $scope.hits = results.hits.total; $scope.hits = results.response.numFound; $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; - // }); + _.each(results.facet_counts.facet_fields, function(v) { for (var i = 0; i < v.length; i++) { var term = v[i]; @@ -201,12 +173,10 @@ function (angular, app, _, $, kbn) { $scope.data.push({label:'Missing field', // data:[[k,results.facets.terms.missing]],meta:"missing",color:'#aaa',opacity:0}); - // TODO: Hard coded to 0 for now. Solr faceting does not provide 'missing' value. data:[[k,0]],meta:"missing",color:'#aaa',opacity:0}); $scope.data.push({label:'Other values', // data:[[k+1,results.facets.terms.other]],meta:"other",color:'#444'}); - // TODO: Hard coded to 0 for now. Solr faceting does not provide 'other' value. data:[[k+1,0]],meta:"other",color:'#444'}); diff --git a/src/app/partials/inspector.html b/src/app/partials/inspector.html index c59552d4f..35026fe17 100755 --- a/src/app/partials/inspector.html +++ b/src/app/partials/inspector.html @@ -1,24 +1,19 @@
diff --git a/src/app/panels/hits/module.js b/src/app/panels/hits/module.js index 066a08157..024f4cece 100755 --- a/src/app/panels/hits/module.js +++ b/src/app/panels/hits/module.js @@ -26,6 +26,8 @@ define([ var module = angular.module('kibana.panels.hits', []); app.useModule(module); + var DEBUG = true; // DEBUG mode + module.controller('hits', function($scope, querySrv, dashboard, filterSrv) { $scope.panelMeta = { modals : [ @@ -48,11 +50,14 @@ define([ var _d = { queries : { mode : 'all', - ids : [] + ids : [], + query : '*:*', + basic_query : '', + custom : '' }, style : { "font-size": '10pt'}, arrangement : 'horizontal', - chart : 'bar', + chart : 'total', counter_pos : 'above', donut : false, tilt : false, @@ -80,24 +85,47 @@ define([ return; } + // Solr + $scope.sjs.client.server(dashboard.current.solr.server + dashboard.current.solr.core_name); + var _segment = _.isUndefined(segment) ? 0 : segment; - var request = $scope.ejs.Request().indices(dashboard.indices[_segment]); + + if (DEBUG) { + console.log('hits:\n\tdashboard',dashboard,'\n\tquerySrv=',querySrv,'\n\tfilterSrv=',filterSrv); + } + + var request = $scope.sjs.Request().indices(dashboard.indices); $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries); // Build the question part of the query _.each($scope.panel.queries.ids, function(id) { - var _q = $scope.ejs.FilteredQuery( + var _q = $scope.sjs.FilteredQuery( querySrv.getEjsObj(id), filterSrv.getBoolFilter(filterSrv.ids)); request = request - .facet($scope.ejs.QueryFacet(id) + .facet($scope.sjs.QueryFacet(id) .query(_q) ).size(0); }); // Populate the inspector panel - $scope.inspector = angular.toJson(JSON.parse(request.toString()),true); + $scope.populate_modal(request); + + //Solr Search Query + var fq = '&' + filterSrv.getSolrFq(); + var wt_json = '&wt=json'; + var rows_limit = '&rows=0'; // for hits, we do not need the actual response doc, so set rows=0 + var facet = ''; + + $scope.panel.queries.query = querySrv.getQuery(0) + fq + facet + wt_json + rows_limit; + + // Set the additional custom query + if ($scope.panel.queries.custom != null) { + request = request.setQuery($scope.panel.queries.query + $scope.panel.queries.custom); + } else { + request = request.setQuery($scope.panel.queries.query); + } // Then run it var results = request.doSearch(); @@ -105,11 +133,8 @@ define([ // Populate scope when we have results results.then(function(results) { $scope.panelMeta.loading = false; - if(_segment === 0) { - $scope.hits = 0; - $scope.data = []; - query_id = $scope.query_id = new Date().getTime(); - } + $scope.hits = results.response.numFound; + $scope.data = []; // Check for error and abort if found if(!(_.isUndefined(results.error))) { @@ -118,19 +143,20 @@ define([ } // Convert facet ids to numbers - var facetIds = _.map(_.keys(results.facets),function(k){return parseInt(k, 10);}); +// var facetIds = _.map(_.keys(results.facets),function(k){return parseInt(k, 10);}); // Make sure we're still on the same query/queries - if($scope.query_id === query_id && - _.intersection(facetIds,$scope.panel.queries.ids).length === $scope.panel.queries.ids.length - ) { +// if($scope.query_id === query_id && +// _.intersection(facetIds,$scope.panel.queries.ids).length === $scope.panel.queries.ids.length +// ) { var i = 0; - _.each($scope.panel.queries.ids, function(id) { - var v = results.facets[id]; - var hits = _.isUndefined($scope.data[i]) || _segment === 0 ? - v.count : $scope.data[i].hits+v.count; - $scope.hits += v.count; - + var id = $scope.panel.queries.ids[0]; +// _.each($scope.panel.queries.ids, function(id) { +// var v = results.facets[id]; +// var hits = _.isUndefined($scope.data[i]) || _segment === 0 ? +// v.count : $scope.data[i].hits+v.count; +// $scope.hits += v.count; + var hits = $scope.hits; // Create series $scope.data[i] = { info: querySrv.list[id], @@ -139,14 +165,15 @@ define([ data: [[i,hits]] }; - i++; - }); +// i++; +// }); + $scope.$emit('render'); - if(_segment < dashboard.indices.length-1) { - $scope.get_data(_segment+1,query_id); - } +// if(_segment < dashboard.indices.length-1) { +// $scope.get_data(_segment+1,query_id); +// } - } +// } }); }; @@ -161,6 +188,11 @@ define([ $scope.refresh = false; $scope.$emit('render'); }; + + $scope.populate_modal = function(request) { + $scope.inspector = angular.toJson(JSON.parse(request.toString()), true); + }; + }); @@ -185,7 +217,7 @@ define([ elem.css({height:scope.panel.height||scope.row.height}); try { - _.each(scope.data,function(series) { + _.each(scope.data, function(series) { series.label = series.info.alias; series.color = series.info.color; }); @@ -267,4 +299,4 @@ define([ } }; }); -}); \ No newline at end of file +}); diff --git a/src/app/panels/table/module.js b/src/app/panels/table/module.js index f8eb4a88f..11519d26a 100755 --- a/src/app/panels/table/module.js +++ b/src/app/panels/table/module.js @@ -43,6 +43,32 @@ function (angular, app, _, kbn, moment) { show: $scope.panel.spyable } ], +// dropdowns : [ +// { +// description: "Export", +// icon: "icon-save", +// list: [ +// { +// "text": "
Export to File
" +// }, +// { +// "text" : " csv format", +// "href": "", +// "click": "exportfile('csv')" +// }, +// { +// "text": " json format", +// "href": "", +// "click": "exportfile('json')" +// }, +// { +// "text": " xml format", +// "href": "", +// "click": "exportfile('xml')" +// } +// ] +// } +// ], editorTabs : [ { title:'Paging', @@ -53,6 +79,7 @@ function (angular, app, _, kbn, moment) { src: 'app/partials/querySelect.html' } ], + exportfile: true, status: "Stable", description: "A paginated table of records matching your query or queries. Click on a row to "+ "expand it and review all of the fields associated with that document.

" @@ -64,7 +91,8 @@ function (angular, app, _, kbn, moment) { queries : { mode : 'all', ids : [], - query : 'q=*:*', + query : '*:*', + basic_query : '', custom : '' }, size : 100, // Per page @@ -86,7 +114,8 @@ function (angular, app, _, kbn, moment) { trimFactor: 300, normTimes : true, spyable : true, - time_field : 'event_timestamp' + time_field : 'event_timestamp', + saveOption : 'json' }; _.defaults($scope.panel,_d); @@ -198,8 +227,7 @@ function (angular, app, _, kbn, moment) { $scope.segment = _segment; if(DEBUG) { - var dummy = new Date(dashboard.current.services.filter.list[0].from).toISOString(); - console.log('table: Begin of get_data():\n\t$scope=',$scope,'\n\t$scope.panel=',$scope.panel,'\n\t_segment='+_segment,'\n\tdashboard.indices[_segment]=',dashboard.indices[_segment],'\n\tdashboard=',dashboard,'\n\tdashboard.current.services.filter.list[0].from='+ dummy,'\n\tquerySrv=',querySrv,'\n\tfilterSrv=',filterSrv); + console.debug('table: Begin of get_data():\n\t$scope=',$scope,'\n\t$scope.panel=',$scope.panel,'\n\t_segment='+_segment,'\n\tdashboard.indices[_segment]=',dashboard.indices[_segment],'\n\tdashboard=',dashboard,'\n\tquerySrv=',querySrv,'\n\tfilterSrv=',filterSrv); } // Solr @@ -225,57 +253,20 @@ function (angular, app, _, kbn, moment) { .size($scope.panel.size*$scope.panel.pages) // Set the size of query result .sort($scope.panel.sort[0],$scope.panel.sort[1]); - $scope.populate_modal(request); + $scope.panel_request = request; - // Create a facet to store and pass on time_field value to request.doSearch() - // var facet = $scope.sjs.RangeFacet('time_facet'); - // facet = facet.field($scope.panel.time_field); - // request = request.facet(facet); + $scope.populate_modal(request); if (DEBUG) { console.log('table:\n\trequest=',request,'\n\trequest.toString()=',request.toString()); } - // TODO: Parse query here and send to request.doSearch() - // declare default Solr params here - // get query - // get from and to time range - // get query.size - // construct the query - // set queryData - // request = request.setQuery(q); - // TODO: Validate dashboard.current.services.filter.list[0], what if it is not the timestamp field? - // This will cause error. - var start_time = new Date(dashboard.current.services.filter.list[0].from).toISOString(); - var end_time = new Date(dashboard.current.services.filter.list[0].to).toISOString(); - var fq = '&fq=' + $scope.panel.time_field + ':[' + start_time + '%20TO%20' + end_time + ']'; + var fq = '&' + filterSrv.getSolrFq(); var query_size = $scope.panel.size * $scope.panel.pages; - var df = '&df=message&df=host&df=path&df=type'; + // var df = '&df=message'; var wt_json = '&wt=json'; var rows_limit; var sorting = ''; - var filter_fq = ''; - var filter_either = []; - - // Apply filters to the query - _.each(dashboard.current.services.filter.list, function(v,k) { - // Skip the timestamp filter because it's already applied to the query using fq param. - // timestamp filter should be in k = 0 - if (k > 0 && v.field != $scope.panel.time_field && v.active) { - if (DEBUG) { console.log('terms: k=',k,' v=',v); } - if (v.mandate == 'must') { - filter_fq = filter_fq + '&fq=' + v.field + ':"' + v.value + '"'; - } else if (v.mandate == 'mustNot') { - filter_fq = filter_fq + '&fq=-' + v.field + ':"' + v.value + '"'; - } else if (v.mandate == 'either') { - filter_either.push(v.field + ':"' + v.value + '"'); - } - } - }); - // parse filter_either array values, if exists - if (filter_either.length > 0) { - filter_fq = filter_fq + '&fq=(' + filter_either.join(' OR ') + ')'; - } if ($scope.panel.sort[0] !== undefined && $scope.panel.sort[1] !== undefined) { sorting = '&sort=' + $scope.panel.sort[0] + ' ' + $scope.panel.sort[1]; @@ -291,7 +282,11 @@ function (angular, app, _, kbn, moment) { } // Set the panel's query - $scope.panel.queries.query = 'q=' + dashboard.current.services.query.list[0].query + df + wt_json + rows_limit + fq + sorting + filter_fq; + // $scope.panel.queries.query = 'q=' + querySrv.list[0].query + df + wt_json + rows_limit + fq + sorting + filter_fq; + $scope.panel.queries.basic_query = querySrv.getQuery(0) + fq + sorting + rows_limit; + $scope.panel.queries.query = $scope.panel.queries.basic_query + wt_json; + + console.debug('table: query=',$scope.panel.queries.query); // Set the additional custom query if ($scope.panel.queries.custom != null) { @@ -394,6 +389,38 @@ function (angular, app, _, kbn, moment) { }); }; + $scope.exportfile = function(filetype) { + var omitHeader = '&omitHeader=true'; + var exportQuery = $scope.panel.queries.basic_query + '&wt=' + filetype + omitHeader; + + var request = $scope.panel_request; + request = request.setQuery(exportQuery); + + var response = request.doSearch(); + + response.then(function(response) { + var blob; // the file to be written + // TODO: manipulating solr requests + // pagination (batch downloading) + // example: 1,000,000 rows will explode the memory ! + if(filetype === 'json') { + blob = new Blob([angular.toJson(response,true)], {type: "application/json;charset=utf-8"}); + } else if(filetype === 'csv') { + blob = new Blob([response.toString()], {type: "text/csv;charset=utf-8"}); + } else if(filetype === 'xml'){ + blob = new Blob([response.toString()], {type: "application/xml;charset=utf-8"}); + } else { + // incorrect file type + alert('incorrect file type'); + return false; + } + // from filesaver.js + window.saveAs(blob, "table"+"-"+new Date().getTime()+"."+filetype); + return true; + }); + + }; + $scope.populate_modal = function(request) { $scope.inspector = angular.toJson(JSON.parse(request.toString()),true); }; @@ -506,4 +533,4 @@ function (angular, app, _, kbn, moment) { }; }); -}); \ No newline at end of file +}); diff --git a/src/config.js b/src/config.js index aa628b3c1..c3ee7cbed 100755 --- a/src/config.js +++ b/src/config.js @@ -46,7 +46,7 @@ function (Settings) { 'timepicker', 'text', // 'fields', // Deprecated, table panel now integrates a field selector - // 'hits', // TODO + 'hits', // TODO // 'dashcontrol', // Deprecated, moved to nav bar 'column', // 'derivequeries', // TODO From ac8f769d66c8ee5d105d3311efc28aab3442d3d9 Mon Sep 17 00:00:00 2001 From: Andrew T Date: Fri, 2 May 2014 12:56:15 +0700 Subject: [PATCH 17/23] Add collections drop-down box to the dashboard. --- src/app/partials/dashLoader.html | 2 ++ src/app/services/dashboard.js | 17 +++++++++++++++- src/app/services/kbnIndex.js | 34 ++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/app/partials/dashLoader.html b/src/app/partials/dashLoader.html index 0caf24189..2bcc54674 100755 --- a/src/app/partials/dashLoader.html +++ b/src/app/partials/dashLoader.html @@ -1,4 +1,6 @@ +

+
  • + +
  • - + + +
  • - +
  • diff --git a/src/app/partials/dasheditor.html b/src/app/partials/dasheditor.html index 871fea1ea..196f7127e 100755 --- a/src/app/partials/dasheditor.html +++ b/src/app/partials/dasheditor.html @@ -103,46 +103,50 @@
    Default Index If
    +
    +
    Show Collections Picker
    + +
    Allow saving to
    - +
    - +
    - +
    - +
    Allow loading from
    - +
    - +
    - +
    -
    - +
    +
    Sharing
    - +
    -
    - +
    +
    -
    - +
    +
    @@ -160,12 +164,6 @@
    Solr Server
    Collection
    - -
    diff --git a/src/app/services/dashboard.js b/src/app/services/dashboard.js index 1be3d323b..d5dcb5469 100755 --- a/src/app/services/dashboard.js +++ b/src/app/services/dashboard.js @@ -28,6 +28,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) { rows: [], services: {}, loader: { + dropdown_collections: true, save_gist: false, save_elasticsearch: true, save_local: true, @@ -171,6 +172,8 @@ function (angular, $, kbn, _, config, moment, Modernizr) { self.indices = [self.current.index.default]; $rootScope.$broadcast('refresh'); } + + if (DEBUG) { console.debug('dashboard: after refresh',self); } }; var dash_defaults = function(dashboard) { From 765386fcf7f075893e20342d998fe38735766c3f Mon Sep 17 00:00:00 2001 From: Andrew T Date: Mon, 5 May 2014 18:32:16 +0700 Subject: [PATCH 19/23] Fix bugs SILK-4 and SILK-29 (timepicker issue when using absolute mode or since mode, the date will skip back one day). --- src/app/panels/timepicker/module.html | 2 +- src/app/panels/timepicker/module.js | 64 +++++++++++++++++++++------ 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/app/panels/timepicker/module.html b/src/app/panels/timepicker/module.html index 786bcc447..498e959af 100755 --- a/src/app/panels/timepicker/module.html +++ b/src/app/panels/timepicker/module.html @@ -34,7 +34,7 @@
    - +
    diff --git a/src/app/panels/timepicker/module.js b/src/app/panels/timepicker/module.js index d325811b2..a93f54c07 100755 --- a/src/app/panels/timepicker/module.js +++ b/src/app/panels/timepicker/module.js @@ -22,6 +22,8 @@ define([ function (angular, app, _, moment, kbn) { 'use strict'; + var DEBUG = true; // DEBUG mode + var module = angular.module('kibana.panels.timepicker', []); app.useModule(module); @@ -32,14 +34,12 @@ function (angular, app, _, moment, kbn) { " or if you're using time stamped indices, you need one of these" }; - // Set and populate defaults var _d = { status : "Stable", mode : "relative", time_options : ['5m','15m','1h','6h','12h','24h','2d','7d','30d'], timespan : '15m', - // timefield : '@timestamp', timefield : 'event_timestamp', timeformat : "", refresh : { @@ -77,6 +77,7 @@ function (angular, app, _, moment, kbn) { }; break; } + $scope.time.field = $scope.panel.timefield; // These 3 statements basicly do everything time_apply() does set_timepicker($scope.time.from,$scope.time.to); @@ -90,7 +91,6 @@ function (angular, app, _, moment, kbn) { dashboard.refresh(); - // Start refresh timer if enabled if ($scope.panel.refresh.enable) { $scope.set_interval($scope.panel.refresh.interval); @@ -143,10 +143,13 @@ function (angular, app, _, moment, kbn) { }; var update_panel = function() { - // 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 // we're in relative mode since we dont want to store the time object in the // json for relative periods if($scope.panel.mode !== 'relative') { + + if (DEBUG) { console.debug('timepicker: update_panel() $scope.time = ',$scope.time,', $scope.timepicker = ',$scope.timepicker); } + $scope.panel.time = { from : $scope.time.from.format("MM/DD/YYYY HH:mm:ss"), to : $scope.time.to.format("MM/DD/YYYY HH:mm:ss"), @@ -184,15 +187,25 @@ function (angular, app, _, moment, kbn) { $scope.time_apply(); }; - // $scope.time_calc = function(){ var from,to; + // If time picker is defined (usually is) TOFIX: Horrible parsing if(!(_.isUndefined($scope.timepicker))) { + if (DEBUG) { + console.debug('timepicker: time_calc() BEFORE $scope.timepicker.from.date moment = ',moment($scope.timepicker.from.date).format('MM/DD/YYYY')); + console.debug('timepicker: time_calc() BEFORE $scope.timepicker.from.time = ',$scope.timepicker.from.time); + } + + // Fix for SILK-4 and SILK-29 bugs + // by using moment.utc() instead of just moment() from = $scope.panel.mode === 'relative' ? moment(kbn.time_ago($scope.panel.timespan)) : - moment(moment($scope.timepicker.from.date).format('MM/DD/YYYY') + " " + $scope.timepicker.from.time,'MM/DD/YYYY HH:mm:ss'); + moment(moment.utc($scope.timepicker.from.date).format('MM/DD/YYYY') + " " + $scope.timepicker.from.time,'MM/DD/YYYY HH:mm:ss'); to = $scope.panel.mode !== 'absolute' ? moment() : - moment(moment($scope.timepicker.to.date).format('MM/DD/YYYY') + " " + $scope.timepicker.to.time,'MM/DD/YYYY HH:mm:ss'); + moment(moment.utc($scope.timepicker.to.date).format('MM/DD/YYYY') + " " + $scope.timepicker.to.time,'MM/DD/YYYY HH:mm:ss'); + + if (DEBUG) { console.debug('timepicker: time_calc() AFTER calculated from = ',from,',to = ',to); } + // Otherwise (probably initialization) } else { from = $scope.panel.mode === 'relative' ? moment(kbn.time_ago($scope.panel.timespan)) : @@ -205,9 +218,18 @@ function (angular, app, _, moment, kbn) { from = moment(to.valueOf() - 1000); } - $timeout(function(){ - set_timepicker(from,to); - }); + // Fix for SILK-4 and SILK-29 bugs + // This $timeout function causes the timepicker skip-back-one-day bugs. + // Because it will set the timepicker values to $scope.time.from and $scope.time.to, which + // are the calculated UTC time values, and depending on your browser timezone, these values + // might be minus one day. And when you press the timepicker button to update the time, it + // will keep decreasing the date by one day. + // $scope.timepicker => time based on browser timezone + // $scope.time => calculated UTC time + // + // $timeout(function(){ + // set_timepicker(from,to); + // }); return { from : from, @@ -216,23 +238,39 @@ function (angular, app, _, moment, kbn) { }; $scope.time_apply = function() { - $scope.panel.error = ""; // Update internal time object - + $scope.panel.error = ""; + // Remove all other time filters filterSrv.removeByType('time'); + if (DEBUG) { console.debug('timepicker: time_apply() BEFORE time_calc() $scope.time = ',$scope.time,' $scope.timepicker.from.date = ',$scope.timepicker.from.date.toString()); } $scope.time = $scope.time_calc(); $scope.time.field = $scope.panel.timefield; + if (DEBUG) { console.debug('timepicker: time_apply() AFTER time_calc() $scope.time = ',$scope.time,' $scope.timepicker = ',$scope.timepicker); } + update_panel(); set_time_filter($scope.time); dashboard.refresh(); + }; + // No need to automatically call time_apply() when changing time mode, + // because it will mess up the timepicker. + // + // $scope.$watch('panel.mode', $scope.time_apply); + + $scope.time_check = function() { + // FOR DEBUGGING timepicker weirdness with UTC time conversion. + if (DEBUG) { + console.debug('timepicker: time_check() $scope.timepicker = ',$scope.timepicker, + ' $scope.timepicker.from.date = ',$scope.timepicker.from.date.toString(), + ' moment.utc($scope.timepicker.from.date).format("MM/DD/YYYY") = ', moment.utc($scope.timepicker.from.date).format('MM/DD/YYYY') + ); + } }; - $scope.$watch('panel.mode', $scope.time_apply); function set_time_filter(time) { time.type = 'time'; From 579066d3005078ea4e36643547a9725b96304d29 Mon Sep 17 00:00:00 2001 From: Andrew T Date: Mon, 5 May 2014 21:57:20 +0700 Subject: [PATCH 20/23] Change the way we include ./leaflet/plugins back to the old Kibana way. Somehow we cannot move the lib to the top of the file. It will cause undefined error. --- src/app/panels/bettermap/module.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/panels/bettermap/module.js b/src/app/panels/bettermap/module.js index 06bb8bc11..3dbdd7821 100755 --- a/src/app/panels/bettermap/module.js +++ b/src/app/panels/bettermap/module.js @@ -13,7 +13,9 @@ define([ 'app', 'underscore', './leaflet/leaflet-src', - './leaflet/plugins', + 'require', + // './leaflet/plugins', // moving it here causing error in the app, fallback to the old Kibana way. + 'css!./module.css', 'css!./leaflet/leaflet.css', 'css!./leaflet/plugins.css' @@ -122,7 +124,7 @@ function (angular, app, _, L, localRequire) { boolQuery = boolQuery.should(querySrv.getEjsObj(id)); }); - var request = $scope.sjs.Request().indices(dashboard.indices[_segment]); + // var request = $scope.sjs.Request().indices(dashboard.indices[_segment]); request = request.query( $scope.sjs.FilteredQuery( @@ -233,7 +235,7 @@ function (angular, app, _, L, localRequire) { var map, layerGroup; function render_panel() { -// scope.require(['./leaflet/plugins'], function () { + scope.require(['./leaflet/plugins'], function () { scope.panelMeta.loading = false; L.Icon.Default.imagePath = 'app/panels/bettermap/leaflet/images'; @@ -269,7 +271,7 @@ function (angular, app, _, L, localRequire) { layerGroup.addTo(map); map.fitBounds(_.pluck(scope.data,'coordinates')); -// }); + }); } } }; From 934654346b95ae0325b3247e8088b384871ff1d1 Mon Sep 17 00:00:00 2001 From: Andrew T Date: Wed, 7 May 2014 15:58:26 -0700 Subject: [PATCH 21/23] Default setting to hide the collection drop-down from dashboard UI. Add sorting by time field to bettermap module. Remove df params from queries used in default.json and guided.json. --- src/app/dashboards/default.json | 11 +++++++---- src/app/dashboards/guided.json | 10 ++++++---- src/app/panels/bettermap/module.js | 7 ++++--- src/app/partials/dasheditor.html | 2 +- src/app/services/dashboard.js | 2 +- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/app/dashboards/default.json b/src/app/dashboards/default.json index 8f190a44d..ae2d80744 100755 --- a/src/app/dashboards/default.json +++ b/src/app/dashboards/default.json @@ -175,7 +175,7 @@ "ids": [ 0 ], - "query": "q=error&df=message&df=host&df=path&df=type&wt=json&rows=0&fq=event_timestamp:[2014-04-01T07:47:30.228Z%20TO%202014-04-02T07:47:30.228Z]&facet=true&facet.range=event_timestamp&facet.range.start=2014-04-01T07:47:30.228Z/DAY&facet.range.end=2014-04-02T07:47:30.228Z%2B1DAY/DAY&facet.range.gap=%2B10MINUTE", + "query": "q=error&wt=json&rows=0&fq=event_timestamp:[2014-04-01T07:47:30.228Z%20TO%202014-04-02T07:47:30.228Z]&facet=true&facet.range=event_timestamp&facet.range.start=2014-04-01T07:47:30.228Z/DAY&facet.range.end=2014-04-02T07:47:30.228Z%2B1DAY/DAY&facet.range.gap=%2B10MINUTE", "custom": "" }, "title": "System Log Messages", @@ -213,7 +213,7 @@ "ids": [ 0 ], - "query": "q=error&df=message&df=host&df=path&df=type&wt=json&rows=0&fq=event_timestamp:[2014-04-01T07:47:30.228Z%20TO%202014-04-02T07:47:30.228Z]&facet=true&facet.field=syslog_program&facet.range=event_timestamp&facet.range.start=2014-04-01T07:47:30.228Z&facet.range.end=2014-04-02T07:47:30.228Z&facet.range.gap=%2B1DAY&facet.limit=10", + "query": "q=error&wt=json&rows=0&fq=event_timestamp:[2014-04-01T07:47:30.228Z%20TO%202014-04-02T07:47:30.228Z]&facet=true&facet.field=syslog_program&facet.range=event_timestamp&facet.range.start=2014-04-01T07:47:30.228Z&facet.range.end=2014-04-02T07:47:30.228Z&facet.range.gap=%2B1DAY&facet.limit=10", "custom": "" }, "field": "syslog_program", @@ -279,7 +279,7 @@ "ids": [ 0 ], - "query": "q=error&df=message&df=host&df=path&df=type&wt=json&rows=1000&fq=event_timestamp:[2014-04-01T07:47:30.228Z%20TO%202014-04-02T07:47:30.228Z]&sort=event_timestamp desc", + "query": "q=error&wt=json&rows=1000&fq=event_timestamp:[2014-04-01T07:47:30.228Z%20TO%202014-04-02T07:47:30.228Z]&sort=event_timestamp desc", "custom": "" }, "field_list": true, @@ -301,6 +301,7 @@ "style": "light", "panel_hints": true, "loader": { + "dropdown_collections": false, "save_gist": false, "save_elasticsearch": true, "save_local": true, @@ -316,6 +317,8 @@ }, "solr": { "server": "http://localhost:8983/solr/", - "core_name": "logstash_logs" + "core_name": "logstash_logs", + "core_list": [], + "global_params": "&df=message" } } \ No newline at end of file diff --git a/src/app/dashboards/guided.json b/src/app/dashboards/guided.json index 76a2aa21f..2197c5854 100755 --- a/src/app/dashboards/guided.json +++ b/src/app/dashboards/guided.json @@ -200,7 +200,7 @@ "ids": [ 0 ], - "query": "q=*&df=message&df=host&df=path&df=type&wt=json&rows=0&fq=event_timestamp:[2014-04-02T05:07:22.000Z%20TO%202014-04-02T05:22:22.000Z]&facet=true&facet.range=event_timestamp&facet.range.start=2014-04-02T05:07:22.000Z/DAY&facet.range.end=2014-04-02T05:22:22.000Z%2B1DAY/DAY&facet.range.gap=%2B10SECOND", + "query": "q=*&wt=json&rows=0&fq=event_timestamp:[2014-04-02T05:07:22.000Z%20TO%202014-04-02T05:22:22.000Z]&facet=true&facet.range=event_timestamp&facet.range.start=2014-04-02T05:07:22.000Z/DAY&facet.range.end=2014-04-02T05:22:22.000Z%2B1DAY/DAY&facet.range.gap=%2B10SECOND", "custom": "" }, "max_rows": 100000, @@ -258,7 +258,7 @@ "ids": [ 0 ], - "query": "q=*&df=message&df=host&df=path&df=type&wt=json&rows=0&fq=event_timestamp:[2014-04-02T05:07:22.000Z%20TO%202014-04-02T05:22:22.000Z]&facet=true&facet.field=message&facet.range=event_timestamp&facet.range.start=2014-04-02T05:07:22.000Z&facet.range.end=2014-04-02T05:22:22.000Z&facet.range.gap=%2B1DAY&facet.limit=100" + "query": "q=*&wt=json&rows=0&fq=event_timestamp:[2014-04-02T05:07:22.000Z%20TO%202014-04-02T05:22:22.000Z]&facet=true&facet.field=message&facet.range=event_timestamp&facet.range.start=2014-04-02T05:07:22.000Z&facet.range.end=2014-04-02T05:22:22.000Z&facet.range.gap=%2B1DAY&facet.limit=100" }, "field": "message", "exclude": [], @@ -320,7 +320,7 @@ "ids": [ 0 ], - "query": "q=*&df=message&df=host&df=path&df=type&wt=json&rows=500&fq=event_timestamp:[2014-04-02T05:07:22.000Z%20TO%202014-04-02T05:22:22.000Z]&sort=id desc" + "query": "q=*&wt=json&rows=500&fq=event_timestamp:[2014-04-02T05:07:22.000Z%20TO%202014-04-02T05:22:22.000Z]&sort=id desc" }, "field_list": true, "status": "Stable", @@ -357,6 +357,8 @@ }, "solr": { "server": "http://localhost:8983/solr/", - "core_name": "collection1" + "core_name": "collection1", + "core_list": [], + "global_params": "&df=message" } } \ No newline at end of file diff --git a/src/app/panels/bettermap/module.js b/src/app/panels/bettermap/module.js index 3dbdd7821..278a7c7f1 100755 --- a/src/app/panels/bettermap/module.js +++ b/src/app/panels/bettermap/module.js @@ -115,7 +115,7 @@ function (angular, app, _, L, localRequire) { var _segment = _.isUndefined(segment) ? 0 : segment; - var request = $scope.sjs.Request().indices(dashboard.indices); + // var request = $scope.sjs.Request().indices(dashboard.indices); $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries); // This could probably be changed to a BoolFilter @@ -124,7 +124,7 @@ function (angular, app, _, L, localRequire) { boolQuery = boolQuery.should(querySrv.getEjsObj(id)); }); - // var request = $scope.sjs.Request().indices(dashboard.indices[_segment]); + var request = $scope.sjs.Request().indices(dashboard.indices[_segment]); request = request.query( $scope.sjs.FilteredQuery( @@ -144,6 +144,7 @@ function (angular, app, _, L, localRequire) { var query_size = $scope.panel.size; var wt_json = '&wt=json'; var rows_limit; + var sorting = '&sort=' + filterSrv.getTimeField() + ' desc'; // Only get the latest data, sorted by time field. // set the size of query result if (query_size !== undefined && query_size !== 0) { @@ -157,7 +158,7 @@ function (angular, app, _, L, localRequire) { } // Set the panel's query - $scope.panel.queries.query = querySrv.getQuery(0) + wt_json + rows_limit + fq; + $scope.panel.queries.query = querySrv.getQuery(0) + wt_json + rows_limit + fq + sorting; // Set the additional custom query if ($scope.panel.queries.custom != null) { diff --git a/src/app/partials/dasheditor.html b/src/app/partials/dasheditor.html index 196f7127e..e49b3ce83 100755 --- a/src/app/partials/dasheditor.html +++ b/src/app/partials/dasheditor.html @@ -167,7 +167,7 @@
    Collection
    -
    Global Query Parameters
    +
    Global Query Parameters Specify query parameters to be appended to all queries of the dashboard panels.
    diff --git a/src/app/services/dashboard.js b/src/app/services/dashboard.js index d5dcb5469..b37aee515 100755 --- a/src/app/services/dashboard.js +++ b/src/app/services/dashboard.js @@ -28,7 +28,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) { rows: [], services: {}, loader: { - dropdown_collections: true, + dropdown_collections: false, save_gist: false, save_elasticsearch: true, save_local: true, From 0fa3175dd72c24ed49521794210b94ef1dd386a9 Mon Sep 17 00:00:00 2001 From: Ravi Krishnamurthy Date: Sun, 11 May 2014 14:42:55 -0700 Subject: [PATCH 22/23] Added Release Notes for Banana 1.2 --- README.md | 77 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 3faab83d3..63e33d32c 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,42 @@ # Banana -__NOTE__: You have reached the Banana repository. -The Banana project is a fork of Kibana 3, which can be found at [http://three.kibana.org](http://three.kibana.org) -The goal is to port the code to work with time series data stored in Apache Solr. +The Banana project was forked from Kibana 3 ([http://three.kibana.org](http://three.kibana.org)), and works with all kinds of time series data stored in Apache Solr. It uses Kibana's powerful dashboard configuration capabilities, ports key panels to work with Solr, and provides significant additional capabilities. + +The goal is to create a rich and flexible UI, enabling users to rapidly develop end-to-end applications that leverage the power of Solr. + ## IMPORTANT -Pull the repo from the "release" branch; version 1.1 will be tagged as banana-1.1. +Pull the repo from the "release" branch; version 1.2 will be tagged as banana-1.2. + + +## Banana 1.2: Released on 11 May 2014 + +Following release 1.1, we have addressed a number of user requests, including: + +1. This release provides panels for representing geo-spatial data—a _map_ module that provides a heat map-style representation based on two-letter country codes or US state codes, and a _bettermap_ module that provides a clustered representation of location (_LatLonType_) data. +2. The _Table Module_ now has a Save button that enables you to save to csv, JSON or XML formats so that you can use other tools like MS Excel for further analysis. The number of rows downloaded will be equal to number of “pageable” hits configured in the _Paging_ tab within the _Table Panel Configuration Menu_ (accessed by clicking on the cog wheel icon near the top right of the table panel). +3. You can now control whether a dashboard can be saved and/or edited from the _Editable_ checkbox in the _General_ tab, and the _Controls_ tab, both within the _Dashboard Configurator_ (accessed from the cog-wheel icon to very top and right of dashboard). +4. We have added a _hits_ panel that provides you with the number of matching results returned while using the global query parameters. This is useful if you want to make the number prominent or if you are not using the histogram panel prominently. +5. You can now provide additional _Global Query Parameters_ that apply to all panels of the dashboard from the _Solr_ tab in the _Dashboard Configurator_. Among other uses, this feature is invaluable for: + * Specifying a custom query parser (Solr query parameter: &defType) or search handler (&qt) + * Specifying a user type for use in custom business rules at the Solr server. + * Specifying default search fields (&df) +6. We fixed a bug in the _values_ mode within the _histogram_ module, where missing values were previously assumed to be zero. This led to jagged graphs when the “group by” option was used. We no longer set them to zero but rather have the individual lines skip the missing values. +7. In the _Absolute Time_ and _Since_ modes, the _timepicker_ used to skip back one day if your browser time was behind UTC. This issue has now been fixed. +8. Banana 1.1 hardcoded certain default search fields (df's) to work with our LogStash output writer. Specifically, it hardcoded a df=message. This means that your old dashboards may not be fetching query results with Banana 1.2, though they were doing so with 1.1. To fix this, add a _Global Query Parameter_ &df=message (or whatever field you want to search on) within the _Dashboard Configurator._ Alternately, you can set the default search field in your solrconfig (recommended). + ## Banana 1.1 is here! We have added a number of exciting new features and fixed key issues, including: -* You can now add a Filtering panel that supports global filter queries (fq's). Now, if you click on a facet in the terms panel, the results will be filtered for that particular value. -* The terms, histogram and table modules allow you to specify a panel-specific filter query (within the Query Tab while configuring the panel) allowing greater flexibility in designing dashboards. -* The inspector icon on these panels shows the Solr query, which is very useful for debugging dashboards. -* The Histogram module allows you to plot values in addition to counts. It also allows you to group values by another field. This would be useful if for example you plot CPU utilization over time and want to group by hostname. -* The sort operation in the Table module is now fixed and works correctly on single-valued fields. -* We have refactored the code to enable easier addition of new modules and fixes to existing modules. + +1. You can now add a _Filtering panel_ that supports global filter queries (fq's). Now, if you click on a facet in the terms panel, the results will be filtered for that particular value. +2. The _terms_, _histogram_ and _table_ modules allow you to specify a panel-specific filter query (within the _Query Tab_ while configuring the panel) allowing greater flexibility in designing dashboards. +3. The _inspector_ icon on these panels shows the Solr query, which is very useful for debugging dashboards. +4. The _Histogram_ module allows you to plot values in addition to counts. It also allows you to group values by another field. This would be useful if for example you plot CPU utilization over time and want to group by hostname. +5. The sort operation in the _Table_ module is now fixed and works correctly on single-valued fields. +6. We have refactored the code to enable easier addition of new modules and fixes to existing modules. ### Changes to your dashboards If you created dashboards for Banana 1.0, you did not have a global filtering panel. In some cases, these filter values can be implicitly set to defaults that may lead to strange search results. We recommend updating your old dashboards by adding a filtering panel. A good way to do it visually is to put the filtering panel on its own row and hide it when it is not needed. @@ -27,7 +47,7 @@ Banana is Apache Licensed and serves as a visualizer and search interface to tim ### Requirements -* A modern web browser. The latest version of Chrome, Safari and Firefox have been tested to work. +* A modern web browser. The latest version of Chrome and Firefox have been tested to work. Safari also works, except for the "Export to File" feature for saving the dashboard. We recommend that you use Chrome or Firefox while building dashboards. * A webserver. * A browser reachable Solr server. The Solr endpoint must be open, or a proxy configured to allow access to it. @@ -45,11 +65,11 @@ Copy banana folder to $SOLR_HOME/example/solr-webapp/webapp/ Browse to http://localhost:8983/solr/banana/src/index.html#/dashboard -If your Solr port is different, edit banana/src/config.js and enter the port you are using. +If your Solr port is different, edit banana/src/config.js and banana/src/app/dashboards/default.json to enter the hostname and port that you are using. Remember that banana runs within the client browser, so the hostname and port number you provide should be resolvable from the client machines. If you have not created the data collections and ingested data into Solr, you will see an error message saying "Collection not found at .." Go to the Solr Output Plug-in for LogStash Page (https://github.com/LucidWorks/solrlogmanager) to learn how to import data into your Solr instance using LogStash -If you want to save and load dashboards from Solr, copy either solr-4.4.0/kibana-int (for Solr 4.4) or solr-4.5.0/kibana-int (for Solr 4.5 and above) directories (as appropriate) into $SOLR_HOME/example/solr in order to setup the required core. Then restart Solr, and if it is not loaded already, load the core from Solr Admin (if you are using Solr Cloud, you will need to upload the +If you want to save and load dashboards from Solr, copy either solr-4.4.0/kibana-int (for Solr 4.4) or solr-4.5.0/kibana-int (for Solr 4.5 and above) directories (as appropriate) into $SOLR_HOME/example/solr in order to setup the required core. Then restart Solr, and if it is not loaded already, load the core from Solr Admin (if you are using Solr Cloud, you will need to upload the configuration into ZooKeeper and then create the collection using that configuration). #### QuickStart for Complete SLK Stack @@ -63,13 +83,12 @@ We have packaged Solr, the open Source LogStash with Solr Output Plug-in and Ban Browse to http://localhost:8983/banana You will see example dashboards which you can use as a starting point for your applications. -Once again, if you choose to run Solr on a different port, please edit the config.js file. +Once again, if you choose to run Solr on a different port, please edit the config.js and default.json files as described above. -THAT'S IT! Now you have a baseline that you can use to build your own applications. -#### Running from a war file -Pull the repo from the "release" branch; version 1.1 will be tagged as banana-1.1. Run "ant" from within the banana directory to build the war file. +#### Installing a war file +Pull the repo from the "release" branch; version 1.1 will be tagged as banana-1.1, while version 1.2 will be tagged as banana-1.2. Run "ant" from within the banana directory to build the war file. cd $BANANA_REPO_HOME ant @@ -91,7 +110,7 @@ Browse to http://localhost:8983/banana Edit config.js to point to the Solr server that will store the Banana dashboards. You will need to make sure that a collection is created with the appropriate conf directory and schema.xml. Conf directories are available at banana/solr-4.4.0 (for Solr 4.4) and banana/solr-4.5.0 (for 4.5 and later). -The Solr server configured in config.js will serve as the default node for each dashboard; you can configure each dashboard to point to a different Solr endpoint as long as your webserver and Solr put out the correct CORS headers. See the README file under the directory fix_cors_issue for a guide. +The Solr server configured in config.js will serve as the default node for each dashboard; you can configure each dashboard to point to a different Solr endpoint as long as your webserver and Solr put out the correct CORS headers. See the README file under the directory _fix\_cors\_issue_ for a guide. Point your browser at your installation based on the contexts you have configured. @@ -100,19 +119,27 @@ Point your browser at your installation based on the contexts you have configure ## FAQ __Q__: How do I secure my solr endpoint so that users do not have access to it? -__A__: The simplest solution is to use a nginx reverse proxy (See for example https://groups.google.com/forum/#!topic/ajax-solr/pLtYfm83I98). +__A__: The simplest solution is to use a Apache or nginx reverse proxy (See for example https://groups.google.com/forum/#!topic/ajax-solr/pLtYfm83I98). -_Q_: The timestamp field in my data is different from "event_timestamp". How do I modify my dashboards to account for this? -_A_: By default, Banana assumes that the time field is "event_timestamp" If your time field is different you will just need to manually change it in ALL panels. We are reviewing this design (of allowing different time fields for different panels) and may move to one time field for all panels, based on user feedback. +__Q__: The timestamp field in my data is different from "event\_timestamp". How do I modify my dashboards to account for this? +__A__: By default, Banana assumes that the time field is "event\_timestamp" If your time field is different you will just need to manually change it in ALL panels. We are reviewing this design (of allowing different time fields for different panels) and may move to one time field for all panels, based on user feedback. -_Q_ : Can I use banana for non-time series data? -_A_: Currently, we are focused on time series data and the dashboards will nto work correctly without a time picker and without configuring the time field in the terms, histogram and table panles. However, in many cases you can use the following workaround, if you are at least storing a timestamp indicating the indexing time of the document: -Create a time picker panel in its own row and move the row to the bottom. Select the time field and then set the date range to be "Since 01/01/2000" (or starting even earlier if necessary). Use the same time field in the other terms panels, and you can get beautiful visualizations of facets. +__Q__ : Can I use banana for non-time series data? +__A__: Currently, we are focused on time series data and the dashboards will not work correctly without a time picker and without configuring the time field in the terms, histogram and table panels. However, many of our users apply the following workaround: + +1. Store a timestamp field indicating the indexing time of the document (or last modified time). +2. Create a _timepicker_ panel in its own row and move the row to the bottom +3. In the in the _timepicker_, select the time field to be the one representing indexing time or last modified time. +4. Set the date range to be "Since 01/01/2000" (or starting even earlier if necessary). Fundamentally, choose a time range that is long enough to encompass the entire data set. +5. Hide the row containing the _timepicker_. It will not be changed... +5. Use the same time field in the other panels, such as the _terms_ panel, and you can get beautiful visualizations of non time-series data. ### Support -Banana preserves most of the features of Kibana (from which it is forked). If you have any questions, please contact Andrew Thanalertvisuti (andrew.thanalertvisuti@lucidworks.com) or Ravi Krishnamurthy (ravi.krishnamurthy@lucidworks.com). +Banana uses the dashboard configuration capabilities of Kibana (from which it is forked) and ports key panels to work with Solr; it provides many additional capabilities like panel specific filters, global parameters, and visualization of "group-by" style queries. We are in the process of adding many new panels that go well beyond what is available in Kibana, helping users build complete applications that leverage the data stored in Apache Solr and HDFS. + +If you have any questions, please contact Andrew Thanalertvisuti (andrew.thanalertvisuti@lucidworks.com) or Ravi Krishnamurthy (ravi.krishnamurthy@lucidworks.com). ###Trademarks From 04b884f40a865c19eff2eeef6e378a70cad1e47a Mon Sep 17 00:00:00 2001 From: Ravi Krishnamurthy Date: Sun, 11 May 2014 14:49:53 -0700 Subject: [PATCH 23/23] Made the Guided Dashboard the default and moved syslog to syslog.json --- src/app/dashboards/default.json | 204 ++++++++++++-------- src/app/dashboards/syslog.json | 324 ++++++++++++++++++++++++++++++++ 2 files changed, 446 insertions(+), 82 deletions(-) create mode 100755 src/app/dashboards/syslog.json diff --git a/src/app/dashboards/default.json b/src/app/dashboards/default.json index ae2d80744..2197c5854 100755 --- a/src/app/dashboards/default.json +++ b/src/app/dashboards/default.json @@ -1,13 +1,16 @@ { - "title": "Welcome Syslog Demo", + "title": "Basic Dashboard With Pointers", "services": { "query": { "idQueue": [ - 1 + 1, + 2, + 3, + 4 ], "list": { "0": { - "query": "error", + "query": "*", "alias": "", "color": "#7EB26D", "id": 0, @@ -21,12 +24,13 @@ }, "filter": { "idQueue": [ - 1 + 1, + 2 ], "list": { "0": { - "from": "2014-04-01T07:47:30.228Z", - "to": "2014-04-02T07:47:30.228Z", + "from": "2014-04-02T05:07:22.216Z", + "to": "2014-04-02T05:22:22.216Z", "field": "event_timestamp", "type": "time", "mandate": "must", @@ -42,21 +46,54 @@ }, "rows": [ { - "title": "Options", + "title": "Overview", "height": "50px", "editable": true, "collapse": false, "collapsable": true, "panels": [ { - "title": "Set time span", - "error": "", + "error": false, + "span": 6, + "editable": true, + "type": "text", + "loadingEditor": false, + "status": "Stable", + "mode": "markdown", + "content": "You may be seeing a message that says Solr is not reachable or that the collection is not found. Click on _Configure Dashboard_ (cog icon) on the top right and set the Solr server and collection. By default, we have configured the dashboard to point to server _http://localhost:8983_ and collection _collection1_. You can also choose how many rows you want to have in the dashboard. \n\nTo configure what panels appear in a specific row, click on the _Configure Row_ (cog icon) at the far left of the row.\n\nEach panel can be configured by clicking on _Configure_ (cog icon) near the top right of the panel (just to the left of the panel type). The span of the panel determines its width; each row has width 12, and each panel can take up all or part of this span.\n\nAfter reading these _text panels_ (which are useful for presenting information about a dashboard), you can delete them by clicking on the \"x\" towards the top right of the panel.", + "style": {}, + "title": "Configure Dashboard" + }, + { + "error": false, "span": 6, "editable": true, "group": [ "default" ], + "type": "text", + "status": "Stable", + "mode": "markdown", + "content": "In the row below, we have put in a _time picker_ and a _search bar._ Banana is primarily designed for time series data, and we expect every dashboard to have a time picker. You will also almost always have a search bar for user searches. \n\nBelow that there is a hidden row that contains a _filtering_ module, which is used to configure global filter queries. You will almost certainly want to have one. Clicking on any facet in the terms module will filter results by that value. You can modify each filter once it is created; you can change the value and/or choose between \"must\", \"must not\" and \"either.\"\n\nClick on the right-facing triangle to the far left of the row to _Expand Row_. You can click on the upward-facing triangle on any row in order to _Hide Row._", + "style": {}, + "title": "Timestamps, Queries and Filters" + } + ] + }, + { + "title": "Query and Time Window", + "height": "50px", + "editable": true, + "collapse": false, + "collapsable": true, + "panels": [ + { + "error": "", + "span": 6, + "editable": true, "type": "timepicker", + "loadingEditor": false, + "status": "Stable", "mode": "relative", "time_options": [ "5m", @@ -67,77 +104,86 @@ "24h", "2d", "7d", - "30d" + "30d", + "90d", + "1y", + "5y" ], - "timespan": "24h", + "timespan": "15m", "timefield": "event_timestamp", "timeformat": "", "refresh": { - "enable": true, - "interval": 600, + "enable": false, + "interval": 30, "min": 3 }, "filter_id": 0, - "status": "Stable" + "title": "Time Window" }, { "error": false, "span": 6, "editable": true, + "group": [ + "default" + ], "type": "query", - "loadingEditor": false, - "query": "*:*", - "pinned": true, + "label": "Search", "history": [ - "error" + "*" ], "remember": 10, + "pinned": true, + "query": "*", "title": "Search", "def_type": "" } ] }, { - "title": "", - "height": "100px", + "title": "Filters", + "height": "50px", "editable": true, - "collapse": false, + "collapse": true, "collapsable": true, "panels": [ { "error": false, "span": 12, "editable": true, - "type": "text", - "loadingEditor": false, - "status": "Stable", - "mode": "markdown", - "content": "This is the demonstration dashboard for Banana, a port of Kibana for Solr. This points to a collection called logstash\\_logs. If you followed the QuickStart instructions provided by the Solr Output Writer for LogStash, you will see example data here. \n\nTo build your own dashboards, first get time series data into a collection. Then you can begin with this dashboard and configure it to fit your needs.", - "style": {}, - "title": "Welcome to Banana" + "group": [ + "default" + ], + "type": "filtering" } ] }, { - "title": "Filter", + "title": "Facets, Histogram and Table", "height": "150px", "editable": true, - "collapse": true, + "collapse": false, "collapsable": true, "panels": [ { "error": false, "span": 12, "editable": true, - "type": "filtering", - "loadingEditor": false, - "title": "Filter" + "group": [ + "default" + ], + "type": "text", + "status": "Stable", + "mode": "markdown", + "content": "Without knowing about your data, I cannot fully configure the panels I have provided below. However, I have provided you with some starting points, assuming that your time field is \"event_timestamp\" and that there is a field called \"message\" that you wish to facet on in order to view the top terms that appear in the \"message\" field and their frequency. If there is no data, these panels will be empty, and may give an error.\n\nThe _histogram_ panel allows you to plot either _counts_ or a specific field's (integer) _values_ across time. If you go to _Configure_, the panel allows you to set the type of chart and what variable is plotted (if choosing the _values_ option). Moreover, when plotting values, you can specify a _group by_ field which will produce multiple charts. You can modify the time window for the entire page from within the histogram panel.\n\nThe _terms_ panel is great for visualizing facets--as pie charts, bar charts or tables. Clicking on a term will create a global filter query restricting the result set (across all panels within the page) to the field value that is selected. You could have many such panels depending on the number of fields you choose to facet on.\n\nThe *table* panel at the bottom provides you a detailed view of search results. It has attempted to list your fields to the left; select a few to view them in the table. You can sort by any field. Click on a particular row to expand the resulting document that was returned.", + "style": {}, + "title": "Facets, Histogram and Table" } ] }, { "title": "Graph", - "height": "350px", + "height": "250px", "editable": true, "collapse": false, "collapsable": true, @@ -145,40 +191,24 @@ { "span": 6, "editable": true, - "group": [ - "default" - ], "type": "histogram", + "loadingEditor": false, "mode": "count", "time_field": "event_timestamp", - "value_field": null, - "max_rows": 100000, - "auto_int": true, - "resolution": 100, - "interval": "10m", - "fill": 3, - "linewidth": 3, - "timezone": "browser", - "spyable": true, - "zoomlinks": true, - "bars": true, - "stack": true, - "points": false, - "lines": false, - "legend": true, - "x-axis": true, - "y-axis": true, - "percentage": false, - "interactive": true, "queries": { "mode": "all", "ids": [ 0 ], - "query": "q=error&wt=json&rows=0&fq=event_timestamp:[2014-04-01T07:47:30.228Z%20TO%202014-04-02T07:47:30.228Z]&facet=true&facet.range=event_timestamp&facet.range.start=2014-04-01T07:47:30.228Z/DAY&facet.range.end=2014-04-02T07:47:30.228Z%2B1DAY/DAY&facet.range.gap=%2B10MINUTE", + "query": "q=*&wt=json&rows=0&fq=event_timestamp:[2014-04-02T05:07:22.000Z%20TO%202014-04-02T05:22:22.000Z]&facet=true&facet.range=event_timestamp&facet.range.start=2014-04-02T05:07:22.000Z/DAY&facet.range.end=2014-04-02T05:22:22.000Z%2B1DAY/DAY&facet.range.gap=%2B10SECOND", "custom": "" }, - "title": "System Log Messages", + "max_rows": 100000, + "value_field": null, + "group_field": null, + "auto_int": true, + "resolution": 100, + "interval": "10s", "intervals": [ "auto", "1s", @@ -194,58 +224,71 @@ "1M", "1y" ], + "fill": 0, + "linewidth": 3, + "timezone": "browser", + "spyable": true, + "zoomlinks": true, + "bars": true, + "stack": true, + "points": false, + "lines": false, + "legend": true, + "x-axis": true, + "y-axis": true, + "percentage": false, + "interactive": true, "options": true, "tooltip": { "value_type": "cumulative", "query_as_alias": false }, - "group_field": null, - "error": false + "title": "Event Counts" }, { "error": false, "span": 6, "editable": true, + "group": [ + "default" + ], "type": "terms", - "loadingEditor": false, "queries": { "mode": "all", "ids": [ 0 ], - "query": "q=error&wt=json&rows=0&fq=event_timestamp:[2014-04-01T07:47:30.228Z%20TO%202014-04-02T07:47:30.228Z]&facet=true&facet.field=syslog_program&facet.range=event_timestamp&facet.range.start=2014-04-01T07:47:30.228Z&facet.range.end=2014-04-02T07:47:30.228Z&facet.range.gap=%2B1DAY&facet.limit=10", - "custom": "" + "query": "q=*&wt=json&rows=0&fq=event_timestamp:[2014-04-02T05:07:22.000Z%20TO%202014-04-02T05:22:22.000Z]&facet=true&facet.field=message&facet.range=event_timestamp&facet.range.start=2014-04-02T05:07:22.000Z&facet.range.end=2014-04-02T05:22:22.000Z&facet.range.gap=%2B1DAY&facet.limit=100" }, - "field": "syslog_program", + "field": "message", "exclude": [], - "missing": true, - "other": true, - "size": 10, + "missing": false, + "other": false, + "size": 100, "order": "count", "style": { "font-size": "10pt" }, "donut": true, "tilt": true, - "labels": true, + "labels": false, "arrangement": "horizontal", "chart": "pie", "counter_pos": "none", + "title": "Message Terms", "spyable": true, - "title": "Programs", "time_field": "event_timestamp" } ] }, { "title": "Events", - "height": "350px", + "height": "650px", "editable": true, "collapse": false, "collapsable": true, "panels": [ { - "title": "All events", "error": false, "span": 12, "editable": true, @@ -253,11 +296,11 @@ "default" ], "type": "table", - "size": 10, - "pages": 100, + "size": 100, + "pages": 5, "offset": 0, "sort": [ - "event_timestamp", + "id", "desc" ], "style": { @@ -265,8 +308,6 @@ }, "overflow": "min-height", "fields": [ - "event_timestamp", - "syslog_program", "message" ], "highlight": [], @@ -279,29 +320,28 @@ "ids": [ 0 ], - "query": "q=error&wt=json&rows=1000&fq=event_timestamp:[2014-04-01T07:47:30.228Z%20TO%202014-04-02T07:47:30.228Z]&sort=event_timestamp desc", - "custom": "" + "query": "q=*&wt=json&rows=500&fq=event_timestamp:[2014-04-02T05:07:22.000Z%20TO%202014-04-02T05:22:22.000Z]&sort=id desc" }, "field_list": true, "status": "Stable", "trimFactor": 300, "normTimes": true, + "title": "Events", "time_field": "event_timestamp" } ] } ], "editable": true, - "failover": false, "index": { "interval": "none", "pattern": "[logstash-]YYYY.MM.DD", - "default": "NO_TIME_FILTER_OR_INDEX_PATTERN_NOT_MATCHED" + "default": "_all" }, "style": "light", + "failover": false, "panel_hints": true, "loader": { - "dropdown_collections": false, "save_gist": false, "save_elasticsearch": true, "save_local": true, @@ -317,7 +357,7 @@ }, "solr": { "server": "http://localhost:8983/solr/", - "core_name": "logstash_logs", + "core_name": "collection1", "core_list": [], "global_params": "&df=message" } diff --git a/src/app/dashboards/syslog.json b/src/app/dashboards/syslog.json new file mode 100755 index 000000000..ae2d80744 --- /dev/null +++ b/src/app/dashboards/syslog.json @@ -0,0 +1,324 @@ +{ + "title": "Welcome Syslog Demo", + "services": { + "query": { + "idQueue": [ + 1 + ], + "list": { + "0": { + "query": "error", + "alias": "", + "color": "#7EB26D", + "id": 0, + "pin": false, + "type": "lucene" + } + }, + "ids": [ + 0 + ] + }, + "filter": { + "idQueue": [ + 1 + ], + "list": { + "0": { + "from": "2014-04-01T07:47:30.228Z", + "to": "2014-04-02T07:47:30.228Z", + "field": "event_timestamp", + "type": "time", + "mandate": "must", + "active": true, + "alias": "", + "id": 0 + } + }, + "ids": [ + 0 + ] + } + }, + "rows": [ + { + "title": "Options", + "height": "50px", + "editable": true, + "collapse": false, + "collapsable": true, + "panels": [ + { + "title": "Set time span", + "error": "", + "span": 6, + "editable": true, + "group": [ + "default" + ], + "type": "timepicker", + "mode": "relative", + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ], + "timespan": "24h", + "timefield": "event_timestamp", + "timeformat": "", + "refresh": { + "enable": true, + "interval": 600, + "min": 3 + }, + "filter_id": 0, + "status": "Stable" + }, + { + "error": false, + "span": 6, + "editable": true, + "type": "query", + "loadingEditor": false, + "query": "*:*", + "pinned": true, + "history": [ + "error" + ], + "remember": 10, + "title": "Search", + "def_type": "" + } + ] + }, + { + "title": "", + "height": "100px", + "editable": true, + "collapse": false, + "collapsable": true, + "panels": [ + { + "error": false, + "span": 12, + "editable": true, + "type": "text", + "loadingEditor": false, + "status": "Stable", + "mode": "markdown", + "content": "This is the demonstration dashboard for Banana, a port of Kibana for Solr. This points to a collection called logstash\\_logs. If you followed the QuickStart instructions provided by the Solr Output Writer for LogStash, you will see example data here. \n\nTo build your own dashboards, first get time series data into a collection. Then you can begin with this dashboard and configure it to fit your needs.", + "style": {}, + "title": "Welcome to Banana" + } + ] + }, + { + "title": "Filter", + "height": "150px", + "editable": true, + "collapse": true, + "collapsable": true, + "panels": [ + { + "error": false, + "span": 12, + "editable": true, + "type": "filtering", + "loadingEditor": false, + "title": "Filter" + } + ] + }, + { + "title": "Graph", + "height": "350px", + "editable": true, + "collapse": false, + "collapsable": true, + "panels": [ + { + "span": 6, + "editable": true, + "group": [ + "default" + ], + "type": "histogram", + "mode": "count", + "time_field": "event_timestamp", + "value_field": null, + "max_rows": 100000, + "auto_int": true, + "resolution": 100, + "interval": "10m", + "fill": 3, + "linewidth": 3, + "timezone": "browser", + "spyable": true, + "zoomlinks": true, + "bars": true, + "stack": true, + "points": false, + "lines": false, + "legend": true, + "x-axis": true, + "y-axis": true, + "percentage": false, + "interactive": true, + "queries": { + "mode": "all", + "ids": [ + 0 + ], + "query": "q=error&wt=json&rows=0&fq=event_timestamp:[2014-04-01T07:47:30.228Z%20TO%202014-04-02T07:47:30.228Z]&facet=true&facet.range=event_timestamp&facet.range.start=2014-04-01T07:47:30.228Z/DAY&facet.range.end=2014-04-02T07:47:30.228Z%2B1DAY/DAY&facet.range.gap=%2B10MINUTE", + "custom": "" + }, + "title": "System Log Messages", + "intervals": [ + "auto", + "1s", + "1m", + "5m", + "10m", + "30m", + "1h", + "3h", + "12h", + "1d", + "1w", + "1M", + "1y" + ], + "options": true, + "tooltip": { + "value_type": "cumulative", + "query_as_alias": false + }, + "group_field": null, + "error": false + }, + { + "error": false, + "span": 6, + "editable": true, + "type": "terms", + "loadingEditor": false, + "queries": { + "mode": "all", + "ids": [ + 0 + ], + "query": "q=error&wt=json&rows=0&fq=event_timestamp:[2014-04-01T07:47:30.228Z%20TO%202014-04-02T07:47:30.228Z]&facet=true&facet.field=syslog_program&facet.range=event_timestamp&facet.range.start=2014-04-01T07:47:30.228Z&facet.range.end=2014-04-02T07:47:30.228Z&facet.range.gap=%2B1DAY&facet.limit=10", + "custom": "" + }, + "field": "syslog_program", + "exclude": [], + "missing": true, + "other": true, + "size": 10, + "order": "count", + "style": { + "font-size": "10pt" + }, + "donut": true, + "tilt": true, + "labels": true, + "arrangement": "horizontal", + "chart": "pie", + "counter_pos": "none", + "spyable": true, + "title": "Programs", + "time_field": "event_timestamp" + } + ] + }, + { + "title": "Events", + "height": "350px", + "editable": true, + "collapse": false, + "collapsable": true, + "panels": [ + { + "title": "All events", + "error": false, + "span": 12, + "editable": true, + "group": [ + "default" + ], + "type": "table", + "size": 10, + "pages": 100, + "offset": 0, + "sort": [ + "event_timestamp", + "desc" + ], + "style": { + "font-size": "9pt" + }, + "overflow": "min-height", + "fields": [ + "event_timestamp", + "syslog_program", + "message" + ], + "highlight": [], + "sortable": true, + "header": true, + "paging": true, + "spyable": true, + "queries": { + "mode": "all", + "ids": [ + 0 + ], + "query": "q=error&wt=json&rows=1000&fq=event_timestamp:[2014-04-01T07:47:30.228Z%20TO%202014-04-02T07:47:30.228Z]&sort=event_timestamp desc", + "custom": "" + }, + "field_list": true, + "status": "Stable", + "trimFactor": 300, + "normTimes": true, + "time_field": "event_timestamp" + } + ] + } + ], + "editable": true, + "failover": false, + "index": { + "interval": "none", + "pattern": "[logstash-]YYYY.MM.DD", + "default": "NO_TIME_FILTER_OR_INDEX_PATTERN_NOT_MATCHED" + }, + "style": "light", + "panel_hints": true, + "loader": { + "dropdown_collections": false, + "save_gist": false, + "save_elasticsearch": true, + "save_local": true, + "save_default": true, + "save_temp": true, + "save_temp_ttl_enable": true, + "save_temp_ttl": "30d", + "load_gist": true, + "load_elasticsearch": true, + "load_elasticsearch_size": 20, + "load_local": true, + "hide": false + }, + "solr": { + "server": "http://localhost:8983/solr/", + "core_name": "logstash_logs", + "core_list": [], + "global_params": "&df=message" + } +} \ No newline at end of file