From 689ddc0b2101dc83aa1284b32e76590909f8110b Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Fri, 12 Dec 2014 22:22:56 +0100 Subject: [PATCH 01/50] adding tests for zero_injection --- .../specs/vislib/components/zero_injection.js | 148 +++++++++++++++++- 1 file changed, 146 insertions(+), 2 deletions(-) diff --git a/test/unit/specs/vislib/components/zero_injection.js b/test/unit/specs/vislib/components/zero_injection.js index 507bff7137502f..e585b742991849 100644 --- a/test/unit/specs/vislib/components/zero_injection.js +++ b/test/unit/specs/vislib/components/zero_injection.js @@ -11,6 +11,126 @@ define(function (require) { angular.module('ZeroFilledArrayUtilService', ['kibana']); describe('Vislib Zero Injection Module Test Suite', function () { + var dateHistogramRows = { + 'rows': [ + { + 'label': 'Top 5 @tags: success', + 'series': [ + { + 'label': 'jpg', + 'values': [ + { 'x': 1418410560000, 'y': 2 }, + { 'x': 1418410620000, 'y': 4 }, + { 'x': 1418410680000, 'y': 1 }, + { 'x': 1418410740000, 'y': 5 }, + { 'x': 1418410800000, 'y': 2 }, + { 'x': 1418410860000, 'y': 3 }, + { 'x': 1418410920000, 'y': 2 } + ] + }, + { + 'label': 'css', + 'values': [ + { 'x': 1418410560000, 'y': 1 }, + { 'x': 1418410620000, 'y': 3 }, + { 'x': 1418410680000, 'y': 1 }, + { 'x': 1418410740000, 'y': 4 }, + { 'x': 1418410800000, 'y': 2 } + ] + }, + { + 'label': 'gif', + 'values': [ + { 'x': 1418410500000, 'y': 1 }, + { 'x': 1418410680000, 'y': 3 }, + { 'x': 1418410740000, 'y': 2 } + ] + } + ] + }, + { + 'label': 'Top 5 @tags: info', + 'series': [ + { + 'label': 'jpg', + 'values': [ + { 'x': 1418410560000, 'y': 4 }, + { 'x': 1418410620000, 'y': 2 }, + { 'x': 1418410680000, 'y': 1 }, + { 'x': 1418410740000, 'y': 5 }, + { 'x': 1418410800000, 'y': 2 }, + { 'x': 1418410860000, 'y': 3 }, + { 'x': 1418410920000, 'y': 2 } + ] + }, + { + 'label': 'css', + 'values': [ + { 'x': 1418410620000, 'y': 3 }, + { 'x': 1418410680000, 'y': 1 }, + { 'x': 1418410740000, 'y': 4 }, + { 'x': 1418410800000, 'y': 2 } + ] + }, + { + 'label': 'gif', + 'values': [ + { 'x': 1418410500000, 'y': 1 } + ] + } + ] + }, + { + 'label': 'Top 5 @tags: security', + 'series': [ + { + 'label': 'jpg', + 'values': [ + { 'x': 1418410560000, 'y': 1 }, + { 'x': 1418410620000, 'y': 3 }, + { 'x': 1418410920000, 'y': 2 } + ] + }, + { + 'label': 'gif', + 'values': [ + { 'x': 1418410680000, 'y': 3 }, + { 'x': 1418410740000, 'y': 1 } + ] + } + ] + }, + { + 'label': 'Top 5 @tags: login', + 'series': [ + { + 'label': 'jpg', + 'values': [ + { 'x': 1418410740000, 'y': 1 } + ] + }, + { + 'label': 'css', + 'values': [ + { 'x': 1418410560000, 'y': 1 } + ] + } + ] + }, + { + 'label': 'Top 5 @tags: warning', + 'series': [ + { + 'label': 'jpg', + 'values': [ + { 'x': 1418410860000, 'y': 2 } + ] + } + ] + } + ] + }; + var seriesData = { series: [ { @@ -84,7 +204,6 @@ define(function (require) { ] }; - var ordered = {}; var childrenObject = { children: [] }; @@ -178,7 +297,7 @@ define(function (require) { expect(_.isFunction(injectZeros)).to.be(true); }); - it('should return an object with series[0].values"', function () { + it('should return an object with series[0].values', function () { expect(_.isObject(sample1)).to.be(true); expect(_.isObject(sample1.series[0].values)).to.be(true); }); @@ -515,5 +634,30 @@ define(function (require) { }); }); + describe('Injected Zeros return in the correct order for dates', function () { + // Need to test that for Date Histogram data, objects are always sorted by time and not by index + var injectZeros; + var dateHistogramData; + + beforeEach(function () { + module('ZeroInjectionUtilService'); + }); + + beforeEach(function () { + inject(function (Private) { + injectZeros = Private(require('components/vislib/components/zero_injection/inject_zeros')); + dateHistogramData = injectZeros(dateHistogramRows); + }); + }); + + afterEach(function () { + dateHistogramData = null; + }); + + it('should return dates in order', function () { + + }); + }); + }); }); From 50adf3779e37384d03d663d27c89e9708c1f5098 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Fri, 19 Dec 2014 13:43:19 -0700 Subject: [PATCH 02/50] Add editor for query string options --- src/kibana/components/config/defaults.js | 4 ++++ .../plugins/settings/sections/advanced/index.html | 15 ++++++++++++++- .../plugins/settings/sections/advanced/index.js | 2 ++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/kibana/components/config/defaults.js b/src/kibana/components/config/defaults.js index c21fa068e482c7..a27c44fe3acfa8 100644 --- a/src/kibana/components/config/defaults.js +++ b/src/kibana/components/config/defaults.js @@ -2,6 +2,10 @@ define(function (require) { var _ = require('lodash'); return { + 'query:queryString:options': { + value: { analyze_wildcard: true }, + description: 'Edit this JSON' + }, 'dateFormat': { value: 'MMMM Do YYYY, HH:mm:ss.SSS', description: 'When displaying a pretty formatted date, use this format', diff --git a/src/kibana/plugins/settings/sections/advanced/index.html b/src/kibana/plugins/settings/sections/advanced/index.html index 95dd418f032d80..71a35c1f856a6b 100644 --- a/src/kibana/plugins/settings/sections/advanced/index.html +++ b/src/kibana/plugins/settings/sections/advanced/index.html @@ -19,6 +19,10 @@

Caution: You can break stuff here

+ + + + {{conf.name}} @@ -46,6 +50,14 @@

Caution: You can break stuff here

placeholder="{{(conf.value || conf.defVal).join(', ')}}" type="text" class="form-control"> + Caution: You can break stuff here {{conf.value || conf.defVal}} {{conf.value === undefined ? conf.defVal : conf.value}} {{(conf.value || conf.defVal).join(', ')}} + {{(conf.value || conf.defVal) | json}} @@ -71,7 +84,7 @@

Caution: You can break stuff here

ng-if="conf.editting" ng-click="save(conf)" class="btn btn-success" - ng-disabled="conf.loading || conf.tooComplex"> + ng-disabled="conf.loading || conf.tooComplex || conf.$$invalid"> diff --git a/src/kibana/plugins/settings/sections/advanced/index.js b/src/kibana/plugins/settings/sections/advanced/index.js index dba9bcd3152510..84f1d823b3763a 100644 --- a/src/kibana/plugins/settings/sections/advanced/index.js +++ b/src/kibana/plugins/settings/sections/advanced/index.js @@ -31,6 +31,8 @@ define(function (require) { default: if (_.isArray(config.get(conf.name))) { conf.array = true; + } else if (_.isObject(config.get(conf.name))) { + conf.json = true; } else if (typeof config.get(conf.name) === 'boolean') { conf.bool = true; } else { From 2c6915640d2305d36cb3c3189850d46b2d34e16c Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Mon, 22 Dec 2014 11:21:06 -0700 Subject: [PATCH 03/50] Add lucene query string options setting --- src/kibana/components/config/config.js | 19 +++- src/kibana/components/config/defaults.js | 5 +- .../validate_query/lib/from_user.js | 57 ++++++------ .../validate_query/validate_query.js | 7 +- src/kibana/directives/validate_json.js | 14 ++- .../sections/advanced/advanced_row.html | 84 ++++++++++++++++++ .../sections/advanced/advanced_row.js | 69 +++++++++++++++ .../settings/sections/advanced/index.html | 88 +------------------ .../settings/sections/advanced/index.js | 53 ++--------- test/unit/specs/directives/validate_query.js | 21 ++++- 10 files changed, 244 insertions(+), 173 deletions(-) create mode 100644 src/kibana/plugins/settings/sections/advanced/advanced_row.html create mode 100644 src/kibana/plugins/settings/sections/advanced/advanced_row.js diff --git a/src/kibana/components/config/config.js b/src/kibana/components/config/config.js index 0726fcb88e2974..5c658e546e60d7 100644 --- a/src/kibana/components/config/config.js +++ b/src/kibana/components/config/config.js @@ -82,20 +82,31 @@ define(function (require) { }); config.get = function (key, defaultVal) { + var keyVal; + if (vals[key] == null) { if (defaultVal == null) { - return defaults[key].value; + keyVal = defaults[key].value; } else { - return _.cloneDeep(defaultVal); + keyVal = _.cloneDeep(defaultVal); } } else { - return vals[key]; + keyVal = vals[key]; + } + + if (defaults[key] && defaults[key].type === 'json') { + return JSON.parse(keyVal); } + return keyVal; }; // sets a value in the config config.set = function (key, val) { - return change(key, val); + if (defaults[key] && defaults[key].type === 'json') { + return change(key, JSON.stringify(val)); + } else { + return change(key, val); + } }; // clears a value from the config diff --git a/src/kibana/components/config/defaults.js b/src/kibana/components/config/defaults.js index a27c44fe3acfa8..c33cb1ea5ee1e7 100644 --- a/src/kibana/components/config/defaults.js +++ b/src/kibana/components/config/defaults.js @@ -3,8 +3,9 @@ define(function (require) { return { 'query:queryString:options': { - value: { analyze_wildcard: true }, - description: 'Edit this JSON' + value: '{ "analyze_wildcard": true }', + description: 'Options for the lucene query string parser', + type: 'json' }, 'dateFormat': { value: 'MMMM Do YYYY, HH:mm:ss.SSS', diff --git a/src/kibana/components/validate_query/lib/from_user.js b/src/kibana/components/validate_query/lib/from_user.js index b1982a35d205d6..fc4f4377a514db 100644 --- a/src/kibana/components/validate_query/lib/from_user.js +++ b/src/kibana/components/validate_query/lib/from_user.js @@ -1,35 +1,42 @@ define(function (require) { var _ = require('lodash'); + return function GetQueryFromUser(es, config) { + /** + * Take text from the user and make it into a query object + * @param {text} user's query input + * @returns {object} + */ + return function (text) { + var queryOptions = config.get('query:queryString:options'); + function getQueryStringQuery(text) { + return {query_string: _.extend({query: text}, queryOptions)}; + } - /** - * Take text from the user and make it into a query object - * @param {text} user's query input - * @returns {object} - */ - return function (text) { - var matchAll = {query_string: {query: '*'}}; + var matchAll = getQueryStringQuery('*'); - // If we get an empty object, treat it as a * - if (_.isObject(text)) { - if (Object.keys(text).length) { - return text; - } else { - return matchAll; + // If we get an empty object, treat it as a * + if (_.isObject(text)) { + if (Object.keys(text).length) { + return text; + } else { + return matchAll; + } } - } - // Nope, not an object. - text = (text || '').trim(); - if (text.length === 0) return matchAll; + // Nope, not an object. + text = (text || '').trim(); + if (text.length === 0) return matchAll; - if (text[0] === '{') { - try { - return JSON.parse(text); - } catch (e) { - return {query_string: {query: text}}; + if (text[0] === '{') { + try { + return JSON.parse(text); + } catch (e) { + return getQueryStringQuery(text); + } + } else { + return getQueryStringQuery(text); } - } else { - return {query_string: {query: text}}; - } + }; }; }); + diff --git a/src/kibana/components/validate_query/validate_query.js b/src/kibana/components/validate_query/validate_query.js index 4c8b8e2cfb7f1a..5e1254e01f74e9 100644 --- a/src/kibana/components/validate_query/validate_query.js +++ b/src/kibana/components/validate_query/validate_query.js @@ -1,14 +1,15 @@ define(function (require) { var _ = require('lodash'); var $ = require('jquery'); - var fromUser = require('components/validate_query/lib/from_user'); - var toUser = require('components/validate_query/lib/to_user'); require('services/debounce'); require('modules') .get('kibana') - .directive('validateQuery', function (es, $compile, timefilter, configFile, debounce) { + .directive('validateQuery', function (es, $compile, timefilter, configFile, debounce, Private) { + var fromUser = Private(require('components/validate_query/lib/from_user')); + var toUser = require('components/validate_query/lib/to_user'); + return { restrict: 'A', require: 'ngModel', diff --git a/src/kibana/directives/validate_json.js b/src/kibana/directives/validate_json.js index ce5262e7bf8b3c..986be7eb4168ee 100644 --- a/src/kibana/directives/validate_json.js +++ b/src/kibana/directives/validate_json.js @@ -21,10 +21,16 @@ define(function (require) { return; } - try { - JSON.parse(newValue); - setValid(); - } catch (err) { + // We actually need a proper object in all JSON inputs + newValue = (newValue || '').trim(); + if (newValue[0] === '{') { + try { + JSON.parse(newValue); + setValid(); + } catch (e) { + setInvalid(); + } + } else { setInvalid(); } } diff --git a/src/kibana/plugins/settings/sections/advanced/advanced_row.html b/src/kibana/plugins/settings/sections/advanced/advanced_row.html new file mode 100644 index 00000000000000..9d740df5a9eeec --- /dev/null +++ b/src/kibana/plugins/settings/sections/advanced/advanced_row.html @@ -0,0 +1,84 @@ + + + {{conf.name}} + + (Default: {{conf.defVal == undefined ? 'null' : conf.defVal}}) + +
+ {{conf.description}} + + +
+ + + + Invalid JSON syntax + +
+ {{conf.value || conf.defVal}} + {{conf.value === undefined ? conf.defVal : conf.value}} + {{(conf.value || conf.defVal).join(', ')}} + + + + + + + + + + + + \ No newline at end of file diff --git a/src/kibana/plugins/settings/sections/advanced/advanced_row.js b/src/kibana/plugins/settings/sections/advanced/advanced_row.js new file mode 100644 index 00000000000000..ba89ec33f7cc15 --- /dev/null +++ b/src/kibana/plugins/settings/sections/advanced/advanced_row.js @@ -0,0 +1,69 @@ +define(function (require) { + var _ = require('lodash'); + var configDefaults = require('components/config/defaults'); + + require('modules').get('apps/settings') + .directive('advancedRow', function (config, Notifier, Private) { + return { + restrict: 'A', + replace: true, + template: require('text!plugins/settings/sections/advanced/advanced_row.html'), + scope: { + conf: '=advancedRow', + configs: '=' + }, + link: function ($scope) { + var notify = new Notifier(); + var keyCodes = { + ESC: 27 + }; + + // To allow passing form validation state back + $scope.forms = {}; + + // setup loading flag, run async op, then clear loading and editting flag (just in case) + var loading = function (conf, fn) { + conf.loading = true; + fn() + .finally(function () { + conf.loading = conf.editting = false; + }) + .catch(notify.fatal); + }; + + $scope.maybeCancel = function ($event, conf) { + if ($event.keyCode === keyCodes.ESC) { + $scope.cancelEdit(conf); + } + }; + + $scope.edit = function (conf) { + conf.unsavedValue = conf.value || conf.defVal; + $scope.configs.forEach(function (c) { + c.editting = (c === conf); + }); + }; + + $scope.save = function (conf) { + loading(conf, function () { + if (conf.unsavedValue === conf.defVal) { + return config.clear(conf.name); + } + + return config.set(conf.name, conf.unsavedValue); + }); + }; + + $scope.cancelEdit = function (conf) { + conf.editting = false; + }; + + $scope.clear = function (conf) { + return loading(conf, function () { + return config.clear(conf.name); + }); + }; + } + }; + }); +}); \ No newline at end of file diff --git a/src/kibana/plugins/settings/sections/advanced/index.html b/src/kibana/plugins/settings/sections/advanced/index.html index 71a35c1f856a6b..5305dec2af9b50 100644 --- a/src/kibana/plugins/settings/sections/advanced/index.html +++ b/src/kibana/plugins/settings/sections/advanced/index.html @@ -18,93 +18,7 @@

Caution: You can break stuff here

- - - - - - - {{conf.name}} - - (Default: {{conf.defVal == undefined ? 'null' : conf.defVal}}) - -
- {{conf.description}} - - -
- - - - -
- {{conf.value || conf.defVal}} - {{conf.value === undefined ? conf.defVal : conf.value}} - {{(conf.value || conf.defVal).join(', ')}} - {{(conf.value || conf.defVal) | json}} - - - - - - - - - - - - + diff --git a/src/kibana/plugins/settings/sections/advanced/index.js b/src/kibana/plugins/settings/sections/advanced/index.js index 84f1d823b3763a..50ab8f31b06ad9 100644 --- a/src/kibana/plugins/settings/sections/advanced/index.js +++ b/src/kibana/plugins/settings/sections/advanced/index.js @@ -2,6 +2,8 @@ define(function (require) { var _ = require('lodash'); var configDefaults = require('components/config/defaults'); + require('plugins/settings/sections/advanced/advanced_row'); + require('routes') .when('/settings/advanced', { template: require('text!plugins/settings/sections/advanced/index.html') @@ -21,18 +23,19 @@ define(function (require) { // determine if a value is too complex to be edditted (at this time) var tooComplex = function (conf) { // get the type of the current value or the default - switch (typeof config.get(conf.name)) { + switch (conf.type) { case 'string': case 'number': case 'null': case 'undefined': conf.normal = true; break; + case 'json': + conf.json = true; + break; default: if (_.isArray(config.get(conf.name))) { conf.array = true; - } else if (_.isObject(config.get(conf.name))) { - conf.json = true; } else if (typeof config.get(conf.name) === 'boolean') { conf.bool = true; } else { @@ -41,20 +44,11 @@ define(function (require) { } }; - // setup loading flag, run async op, then clear loading and editting flag (just in case) - var loading = function (conf, fn) { - conf.loading = true; - fn() - .finally(function () { - conf.loading = conf.editting = false; - }) - .catch(notify.fatal); - }; - $scope.configs = _.map(configDefaults, function (def, name) { var conf = { name: name, defVal: def.value, + type: (def.type || typeof config.get(name)), description: def.description, value: configVals[name] }; @@ -69,39 +63,6 @@ define(function (require) { return conf; }); - - $scope.maybeCancel = function ($event, conf) { - if ($event.keyCode === keyCodes.ESC) { - $scope.cancelEdit(conf); - } - }; - - $scope.edit = function (conf) { - conf.unsavedValue = conf.value || conf.defVal; - $scope.configs.forEach(function (c) { - c.editting = (c === conf); - }); - }; - - $scope.save = function (conf) { - loading(conf, function () { - if (conf.unsavedValue === conf.defVal) { - return config.clear(conf.name); - } - - return config.set(conf.name, conf.unsavedValue); - }); - }; - - $scope.cancelEdit = function (conf) { - conf.editting = false; - }; - - $scope.clear = function (conf) { - return loading(conf, function () { - return config.clear(conf.name); - }); - }; } }; }); diff --git a/test/unit/specs/directives/validate_query.js b/test/unit/specs/directives/validate_query.js index 30c76c06a13b71..fcb940cc6cb200 100644 --- a/test/unit/specs/directives/validate_query.js +++ b/test/unit/specs/directives/validate_query.js @@ -9,6 +9,8 @@ define(function (require) { var $timeout; var $compile; var Promise; + var Private; + var config; var debounceDelay = 300; var $elemScope; var $elem; @@ -16,7 +18,7 @@ define(function (require) { var cycleIndex = 0; var mockValidateQuery; var markup = ''; - var fromUser = require('components/validate_query/lib/from_user'); + var fromUser; var toUser = require('components/validate_query/lib/to_user'); @@ -47,10 +49,12 @@ define(function (require) { }); // Create the scope - inject(function ($injector, _$rootScope_, _$compile_, _$timeout_, _Promise_) { + inject(function ($injector, _$rootScope_, _$compile_, _$timeout_, _Promise_, _Private_, _config_) { $timeout = _$timeout_; $compile = _$compile_; Promise = _Promise_; + Private = _Private_; + config = _config_; // Give us a scope $rootScope = _$rootScope_; @@ -157,6 +161,12 @@ define(function (require) { }); describe('user input parser', function () { + + beforeEach(function () { + fromUser = Private(require('components/validate_query/lib/from_user')); + config.set('query:queryString:options', '{}'); + }); + it('should return the input if passed an object', function () { expect(fromUser({foo: 'bar'})).to.eql({foo: 'bar'}); }); @@ -169,6 +179,13 @@ define(function (require) { expect(fromUser('')).to.eql({query_string: {query: '*'}}); }); + it('should merge in the query string options', function () { + config.set('query:queryString:options', {analyze_wildcard: true}); + expect(fromUser('foo')).to.eql({query_string: {query: 'foo', analyze_wildcard: true}}); + expect(fromUser('')).to.eql({query_string: {query: '*', analyze_wildcard: true}}); + }); + + it('should treat input that does not start with { as a query string', function () { expect(fromUser('foo')).to.eql({query_string: {query: 'foo'}}); expect(fromUser('400')).to.eql({query_string: {query: '400'}}); From b24f3b2b211a3c4be555a0b55eeed989140d9279 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Mon, 22 Dec 2014 12:15:39 -0700 Subject: [PATCH 04/50] Stub config service --- test/unit/specs/directives/validate_query.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/unit/specs/directives/validate_query.js b/test/unit/specs/directives/validate_query.js index fcb940cc6cb200..ab79b36aa090f7 100644 --- a/test/unit/specs/directives/validate_query.js +++ b/test/unit/specs/directives/validate_query.js @@ -43,6 +43,15 @@ define(function (require) { return { indices: { validateQuery: function () {} } }; }); + // Super simple config stub + $provide.service('config', function () { + var keys = {}; + return { + get: function (key) { return keys[key]; }, + set: function (key, value) { keys[key] = value; } + }; + }); + $provide.constant('configFile', { kibana_index: 'test-index' }); @@ -164,7 +173,7 @@ define(function (require) { beforeEach(function () { fromUser = Private(require('components/validate_query/lib/from_user')); - config.set('query:queryString:options', '{}'); + config.set('query:queryString:options', {}); }); it('should return the input if passed an object', function () { From 662ca9e7a1d7de39f5e2e415c8f229edea51b28d Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Mon, 22 Dec 2014 15:00:56 -0700 Subject: [PATCH 05/50] Teach dashboard to load saved searches --- src/kibana/plugins/dashboard/directives/grid.js | 11 ++++++++++- src/kibana/plugins/dashboard/directives/panel.js | 9 +++++---- src/kibana/plugins/dashboard/index.js | 7 ++++++- src/kibana/plugins/dashboard/partials/panel.html | 11 +++++++++-- .../dashboard/partials/pick_visualization.html | 3 ++- src/kibana/plugins/dashboard/styles/main.less | 2 +- 6 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/kibana/plugins/dashboard/directives/grid.js b/src/kibana/plugins/dashboard/directives/grid.js index 7264540d7609c3..6f5274b2a471f5 100644 --- a/src/kibana/plugins/dashboard/directives/grid.js +++ b/src/kibana/plugins/dashboard/directives/grid.js @@ -130,7 +130,16 @@ define(function (require) { }); // ignore panels that don't have vis id's - if (!panel.visId) throw new Error('missing visId on panel'); + if (!panel.id) { + // In the interest of backwards compat + if (panel.visId) { + panel.id = panel.visId; + panel.type = 'visualization'; + delete panel.visId; + } else { + throw new Error('missing object id on panel'); + } + } panel.$scope = $scope.$new(); panel.$scope.panel = panel; diff --git a/src/kibana/plugins/dashboard/directives/panel.js b/src/kibana/plugins/dashboard/directives/panel.js index 9f5749e1cba744..bf5e41393cb204 100644 --- a/src/kibana/plugins/dashboard/directives/panel.js +++ b/src/kibana/plugins/dashboard/directives/panel.js @@ -2,7 +2,7 @@ define(function (require) { var moment = require('moment'); require('modules') .get('app/dashboard') - .directive('dashboardPanel', function (savedVisualizations, Notifier, Private) { + .directive('dashboardPanel', function (savedVisualizations, savedSearches, Notifier, Private) { var _ = require('lodash'); var filterBarClickHandler = Private(require('components/filter_bar/filter_bar_click_handler')); @@ -20,11 +20,12 @@ define(function (require) { var $state = $scope.state; // receives panel object from the dashboard grid directive - $scope.$watch('visId', function (visId) { + $scope.$watch('id', function (id) { delete $scope.vis; - if (!$scope.panel.visId) return; + if (!$scope.panel.id || !$scope.panel.type) return; - savedVisualizations.get($scope.panel.visId) + + savedVisualizations.get($scope.panel.id) .then(function (savedVis) { $scope.savedVis = savedVis; $scope.$on('$destroy', savedVis.destroy); diff --git a/src/kibana/plugins/dashboard/index.js b/src/kibana/plugins/dashboard/index.js index ece24c6b6d5e02..1830a0f6b3db4a 100644 --- a/src/kibana/plugins/dashboard/index.js +++ b/src/kibana/plugins/dashboard/index.js @@ -164,7 +164,11 @@ define(function (require) { // called by the saved-object-finder when a user clicks a vis $scope.addVis = function (hit) { pendingVis++; - $state.panels.push({ visId: hit.id }); + $state.panels.push({ id: hit.id, type: 'visualization' }); + }; + + $scope.addSearch = function (hit) { + $state.panels.push({ id: hit.id, type: 'search' }); }; // Setup configurable values for config directive, after objects are initialized @@ -172,6 +176,7 @@ define(function (require) { dashboard: dash, save: $scope.save, addVis: $scope.addVis, + addSearch: $scope.addSearch, shareData: function () { return { link: $location.absUrl(), diff --git a/src/kibana/plugins/dashboard/partials/panel.html b/src/kibana/plugins/dashboard/partials/panel.html index c41fc0297690a4..05377ba4a0c9b9 100644 --- a/src/kibana/plugins/dashboard/partials/panel.html +++ b/src/kibana/plugins/dashboard/partials/panel.html @@ -2,7 +2,7 @@
{{savedVis.title}}
- +
@@ -12,5 +12,12 @@
- + + + +
\ No newline at end of file diff --git a/src/kibana/plugins/dashboard/partials/pick_visualization.html b/src/kibana/plugins/dashboard/partials/pick_visualization.html index 94c92f7d79b039..583dde2331aaf8 100644 --- a/src/kibana/plugins/dashboard/partials/pick_visualization.html +++ b/src/kibana/plugins/dashboard/partials/pick_visualization.html @@ -1 +1,2 @@ - \ No newline at end of file + + \ No newline at end of file diff --git a/src/kibana/plugins/dashboard/styles/main.less b/src/kibana/plugins/dashboard/styles/main.less index bb9b7e70be5dc1..de50202a01f6fb 100644 --- a/src/kibana/plugins/dashboard/styles/main.less +++ b/src/kibana/plugins/dashboard/styles/main.less @@ -101,7 +101,7 @@ dashboard-grid { } } - visualize { + .panel-content { .flex(1, 1, 100%); height: auto; } From 52630a069b99a456ef538de94ea822db26ceaa9e Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Mon, 22 Dec 2014 15:05:21 -0700 Subject: [PATCH 06/50] Check if input is object --- src/kibana/components/config/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kibana/components/config/config.js b/src/kibana/components/config/config.js index 5c658e546e60d7..ad0f0e344a08d6 100644 --- a/src/kibana/components/config/config.js +++ b/src/kibana/components/config/config.js @@ -102,7 +102,7 @@ define(function (require) { // sets a value in the config config.set = function (key, val) { - if (defaults[key] && defaults[key].type === 'json') { + if (_.isPlainObject(val)) { return change(key, JSON.stringify(val)); } else { return change(key, val); From fd01e52e5ece238dbca5fcc341c246422db0b11a Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Mon, 22 Dec 2014 16:16:09 -0700 Subject: [PATCH 07/50] Show the resp in the panel --- .../plugins/dashboard/directives/panel.js | 43 ++++++++++++++----- src/kibana/plugins/dashboard/index.js | 3 +- .../plugins/dashboard/partials/panel.html | 6 ++- .../partials/pick_visualization.html | 27 +++++++++++- 4 files changed, 64 insertions(+), 15 deletions(-) diff --git a/src/kibana/plugins/dashboard/directives/panel.js b/src/kibana/plugins/dashboard/directives/panel.js index bf5e41393cb204..3dcd7c90ebeb12 100644 --- a/src/kibana/plugins/dashboard/directives/panel.js +++ b/src/kibana/plugins/dashboard/directives/panel.js @@ -19,22 +19,45 @@ define(function (require) { // using $scope inheritance, panels are available in AppState var $state = $scope.state; + $scope.view = {}; + // receives panel object from the dashboard grid directive $scope.$watch('id', function (id) { delete $scope.vis; if (!$scope.panel.id || !$scope.panel.type) return; + switch ($scope.panel.type) { + case 'visualization': + savedVisualizations.get($scope.panel.id) + .then(function (savedVis) { + $scope.savedVis = savedVis; + $scope.view.title = savedVis.title; + $scope.$on('$destroy', savedVis.destroy); + savedVis.vis.listeners.click = filterBarClickHandler($state); + savedVis.vis.listeners.brush = brushEvent; + }) + .catch(function (e) { + $scope.error = e.message; + }); + break; + case 'search': + savedSearches.get($scope.panel.id) + .then(function (savedSearch) { + $scope.$root.$broadcast('ready:vis'); - savedVisualizations.get($scope.panel.id) - .then(function (savedVis) { - $scope.savedVis = savedVis; - $scope.$on('$destroy', savedVis.destroy); - savedVis.vis.listeners.click = filterBarClickHandler($state); - savedVis.vis.listeners.brush = brushEvent; - }) - .catch(function (e) { - $scope.error = e.message; - }); + var searchSource = savedSearch.searchSource; + $scope.view.title = savedSearch.title; + searchSource.onResults().then(function onResults(resp) { + $scope.view.content = resp; + }).catch(notify.fatal); + }) + .catch(function (e) { + $scope.error = e.message; + }); + break; + default: + $scope.error = 'Unknown panel type'; + } }); $scope.remove = function () { diff --git a/src/kibana/plugins/dashboard/index.js b/src/kibana/plugins/dashboard/index.js index 1830a0f6b3db4a..1a207e92141f67 100644 --- a/src/kibana/plugins/dashboard/index.js +++ b/src/kibana/plugins/dashboard/index.js @@ -47,7 +47,7 @@ define(function (require) { } }); - app.directive('dashboardApp', function (Notifier, courier, savedVisualizations, AppState, timefilter, kbnUrl) { + app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter, kbnUrl) { return { controller: function ($scope, $route, $routeParams, $location, configFile, Private) { var notify = new Notifier({ @@ -168,6 +168,7 @@ define(function (require) { }; $scope.addSearch = function (hit) { + pendingVis++; $state.panels.push({ id: hit.id, type: 'search' }); }; diff --git a/src/kibana/plugins/dashboard/partials/panel.html b/src/kibana/plugins/dashboard/partials/panel.html index 05377ba4a0c9b9..40f6711702981f 100644 --- a/src/kibana/plugins/dashboard/partials/panel.html +++ b/src/kibana/plugins/dashboard/partials/panel.html @@ -1,6 +1,6 @@
- {{savedVis.title}} + {{view.title}}
@@ -19,5 +19,7 @@ class="panel-content">
+ class="panel-content"> + {{view.content}} +
\ No newline at end of file diff --git a/src/kibana/plugins/dashboard/partials/pick_visualization.html b/src/kibana/plugins/dashboard/partials/pick_visualization.html index 583dde2331aaf8..6c78e733053e8f 100644 --- a/src/kibana/plugins/dashboard/partials/pick_visualization.html +++ b/src/kibana/plugins/dashboard/partials/pick_visualization.html @@ -1,2 +1,25 @@ - - \ No newline at end of file +
    + +
  • + Add a visualization panel +
  • +
  • + + +
  • + +
  • + Or add a saved search as a table of hits +
  • +
  • + + +
  • + +
+ + From 56f8bae7007baa7f1008b9a840eb6b4f80cc5760 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Mon, 22 Dec 2014 17:01:23 -0700 Subject: [PATCH 08/50] Inject query string options pre-flight --- .../components/agg_types/buckets/filters.js | 6 ++- .../courier/data_source/_abstract.js | 12 ++++++ .../courier/data_source/_decorate_query.js | 20 +++++++++ .../validate_query/lib/from_user.js | 7 ++-- .../courier/data_source/decorate_query.js | 41 +++++++++++++++++++ 5 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 src/kibana/components/courier/data_source/_decorate_query.js create mode 100644 test/unit/specs/courier/data_source/decorate_query.js diff --git a/src/kibana/components/agg_types/buckets/filters.js b/src/kibana/components/agg_types/buckets/filters.js index 6cbdba5e393e6a..2e7f697a8fbd1d 100644 --- a/src/kibana/components/agg_types/buckets/filters.js +++ b/src/kibana/components/agg_types/buckets/filters.js @@ -1,5 +1,5 @@ define(function (require) { - return function FiltersAggDefinition(Private, Notifier) { + return function FiltersAggDefinition(Private, Notifier, config) { var _ = require('lodash'); var AggType = Private(require('components/agg_types/_agg_type')); var createFilter = Private(require('components/agg_types/buckets/create_filter/filters')); @@ -25,6 +25,10 @@ define(function (require) { var query = input.query; if (!query) return notif.log('malformed filter agg params, missing "query" on input'); + if (_.deepHas(query, 'query_string.query')) { + _.merge(query.query_string, config.get('query:queryString:options')); + } + var label = _.deepGet(query, 'query_string.query') || JSON.stringify(query); filters[label] = input; }, {}); diff --git a/src/kibana/components/courier/data_source/_abstract.js b/src/kibana/components/courier/data_source/_abstract.js index 9a4dfcfdd07ea5..ce4aeb0dfc6677 100644 --- a/src/kibana/components/courier/data_source/_abstract.js +++ b/src/kibana/components/courier/data_source/_abstract.js @@ -259,6 +259,10 @@ define(function (require) { }()) .then(function () { if (type === 'search') { + + // This is down here to prevent the circular + var decorateQuery = Private(require('components/courier/data_source/_decorate_query')); + // defaults for the query if (!flatState.body.query) { flatState.body.query = { @@ -266,6 +270,8 @@ define(function (require) { }; } + decorateQuery(flatState.body.query); + var computedFields = flatState.index.getComputedFields(); flatState.body.fields = computedFields.fields; flatState.body.script_fields = flatState.body.script_fields || {}; @@ -301,6 +307,12 @@ define(function (require) { // switch to filtered query if there are filters if (flatState.filters) { if (flatState.filters.length) { + _.each(flatState.filters, function (filter) { + if (filter.query) { + decorateQuery(filter.query); + } + }); + flatState.body.query = { filtered: { query: flatState.body.query, diff --git a/src/kibana/components/courier/data_source/_decorate_query.js b/src/kibana/components/courier/data_source/_decorate_query.js new file mode 100644 index 00000000000000..6fdd205c3c2af8 --- /dev/null +++ b/src/kibana/components/courier/data_source/_decorate_query.js @@ -0,0 +1,20 @@ +define(function (require) { + var _ = require('lodash'); + return function DecorateQuery(config) { + /** + * Decorate queries with default parameters + * @param {query} query object + * @returns {object} + */ + return function (query) { + var queryOptions = config.get('query:queryString:options'); + + if (_.deepHas(query, 'query_string.query')) { + _.extend(query.query_string, queryOptions); + } + + return query; + }; + }; +}); + diff --git a/src/kibana/components/validate_query/lib/from_user.js b/src/kibana/components/validate_query/lib/from_user.js index fc4f4377a514db..cbb9cef039136d 100644 --- a/src/kibana/components/validate_query/lib/from_user.js +++ b/src/kibana/components/validate_query/lib/from_user.js @@ -1,15 +1,16 @@ define(function (require) { var _ = require('lodash'); - return function GetQueryFromUser(es, config) { + return function GetQueryFromUser(es, Private) { + var decorateQuery = Private(require('components/courier/data_source/_decorate_query')); + /** * Take text from the user and make it into a query object * @param {text} user's query input * @returns {object} */ return function (text) { - var queryOptions = config.get('query:queryString:options'); function getQueryStringQuery(text) { - return {query_string: _.extend({query: text}, queryOptions)}; + return decorateQuery({query_string: {query: text}}); } var matchAll = getQueryStringQuery('*'); diff --git a/test/unit/specs/courier/data_source/decorate_query.js b/test/unit/specs/courier/data_source/decorate_query.js new file mode 100644 index 00000000000000..f5054341460609 --- /dev/null +++ b/test/unit/specs/courier/data_source/decorate_query.js @@ -0,0 +1,41 @@ +/* global sinon */ +define(function (require) { + var moment = require('moment'); + describe('Query decorator', function () { + + var _ = require('lodash'); + var config; + + var indexPattern, getComputedFields, fn; + beforeEach(function () { + module('kibana'); + + module('kibana', function ($provide) { + + // Super simple config stub + $provide.service('config', function () { + var keys = {}; + return { + get: function (key) { return keys[key]; }, + set: function (key, value) { keys[key] = value; } + }; + }); + }); + }); + + beforeEach(inject(function (Private, $injector, _config_) { + config = _config_; + fn = Private(require('components/courier/data_source/_decorate_query')); + })); + + it('should be a function', function () { + expect(fn).to.be.a(Function); + }); + + it('should merge in the query string options', function () { + config.set('query:queryString:options', {analyze_wildcard: true}); + expect(fn({query_string: {query: '*'}})).to.eql({query_string: {query: '*', analyze_wildcard: true}}); + }); + + }); +}); \ No newline at end of file From 204e5d0feaaf1f90e813807da7fe911d5ed8e9e7 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Tue, 23 Dec 2014 08:27:57 -0700 Subject: [PATCH 09/50] Move doc_table into directive --- .../components/doc_table/doc_table.html | 3 + src/kibana/components/doc_table/doc_table.js | 59 +++++++++++++++++++ .../components/doc_table/doc_table.less | 24 ++++++++ .../plugins/dashboard/directives/panel.js | 10 ++-- .../plugins/dashboard/partials/panel.html | 7 ++- 5 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 src/kibana/components/doc_table/doc_table.html create mode 100644 src/kibana/components/doc_table/doc_table.js create mode 100644 src/kibana/components/doc_table/doc_table.less diff --git a/src/kibana/components/doc_table/doc_table.html b/src/kibana/components/doc_table/doc_table.html new file mode 100644 index 00000000000000..010e70fb8d342b --- /dev/null +++ b/src/kibana/components/doc_table/doc_table.html @@ -0,0 +1,3 @@ +
+ {{content}} +
\ No newline at end of file diff --git a/src/kibana/components/doc_table/doc_table.js b/src/kibana/components/doc_table/doc_table.js new file mode 100644 index 00000000000000..01abd8d506a446 --- /dev/null +++ b/src/kibana/components/doc_table/doc_table.js @@ -0,0 +1,59 @@ +define(function (require) { + var _ = require('lodash'); + + var html = require('text!components/doc_table/doc_table.html'); + require('css!components/doc_table/doc_table.css'); + + require('modules').get('kibana') + .directive('docTable', function (config, Private, Notifier) { + var formats = Private(require('components/index_patterns/_field_formats')); + + return { + restrict: 'E', + template: html, + scope: { + searchSource: '=', + columns: '=', + filter: '=?', + }, + link: function ($scope, $el, attr) { + var notify = new Notifier(); + + var prereq = (function () { + var fns = []; + + return function register(fn) { + fns.push(fn); + + return function () { + fn.apply(this, arguments); + + if (fns.length) { + _.pull(fns, fn); + if (!fns.length) { + $scope.$root.$broadcast('ready:vis'); + } + } + }; + }; + }()); + + $scope.$watch('searchSource', prereq(function (searchSource) { + if (!searchSource) return; + + // TODO: we need to have some way to clean up result requests + searchSource.onResults().then(function onResults(resp) { + if ($scope.searchSource !== searchSource) return; + + $scope.content = resp; + + return searchSource.onResults().then(onResults); + }).catch(notify.fatal); + + searchSource.onError(notify.error).catch(notify.fatal); + })); + + } + }; + }); +}); \ No newline at end of file diff --git a/src/kibana/components/doc_table/doc_table.less b/src/kibana/components/doc_table/doc_table.less new file mode 100644 index 00000000000000..321d297bf31d55 --- /dev/null +++ b/src/kibana/components/doc_table/doc_table.less @@ -0,0 +1,24 @@ +@import (reference) "../../styles/_bootstrap.less"; +@import (reference) "../../styles/theme/_theme.less"; +@import (reference) "lesshat.less"; + +doc-viewer .doc-viewer { + + &-buttons, &-field { + white-space: nowrap; + } + + &-value, pre { + word-break: break-all; + word-wrap: break-word; + white-space: pre-wrap; + } + + td, pre { + font-family: "Lucida Console", Monaco, monospace; + } + + .content { + margin-top: @padding-base-vertical; + } +} \ No newline at end of file diff --git a/src/kibana/plugins/dashboard/directives/panel.js b/src/kibana/plugins/dashboard/directives/panel.js index 3dcd7c90ebeb12..bc44419ba614fa 100644 --- a/src/kibana/plugins/dashboard/directives/panel.js +++ b/src/kibana/plugins/dashboard/directives/panel.js @@ -9,6 +9,8 @@ define(function (require) { var notify = new Notifier(); require('components/visualize/visualize'); + require('components/doc_table/doc_table'); + var brushEvent = Private(require('utils/brush_event')); return { @@ -43,13 +45,9 @@ define(function (require) { case 'search': savedSearches.get($scope.panel.id) .then(function (savedSearch) { - $scope.$root.$broadcast('ready:vis'); - - var searchSource = savedSearch.searchSource; + $scope.searchSource = savedSearch.searchSource; + $scope.columns = []; $scope.view.title = savedSearch.title; - searchSource.onResults().then(function onResults(resp) { - $scope.view.content = resp; - }).catch(notify.fatal); }) .catch(function (e) { $scope.error = e.message; diff --git a/src/kibana/plugins/dashboard/partials/panel.html b/src/kibana/plugins/dashboard/partials/panel.html index 40f6711702981f..86a72f8a5ec8ca 100644 --- a/src/kibana/plugins/dashboard/partials/panel.html +++ b/src/kibana/plugins/dashboard/partials/panel.html @@ -18,8 +18,9 @@ search-source="savedVis.searchSource" class="panel-content"> -
- {{view.content}} -
+
\ No newline at end of file From 54787ffcd8507a74cda42dc786b68431a3fb0cca Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Tue, 23 Dec 2014 15:06:52 -0500 Subject: [PATCH 10/50] updating tests --- .../specs/vislib/components/zero_injection.js | 71 +++++++++++++++---- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/test/unit/specs/vislib/components/zero_injection.js b/test/unit/specs/vislib/components/zero_injection.js index e585b742991849..ba3fedcc691f74 100644 --- a/test/unit/specs/vislib/components/zero_injection.js +++ b/test/unit/specs/vislib/components/zero_injection.js @@ -15,6 +15,12 @@ define(function (require) { 'rows': [ { 'label': 'Top 5 @tags: success', + 'ordered': { + 'date': true, + 'interval': 60000, + 'min': 1418410540548, + 'max': 1418410936568 + }, 'series': [ { 'label': 'jpg', @@ -50,6 +56,12 @@ define(function (require) { }, { 'label': 'Top 5 @tags: info', + 'ordered': { + 'date': true, + 'interval': 60000, + 'min': 1418410540548, + 'max': 1418410936568 + }, 'series': [ { 'label': 'jpg', @@ -82,6 +94,12 @@ define(function (require) { }, { 'label': 'Top 5 @tags: security', + 'ordered': { + 'date': true, + 'interval': 60000, + 'min': 1418410540548, + 'max': 1418410936568 + }, 'series': [ { 'label': 'jpg', @@ -102,6 +120,12 @@ define(function (require) { }, { 'label': 'Top 5 @tags: login', + 'ordered': { + 'date': true, + 'interval': 60000, + 'min': 1418410540548, + 'max': 1418410936568 + }, 'series': [ { 'label': 'jpg', @@ -119,6 +143,12 @@ define(function (require) { }, { 'label': 'Top 5 @tags: warning', + 'ordered': { + 'date': true, + 'interval': 60000, + 'min': 1418410540548, + 'max': 1418410936568 + }, 'series': [ { 'label': 'jpg', @@ -204,6 +234,7 @@ define(function (require) { ] }; + var ordered = {}; var childrenObject = { children: [] }; @@ -270,7 +301,7 @@ define(function (require) { }); it('should throw an error if property series, rows, or columns is not ' + - 'present', function () { + 'present', function () { expect(function () { injectZeros(childrenObject); @@ -278,7 +309,7 @@ define(function (require) { }); it('should not throw an error if object has property series, rows, or ' + - 'columns', function () { + 'columns', function () { expect(function () { injectZeros(seriesObject); @@ -581,6 +612,7 @@ define(function (require) { zeroFillArray = Private(require('components/vislib/components/zero_injection/zero_fill_data_array')); createZeroArray = Private(require('components/vislib/components/zero_injection/zero_filled_array')); arr1 = createZeroArray(xValueArr); + // Takes zero array as 1st arg and data array as 2nd arg results = zeroFillArray(arr1, arr2); }); @@ -634,10 +666,9 @@ define(function (require) { }); }); - describe('Injected Zeros return in the correct order for dates', function () { - // Need to test that for Date Histogram data, objects are always sorted by time and not by index + describe('Injected Zero values return in the correct order', function () { var injectZeros; - var dateHistogramData; + var results; beforeEach(function () { module('ZeroInjectionUtilService'); @@ -646,18 +677,30 @@ define(function (require) { beforeEach(function () { inject(function (Private) { injectZeros = Private(require('components/vislib/components/zero_injection/inject_zeros')); - dateHistogramData = injectZeros(dateHistogramRows); + results = injectZeros(dateHistogramRows); }); }); - afterEach(function () { - dateHistogramData = null; - }); - - it('should return dates in order', function () { - + it('should return an array of objects', function () { + console.log(results); + expect(_.isArray(results.rows[0].series[0].values)).to.be(true); + expect(_.isArray(results.rows[1].series[0].values)).to.be(true); + expect(_.isArray(results.rows[2].series[0].values)).to.be(true); + expect(_.isArray(results.rows[3].series[0].values)).to.be(true); + expect(_.isArray(results.rows[4].series[0].values)).to.be(true); + }); + + it('should return ordered x values', function () { + var values = results.rows[0].series[0].values; + console.log(values); + expect(values[0].x).to.be.lessThan(values[1].x); + expect(values[1].x).to.be.lessThan(values[2].x); + expect(values[2].x).to.be.lessThan(values[3].x); + expect(values[3].x).to.be.lessThan(values[4].x); + expect(values[4].x).to.be.lessThan(values[5].x); + expect(values[5].x).to.be.lessThan(values[6].x); + expect(values[6].x).to.be.lessThan(values[7].x); }); }); - }); -}); +}); \ No newline at end of file From 8122327838ae1cf299b97fa5d61e558c0fec5e45 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Wed, 24 Dec 2014 08:34:02 -0700 Subject: [PATCH 11/50] Remove timefield, use index pattern, add initial sorting --- .../doc_table/components}/table_header.html | 0 .../doc_table/components}/table_header.js | 5 ++- .../doc_table/components}/table_row.js | 40 +++++++++++++---- .../components}/table_row/_source.html | 0 .../doc_table/components}/table_row/cell.html | 0 .../components}/table_row/details.html | 0 .../doc_table/components}/table_row/open.html | 0 .../components/doc_table/doc_table.html | 20 +++++++-- src/kibana/components/doc_table/doc_table.js | 43 +++++++++++++++---- .../components/doc_table/doc_table.less | 22 ++-------- .../components/doc_table/lib/get_sort.js | 23 ++++++++++ .../plugins/dashboard/directives/panel.js | 3 +- .../plugins/dashboard/partials/panel.html | 3 +- .../plugins/discover/directives/table.js | 5 +-- .../plugins/discover/partials/table.html | 2 - src/kibana/styles/_table.less | 2 +- src/kibana/styles/main.less | 2 +- 17 files changed, 119 insertions(+), 51 deletions(-) rename src/kibana/{plugins/discover/partials => components/doc_table/components}/table_header.html (100%) rename src/kibana/{plugins/discover/directives => components/doc_table/components}/table_header.js (91%) rename src/kibana/{plugins/discover/directives => components/doc_table/components}/table_row.js (80%) rename src/kibana/{plugins/discover/partials => components/doc_table/components}/table_row/_source.html (100%) rename src/kibana/{plugins/discover/partials => components/doc_table/components}/table_row/cell.html (100%) rename src/kibana/{plugins/discover/partials => components/doc_table/components}/table_row/details.html (100%) rename src/kibana/{plugins/discover/partials => components/doc_table/components}/table_row/open.html (100%) create mode 100644 src/kibana/components/doc_table/lib/get_sort.js diff --git a/src/kibana/plugins/discover/partials/table_header.html b/src/kibana/components/doc_table/components/table_header.html similarity index 100% rename from src/kibana/plugins/discover/partials/table_header.html rename to src/kibana/components/doc_table/components/table_header.html diff --git a/src/kibana/plugins/discover/directives/table_header.js b/src/kibana/components/doc_table/components/table_header.js similarity index 91% rename from src/kibana/plugins/discover/directives/table_header.js rename to src/kibana/components/doc_table/components/table_header.js index 0ff7386742aef6..ba19fe5f49b088 100644 --- a/src/kibana/plugins/discover/directives/table_header.js +++ b/src/kibana/components/doc_table/components/table_header.js @@ -5,17 +5,18 @@ define(function (require) { require('filters/short_dots'); module.directive('kbnTableHeader', function () { - var headerHtml = require('text!plugins/discover/partials/table_header.html'); + var headerHtml = require('text!components/doc_table/components/table_header.html'); return { restrict: 'A', scope: { columns: '=', sorting: '=', indexPattern: '=', - timefield: '=?' }, template: headerHtml, controller: function ($scope) { + $scope.timefield = $scope.indexPattern.timeFieldName; + var sortableField = function (field) { return $scope.indexPattern.fields.byName[field].sortable; }; diff --git a/src/kibana/plugins/discover/directives/table_row.js b/src/kibana/components/doc_table/components/table_row.js similarity index 80% rename from src/kibana/plugins/discover/directives/table_row.js rename to src/kibana/components/doc_table/components/table_row.js index 4d8f5e39ba6836..c97012b3c70c11 100644 --- a/src/kibana/plugins/discover/directives/table_row.js +++ b/src/kibana/components/doc_table/components/table_row.js @@ -21,12 +21,12 @@ define(function (require) { * * ``` */ - module.directive('kbnTableRow', function ($compile, config, highlightFilter, shortDotsFilter) { - var openRowHtml = require('text!plugins/discover/partials/table_row/open.html'); - var detailsHtml = require('text!plugins/discover/partials/table_row/details.html'); - var cellTemplate = _.template(require('text!plugins/discover/partials/table_row/cell.html')); + module.directive('kbnTableRow', function ($compile, config, highlightFilter, shortDotsFilter, courier) { + var openRowHtml = require('text!components/doc_table/components/table_row/open.html'); + var detailsHtml = require('text!components/doc_table/components/table_row/details.html'); + var cellTemplate = _.template(require('text!components/doc_table/components/table_row/cell.html')); var truncateByHeightTemplate = _.template(require('text!partials/truncate_by_height.html')); - var sourceTemplate = _.template(noWhiteSpace(require('text!plugins/discover/partials/table_row/_source.html'))); + var sourceTemplate = _.template(noWhiteSpace(require('text!components/doc_table/components/table_row/_source.html'))); return { restrict: 'A', @@ -34,7 +34,6 @@ define(function (require) { columns: '=', filter: '=', indexPattern: '=', - timefield: '=?', row: '=kbnTableRow' }, link: function ($scope, $el, attrs) { @@ -42,7 +41,12 @@ define(function (require) { $el.empty(); var init = function () { + _formatRow($scope.row); createSummaryRow($scope.row, $scope.row._id); + + // Check for timefield on indexPattern + $scope.timefield = $scope.indexPattern.timeFieldName; + }; // when we compile the details, we use this $scope @@ -187,8 +191,8 @@ define(function (require) { function _getValForField(row, field) { var val; - // discover formats all of the values and puts them in _formatted for display - val = row.$$_formatted[field] || row[field]; + // discover formats all of the values and puts them in $$_formatted for display + val = (row.$$_formatted || _formatRow(row))[field]; // undefined and null should just be an empty string val = (val == null) ? '' : val; @@ -196,6 +200,26 @@ define(function (require) { return val; } + /* + * Format a field with the index pattern on scope. + */ + function _formatField(value, name) { + var defaultFormat = courier.indexPatterns.fieldFormats.defaultByType.string; + var field = $scope.indexPattern.fields.byName[name]; + var formatter = (field && field.format) ? field.format : defaultFormat; + + return formatter.convert(value); + } + + /* + * Create the $$_formatted key on a row + */ + function _formatRow(row) { + row.$$_flattened = row.$$_flattened || $scope.indexPattern.flattenHit(row); + row.$$_formatted = _.mapValues(row.$$_flattened, _formatField); + return row.$$_formatted; + } + init(); } }; diff --git a/src/kibana/plugins/discover/partials/table_row/_source.html b/src/kibana/components/doc_table/components/table_row/_source.html similarity index 100% rename from src/kibana/plugins/discover/partials/table_row/_source.html rename to src/kibana/components/doc_table/components/table_row/_source.html diff --git a/src/kibana/plugins/discover/partials/table_row/cell.html b/src/kibana/components/doc_table/components/table_row/cell.html similarity index 100% rename from src/kibana/plugins/discover/partials/table_row/cell.html rename to src/kibana/components/doc_table/components/table_row/cell.html diff --git a/src/kibana/plugins/discover/partials/table_row/details.html b/src/kibana/components/doc_table/components/table_row/details.html similarity index 100% rename from src/kibana/plugins/discover/partials/table_row/details.html rename to src/kibana/components/doc_table/components/table_row/details.html diff --git a/src/kibana/plugins/discover/partials/table_row/open.html b/src/kibana/components/doc_table/components/table_row/open.html similarity index 100% rename from src/kibana/plugins/discover/partials/table_row/open.html rename to src/kibana/components/doc_table/components/table_row/open.html diff --git a/src/kibana/components/doc_table/doc_table.html b/src/kibana/components/doc_table/doc_table.html index 010e70fb8d342b..5c54efcb3e6faf 100644 --- a/src/kibana/components/doc_table/doc_table.html +++ b/src/kibana/components/doc_table/doc_table.html @@ -1,3 +1,17 @@ -
- {{content}} -
\ No newline at end of file + + + + + + +
+ \ No newline at end of file diff --git a/src/kibana/components/doc_table/doc_table.js b/src/kibana/components/doc_table/doc_table.js index 01abd8d506a446..d065fcdcffcccf 100644 --- a/src/kibana/components/doc_table/doc_table.js +++ b/src/kibana/components/doc_table/doc_table.js @@ -2,7 +2,11 @@ define(function (require) { var _ = require('lodash'); var html = require('text!components/doc_table/doc_table.html'); + var getSort = require('components/doc_table/lib/get_sort'); require('css!components/doc_table/doc_table.css'); + require('directives/truncated'); + require('components/doc_table/components/table_header'); + require('components/doc_table/components/table_row'); require('modules').get('kibana') .directive('docTable', function (config, Private, Notifier) { @@ -12,12 +16,12 @@ define(function (require) { restrict: 'E', template: html, scope: { - searchSource: '=', - columns: '=', + savedSearch: '=', filter: '=?', }, link: function ($scope, $el, attr) { var notify = new Notifier(); + $scope.columns = []; var prereq = (function () { var fns = []; @@ -38,19 +42,42 @@ define(function (require) { }; }()); - $scope.$watch('searchSource', prereq(function (searchSource) { - if (!searchSource) return; + $scope.addRows = function (foo) { + $scope.limit += 50; + }; + + $scope.$watch('sorting', function (sorting) { + if (!$scope.indexPattern || !$scope.searchSource) return; + $scope.searchSource.sort(getSort(sorting, $scope.indexPattern)); + }); + + $scope.$watch('savedSearch', prereq(function (savedSearch) { + if (!(savedSearch && savedSearch.searchSource)) return; + + $scope.searchSource = savedSearch.searchSource; + $scope.indexPattern = savedSearch.searchSource.get('index'); + + $scope.columns = savedSearch.columns; + + $scope.searchSource.size(config.get('discover:sampleSize')); + + // Should trigger the above watcher + $scope.sorting = savedSearch.sort; // TODO: we need to have some way to clean up result requests - searchSource.onResults().then(function onResults(resp) { - if ($scope.searchSource !== searchSource) return; + $scope.searchSource.onResults().then(function onResults(resp) { + // Reset infinite scroll limit + $scope.limit = 50; + + if ($scope.searchSource !== savedSearch.searchSource) return; $scope.content = resp; + $scope.hits = resp.hits.hits; - return searchSource.onResults().then(onResults); + return $scope.searchSource.onResults().then(onResults); }).catch(notify.fatal); - searchSource.onError(notify.error).catch(notify.fatal); + $scope.searchSource.onError(notify.error).catch(notify.fatal); })); } diff --git a/src/kibana/components/doc_table/doc_table.less b/src/kibana/components/doc_table/doc_table.less index 321d297bf31d55..fc50a72534da34 100644 --- a/src/kibana/components/doc_table/doc_table.less +++ b/src/kibana/components/doc_table/doc_table.less @@ -2,23 +2,7 @@ @import (reference) "../../styles/theme/_theme.less"; @import (reference) "lesshat.less"; -doc-viewer .doc-viewer { - - &-buttons, &-field { - white-space: nowrap; - } - - &-value, pre { - word-break: break-all; - word-wrap: break-word; - white-space: pre-wrap; - } - - td, pre { - font-family: "Lucida Console", Monaco, monospace; - } - - .content { - margin-top: @padding-base-vertical; - } +doc-table { + overflow: auto; + margin: 5px; } \ No newline at end of file diff --git a/src/kibana/components/doc_table/lib/get_sort.js b/src/kibana/components/doc_table/lib/get_sort.js new file mode 100644 index 00000000000000..45de25c4734d8b --- /dev/null +++ b/src/kibana/components/doc_table/lib/get_sort.js @@ -0,0 +1,23 @@ +define(function (require) { + var _ = require('lodash'); + + /** + * Take a sorting array and make it into an object + * @param {array} 2 item array [fieldToSort, directionToSort] + * @param {object} indexPattern used for determining default sort + * @returns {object} a sort object suitable for returning to elasticsearch + */ + return function (sort, indexPattern) { + var sortObj = {}; + if (_.isArray(sort)) { + // At some point we need to refact the sorting logic, this array sucks. + if (sort.length !== 2) throw new Error('The sorting array contain exactly 2 items'); + sortObj[sort[0]] = sort[1]; + } else if (indexPattern.timeFieldName) { + sortObj[indexPattern.timeFieldName] = 'desc'; + } else { + sortObj._score = 'desc'; + } + return sortObj; + }; +}); diff --git a/src/kibana/plugins/dashboard/directives/panel.js b/src/kibana/plugins/dashboard/directives/panel.js index bc44419ba614fa..92f0475d356926 100644 --- a/src/kibana/plugins/dashboard/directives/panel.js +++ b/src/kibana/plugins/dashboard/directives/panel.js @@ -45,8 +45,7 @@ define(function (require) { case 'search': savedSearches.get($scope.panel.id) .then(function (savedSearch) { - $scope.searchSource = savedSearch.searchSource; - $scope.columns = []; + $scope.savedSearch = savedSearch; $scope.view.title = savedSearch.title; }) .catch(function (e) { diff --git a/src/kibana/plugins/dashboard/partials/panel.html b/src/kibana/plugins/dashboard/partials/panel.html index 86a72f8a5ec8ca..e744763cf47b61 100644 --- a/src/kibana/plugins/dashboard/partials/panel.html +++ b/src/kibana/plugins/dashboard/partials/panel.html @@ -19,8 +19,7 @@ class="panel-content"> \ No newline at end of file diff --git a/src/kibana/plugins/discover/directives/table.js b/src/kibana/plugins/discover/directives/table.js index 3316e12309208e..abe99884a7eee7 100644 --- a/src/kibana/plugins/discover/directives/table.js +++ b/src/kibana/plugins/discover/directives/table.js @@ -3,8 +3,8 @@ define(function (require) { require('directives/truncated'); require('directives/infinite_scroll'); - require('plugins/discover/directives/table_header'); - require('plugins/discover/directives/table_row'); + require('components/doc_table/components/table_header'); + require('components/doc_table/components/table_row'); var module = require('modules').get('app/discover'); @@ -22,7 +22,6 @@ define(function (require) { restrict: 'E', template: html, scope: { - fields: '=', columns: '=', rows: '=', sorting: '=', diff --git a/src/kibana/plugins/discover/partials/table.html b/src/kibana/plugins/discover/partials/table.html index 93c70755eac7fd..3f98d17c8669fc 100644 --- a/src/kibana/plugins/discover/partials/table.html +++ b/src/kibana/plugins/discover/partials/table.html @@ -4,14 +4,12 @@ columns="columns" index-pattern="indexPattern" sorting="sorting" - timefield="timefield"> diff --git a/src/kibana/styles/_table.less b/src/kibana/styles/_table.less index 25af5bef47a89a..a567db705ff7bf 100644 --- a/src/kibana/styles/_table.less +++ b/src/kibana/styles/_table.less @@ -1,4 +1,4 @@ -kbn-table,tbody[kbn-rows] { +kbn-table, .kbn-table, tbody[kbn-rows] { // sub tables should not have a leading border .table .table { margin-bottom: 0px; diff --git a/src/kibana/styles/main.less b/src/kibana/styles/main.less index 09dcf55ad0fdd6..37a7bbbcf26b57 100644 --- a/src/kibana/styles/main.less +++ b/src/kibana/styles/main.less @@ -253,7 +253,7 @@ notifications { //== Table -kbn-table { +kbn-table, .kbn-table { font-size: @font-size-small; th { From 92d206cadbbc37452b4bfc04b8850883215858d0 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Wed, 24 Dec 2014 11:12:07 -0700 Subject: [PATCH 12/50] Remove super slow cloneDeep --- src/kibana/components/courier/fetch/fetch.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/kibana/components/courier/fetch/fetch.js b/src/kibana/components/courier/fetch/fetch.js index 513f670293181b..5cbff0f75e1a47 100644 --- a/src/kibana/components/courier/fetch/fetch.js +++ b/src/kibana/components/courier/fetch/fetch.js @@ -86,7 +86,9 @@ define(function (require) { } else { req._merged.forEach(function (mergedReq) { mergedReq.state = state; - sendResponse(mergedReq, _.cloneDeep(resp)); + // HEY *HEY* There was a _.cloneDeep(resp) down there, it was super slow + // I removed it, someone tell me if there are serious consequences to that. + sendResponse(mergedReq, resp); }); } }); From 970349841f4de9ef1be190170ce173eed4f26e0e Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Wed, 24 Dec 2014 11:12:19 -0700 Subject: [PATCH 13/50] Add interactive sorting --- src/kibana/components/doc_table/doc_table.html | 4 ++-- src/kibana/components/doc_table/doc_table.js | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/kibana/components/doc_table/doc_table.html b/src/kibana/components/doc_table/doc_table.html index 5c54efcb3e6faf..89460c93e2f00a 100644 --- a/src/kibana/components/doc_table/doc_table.html +++ b/src/kibana/components/doc_table/doc_table.html @@ -3,13 +3,13 @@ kbn-table-header columns="columns" index-pattern="indexPattern" - sorting="sorting"> + sorting="panel.sorting"> diff --git a/src/kibana/components/doc_table/doc_table.js b/src/kibana/components/doc_table/doc_table.js index d065fcdcffcccf..055e080c3004fe 100644 --- a/src/kibana/components/doc_table/doc_table.js +++ b/src/kibana/components/doc_table/doc_table.js @@ -19,9 +19,13 @@ define(function (require) { savedSearch: '=', filter: '=?', }, - link: function ($scope, $el, attr) { + link: function ($scope) { var notify = new Notifier(); $scope.columns = []; + $scope.panel = { + columns: [], + sorting: [] + }; var prereq = (function () { var fns = []; @@ -46,9 +50,10 @@ define(function (require) { $scope.limit += 50; }; - $scope.$watch('sorting', function (sorting) { + $scope.$watch('panel.sorting', function (sorting) { if (!$scope.indexPattern || !$scope.searchSource) return; $scope.searchSource.sort(getSort(sorting, $scope.indexPattern)); + $scope.searchSource.fetch(); }); $scope.$watch('savedSearch', prereq(function (savedSearch) { @@ -62,7 +67,7 @@ define(function (require) { $scope.searchSource.size(config.get('discover:sampleSize')); // Should trigger the above watcher - $scope.sorting = savedSearch.sort; + $scope.panel.sorting = savedSearch.sort; // TODO: we need to have some way to clean up result requests $scope.searchSource.onResults().then(function onResults(resp) { From 6e7cf2d105617fee608af992fb4b383f4e1be232 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Fri, 26 Dec 2014 10:16:13 -0700 Subject: [PATCH 14/50] Do not flatten _source without knowledge of the mapping --- src/kibana/components/courier/fetch/strategy/search.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/kibana/components/courier/fetch/strategy/search.js b/src/kibana/components/courier/fetch/strategy/search.js index 018316f629939e..3b44da2999c2bd 100644 --- a/src/kibana/components/courier/fetch/strategy/search.js +++ b/src/kibana/components/courier/fetch/strategy/search.js @@ -48,11 +48,8 @@ define(function (require) { * @return {Promise} - the promise created by responding to the request */ resolveRequest: function (req, resp) { - if (resp && resp.hits) { - resp.hits.hits.forEach(function (hit) { - hit._source = _.flattenWith('.', hit._source); - }); - } + // Whats was going on here? This was flattening without context. + // Eg, geo object were flattened beyond the mapping req.defer.resolve(resp); }, From 22ac539107a40255cbb531b1f69346f6c4dd7756 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Fri, 26 Dec 2014 10:36:10 -0700 Subject: [PATCH 15/50] Make panel adder tabbed, properly destroy searchSources --- .../components/doc_table/doc_table.html | 8 +++--- src/kibana/components/doc_table/doc_table.js | 27 ++++++++++--------- .../components/doc_table/lib/get_sort.js | 1 - .../plugins/dashboard/directives/panel.js | 10 ++++--- .../plugins/dashboard/partials/panel.html | 14 +++++----- .../partials/pick_visualization.html | 23 +++++----------- src/kibana/plugins/dashboard/styles/main.less | 4 +++ 7 files changed, 41 insertions(+), 46 deletions(-) diff --git a/src/kibana/components/doc_table/doc_table.html b/src/kibana/components/doc_table/doc_table.html index 89460c93e2f00a..935dd14f92e5a8 100644 --- a/src/kibana/components/doc_table/doc_table.html +++ b/src/kibana/components/doc_table/doc_table.html @@ -1,15 +1,15 @@ + sorting="persist.sorting"> diff --git a/src/kibana/components/doc_table/doc_table.js b/src/kibana/components/doc_table/doc_table.js index 055e080c3004fe..410a4971962c50 100644 --- a/src/kibana/components/doc_table/doc_table.js +++ b/src/kibana/components/doc_table/doc_table.js @@ -22,10 +22,8 @@ define(function (require) { link: function ($scope) { var notify = new Notifier(); $scope.columns = []; - $scope.panel = { - columns: [], - sorting: [] - }; + $scope.persist = $scope.persist || {}; + var prereq = (function () { var fns = []; @@ -50,33 +48,36 @@ define(function (require) { $scope.limit += 50; }; - $scope.$watch('panel.sorting', function (sorting) { - if (!$scope.indexPattern || !$scope.searchSource) return; - $scope.searchSource.sort(getSort(sorting, $scope.indexPattern)); - $scope.searchSource.fetch(); + $scope.$on('$destroy', function () { + if ($scope.searchSource) $scope.searchSource.destroy(); }); $scope.$watch('savedSearch', prereq(function (savedSearch) { if (!(savedSearch && savedSearch.searchSource)) return; + $scope.persist.sorting = savedSearch.sort; + $scope.persist.columns = savedSearch.columns; $scope.searchSource = savedSearch.searchSource; $scope.indexPattern = savedSearch.searchSource.get('index'); - $scope.columns = savedSearch.columns; - $scope.searchSource.size(config.get('discover:sampleSize')); + $scope.searchSource.sort(getSort(savedSearch.sort, $scope.indexPattern)); - // Should trigger the above watcher - $scope.panel.sorting = savedSearch.sort; + // Set the watcher after initialization + $scope.$watch('persist.sorting', function (sorting) { + if (!$scope.indexPattern || !$scope.searchSource || !sorting) return; + $scope.searchSource.sort(getSort(sorting, $scope.indexPattern)); + $scope.searchSource.fetch(); + }); // TODO: we need to have some way to clean up result requests $scope.searchSource.onResults().then(function onResults(resp) { // Reset infinite scroll limit $scope.limit = 50; + // Abort if something changed if ($scope.searchSource !== savedSearch.searchSource) return; - $scope.content = resp; $scope.hits = resp.hits.hits; return $scope.searchSource.onResults().then(onResults); diff --git a/src/kibana/components/doc_table/lib/get_sort.js b/src/kibana/components/doc_table/lib/get_sort.js index 45de25c4734d8b..db8d7ebc4799fc 100644 --- a/src/kibana/components/doc_table/lib/get_sort.js +++ b/src/kibana/components/doc_table/lib/get_sort.js @@ -11,7 +11,6 @@ define(function (require) { var sortObj = {}; if (_.isArray(sort)) { // At some point we need to refact the sorting logic, this array sucks. - if (sort.length !== 2) throw new Error('The sorting array contain exactly 2 items'); sortObj[sort[0]] = sort[1]; } else if (indexPattern.timeFieldName) { sortObj[indexPattern.timeFieldName] = 'desc'; diff --git a/src/kibana/plugins/dashboard/directives/panel.js b/src/kibana/plugins/dashboard/directives/panel.js index 92f0475d356926..b079c9722d2bc3 100644 --- a/src/kibana/plugins/dashboard/directives/panel.js +++ b/src/kibana/plugins/dashboard/directives/panel.js @@ -30,10 +30,11 @@ define(function (require) { switch ($scope.panel.type) { case 'visualization': + + $scope.edit = '#visualize/edit'; savedVisualizations.get($scope.panel.id) .then(function (savedVis) { - $scope.savedVis = savedVis; - $scope.view.title = savedVis.title; + $scope.savedObj = savedVis; $scope.$on('$destroy', savedVis.destroy); savedVis.vis.listeners.click = filterBarClickHandler($state); savedVis.vis.listeners.brush = brushEvent; @@ -43,10 +44,11 @@ define(function (require) { }); break; case 'search': + + $scope.edit = '#discover'; savedSearches.get($scope.panel.id) .then(function (savedSearch) { - $scope.savedSearch = savedSearch; - $scope.view.title = savedSearch.title; + $scope.savedObj = savedSearch; }) .catch(function (e) { $scope.error = e.message; diff --git a/src/kibana/plugins/dashboard/partials/panel.html b/src/kibana/plugins/dashboard/partials/panel.html index e744763cf47b61..a04b8a51dac0fc 100644 --- a/src/kibana/plugins/dashboard/partials/panel.html +++ b/src/kibana/plugins/dashboard/partials/panel.html @@ -1,8 +1,8 @@ -
+
- {{view.title}} + {{savedObj.title}}
- +
@@ -13,13 +13,13 @@
-
\ No newline at end of file diff --git a/src/kibana/plugins/dashboard/partials/pick_visualization.html b/src/kibana/plugins/dashboard/partials/pick_visualization.html index 6c78e733053e8f..a4bb0ada361f41 100644 --- a/src/kibana/plugins/dashboard/partials/pick_visualization.html +++ b/src/kibana/plugins/dashboard/partials/pick_visualization.html @@ -1,25 +1,14 @@ -
    +
    + -
  • - Add a visualization panel -
  • -
  • -
  • - Or add a saved search as a table of hits -
  • -
  • - -
- - +
\ No newline at end of file diff --git a/src/kibana/plugins/dashboard/styles/main.less b/src/kibana/plugins/dashboard/styles/main.less index de50202a01f6fb..294b87ef6194a0 100644 --- a/src/kibana/plugins/dashboard/styles/main.less +++ b/src/kibana/plugins/dashboard/styles/main.less @@ -109,6 +109,10 @@ dashboard-grid { } } +.dashboard-panel-picker > li.list-group-item { + border-top: 0px; +} + .dashboard-load { margin: 10px; } From 6b15169bbf4126ca07f6724fe60caf20a88a4dec Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Fri, 26 Dec 2014 11:30:52 -0700 Subject: [PATCH 16/50] Use formatted values if already present --- src/kibana/components/doc_table/components/table_row.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kibana/components/doc_table/components/table_row.js b/src/kibana/components/doc_table/components/table_row.js index c97012b3c70c11..7a16a33f8358cb 100644 --- a/src/kibana/components/doc_table/components/table_row.js +++ b/src/kibana/components/doc_table/components/table_row.js @@ -216,7 +216,7 @@ define(function (require) { */ function _formatRow(row) { row.$$_flattened = row.$$_flattened || $scope.indexPattern.flattenHit(row); - row.$$_formatted = _.mapValues(row.$$_flattened, _formatField); + row.$$_formatted = row.$$_formatted || _.mapValues(row.$$_flattened, _formatField); return row.$$_formatted; } From acc1e596de209bd44167f8cbd1a8a46f7692c41f Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Fri, 26 Dec 2014 11:41:47 -0700 Subject: [PATCH 17/50] Remove scope.timefield now that we have the indexPattern --- .../components/doc_table/components/table_header.html | 4 ++-- .../components/doc_table/components/table_header.js | 1 - .../components/doc_table/components/table_row.js | 10 +++------- test/unit/fixtures/stubbed_logstash_index_pattern.js | 2 +- test/unit/specs/apps/discover/directives/table.js | 5 ++--- 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/kibana/components/doc_table/components/table_header.html b/src/kibana/components/doc_table/components/table_header.html index f1767a839be4fb..a637796e4f6c76 100644 --- a/src/kibana/components/doc_table/components/table_header.html +++ b/src/kibana/components/doc_table/components/table_header.html @@ -1,6 +1,6 @@ -') .attr({ @@ -395,7 +395,6 @@ define(function (require) { 'sorting': 'sortin', 'filtering': 'filtering', 'index-pattern': 'indexPattern', - 'timefield': 'timefield', }); $scope = $root.$new(); From cab6f804406e61646fd49c86c317c809a3d74725 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Fri, 26 Dec 2014 12:16:55 -0700 Subject: [PATCH 18/50] Add 'time' field to stubbed pattern, use as default time field --- test/unit/fixtures/logstash_fields.js | 1 + .../stubbed_logstash_index_pattern.js | 2 +- .../specs/apps/discover/directives/table.js | 32 ++++++++++--------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/test/unit/fixtures/logstash_fields.js b/test/unit/fixtures/logstash_fields.js index dcfdde7cb35035..67d005d5d0bea1 100644 --- a/test/unit/fixtures/logstash_fields.js +++ b/test/unit/fixtures/logstash_fields.js @@ -4,6 +4,7 @@ define(function (require) { { name: 'bytes', type: 'number', indexed: true, analyzed: true, count: 10 }, { name: 'ssl', type: 'boolean', indexed: true, analyzed: true, count: 20 }, { name: '@timestamp', type: 'date', indexed: true, analyzed: true, count: 30 }, + { name: 'time', type: 'date', indexed: true, analyzed: true, count: 30 }, { name: 'utc_time', type: 'date', indexed: true, analyzed: true }, { name: 'phpmemory', type: 'number', indexed: true, analyzed: true }, { name: 'ip', type: 'ip', indexed: true, analyzed: true }, diff --git a/test/unit/fixtures/stubbed_logstash_index_pattern.js b/test/unit/fixtures/stubbed_logstash_index_pattern.js index 2121b16d8c2df0..834a0a312c5ed7 100644 --- a/test/unit/fixtures/stubbed_logstash_index_pattern.js +++ b/test/unit/fixtures/stubbed_logstash_index_pattern.js @@ -18,7 +18,7 @@ define(function (require) { return field; }); - var indexPattern = new StubIndexPattern('logstash-*', '@timestamp', fields); + var indexPattern = new StubIndexPattern('logstash-*', 'time', fields); indexPattern.getComputedFields = _.bind(getComputedFields, indexPattern); indexPattern.flattenSearchResponse = _.bind(flattenSearchResponse, indexPattern); diff --git a/test/unit/specs/apps/discover/directives/table.js b/test/unit/specs/apps/discover/directives/table.js index fcff6e967ae25c..5efd5d168117f2 100644 --- a/test/unit/specs/apps/discover/directives/table.js +++ b/test/unit/specs/apps/discover/directives/table.js @@ -41,16 +41,8 @@ define(function (require) { // For testing column removing/adding for the header and the rows // var columnTests = function (elemType, parentElem) { - it('should create only the toggle column by default', function (done) { - var childElems = parentElem.find(elemType); - expect(childElems.length).to.be(1); - done(); - }); it('should create a time column if the timefield is defined', function (done) { - // Should include a column for toggling and the time column by default - $parentScope.timefield = '@timestamp'; - parentElem.scope().$digest(); var childElems = parentElem.find(elemType); expect(childElems.length).to.be(2); done(); @@ -62,22 +54,32 @@ define(function (require) { $parentScope.columns = ['bytes']; parentElem.scope().$digest(); childElems = parentElem.find(elemType); - expect(childElems.length).to.be(2); - expect($(childElems[1]).text()).to.contain('bytes'); + expect(childElems.length).to.be(3); + expect($(childElems[2]).text()).to.contain('bytes'); $parentScope.columns = ['bytes', 'request_body']; parentElem.scope().$digest(); childElems = parentElem.find(elemType); - expect(childElems.length).to.be(3); - expect($(childElems[2]).text()).to.contain('request_body'); + expect(childElems.length).to.be(4); + expect($(childElems[3]).text()).to.contain('request_body'); $parentScope.columns = ['request_body']; parentElem.scope().$digest(); childElems = parentElem.find(elemType); - expect(childElems.length).to.be(2); - expect($(childElems[1]).text()).to.contain('request_body'); + expect(childElems.length).to.be(3); + expect($(childElems[2]).text()).to.contain('request_body'); + done(); + }); + + it('should create only the toggle column if there is no timeField', function (done) { + delete parentElem.scope().indexPattern.timeFieldName; + parentElem.scope().$digest(); + + var childElems = parentElem.find(elemType); + expect(childElems.length).to.be(1); done(); }); + }; @@ -404,7 +406,7 @@ define(function (require) { $before = $row.find('td'); expect($before).to.have.length(3); expect($before.eq(0).text().trim()).to.be(''); - expect($before.eq(1).text().trim()).to.match(/^@timestamp_formatted/); + expect($before.eq(1).text().trim()).to.match(/^time_formatted/); expect($before.eq(2).find('dl dt').length).to.be(_.keys($scope.row.$$_flattened).length); })); From 811d46d31e0d85b0cdc7843f6b7889799a6c6653 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Fri, 26 Dec 2014 13:24:51 -0700 Subject: [PATCH 19/50] Remove unused scope.timefield mentions --- src/kibana/plugins/discover/directives/table.js | 1 - src/kibana/plugins/discover/index.html | 1 - test/unit/specs/apps/discover/directives/table.js | 6 +----- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/kibana/plugins/discover/directives/table.js b/src/kibana/plugins/discover/directives/table.js index abe99884a7eee7..9b9cda7be72b38 100644 --- a/src/kibana/plugins/discover/directives/table.js +++ b/src/kibana/plugins/discover/directives/table.js @@ -28,7 +28,6 @@ define(function (require) { filtering: '=', refresh: '=', indexPattern: '=', - timefield: '=?' }, link: function ($scope, $el) { $scope.limit = 50; diff --git a/src/kibana/plugins/discover/index.html b/src/kibana/plugins/discover/index.html index 4a4ae11a40e311..92c11cb80b49f8 100644 --- a/src/kibana/plugins/discover/index.html +++ b/src/kibana/plugins/discover/index.html @@ -162,7 +162,6 @@

Searching

sorting="state.sort" filtering="filterQuery" refresh="fetch" - timefield="opts.timefield" index-pattern="indexPattern"> diff --git a/test/unit/specs/apps/discover/directives/table.js b/test/unit/specs/apps/discover/directives/table.js index 5efd5d168117f2..271920149a9775 100644 --- a/test/unit/specs/apps/discover/directives/table.js +++ b/test/unit/specs/apps/discover/directives/table.js @@ -88,7 +88,7 @@ define(function (require) { describe('kbnTableHeader', function () { var $elem = angular.element( - '
' + '' ); beforeEach(function () { @@ -208,7 +208,6 @@ define(function (require) { 'filter="filtering"' + 'maxLength=maxLength ' + 'index-pattern="indexPattern"' + - 'timefield="timefield" ' + '>' ); @@ -229,7 +228,6 @@ define(function (require) { sorting: [], filtering: sinon.spy(), maxLength: 50, - timefield: '@timestamp' }); }); afterEach(function () { @@ -267,7 +265,6 @@ define(function (require) { 'sorting="sorting"' + 'filter="filter"' + 'index-pattern="indexPattern"' + - 'timefield="timefield" ' + '>' ); @@ -337,7 +334,6 @@ define(function (require) { 'sorting="sorting"' + 'filtering="filtering"' + 'index-pattern="indexPattern"' + - 'timefield="timefield" ' + '>' ); var $details; From 375a21a1e578cf6f315e1588095c346083aadf9f Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Sun, 28 Dec 2014 17:06:42 -0700 Subject: [PATCH 20/50] Break out panel type functions --- .../components/panel/lib/load_panel.js | 18 +++++ .../dashboard/components/panel/lib/search.js | 14 ++++ .../components/panel/lib/visualization.js | 21 ++++++ .../{partials => components/panel}/panel.html | 6 +- .../dashboard/components/panel/panel.js | 45 ++++++++++++ .../plugins/dashboard/directives/panel.js | 68 ------------------- src/kibana/plugins/dashboard/index.js | 2 +- 7 files changed, 102 insertions(+), 72 deletions(-) create mode 100644 src/kibana/plugins/dashboard/components/panel/lib/load_panel.js create mode 100644 src/kibana/plugins/dashboard/components/panel/lib/search.js create mode 100644 src/kibana/plugins/dashboard/components/panel/lib/visualization.js rename src/kibana/plugins/dashboard/{partials => components/panel}/panel.html (80%) create mode 100644 src/kibana/plugins/dashboard/components/panel/panel.js delete mode 100644 src/kibana/plugins/dashboard/directives/panel.js diff --git a/src/kibana/plugins/dashboard/components/panel/lib/load_panel.js b/src/kibana/plugins/dashboard/components/panel/lib/load_panel.js new file mode 100644 index 00000000000000..2deffc463b25e1 --- /dev/null +++ b/src/kibana/plugins/dashboard/components/panel/lib/load_panel.js @@ -0,0 +1,18 @@ +define(function (require) { + var _ = require('lodash'); + return function loadPanelFunction(Private) { // Inject services here + return function (panel, $scope) { // Function parameters here + var panelTypes = { + visualization: Private(require('plugins/dashboard/components/panel/lib/visualization')), + search: Private(require('plugins/dashboard/components/panel/lib/search')) + }; + + try { + return panelTypes[panel.type](panel, $scope); + } catch (e) { + throw new Error('Loader not found for unknown panel type: ' + panel.type); + } + + }; + }; +}); diff --git a/src/kibana/plugins/dashboard/components/panel/lib/search.js b/src/kibana/plugins/dashboard/components/panel/lib/search.js new file mode 100644 index 00000000000000..5d54d5ce1d59d7 --- /dev/null +++ b/src/kibana/plugins/dashboard/components/panel/lib/search.js @@ -0,0 +1,14 @@ +define(function (require) { + return function searchLoader(savedSearches, Private) { // Inject services here + return function (panel, $scope) { // Function parameters here + return savedSearches.get(panel.id) + .then(function (savedSearch) { + return { + savedObj: savedSearch, + panel: panel, + edit: '#discover' + }; + }); + }; + }; +}); diff --git a/src/kibana/plugins/dashboard/components/panel/lib/visualization.js b/src/kibana/plugins/dashboard/components/panel/lib/visualization.js new file mode 100644 index 00000000000000..38f18ef7af678f --- /dev/null +++ b/src/kibana/plugins/dashboard/components/panel/lib/visualization.js @@ -0,0 +1,21 @@ +define(function (require) { + return function visualizationLoader(savedVisualizations, Private) { // Inject services here + var brushEvent = Private(require('utils/brush_event')); + var filterBarClickHandler = Private(require('components/filter_bar/filter_bar_click_handler')); + + return function (panel, $scope) { // Function parameters here + return savedVisualizations.get(panel.id) + .then(function (savedVis) { + // $scope.state comes via $scope inheritence from the dashboard app. Don't love this. + savedVis.vis.listeners.click = filterBarClickHandler($scope.state); + savedVis.vis.listeners.brush = brushEvent; + + return { + savedObj: savedVis, + panel: panel, + edit: '#visualize/edit' + }; + }); + }; + }; +}); diff --git a/src/kibana/plugins/dashboard/partials/panel.html b/src/kibana/plugins/dashboard/components/panel/panel.html similarity index 80% rename from src/kibana/plugins/dashboard/partials/panel.html rename to src/kibana/plugins/dashboard/components/panel/panel.html index a04b8a51dac0fc..cebebeff743044 100644 --- a/src/kibana/plugins/dashboard/partials/panel.html +++ b/src/kibana/plugins/dashboard/components/panel/panel.html @@ -1,4 +1,4 @@ -
+
{{savedObj.title}}
@@ -13,12 +13,12 @@
- - diff --git a/src/kibana/plugins/dashboard/components/panel/panel.js b/src/kibana/plugins/dashboard/components/panel/panel.js new file mode 100644 index 00000000000000..24c2a7f3b93fe8 --- /dev/null +++ b/src/kibana/plugins/dashboard/components/panel/panel.js @@ -0,0 +1,45 @@ +define(function (require) { + var moment = require('moment'); + require('modules') + .get('app/dashboard') + .directive('dashboardPanel', function (savedVisualizations, savedSearches, Notifier, Private) { + var _ = require('lodash'); + var filterBarClickHandler = Private(require('components/filter_bar/filter_bar_click_handler')); + var loadPanel = Private(require('plugins/dashboard/components/panel/lib/load_panel')); + var notify = new Notifier(); + + require('components/visualize/visualize'); + require('components/doc_table/doc_table'); + + var brushEvent = Private(require('utils/brush_event')); + + return { + restrict: 'E', + template: require('text!plugins/dashboard/components/panel/panel.html'), + requires: '^dashboardGrid', + link: function ($scope, $el) { + // using $scope inheritance, panels are available in AppState + var $state = $scope.state; + + // receives $scope.panel from the dashboard grid directive, seems like should be isolate? + $scope.$watch('id', function (id) { + if (!$scope.panel.id || !$scope.panel.type) return; + + loadPanel($scope.panel, $scope).then(function (panelConfig) { + // These could be done in loadPanel, putting them here to make them more explicit + $scope.savedObj = panelConfig.savedObj; + $scope.edit = panelConfig.edit; + $scope.$on('$destroy', panelConfig.savedObj.destroy); + }).catch(function (e) { + $scope.error = e.message; + }); + + }); + + $scope.remove = function () { + _.pull($state.panels, $scope.panel); + }; + } + }; + }); +}); diff --git a/src/kibana/plugins/dashboard/directives/panel.js b/src/kibana/plugins/dashboard/directives/panel.js deleted file mode 100644 index b079c9722d2bc3..00000000000000 --- a/src/kibana/plugins/dashboard/directives/panel.js +++ /dev/null @@ -1,68 +0,0 @@ -define(function (require) { - var moment = require('moment'); - require('modules') - .get('app/dashboard') - .directive('dashboardPanel', function (savedVisualizations, savedSearches, Notifier, Private) { - var _ = require('lodash'); - var filterBarClickHandler = Private(require('components/filter_bar/filter_bar_click_handler')); - - var notify = new Notifier(); - - require('components/visualize/visualize'); - require('components/doc_table/doc_table'); - - var brushEvent = Private(require('utils/brush_event')); - - return { - restrict: 'E', - template: require('text!plugins/dashboard/partials/panel.html'), - requires: '^dashboardGrid', - link: function ($scope, $el) { - // using $scope inheritance, panels are available in AppState - var $state = $scope.state; - - $scope.view = {}; - - // receives panel object from the dashboard grid directive - $scope.$watch('id', function (id) { - delete $scope.vis; - if (!$scope.panel.id || !$scope.panel.type) return; - - switch ($scope.panel.type) { - case 'visualization': - - $scope.edit = '#visualize/edit'; - savedVisualizations.get($scope.panel.id) - .then(function (savedVis) { - $scope.savedObj = savedVis; - $scope.$on('$destroy', savedVis.destroy); - savedVis.vis.listeners.click = filterBarClickHandler($state); - savedVis.vis.listeners.brush = brushEvent; - }) - .catch(function (e) { - $scope.error = e.message; - }); - break; - case 'search': - - $scope.edit = '#discover'; - savedSearches.get($scope.panel.id) - .then(function (savedSearch) { - $scope.savedObj = savedSearch; - }) - .catch(function (e) { - $scope.error = e.message; - }); - break; - default: - $scope.error = 'Unknown panel type'; - } - }); - - $scope.remove = function () { - _.pull($state.panels, $scope.panel); - }; - } - }; - }); -}); diff --git a/src/kibana/plugins/dashboard/index.js b/src/kibana/plugins/dashboard/index.js index 1a207e92141f67..d6e5e5ef045cc6 100644 --- a/src/kibana/plugins/dashboard/index.js +++ b/src/kibana/plugins/dashboard/index.js @@ -13,7 +13,7 @@ define(function (require) { require('plugins/dashboard/directives/grid'); - require('plugins/dashboard/directives/panel'); + require('plugins/dashboard/components/panel/panel'); require('plugins/dashboard/services/saved_dashboards'); require('css!plugins/dashboard/styles/main.css'); From 2289ddde7b29d9560e3e6865a70f3bdd1dae8e21 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Sun, 28 Dec 2014 19:39:29 -0700 Subject: [PATCH 21/50] Remove dependence on saved object from doc-table directive --- src/kibana/components/doc_table/doc_table.js | 23 ++++++++++--------- .../dashboard/components/panel/panel.html | 7 ++++-- .../dashboard/components/panel/panel.js | 4 +++- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/kibana/components/doc_table/doc_table.js b/src/kibana/components/doc_table/doc_table.js index 410a4971962c50..495ec029b9f7c1 100644 --- a/src/kibana/components/doc_table/doc_table.js +++ b/src/kibana/components/doc_table/doc_table.js @@ -16,14 +16,18 @@ define(function (require) { restrict: 'E', template: html, scope: { - savedSearch: '=', + searchSource: '=', + sorting: '=', + columns: '=', filter: '=?', }, link: function ($scope) { var notify = new Notifier(); - $scope.columns = []; - $scope.persist = $scope.persist || {}; + $scope.persist = { + sorting: $scope.sorting, + columns: $scope.columns + }; var prereq = (function () { var fns = []; @@ -52,16 +56,13 @@ define(function (require) { if ($scope.searchSource) $scope.searchSource.destroy(); }); - $scope.$watch('savedSearch', prereq(function (savedSearch) { - if (!(savedSearch && savedSearch.searchSource)) return; + $scope.$watch('searchSource', prereq(function (searchSource) { + if (!$scope.searchSource) return; - $scope.persist.sorting = savedSearch.sort; - $scope.persist.columns = savedSearch.columns; - $scope.searchSource = savedSearch.searchSource; - $scope.indexPattern = savedSearch.searchSource.get('index'); + $scope.indexPattern = $scope.searchSource.get('index'); $scope.searchSource.size(config.get('discover:sampleSize')); - $scope.searchSource.sort(getSort(savedSearch.sort, $scope.indexPattern)); + $scope.searchSource.sort(getSort($scope.sorting, $scope.indexPattern)); // Set the watcher after initialization $scope.$watch('persist.sorting', function (sorting) { @@ -76,7 +77,7 @@ define(function (require) { $scope.limit = 50; // Abort if something changed - if ($scope.searchSource !== savedSearch.searchSource) return; + if ($scope.searchSource !== $scope.searchSource) return; $scope.hits = resp.hits.hits; diff --git a/src/kibana/plugins/dashboard/components/panel/panel.html b/src/kibana/plugins/dashboard/components/panel/panel.html index cebebeff743044..31ae6b816bcf4c 100644 --- a/src/kibana/plugins/dashboard/components/panel/panel.html +++ b/src/kibana/plugins/dashboard/components/panel/panel.html @@ -16,10 +16,13 @@ + class="panel-content"> +
\ No newline at end of file diff --git a/src/kibana/plugins/dashboard/components/panel/panel.js b/src/kibana/plugins/dashboard/components/panel/panel.js index 24c2a7f3b93fe8..7f308049952bcd 100644 --- a/src/kibana/plugins/dashboard/components/panel/panel.js +++ b/src/kibana/plugins/dashboard/components/panel/panel.js @@ -1,8 +1,9 @@ define(function (require) { var moment = require('moment'); + var $ = require('jquery'); require('modules') .get('app/dashboard') - .directive('dashboardPanel', function (savedVisualizations, savedSearches, Notifier, Private) { + .directive('dashboardPanel', function (savedVisualizations, savedSearches, Notifier, Private, $compile) { var _ = require('lodash'); var filterBarClickHandler = Private(require('components/filter_bar/filter_bar_click_handler')); var loadPanel = Private(require('plugins/dashboard/components/panel/lib/load_panel')); @@ -20,6 +21,7 @@ define(function (require) { link: function ($scope, $el) { // using $scope inheritance, panels are available in AppState var $state = $scope.state; + var panelContentElem; // receives $scope.panel from the dashboard grid directive, seems like should be isolate? $scope.$watch('id', function (id) { From d8b857ffb4d34afd8f07cf8e8a04f97c7828f16c Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Sun, 28 Dec 2014 21:43:51 -0700 Subject: [PATCH 22/50] remove unused variable --- src/kibana/components/doc_table/doc_table.js | 3 ++- src/kibana/plugins/dashboard/components/panel/panel.js | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/kibana/components/doc_table/doc_table.js b/src/kibana/components/doc_table/doc_table.js index 495ec029b9f7c1..7aedf184af901c 100644 --- a/src/kibana/components/doc_table/doc_table.js +++ b/src/kibana/components/doc_table/doc_table.js @@ -3,6 +3,7 @@ define(function (require) { var html = require('text!components/doc_table/doc_table.html'); var getSort = require('components/doc_table/lib/get_sort'); + require('css!components/doc_table/doc_table.css'); require('directives/truncated'); require('components/doc_table/components/table_header'); @@ -66,7 +67,7 @@ define(function (require) { // Set the watcher after initialization $scope.$watch('persist.sorting', function (sorting) { - if (!$scope.indexPattern || !$scope.searchSource || !sorting) return; + if (!sorting) return; $scope.searchSource.sort(getSort(sorting, $scope.indexPattern)); $scope.searchSource.fetch(); }); diff --git a/src/kibana/plugins/dashboard/components/panel/panel.js b/src/kibana/plugins/dashboard/components/panel/panel.js index 7f308049952bcd..90e3f1b44bd9cf 100644 --- a/src/kibana/plugins/dashboard/components/panel/panel.js +++ b/src/kibana/plugins/dashboard/components/panel/panel.js @@ -21,7 +21,6 @@ define(function (require) { link: function ($scope, $el) { // using $scope inheritance, panels are available in AppState var $state = $scope.state; - var panelContentElem; // receives $scope.panel from the dashboard grid directive, seems like should be isolate? $scope.$watch('id', function (id) { From 074a98324f8ae0ca12d775c89f660a1030d8ddbb Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Sun, 28 Dec 2014 21:44:37 -0700 Subject: [PATCH 23/50] remove unused function --- src/kibana/components/doc_table/doc_table.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/kibana/components/doc_table/doc_table.js b/src/kibana/components/doc_table/doc_table.js index 7aedf184af901c..164268f3db056f 100644 --- a/src/kibana/components/doc_table/doc_table.js +++ b/src/kibana/components/doc_table/doc_table.js @@ -11,8 +11,6 @@ define(function (require) { require('modules').get('kibana') .directive('docTable', function (config, Private, Notifier) { - var formats = Private(require('components/index_patterns/_field_formats')); - return { restrict: 'E', template: html, From b09a4f4fae17566dc8c13b6f89f2650c90691f64 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Mon, 29 Dec 2014 09:00:41 -0700 Subject: [PATCH 24/50] Add a few simple tests --- src/kibana/components/doc_table/doc_table.js | 4 +- test/unit/fixtures/search_response.js | 18 ++++ test/unit/fixtures/stubbed_search_source.js | 36 ++++++++ .../specs/components/doc_table/doc_table.js | 85 +++++++++++++++++++ 4 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 test/unit/fixtures/search_response.js create mode 100644 test/unit/fixtures/stubbed_search_source.js create mode 100644 test/unit/specs/components/doc_table/doc_table.js diff --git a/src/kibana/components/doc_table/doc_table.js b/src/kibana/components/doc_table/doc_table.js index 164268f3db056f..7be8f211a8a2a7 100644 --- a/src/kibana/components/doc_table/doc_table.js +++ b/src/kibana/components/doc_table/doc_table.js @@ -10,7 +10,7 @@ define(function (require) { require('components/doc_table/components/table_row'); require('modules').get('kibana') - .directive('docTable', function (config, Private, Notifier) { + .directive('docTable', function (config, Notifier) { return { restrict: 'E', template: html, @@ -47,7 +47,7 @@ define(function (require) { }; }()); - $scope.addRows = function (foo) { + $scope.addRows = function () { $scope.limit += 50; }; diff --git a/test/unit/fixtures/search_response.js b/test/unit/fixtures/search_response.js new file mode 100644 index 00000000000000..3c0b495da06a4e --- /dev/null +++ b/test/unit/fixtures/search_response.js @@ -0,0 +1,18 @@ +define(function (require) { + var hits = require('fixtures/real_hits'); + + return { + took: 73, + timed_out: false, + _shards: { + total: 144, + successful: 144, + failed: 0 + }, + hits: { + total : 49487, + max_score : 1.0, + hits: hits + } + }; +}); \ No newline at end of file diff --git a/test/unit/fixtures/stubbed_search_source.js b/test/unit/fixtures/stubbed_search_source.js new file mode 100644 index 00000000000000..d7067d868534b1 --- /dev/null +++ b/test/unit/fixtures/stubbed_search_source.js @@ -0,0 +1,36 @@ +define(function (require) { + var sinon = require('test_utils/auto_release_sinon'); + var searchResponse = require('fixtures/search_response'); + + return function stubSearchSource(Private, $q) { + var deferedResult = $q.defer(); + + return { + sort: sinon.spy(), + size: sinon.spy(), + fetch: sinon.spy(), + destroy: sinon.spy(), + get: function (param) { + switch (param) { + case 'index': + return Private(require('fixtures/stubbed_logstash_index_pattern')); + default: + throw new Error('Param "' + param + '" is not implemented in the stubbed search source'); + } + }, + crankResults: function () { + deferedResult.resolve(searchResponse); + deferedResult = $q.defer(); + }, + onResults: function () { + // Up to the test to resolve this manually + // For example: + // someHandler.resolve(require('fixtures/search_response')) + return deferedResult.promise; + }, + onError: function () { return $q.defer().promise; }, + + }; + + }; +}); diff --git a/test/unit/specs/components/doc_table/doc_table.js b/test/unit/specs/components/doc_table/doc_table.js new file mode 100644 index 00000000000000..a2dc4c47655421 --- /dev/null +++ b/test/unit/specs/components/doc_table/doc_table.js @@ -0,0 +1,85 @@ +define(function (require) { + var angular = require('angular'); + var $ = require('jquery'); + var _ = require('lodash'); + var sinon = require('test_utils/auto_release_sinon'); + var searchResponse = require('fixtures/search_response'); + + // Load the kibana app dependencies. + require('services/private'); + require('components/doc_table/doc_table'); + + + var $parentScope, $scope, $timeout, searchSource; + + var init = function ($elem, props) { + inject(function ($rootScope, $compile, _$timeout_) { + $timeout = _$timeout_; + $parentScope = $rootScope; + _.assign($parentScope, props); + + $compile($elem)($parentScope); + + // I think the prereq requires this? + $timeout(function () { + $elem.scope().$digest(); + }, 0); + + $scope = $elem.isolateScope(); + + }); + }; + + var destroy = function () { + $scope.$destroy(); + $parentScope.$destroy(); + }; + + describe('docTable', function () { + var $elem; + + beforeEach(module('kibana')); + beforeEach(function () { + $elem = angular.element(''); + inject(function (Private) { + searchSource = Private(require('fixtures/stubbed_search_source')); + }); + init($elem, { + searchSource: searchSource, + columns: [], + sorting: ['@timestamp', 'desc'] + }); + $scope.$digest(); + + }); + + afterEach(function () { + destroy(); + }); + + it('should compile', function () { + expect($elem.text()).to.not.be.empty(); + }); + + it('should have an addRows function', function () { + expect($scope.addRows).to.be.a(Function); + }); + + it('should set the indexPattern to that of the searchSource', function () { + expect($scope.indexPattern).to.be(searchSource.get('index')); + }); + + it('should set size and sort on the searchSource', function () { + expect($scope.searchSource.sort.called).to.be(true); + expect($scope.searchSource.size.called).to.be(true); + }); + + it('should set a row limit when results are received', function () { + expect($scope.limit).to.be(undefined); + searchSource.crankResults(); + $scope.$digest(); + expect($scope.limit).to.be(50); + }); + + }); +}); \ No newline at end of file From 601aec3b993d67736822ee81fad1e1b577a46df8 Mon Sep 17 00:00:00 2001 From: Juan Thomassie Date: Mon, 29 Dec 2014 13:05:19 -0600 Subject: [PATCH 25/50] add css filter to desaturate mapquest tiles --- src/kibana/components/vislib/styles/_tilemap.less | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/kibana/components/vislib/styles/_tilemap.less b/src/kibana/components/vislib/styles/_tilemap.less index 5a4bcfa0229380..025bf9c4c4b757 100644 --- a/src/kibana/components/vislib/styles/_tilemap.less +++ b/src/kibana/components/vislib/styles/_tilemap.less @@ -109,3 +109,13 @@ margin: 0 !important; padding: 0 !important; } + +/* filter to desaturate mapquest tiles */ + +img.leaflet-tile-loaded { + filter: brightness(1.06) grayscale(1.0); + -webkit-filter: brightness(1.06) grayscale(1.0); + -moz-filter: brightness(1.06) grayscale(1.0); + -ms-filter: brightness(1.06) grayscale(1.0); + -o-filter: brightness(1.06) grayscale(1.0); +} From b3e1a9f7c042be098981137684f7a987c1e3d3c1 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Mon, 29 Dec 2014 14:01:00 -0700 Subject: [PATCH 26/50] getSort tests, more docTable tests --- .../components/doc_table/lib/get_sort.js | 2 +- .../specs/components/doc_table/doc_table.js | 31 ++++++++++++---- .../components/doc_table/lib/get_sort.js | 35 +++++++++++++++++++ 3 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 test/unit/specs/components/doc_table/lib/get_sort.js diff --git a/src/kibana/components/doc_table/lib/get_sort.js b/src/kibana/components/doc_table/lib/get_sort.js index db8d7ebc4799fc..8a8331ca8200cb 100644 --- a/src/kibana/components/doc_table/lib/get_sort.js +++ b/src/kibana/components/doc_table/lib/get_sort.js @@ -9,7 +9,7 @@ define(function (require) { */ return function (sort, indexPattern) { var sortObj = {}; - if (_.isArray(sort)) { + if (_.isArray(sort) && sort.length === 2) { // At some point we need to refact the sorting logic, this array sucks. sortObj[sort[0]] = sort[1]; } else if (indexPattern.timeFieldName) { diff --git a/test/unit/specs/components/doc_table/doc_table.js b/test/unit/specs/components/doc_table/doc_table.js index a2dc4c47655421..0283bc35b9bcc8 100644 --- a/test/unit/specs/components/doc_table/doc_table.js +++ b/test/unit/specs/components/doc_table/doc_table.js @@ -61,10 +61,6 @@ define(function (require) { expect($elem.text()).to.not.be.empty(); }); - it('should have an addRows function', function () { - expect($scope.addRows).to.be.a(Function); - }); - it('should set the indexPattern to that of the searchSource', function () { expect($scope.indexPattern).to.be(searchSource.get('index')); }); @@ -74,11 +70,34 @@ define(function (require) { expect($scope.searchSource.size.called).to.be(true); }); - it('should set a row limit when results are received', function () { - expect($scope.limit).to.be(undefined); + it('should have an addRows function that increases the row cound', function () { + expect($scope.addRows).to.be.a(Function); searchSource.crankResults(); $scope.$digest(); expect($scope.limit).to.be(50); + $scope.addRows(); + expect($scope.limit).to.be(100); + }); + + it('should reset the row limit when results are received', function () { + $scope.limit = 100; + expect($scope.limit).to.be(100); + searchSource.crankResults(); + $scope.$digest(); + expect($scope.limit).to.be(50); + }); + + it('should put the hits array on scope', function () { + expect($scope.hits).to.be(undefined); + searchSource.crankResults(); + $scope.$digest(); + expect($scope.hits).to.be.an(Array); + }); + + it('should destroy the searchSource when the scope is destroyed', function () { + expect(searchSource.destroy.called).to.be(false); + $scope.$destroy(); + expect(searchSource.destroy.called).to.be(true); }); }); diff --git a/test/unit/specs/components/doc_table/lib/get_sort.js b/test/unit/specs/components/doc_table/lib/get_sort.js new file mode 100644 index 00000000000000..8452485e1c7f50 --- /dev/null +++ b/test/unit/specs/components/doc_table/lib/get_sort.js @@ -0,0 +1,35 @@ +define(function (require) { + var getSort = require('components/doc_table/lib/get_sort'); + var indexPattern = + describe('docTable', function () { + describe('getSort function', function () { + + var timePattern = { + timeFieldName: 'time' + }; + var noTimePattern = {}; + + it('should be a function', function () { + expect(getSort).to.be.a(Function); + }); + + it('should return an object if passed a 2 item array', function () { + expect(getSort(['foo', 'bar'], timePattern)).to.eql({foo: 'bar'}); + expect(getSort(['foo', 'bar'], noTimePattern)).to.eql({foo: 'bar'}); + }); + + it('should sort in reverse chrono order otherwise on time based patterns', function () { + expect(getSort([], timePattern)).to.eql({time: 'desc'}); + expect(getSort(['foo'], timePattern)).to.eql({time: 'desc'}); + expect(getSort({foo: 'bar'}, timePattern)).to.eql({time: 'desc'}); + }); + + it('should sort by score on non-time patterns', function () { + expect(getSort([], noTimePattern)).to.eql({_score: 'desc'}); + expect(getSort(['foo'], noTimePattern)).to.eql({_score: 'desc'}); + expect(getSort({foo: 'bar'}, noTimePattern)).to.eql({_score: 'desc'}); + }); + + }); + }); +}); From 6366e23e0b20fc625cc8eadea6a785617f91565f Mon Sep 17 00:00:00 2001 From: lukasolson Date: Mon, 29 Dec 2014 14:04:23 -0700 Subject: [PATCH 27/50] Rearrange steps for creating a new visualization (#2182) --- .../plugins/visualize/wizard/step_1.html | 59 +++++-------------- .../plugins/visualize/wizard/step_2.html | 40 ++++++++++--- src/kibana/plugins/visualize/wizard/wizard.js | 52 ++++++++-------- 3 files changed, 68 insertions(+), 83 deletions(-) diff --git a/src/kibana/plugins/visualize/wizard/step_1.html b/src/kibana/plugins/visualize/wizard/step_1.html index 25b0077f22b05b..06d3f20d93619e 100644 --- a/src/kibana/plugins/visualize/wizard/step_1.html +++ b/src/kibana/plugins/visualize/wizard/step_1.html @@ -1,53 +1,22 @@

- Create a visualization + Create a new visualization
Step 1

-
    -
  • - From a new search -
  • -
  • - - Select an index pattern -
    - -
    -
  • -
  • - From a saved search -
  • -
  • - - - -
  • - -
  • - From an existing visualization -
  • -
  • - - - -
  • + +

    Or, open a saved visualization

    - + + \ No newline at end of file diff --git a/src/kibana/plugins/visualize/wizard/step_2.html b/src/kibana/plugins/visualize/wizard/step_2.html index 8bc960d2834704..48703631342078 100644 --- a/src/kibana/plugins/visualize/wizard/step_2.html +++ b/src/kibana/plugins/visualize/wizard/step_2.html @@ -1,15 +1,37 @@

    - I want to create a
    + Select a search source Step 2

    +
      +
    • + From a new search +
    • +
    • + + Select an index pattern +
      + +
      +
    • - \ No newline at end of file diff --git a/src/kibana/plugins/visualize/wizard/wizard.js b/src/kibana/plugins/visualize/wizard/wizard.js index 0ffb0ae98b792c..aeeb62001b7465 100644 --- a/src/kibana/plugins/visualize/wizard/wizard.js +++ b/src/kibana/plugins/visualize/wizard/wizard.js @@ -16,7 +16,23 @@ define(function (require) { /** Wizard Step 1 /********/ routes.when('/visualize/step/1', { - template: templateStep(1, require('text!plugins/visualize/wizard/step_1.html')), + template: templateStep(1, require('text!plugins/visualize/wizard/step_1.html')) + }); + + module.controller('VisualizeWizardStep1', function ($scope, $route, $location, timefilter, Private) { + timefilter.enabled = false; + + $scope.visTypes = Private(require('registry/vis_types')); + $scope.visTypeUrl = function (visType) { + return '#/visualize/step/2?type=' + encodeURIComponent(visType.name); + }; + }); + + /******** + /** Wizard Step 2 + /********/ + routes.when('/visualize/step/2', { + template: templateStep(2, require('text!plugins/visualize/wizard/step_2.html')), resolve: { indexPatternIds: function (courier) { return courier.indexPatterns.getIds(); @@ -24,9 +40,11 @@ define(function (require) { } }); - module.controller('VisualizeWizardStep1', function ($route, $scope, $location, timefilter, kbnUrl) { + module.controller('VisualizeWizardStep2', function ($route, $scope, $location, timefilter, kbnUrl) { + var type = $route.current.params.type; + $scope.step2WithSearchUrl = function (hit) { - return kbnUrl.eval('#/visualize/step/2?savedSearchId={{id}}', {id: hit.id}); + return kbnUrl.eval('#/visualize/create?&type={{type}}&savedSearchId={{id}}', {type: type, id: hit.id}); }; timefilter.enabled = false; @@ -36,7 +54,7 @@ define(function (require) { list: $route.current.locals.indexPatternIds }; - $scope.$watch('stepOneMode', function (mode) { + $scope.$watch('stepTwoMode', function (mode) { if (mode === 'new') { if ($scope.indexPattern.list && $scope.indexPattern.list.length === 1) { $scope.indexPattern.selection = $scope.indexPattern.list[0]; @@ -46,31 +64,7 @@ define(function (require) { $scope.$watch('indexPattern.selection', function (pattern) { if (!pattern) return; - kbnUrl.change('/visualize/step/2?indexPattern={{pattern}}', {pattern: pattern}); + kbnUrl.change('/visualize/create?type={{type}}&indexPattern={{pattern}}', {type: type, pattern: pattern}); }); }); - - /******** - /** Wizard Step 2 - /********/ - routes.when('/visualize/step/2', { - template: templateStep(2, require('text!plugins/visualize/wizard/step_2.html')) - }); - - module.controller('VisualizeWizardStep2', function ($scope, $route, $location, timefilter, Private) { - var existing = _.pick($route.current.params, 'indexPattern', 'savedSearchId'); - - timefilter.enabled = false; - - $scope.visTypes = Private(require('registry/vis_types')); - $scope.visTypeUrl = function (visType) { - var query = _.defaults({ - type: visType.name - }, existing); - - return '#/visualize/create?' + _.map(query, function (val, key) { - return encodeURIComponent(key) + '=' + encodeURIComponent(val); - }).join('&'); - }; - }); }); \ No newline at end of file From dfd30eae21850d4af374ef1eb7546f8df9036449 Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Mon, 29 Dec 2014 16:04:48 -0500 Subject: [PATCH 28/50] Closes #2253. Adds a better message when only 1 data point is supplied to an area chart --- src/kibana/components/errors.js | 6 ++---- src/kibana/components/vislib/visualizations/area_chart.js | 4 +++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/kibana/components/errors.js b/src/kibana/components/errors.js index 877b30efc8137c..9aa208ade46784 100644 --- a/src/kibana/components/errors.js +++ b/src/kibana/components/errors.js @@ -207,10 +207,8 @@ define(function (require) { * than the required number of data points * @param {String} message - the message to provide with the error */ - errors.NotEnoughData = function NotEnoughData() { - KbnError.call(this, - 'There are not enough data points to render this chart', - errors.NotEnoughData); + errors.NotEnoughData = function NotEnoughData(message) { + KbnError.call(this, message, errors.NotEnoughData); }; inherits(errors.NotEnoughData, KbnError); diff --git a/src/kibana/components/vislib/visualizations/area_chart.js b/src/kibana/components/vislib/visualizations/area_chart.js index 0aadbbf6434f22..0d3a2b8268f274 100644 --- a/src/kibana/components/vislib/visualizations/area_chart.js +++ b/src/kibana/components/vislib/visualizations/area_chart.js @@ -265,13 +265,15 @@ define(function (require) { AreaChart.prototype.checkIfEnoughData = function () { var series = this.chartData.series; + var message = 'Area charts require more than one data point. Try adding ' + + 'an X-Axis Aggregation'; var notEnoughData = series.some(function (obj) { return obj.values.length < 2; }); if (notEnoughData) { - throw new errors.NotEnoughData(); + throw new errors.NotEnoughData(message); } }; From 4e86837a5781045591808f5754ebae8c6ae9b642 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Mon, 29 Dec 2014 14:37:33 -0700 Subject: [PATCH 29/50] Use decorateQuery instead --- src/kibana/components/agg_types/buckets/filters.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/kibana/components/agg_types/buckets/filters.js b/src/kibana/components/agg_types/buckets/filters.js index 2e7f697a8fbd1d..59bb98d58492f9 100644 --- a/src/kibana/components/agg_types/buckets/filters.js +++ b/src/kibana/components/agg_types/buckets/filters.js @@ -3,6 +3,7 @@ define(function (require) { var _ = require('lodash'); var AggType = Private(require('components/agg_types/_agg_type')); var createFilter = Private(require('components/agg_types/buckets/create_filter/filters')); + var decorateQuery = Private(require('components/courier/data_source/_decorate_query')); var notif = new Notifier({ location: 'Filters Agg' }); return new AggType({ @@ -25,9 +26,7 @@ define(function (require) { var query = input.query; if (!query) return notif.log('malformed filter agg params, missing "query" on input'); - if (_.deepHas(query, 'query_string.query')) { - _.merge(query.query_string, config.get('query:queryString:options')); - } + decorateQuery(query); var label = _.deepGet(query, 'query_string.query') || JSON.stringify(query); filters[label] = input; From 52b3c2b74a338020cf2892912958b58b7996bf9a Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Mon, 29 Dec 2014 16:25:24 -0700 Subject: [PATCH 30/50] Remove unused config inhjection --- src/kibana/components/agg_types/buckets/filters.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kibana/components/agg_types/buckets/filters.js b/src/kibana/components/agg_types/buckets/filters.js index 59bb98d58492f9..352aa1fa7e10f9 100644 --- a/src/kibana/components/agg_types/buckets/filters.js +++ b/src/kibana/components/agg_types/buckets/filters.js @@ -1,5 +1,5 @@ define(function (require) { - return function FiltersAggDefinition(Private, Notifier, config) { + return function FiltersAggDefinition(Private, Notifier) { var _ = require('lodash'); var AggType = Private(require('components/agg_types/_agg_type')); var createFilter = Private(require('components/agg_types/buckets/create_filter/filters')); From b282449788b7a2d556bb180911f88c23fa3e7a25 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Mon, 29 Dec 2014 17:13:57 -0700 Subject: [PATCH 31/50] Make infinite scroll optional, pagination by default --- .../components/doc_table/doc_table.html | 23 +++++++++++++++++-- src/kibana/components/doc_table/doc_table.js | 1 + .../components/doc_table/doc_table.less | 1 + src/kibana/plugins/dashboard/styles/main.less | 1 + 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/kibana/components/doc_table/doc_table.html b/src/kibana/components/doc_table/doc_table.html index 935dd14f92e5a8..4899002684fe80 100644 --- a/src/kibana/components/doc_table/doc_table.html +++ b/src/kibana/components/doc_table/doc_table.html @@ -1,4 +1,23 @@ -
- Time + + Time diff --git a/src/kibana/components/doc_table/components/table_header.js b/src/kibana/components/doc_table/components/table_header.js index ba19fe5f49b088..b5f88dbe6804b2 100644 --- a/src/kibana/components/doc_table/components/table_header.js +++ b/src/kibana/components/doc_table/components/table_header.js @@ -15,7 +15,6 @@ define(function (require) { }, template: headerHtml, controller: function ($scope) { - $scope.timefield = $scope.indexPattern.timeFieldName; var sortableField = function (field) { return $scope.indexPattern.fields.byName[field].sortable; diff --git a/src/kibana/components/doc_table/components/table_row.js b/src/kibana/components/doc_table/components/table_row.js index 7a16a33f8358cb..74357134988f44 100644 --- a/src/kibana/components/doc_table/components/table_row.js +++ b/src/kibana/components/doc_table/components/table_row.js @@ -43,10 +43,6 @@ define(function (require) { var init = function () { _formatRow($scope.row); createSummaryRow($scope.row, $scope.row._id); - - // Check for timefield on indexPattern - $scope.timefield = $scope.indexPattern.timeFieldName; - }; // when we compile the details, we use this $scope @@ -88,7 +84,7 @@ define(function (require) { createSummaryRow($scope.row, $scope.row._id); }); - $scope.$watchMulti(['timefield', 'row.highlight'], function () { + $scope.$watchMulti(['indexPattern.timeFieldName', 'row.highlight'], function () { createSummaryRow($scope.row, $scope.row._id); }); @@ -99,10 +95,10 @@ define(function (require) { openRowHtml ]; - if ($scope.timefield) { + if ($scope.indexPattern.timeFieldName) { newHtmls.push(cellTemplate({ timefield: true, - formatted: _displayField(row, $scope.timefield) + formatted: _displayField(row, $scope.indexPattern.timeFieldName) })); } diff --git a/test/unit/fixtures/stubbed_logstash_index_pattern.js b/test/unit/fixtures/stubbed_logstash_index_pattern.js index 834a0a312c5ed7..2121b16d8c2df0 100644 --- a/test/unit/fixtures/stubbed_logstash_index_pattern.js +++ b/test/unit/fixtures/stubbed_logstash_index_pattern.js @@ -18,7 +18,7 @@ define(function (require) { return field; }); - var indexPattern = new StubIndexPattern('logstash-*', 'time', fields); + var indexPattern = new StubIndexPattern('logstash-*', '@timestamp', fields); indexPattern.getComputedFields = _.bind(getComputedFields, indexPattern); indexPattern.flattenSearchResponse = _.bind(flattenSearchResponse, indexPattern); diff --git a/test/unit/specs/apps/discover/directives/table.js b/test/unit/specs/apps/discover/directives/table.js index 8a99966946ce65..fcff6e967ae25c 100644 --- a/test/unit/specs/apps/discover/directives/table.js +++ b/test/unit/specs/apps/discover/directives/table.js @@ -378,7 +378,7 @@ define(function (require) { var $before; beforeEach(module('kibana', 'apps/discover')); - beforeEach(inject(function ($rootScope, $compile) { + beforeEach(inject(function ($rootScope, $compile, Private) { $root = $rootScope; $root.row = getFakeRow(0, mapping); $root.columns = ['_source']; @@ -386,7 +386,7 @@ define(function (require) { $root.filtering = sinon.spy(); $root.maxLength = 50; $root.mapping = mapping; - $root.timefield = '@timestamp'; + $root.indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern')); $row = $('
+ +
+ + + + + +
+ + +
- \ No newline at end of file + \ No newline at end of file diff --git a/src/kibana/components/doc_table/doc_table.js b/src/kibana/components/doc_table/doc_table.js index 7be8f211a8a2a7..bfe745a5053236 100644 --- a/src/kibana/components/doc_table/doc_table.js +++ b/src/kibana/components/doc_table/doc_table.js @@ -18,6 +18,7 @@ define(function (require) { searchSource: '=', sorting: '=', columns: '=', + infiniteScroll: '=?', filter: '=?', }, link: function ($scope) { diff --git a/src/kibana/components/doc_table/doc_table.less b/src/kibana/components/doc_table/doc_table.less index fc50a72534da34..dc151cbbd97a02 100644 --- a/src/kibana/components/doc_table/doc_table.less +++ b/src/kibana/components/doc_table/doc_table.less @@ -5,4 +5,5 @@ doc-table { overflow: auto; margin: 5px; + .flex(1, 1, 100%); } \ No newline at end of file diff --git a/src/kibana/plugins/dashboard/styles/main.less b/src/kibana/plugins/dashboard/styles/main.less index 294b87ef6194a0..f846d8d93685b8 100644 --- a/src/kibana/plugins/dashboard/styles/main.less +++ b/src/kibana/plugins/dashboard/styles/main.less @@ -102,6 +102,7 @@ dashboard-grid { } .panel-content { + display: flex; .flex(1, 1, 100%); height: auto; } From 071d3ee9598c08129d8be3799101e25a070a56a9 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Tue, 30 Dec 2014 10:16:00 -0700 Subject: [PATCH 32/50] Add pagination controls, and option to put pagination controls at the top --- src/kibana/components/doc_table/doc_table.html | 2 +- src/kibana/directives/paginate.js | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/kibana/components/doc_table/doc_table.html b/src/kibana/components/doc_table/doc_table.html index 4899002684fe80..71e199d026ab48 100644 --- a/src/kibana/components/doc_table/doc_table.html +++ b/src/kibana/components/doc_table/doc_table.html @@ -1,4 +1,4 @@ - + ')($scope)); + pre: function ($scope, $el, attrs) { + if (_.isUndefined(attrs.bottomControls)) attrs.bottomControls = true; + if ($el.find('paginate-controls.paginate-bottom').size() === 0 && attrs.bottomControls) { + $el.append($compile('')($scope)); } }, post: function ($scope, $el, attrs) { + if (_.isUndefined(attrs.topControls)) attrs.topControls = false; + if ($el.find('paginate-controls.paginate-top').size() === 0 && attrs.topControls) { + $el.prepend($compile('')($scope)); + } + var paginate = $scope.paginate; // add some getters to the controller powered by attributes From f15fa136ce6a6989042c3aa690323543103d3b0f Mon Sep 17 00:00:00 2001 From: Juan Thomassie Date: Tue, 30 Dec 2014 12:19:03 -0600 Subject: [PATCH 33/50] added checkbox to tilemap options, defaults to desat, revised css filters for better contrast --- .../components/vislib/styles/_tilemap.less | 20 +++++++++++++------ .../vislib/visualizations/tile_map.js | 18 +++++++++++++---- .../vis_types/vislib/editors/tile_map.html | 6 ++++++ .../plugins/vis_types/vislib/tile_map.js | 3 ++- 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/kibana/components/vislib/styles/_tilemap.less b/src/kibana/components/vislib/styles/_tilemap.less index 025bf9c4c4b757..873fe6d08a4fd1 100644 --- a/src/kibana/components/vislib/styles/_tilemap.less +++ b/src/kibana/components/vislib/styles/_tilemap.less @@ -112,10 +112,18 @@ /* filter to desaturate mapquest tiles */ -img.leaflet-tile-loaded { - filter: brightness(1.06) grayscale(1.0); - -webkit-filter: brightness(1.06) grayscale(1.0); - -moz-filter: brightness(1.06) grayscale(1.0); - -ms-filter: brightness(1.06) grayscale(1.0); - -o-filter: brightness(1.06) grayscale(1.0); +img.leaflet-tile { + filter: brightness(1.03) grayscale(0.83) contrast(1.07); + -webkit-filter: brightness(1.03) grayscale(0.83) contrast(1.07); + -moz-filter: brightness(1.03) grayscale(0.83) contrast(1.07); + -ms-filter: brightness(1.03) grayscale(0.83) contrast(1.07); + -o-filter: brightness(1.03) grayscale(0.83) contrast(1.07); } + +img.leaflet-tile.filters-off { + filter: none; + -webkit-filter: none; + -moz-filter: none; + -ms-filter: none; + -o-filter: none; +} \ No newline at end of file diff --git a/src/kibana/components/vislib/visualizations/tile_map.js b/src/kibana/components/vislib/visualizations/tile_map.js index 477a6e7e7b79e7..1f16074a7fdb09 100644 --- a/src/kibana/components/vislib/visualizations/tile_map.js +++ b/src/kibana/components/vislib/visualizations/tile_map.js @@ -70,7 +70,7 @@ define(function (require) { mapCenter = self._attr.lastCenter; } - var mapLayer = L.tileLayer('http://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpeg', { + var tileLayer = L.tileLayer('http://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpeg', { attribution: 'Tiles by MapQuest — ' + 'Map data © OpenStreetMap contributors, ' + 'CC-BY-SA', @@ -80,7 +80,7 @@ define(function (require) { var mapOptions = { minZoom: 2, maxZoom: 16, - layers: mapLayer, + layers: tileLayer, center: mapCenter, zoom: mapZoom, continuousWorld: true, @@ -93,6 +93,10 @@ define(function (require) { var map = L.map(div[0], mapOptions); self.maps.push(map); + tileLayer.on('tileload', function (e) { + saturateTiles(); + }); + map.on('zoomend dragend', function () { mapZoom = self._attr.lastZoom = map.getZoom(); mapCenter = self._attr.lastCenter = map.getCenter(); @@ -127,10 +131,16 @@ define(function (require) { map.addControl(new FitControl()); } - function fitBounds() { map.fitBounds(featureLayer.getBounds()); } + + function saturateTiles() { + if (!self._attr.isDesaturated) { + $('img.leaflet-tile-loaded').addClass('filters-off'); + } + } + }); }; }; @@ -540,4 +550,4 @@ define(function (require) { return TileMap; }; -}); +}); \ No newline at end of file diff --git a/src/kibana/plugins/vis_types/vislib/editors/tile_map.html b/src/kibana/plugins/vis_types/vislib/editors/tile_map.html index 78aa3489e1857e..f70356a766df03 100644 --- a/src/kibana/plugins/vis_types/vislib/editors/tile_map.html +++ b/src/kibana/plugins/vis_types/vislib/editors/tile_map.html @@ -4,4 +4,10 @@ {{mapType}} + +
+
\ No newline at end of file diff --git a/src/kibana/plugins/vis_types/vislib/tile_map.js b/src/kibana/plugins/vis_types/vislib/tile_map.js index da4050b11dd11b..343013b11211e2 100644 --- a/src/kibana/plugins/vis_types/vislib/tile_map.js +++ b/src/kibana/plugins/vis_types/vislib/tile_map.js @@ -10,7 +10,8 @@ define(function (require) { icon: 'fa-map-marker', params: { defaults: { - mapType: 'Shaded Circle Markers' + mapType: 'Shaded Circle Markers', + isDesaturated: true }, mapTypes: ['Shaded Circle Markers', 'Scaled Circle Markers'], editor: require('text!plugins/vis_types/vislib/editors/tile_map.html') From 7f11e82bd5acddea9fe7b678ccc59b889486f521 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Tue, 30 Dec 2014 12:22:06 -0700 Subject: [PATCH 34/50] Check that the sort actually changed --- src/kibana/components/doc_table/doc_table.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/kibana/components/doc_table/doc_table.js b/src/kibana/components/doc_table/doc_table.js index bfe745a5053236..4a24c022462510 100644 --- a/src/kibana/components/doc_table/doc_table.js +++ b/src/kibana/components/doc_table/doc_table.js @@ -65,10 +65,11 @@ define(function (require) { $scope.searchSource.sort(getSort($scope.sorting, $scope.indexPattern)); // Set the watcher after initialization - $scope.$watch('persist.sorting', function (sorting) { - if (!sorting) return; - $scope.searchSource.sort(getSort(sorting, $scope.indexPattern)); - $scope.searchSource.fetch(); + $scope.$watch('persist.sorting', function (newSort, oldSort) { + // Don't react if sort values didn't really change + if (newSort === oldSort) return; + $scope.searchSource.sort(getSort(newSort, $scope.indexPattern)); + $scope.searchSource.fetchQueued(); }); // TODO: we need to have some way to clean up result requests From f01266da7b0725af5ddd40cccee96712d7d54b20 Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Tue, 30 Dec 2014 14:48:41 -0500 Subject: [PATCH 35/50] addressing review comments --- test/unit/specs/vislib/components/zero_injection.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/test/unit/specs/vislib/components/zero_injection.js b/test/unit/specs/vislib/components/zero_injection.js index 272afe749b0ea0..5ae121d8b92bbc 100644 --- a/test/unit/specs/vislib/components/zero_injection.js +++ b/test/unit/specs/vislib/components/zero_injection.js @@ -688,17 +688,13 @@ define(function (require) { }); it('should return an array of objects', function () { - console.log(results); - expect(_.isArray(results.rows[0].series[0].values)).to.be(true); - expect(_.isArray(results.rows[1].series[0].values)).to.be(true); - expect(_.isArray(results.rows[2].series[0].values)).to.be(true); - expect(_.isArray(results.rows[3].series[0].values)).to.be(true); - expect(_.isArray(results.rows[4].series[0].values)).to.be(true); + results.rows.forEach(function (row) { + expect(_.isArray(row.series[0].values)).to.be(true); + }); }); it('should return ordered x values', function () { var values = results.rows[0].series[0].values; - console.log(values); expect(values[0].x).to.be.lessThan(values[1].x); expect(values[1].x).to.be.lessThan(values[2].x); expect(values[2].x).to.be.lessThan(values[3].x); From 1b0bb932a6b4176636bc181a10327c1baebffc28 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Tue, 30 Dec 2014 12:59:44 -0700 Subject: [PATCH 36/50] seperated all chainable and notchainable lodash mixins --- src/kibana/utils/_mixins.js | 217 +---------------------- src/kibana/utils/_mixins_chainable.js | 181 +++++++++++++++++++ src/kibana/utils/_mixins_notchainable.js | 185 +++++++++++++++++++ 3 files changed, 368 insertions(+), 215 deletions(-) create mode 100644 src/kibana/utils/_mixins_chainable.js create mode 100644 src/kibana/utils/_mixins_notchainable.js diff --git a/src/kibana/utils/_mixins.js b/src/kibana/utils/_mixins.js index fd28f614cdc0ff..c6742629a48c99 100644 --- a/src/kibana/utils/_mixins.js +++ b/src/kibana/utils/_mixins.js @@ -11,221 +11,8 @@ define(function (require) { */ var _ = require('lodash_src'); _.mixin(require('lodash-deep')); - _.mixin({ - inherits: function (Sub, Super) { - Sub.prototype = _.create(Super.prototype, { 'constructor': Sub }); - Sub.Super = Super; - }, - remove: function (array, index) { - array.splice(index, 1); - return array; - }, - // If variable is value, then return alt. If variable is anything else, return value; - toggle: function (variable, value, alt) { - return variable === value ? alt : value; - }, - toggleInOut: function (array, value) { - if (_.contains(array, value)) { - array = _.without(array, value); - } else { - array.push(value); - } - return array; - }, - // NOTE: The flatten behavior here works if you don't need to keep a reference to the - // original value - flattenWith: function (dot, nestedObj, flattenArrays) { - var key; // original key - var stack = []; // track key stack - var flatObj = {}; - (function flattenObj(obj) { - _.keys(obj).forEach(function (key) { - stack.push(key); - if (!flattenArrays && _.isArray(obj[key])) flatObj[stack.join(dot)] = obj[key]; - else if (_.isObject(obj[key])) flattenObj(obj[key]); - else flatObj[stack.join(dot)] = obj[key]; - stack.pop(); - }); - }(nestedObj)); - return flatObj; - }, - // assign the properties of an object's subObject to the parent object. - // obj = { prop: { a: 1} } ===> obj = { a: 1 } - unwrapProp: function (obj, prop) { - var wrapped = obj[prop]; - delete obj[prop]; - _.assign(obj, wrapped); - }, - optMemoize: function (fn) { - var memo = _.memoize(fn); - return function () { - if (arguments[0] == null) { - return fn.apply(this, arguments); - } else { - return memo.apply(this, arguments); - } - }; - }, - isNumeric: function (v) { - return !_.isNaN(v) && (typeof v === 'number' || (!_.isArray(v) && !_.isNaN(parseInt(v, 10)))); - }, - setValue: function (obj, name, value) { - var path = name.split('.'); - var current = obj; - - (function recurse() { - var step = path.shift(); - - if (path.length === 0) { - current[step] = value; - } - - if (_.isObject(current)) { - current = current[step]; - recurse(); - } - }()); - }, - // limit the number of arguments that are passed to the function - limit: function (context, fn, count) { - // syntax without context limit(fn, 1) - if (count == null && _.isNumeric(fn)) { - count = fn; - fn = context; - context = null; - } - - count = count || 0; - - // shortcuts for common paths - // !!!! PLEASE don't use more than two arg - if (count === 0) return function () { return fn.call(context); }; - if (count === 1) return function (a) { return fn.call(context, a); }; - if (count === 2) return function (a, b) { return fn.call(context, a, b); }; - - // catch all version - return function () { - return fn.apply(context, [].slice.call(arguments, 0, count)); - }; - }, - // call all functions in an array - callEach: function (arr) { - _.invoke(arr, 'call'); - }, - onceWithCb: function (fn) { - var callbacks = []; - - // on initial flush, call the init function, but ensure - // that it only happens once - var flush = _.once(function (cntx, args) { - args.push(function finishedOnce() { - // override flush to simply schedule an asynchronous clear - flush = function () { - setTimeout(function () { - _.callEach(callbacks.splice(0)); - }, 0); - }; - - flush(); - }); - - fn.apply(cntx, args); - }); - - return function runOnceWithCb() { - var args = [].slice.call(arguments, 0); - var cb = args[args.length - 1]; - - if (typeof cb === 'function') { - callbacks.push(cb); - // trim the arg list so the other callback can - // be pushed if needed - args = args.slice(0, -1); - } - - // always call flush, it might not do anything - flush(this, args); - }; - }, - chunk: function (arr, count) { - var size = Math.ceil(arr.length / count); - var chunks = new Array(count); - for (var i = 0; i < count; i ++) { - var start = i * size; - chunks[i] = arr.slice(start, start + size); - } - return chunks; - }, - repeat: function (string, times) { - var out = ''; - for (var i = 0; i < times; i++) out += string; - return out; - }, - /** - * move an obj either up or down in the collection by - * injecting it either before/after the prev/next obj that - * satisfied the qualifier - * - * or, just from one index to another... - * - * @param {array} objs - the list to move the object within - * @param {number|any} obj - the object that should be moved, or the index that the object is currently at - * @param {number|boolean} below - the index to move the object to, or whether it should be moved up or down - * @param {function} qualifier - a lodash-y callback, object = _.where, string = _.pluck - * @return {array} - the objs argument - */ - move: function (objs, obj, below, qualifier) { - var origI = _.isNumber(obj) ? obj : objs.indexOf(obj); - if (origI === -1) return objs; - - if (_.isNumber(below)) { - // move to a specific index - objs.splice(below, 0, objs.splice(origI, 1)[0]); - return objs; - } - - below = !!below; - qualifier = _.createCallback(qualifier, null, 2); - - var above = !below; - var finder = below ? _.findIndex : _.findLastIndex; - - // find the index of the next/previous obj that meets the qualifications - var targetI = finder(objs, function (otherAgg, otherI) { - if (below && otherI <= origI) return; - if (above && otherI >= origI) return; - return !!qualifier(otherAgg, otherI); - }); - - if (targetI === -1) return objs; - - // place the obj at it's new index - objs.splice(targetI, 0, objs.splice(origI, 1)[0]); - } - }); - - /** - * Inverse of _.some(), reads better in some cases, - * seperated because it returns a boolean - * - * @param {array} arr - * @param {Function} fn - * @param {any} cntx - * @return {Boolean} - */ - _.mixin({ - none: function (arr, fn, cntx) { - return !_.some(arr, fn, cntx); - }, - isOrdinal: function (arr) { - return _.all(arr, function (num, i, arr) { - if (i === 0) return true; - return num > arr[i - 1]; - }); - } - }, { - chain: false - }); + _.mixin(require('utils/_mixins_chainable'), { chain: true }); + _.mixin(require('utils/_mixins_notchainable'), { chain: false }); return _; }); diff --git a/src/kibana/utils/_mixins_chainable.js b/src/kibana/utils/_mixins_chainable.js new file mode 100644 index 00000000000000..01404ce87d4352 --- /dev/null +++ b/src/kibana/utils/_mixins_chainable.js @@ -0,0 +1,181 @@ +define(function (require) { + /** + * THESE ARE AUTOMATICALLY INCLUDED IN LODASH + * + * use: + * var _ = require('lodash'); + * + * require.js config points the 'lodash' id to + * this module, which provides a modified version + * of lodash. + */ + var _ = require('lodash_src'); + + return { + /** + * Remove an element at a specific index from an array + * + * @param {array} arr + * @param {number} index + * @return {array} arr + */ + remove: function (arr, index) { + arr.splice(index, 1); + return arr; + }, + + /** + * Remove or add a value to an array based on it's presense in the + * array initially. + * + * @param {array} arr + * @param {any} value - the value to toggle + * @return {array} arr + */ + toggleInOut: function (arr, value) { + if (_.contains(arr, value)) { + arr = _.without(arr, value); + } else { + arr.push(value); + } + return arr; + }, + + + /** + * Flatten an object into a single-level object. + * NOTE: The flatten behavior here works if you don't need to keep a reference to the original value + * + * set flattenArrays to traverse into arrays and create properties like: + * { + * 'users.0.name': 'username1', + * 'users.1.name': 'username2', + * 'users.2.name': 'username3', + * } + * + * @param {string} dot - the seperator for keys, '.' is generally preferred + * @param {object} nestedObj - the object to flatten + * @param {Boolean} flattenArrays - should arrays be travered or left alone? + * @return {object} + */ + flattenWith: function (dot, nestedObj, flattenArrays) { + var key; // original key + var stack = []; // track key stack + var flatObj = {}; + (function flattenObj(obj) { + _.keys(obj).forEach(function (key) { + stack.push(key); + if (!flattenArrays && _.isArray(obj[key])) flatObj[stack.join(dot)] = obj[key]; + else if (_.isObject(obj[key])) flattenObj(obj[key]); + else flatObj[stack.join(dot)] = obj[key]; + stack.pop(); + }); + }(nestedObj)); + return flatObj; + }, + + /** + * assign the properties of an object's subObject to the parent object. + * + * var obj = { prop: { a: 1} }; + * _.unwrapProp(obj, 'prop'); // { a: 1 }; + * + * @param {[type]} obj [description] + * @param {[type]} prop [description] + * @return {[type]} [description] + */ + unwrapProp: function (obj, prop) { + var wrapped = obj[prop]; + delete obj[prop]; + _.assign(obj, wrapped); + return obj; + }, + + /** + * Memoize a function, but only use the memoized version if + * a first argument is passed, otherwise execute the original method + * every time. + * + * @param {Function} fn + * @return {Function} + */ + optMemoize: function (fn) { + var memo = _.memoize(fn); + return function () { + if (arguments[0] == null) { + return fn.apply(this, arguments); + } else { + return memo.apply(this, arguments); + } + }; + }, + + /** + * Alias to _.deepSet + */ + setValue: function (obj, path, value) { + _.deepSet(obj, path, value); + return obj; + }, + + /** + * Split an array into a series of smaller arrays, containing + * portions of the previous array. + * + * @param {array} arr - the array to chunk + * @param {number} count - the number of chunks to create + * @return {array[array]} - array of slice of arr + */ + chunk: function (arr, count) { + var size = Math.ceil(arr.length / count); + var chunks = new Array(count); + for (var i = 0; i < count; i ++) { + var start = i * size; + chunks[i] = arr.slice(start, start + size); + } + return chunks; + }, + + /** + * move an obj either up or down in the collection by + * injecting it either before/after the prev/next obj that + * satisfied the qualifier + * + * or, just from one index to another... + * + * @param {array} objs - the list to move the object within + * @param {number|any} obj - the object that should be moved, or the index that the object is currently at + * @param {number|boolean} below - the index to move the object to, or whether it should be moved up or down + * @param {function} qualifier - a lodash-y callback, object = _.where, string = _.pluck + * @return {array} - the objs argument + */ + move: function (objs, obj, below, qualifier) { + var origI = _.isNumber(obj) ? obj : objs.indexOf(obj); + if (origI === -1) return objs; + + if (_.isNumber(below)) { + // move to a specific index + objs.splice(below, 0, objs.splice(origI, 1)[0]); + return objs; + } + + below = !!below; + qualifier = _.createCallback(qualifier, null, 2); + + var above = !below; + var finder = below ? _.findIndex : _.findLastIndex; + + // find the index of the next/previous obj that meets the qualifications + var targetI = finder(objs, function (otherAgg, otherI) { + if (below && otherI <= origI) return; + if (above && otherI >= origI) return; + return !!qualifier(otherAgg, otherI); + }); + + if (targetI === -1) return objs; + + // place the obj at it's new index + objs.splice(targetI, 0, objs.splice(origI, 1)[0]); + } + }; +}); diff --git a/src/kibana/utils/_mixins_notchainable.js b/src/kibana/utils/_mixins_notchainable.js new file mode 100644 index 00000000000000..893e0f0884bb89 --- /dev/null +++ b/src/kibana/utils/_mixins_notchainable.js @@ -0,0 +1,185 @@ +define(function (require) { + /** + * THESE ARE AUTOMATICALLY INCLUDED IN LODASH + * + * use: + * var _ = require('lodash'); + * + * require.js config points the 'lodash' id to + * this module, which provides a modified version + * of lodash. + */ + var _ = require('lodash_src'); + + return { + /** + * Setup Class-like inheritance between two constructors. + * Exposes the Super class at SubClass.Super; + * + * @param {Constructor} Sub - The "Class" that should be extended + * @param {Constructor} Super - The parent "Class" + * @return {Constructor} - the sub argument; + */ + inherits: function (Sub, Super) { + Sub.prototype = _.create(Super.prototype, { 'constructor': Sub }); + Sub.Super = Super; + return Sub; + }, + + /** + * Create a string by repeating another string n-times + * + * @param {string} str - the string to repeat + * @param {number} times - the number of times to repeat the string + * @return {string} + */ + repeat: function (str, times) { + var out = ''; + for (var i = 0; i < times; i++) out += str; + return out; + }, + + /** + * If current is value (===), then return alt. If current is anything else, return value + * + * @param {any} current + * @param {any} value + * @param {any} alt + * @return {any} alt|value + */ + toggle: function (current, value, alt) { + return current === value ? alt : value; + }, + + /** + * Inverse of _.some(), reads better in some cases, + * + * @param {array} arr + * @param {Function} fn + * @param {any} cntx + * @return {Boolean} + */ + none: function (arr, fn, cntx) { + return !_.some(arr, fn, cntx); + }, + + /** + * check if the values in an array are all numbers, unique, and + * @param {[type]} arr [description] + * @return {Boolean} [description] + */ + isOrdinal: function (arr) { + if (!_.isArray(arr)) return false; + + return _.all(arr, function (num, i, arr) { + if (!_.isNumber(num)) return false; + if (i === 0) return true; + return num > arr[i - 1]; + }); + }, + + /** + * Checks to see if an input value is number-like, this + * includes strings that parse into valid numbers and objects + * that don't have a type of number but still parse properly + * via-some sort of valueOf magic + * + * @param {any} v - the value to check + * @return {Boolean} + */ + isNumeric: function (v) { + return !_.isNaN(v) && (typeof v === 'number' || (!_.isArray(v) && !_.isNaN(parseFloat(v)))); + }, + + /** + * Create a method that wraps another method which expects a callback as it's last + * argument. The wrapper method will call the wrapped function only once (the first + * time it is called), but will always call the callbacks passed to it. This has a + * similar effect to calling a promise-returning function that is wrapped with _.once + * but can be used outside of angular. + * + * @param {Function} fn - the function that should only be executed once and accepts + * a callback as it's last arg + * @return {Function} - the wrapper method + */ + onceWithCb: function (fn) { + var callbacks = []; + + // on initial flush, call the init function, but ensure + // that it only happens once + var flush = _.once(function (cntx, args) { + args.push(function finishedOnce() { + // override flush to simply schedule an asynchronous clear + flush = function () { + setTimeout(function () { + _.callEach(callbacks.splice(0)); + }, 0); + }; + + flush(); + }); + + fn.apply(cntx, args); + }); + + return function runOnceWithCb() { + var args = [].slice.call(arguments, 0); + var cb = args[args.length - 1]; + + if (typeof cb === 'function') { + callbacks.push(cb); + // trim the arg list so the other callback can + // be pushed if needed + args = args.slice(0, -1); + } + + // always call flush, it might not do anything + flush(this, args); + }; + }, + + /** + * Create a function that will ignore all but n-number of arguments. + * This is useful for passing functions like _.parseInt to Array#map. + * Since _.parseInt accepts a second argument, it will try to use the + * index of the value passed as the base for that number and weird + * errors occur. + * + * @param {this} [context] - this value for fn, optional + * @param {Function} fn - the function to wrap + * @param {number} count - the number of args to accept + * @return {Function} + */ + limit: function (context, fn, count) { + // syntax without context limit(fn, 1) + if (count == null && _.isNumeric(fn)) { + count = fn; + fn = context; + context = null; + } + + count = count || 0; + + // shortcuts for common paths + // !!!! PLEASE don't use more than two arg + if (count === 0) return function () { return fn.call(context); }; + if (count === 1) return function (a) { return fn.call(context, a); }; + if (count === 2) return function (a, b) { return fn.call(context, a, b); }; + + // catch all version + return function () { + return fn.apply(context, [].slice.call(arguments, 0, count)); + }; + }, + + /** + * Call all of the function in an array + * + * @param {array[functions]} arr + * @return {undefined} + */ + callEach: function (arr) { + _.invoke(arr, 'call'); + } + }; +}); From 6292e1e55381135016f404bf20d1e56a5e0fabc6 Mon Sep 17 00:00:00 2001 From: Juan Thomassie Date: Tue, 30 Dec 2014 16:43:08 -0600 Subject: [PATCH 37/50] improve tilemap legend display of colors and value ranges. closes #1881 --- .../vislib/visualizations/tile_map.js | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/src/kibana/components/vislib/visualizations/tile_map.js b/src/kibana/components/vislib/visualizations/tile_map.js index 477a6e7e7b79e7..bce702f11d7d10 100644 --- a/src/kibana/components/vislib/visualizations/tile_map.js +++ b/src/kibana/components/vislib/visualizations/tile_map.js @@ -127,7 +127,6 @@ define(function (require) { map.addControl(new FitControl()); } - function fitBounds() { map.fitBounds(featureLayer.getBounds()); } @@ -290,14 +289,29 @@ define(function (require) { var div = L.DomUtil.create('div', 'tilemap-legend'); var colors = self._attr.colors; var labels = []; - for (var i = 0; i < colors.length; i++) { - var vals = self._attr.cScale.invertExtent(colors[i]); - var strokecol = self.darkerColor(colors[i]); + var i = 0; + var vals; + var strokecol; + + if (data.properties.min === data.properties.max) { + // 1 val for legend + vals = self._attr.cScale.invertExtent(colors[i]); + strokecol = self.darkerColor(colors[i]); labels.push( ' ' + - vals[0].toFixed(1) + ' – ' + vals[1].toFixed(1)); + vals[0].toFixed(0)); + } else { + // 3 to 5 vals for legend + for (i = 0; i < colors.length; i++) { + vals = self._attr.cScale.invertExtent(colors[i]); + strokecol = self.darkerColor(colors[i]); + labels.push( + ' ' + + vals[0].toFixed(0) + ' – ' + vals[1].toFixed(0)); + } } div.innerHTML = labels.join('
'); + return div; }; legend.addTo(map); @@ -500,14 +514,20 @@ define(function (require) { */ TileMap.prototype.quantizeColorScale = function (count, min, max) { var self = this; - var greens = ['#c7e9b4', '#7fcdbb', '#41b6c4', '#2c7fb8', '#253494']; - var reds = ['#fed976', '#feb24c', '#fd8d3c', '#f03b20', '#bd0026']; - var blues = ['#9ecae1', '#6baed6', '#4292c6', '#2171b5', '#084594']; - var colors = self._attr.colors = reds; + var reds5 = ['#fed976', '#feb24c', '#fd8d3c', '#f03b20', '#bd0026']; + var reds3 = ['#fed976', '#fd8d3c', '#bd0026']; + var reds1 = ['#bd0026']; + var colors = self._attr.colors = reds5; + + if (max - min < 3) { + colors = self._attr.colors = reds1; + } else if (max - min < 25) { + colors = self._attr.colors = reds3; + } + var cScale = self._attr.cScale = d3.scale.quantize() .domain([min, max]) .range(colors); - if (max === min) { return colors[0]; } else { From 8e9d76571195b6020616330a7bbe70ec702fbc11 Mon Sep 17 00:00:00 2001 From: Juan Thomassie Date: Tue, 30 Dec 2014 17:23:04 -0600 Subject: [PATCH 38/50] updated sizes for circle marker opacity and scaling to display dense geohash data better --- .../vislib/visualizations/tile_map.js | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/kibana/components/vislib/visualizations/tile_map.js b/src/kibana/components/vislib/visualizations/tile_map.js index bce702f11d7d10..fafe42102bef5a 100644 --- a/src/kibana/components/vislib/visualizations/tile_map.js +++ b/src/kibana/components/vislib/visualizations/tile_map.js @@ -172,7 +172,7 @@ define(function (require) { color: self.darkerColor(defaultColor), weight: 1.0, opacity: 1, - fillOpacity: 0.7 + fillOpacity: 0.75 }; } }).addTo(map); @@ -233,7 +233,7 @@ define(function (require) { color: self.darkerColor(color), weight: 1.0, opacity: 1, - fillOpacity: 0.7 + fillOpacity: 0.75 }; } }).addTo(map); @@ -408,7 +408,7 @@ define(function (require) { }; /** - * radiusScale returns a circle radius from + * radiusScale returns a a number for scaled circle markers * approx. square root of count * which is multiplied by a factor based on the geohash precision * for relative sizing of markers @@ -426,40 +426,40 @@ define(function (require) { var maxr; switch (precision) { case 1: - maxr = 200; + maxr = 150; break; case 2: - maxr = 30; + maxr = 28; break; case 3: - maxr = 9; + maxr = 8; break; case 4: - maxr = 3; + maxr = 2; break; case 5: - maxr = 1.44; + maxr = 1.4; break; case 6: - maxr = 1.12; + maxr = 0.6; break; case 7: - maxr = 0.6; + maxr = 0.4; break; case 8: - maxr = 0.3; + maxr = 0.2; break; case 9: - maxr = 0.22; + maxr = 0.12; break; default: - maxr = 9; + maxr = 8; } return Math.pow(count, exp) / Math.pow(max, exp) * maxr; }; /** - * returns a number to scale circle markers + * returns a number to scale shaded circle markers * based on the geohash precision * * @method quantRadiusScale @@ -470,34 +470,34 @@ define(function (require) { var maxr; switch (precision) { case 1: - maxr = 100; + maxr = 150; break; case 2: - maxr = 12; + maxr = 18; break; case 3: - maxr = 3; + maxr = 4.5; break; case 4: - maxr = 0.6; + maxr = 0.7; break; case 5: - maxr = 0.3; + maxr = 0.26; break; case 6: - maxr = 0.22; + maxr = 0.20; break; case 7: - maxr = 0.18; + maxr = 0.16; break; case 8: - maxr = 0.16; + maxr = 0.13; break; case 9: - maxr = 0.14; + maxr = 0.11; break; default: - maxr = 3; + maxr = 4.5; } return maxr; }; @@ -515,8 +515,8 @@ define(function (require) { TileMap.prototype.quantizeColorScale = function (count, min, max) { var self = this; var reds5 = ['#fed976', '#feb24c', '#fd8d3c', '#f03b20', '#bd0026']; - var reds3 = ['#fed976', '#fd8d3c', '#bd0026']; - var reds1 = ['#bd0026']; + var reds3 = ['#fecc5c', '#fd8d3c', '#e31a1c']; + var reds1 = ['#ff6128']; var colors = self._attr.colors = reds5; if (max - min < 3) { From 1b7f8e34ff451c64a7851409d973d82b6a50ac8a Mon Sep 17 00:00:00 2001 From: Juan Thomassie Date: Wed, 31 Dec 2014 10:18:35 -0600 Subject: [PATCH 39/50] now uses less-hat filter mixin and removes tilelayer event on map unload --- src/kibana/components/vislib/styles/_tilemap.less | 12 ++---------- .../components/vislib/visualizations/tile_map.js | 13 ++++++++----- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/kibana/components/vislib/styles/_tilemap.less b/src/kibana/components/vislib/styles/_tilemap.less index 873fe6d08a4fd1..5a4a4ccb0328b2 100644 --- a/src/kibana/components/vislib/styles/_tilemap.less +++ b/src/kibana/components/vislib/styles/_tilemap.less @@ -113,17 +113,9 @@ /* filter to desaturate mapquest tiles */ img.leaflet-tile { - filter: brightness(1.03) grayscale(0.83) contrast(1.07); - -webkit-filter: brightness(1.03) grayscale(0.83) contrast(1.07); - -moz-filter: brightness(1.03) grayscale(0.83) contrast(1.07); - -ms-filter: brightness(1.03) grayscale(0.83) contrast(1.07); - -o-filter: brightness(1.03) grayscale(0.83) contrast(1.07); + .filter(brightness(1.03) grayscale(0.83) contrast(1.07)); } img.leaflet-tile.filters-off { - filter: none; - -webkit-filter: none; - -moz-filter: none; - -ms-filter: none; - -o-filter: none; + .filter(none); } \ No newline at end of file diff --git a/src/kibana/components/vislib/visualizations/tile_map.js b/src/kibana/components/vislib/visualizations/tile_map.js index 1f16074a7fdb09..f34f3bfc019b9b 100644 --- a/src/kibana/components/vislib/visualizations/tile_map.js +++ b/src/kibana/components/vislib/visualizations/tile_map.js @@ -50,8 +50,8 @@ define(function (require) { var self = this; var $elem = $(this.chartEl); var div; - var worldBounds = L.latLngBounds([-200, -220], [200, 220]); - var featureLayer; + var worldBounds = L.latLngBounds([-90, -180], [90, 180]); + // clean up old maps _.invoke(self.maps, 'destroy'); @@ -70,6 +70,7 @@ define(function (require) { mapCenter = self._attr.lastCenter; } + var featureLayer; var tileLayer = L.tileLayer('http://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpeg', { attribution: 'Tiles by MapQuest — ' + 'Map data © OpenStreetMap contributors, ' + @@ -93,11 +94,13 @@ define(function (require) { var map = L.map(div[0], mapOptions); self.maps.push(map); - tileLayer.on('tileload', function (e) { - saturateTiles(); + tileLayer.on('tileload', saturateTiles); + + map.on('unload', function () { + tileLayer.off('tileload', saturateTiles); }); - map.on('zoomend dragend', function () { + map.on('zoomend dragend', function setZoomCenter() { mapZoom = self._attr.lastZoom = map.getZoom(); mapCenter = self._attr.lastCenter = map.getCenter(); }); From eb5f7bac9adc259b566a5095da62178fc8c518de Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Fri, 2 Jan 2015 14:02:06 -0700 Subject: [PATCH 40/50] [aggType/aggParams] choose AggParam classes by type --- .../components/agg_types/_agg_params.js | 59 ++++++------------- src/kibana/components/agg_types/_agg_type.js | 13 +++- src/kibana/factories/base_object.js | 9 +-- 3 files changed, 32 insertions(+), 49 deletions(-) diff --git a/src/kibana/components/agg_types/_agg_params.js b/src/kibana/components/agg_types/_agg_params.js index d1d2f86a337538..79ae567ff5ea9d 100644 --- a/src/kibana/components/agg_types/_agg_params.js +++ b/src/kibana/components/agg_types/_agg_params.js @@ -4,19 +4,21 @@ define(function (require) { var _ = require('lodash'); var IndexedArray = require('utils/indexed_array/index'); - var BaseAggParam = Private(require('components/agg_types/param_types/base')); - var FieldAggParam = Private(require('components/agg_types/param_types/field')); - var OptionedAggParam = Private(require('components/agg_types/param_types/optioned')); - var RegexAggParam = Private(require('components/agg_types/param_types/regex')); - var StringAggParam = Private(require('components/agg_types/param_types/string')); - var RawJSONAggParam = Private(require('components/agg_types/param_types/raw_json')); + var paramTypeMap = { + field: Private(require('components/agg_types/param_types/field')), + optioned: Private(require('components/agg_types/param_types/optioned')), + regex: Private(require('components/agg_types/param_types/regex')), + string: Private(require('components/agg_types/param_types/string')), + raw_json: Private(require('components/agg_types/param_types/raw_json')), + _default: Private(require('components/agg_types/param_types/base')) + }; /** * Wraps a list of {{#crossLink "AggParam"}}{{/crossLink}} objects; owned by an {{#crossLink "AggType"}}{{/crossLink}} * * used to create: - * - `OptionedAggParam` – When the config has an array of `options: []` * - `FieldAggParam` – When the config has `name: "field"` + * - `*AggParam` – When the type matches something in the map above * - `BaseAggParam` – All other params * * @class AggParams @@ -26,42 +28,19 @@ define(function (require) { */ _(AggParams).inherits(IndexedArray); function AggParams(params) { - if (_.isPlainObject(params)) { - // convert the names: details format into details[].name - params = _.map(params, function (param, name) { - param.name = name; - return param; - }); - } - - // always append the raw JSON param - params.push({ - name: 'json', - type: 'json', - advanced: true - }); - AggParams.Super.call(this, { index: ['name'], - initialSet: params.map(function (param) { - if (param.name === 'field') { - return new FieldAggParam(param); - } - else if (param.type === 'optioned') { - return new OptionedAggParam(param); - } - else if (param.type === 'regex') { - return new RegexAggParam(param); - } - else if (param.type === 'string') { - return new StringAggParam(param); - } - else if (param.type === 'json') { - return new RawJSONAggParam(param); - } - else { - return new BaseAggParam(param); + initialSet: params.map(function (config) { + var type = config.name === 'field' ? config.name : config.type; + var Class = paramTypeMap[type] || paramTypeMap._default; + var param = new Class(config); + + // recursively init sub params + if (param.params && !(params.params instanceof AggParams)) { + param.params = new AggParams(param.params); } + + return param; }) }); } diff --git a/src/kibana/components/agg_types/_agg_type.js b/src/kibana/components/agg_types/_agg_type.js index 3531e31463821c..f1cba5c42bc050 100644 --- a/src/kibana/components/agg_types/_agg_type.js +++ b/src/kibana/components/agg_types/_agg_type.js @@ -79,9 +79,16 @@ define(function (require) { * @property params * @type {AggParams} */ - var params = this.params = config.params || []; - if (!(params instanceof AggParams)) { - params = this.params = new AggParams(params); + this.params = config.params || []; + if (!(this.params instanceof AggParams)) { + // always append the raw JSON param + this.params.push({ + name: 'json', + type: 'json', + advanced: true + }); + + this.params = new AggParams(this.params); } } diff --git a/src/kibana/factories/base_object.js b/src/kibana/factories/base_object.js index fbcc95a62def10..e040eefe5a247c 100644 --- a/src/kibana/factories/base_object.js +++ b/src/kibana/factories/base_object.js @@ -1,9 +1,8 @@ define(function (require) { - var _ = require('lodash'); - var rison = require('utils/rison'); - var angular = require('angular'); - return function BaseObjectProvider() { + var _ = require('lodash'); + var rison = require('utils/rison'); + var angular = require('angular'); function BaseObject(attributes) { // Set the attributes or default to an empty object @@ -40,7 +39,5 @@ define(function (require) { }; return BaseObject; - - }; }); From bf5964f661a91becf47ff703fd377d6e99ed1ce6 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Sat, 3 Jan 2015 19:26:50 -0700 Subject: [PATCH 41/50] [courier/fetch] properly order search source history, and attach fetch params --- .../components/courier/fetch/_call_client.js | 10 +++++- .../courier/fetch/request/request.js | 3 +- .../components/courier/fetch/strategy/doc.js | 13 +++---- .../courier/fetch/strategy/search.js | 36 ++++++++----------- 4 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/kibana/components/courier/fetch/_call_client.js b/src/kibana/components/courier/fetch/_call_client.js index 7df3d7a574d4d8..2ac51a3604b7d8 100644 --- a/src/kibana/components/courier/fetch/_call_client.js +++ b/src/kibana/components/courier/fetch/_call_client.js @@ -67,7 +67,15 @@ define(function (require) { // Now that all of THAT^^^ is out of the way, lets actually // call out to elasticsearch - Promise.resolve(strategy.convertReqsToBody(executable)) + Promise.map(executable, function (req) { + return Promise.try(req.getFetchParams, void 0, req) + .then(function (fetchParams) { + return (req.fetchParams = fetchParams); + }); + }) + .then(function (reqsFetchParams) { + return strategy.reqsFetchParamsToBody(reqsFetchParams); + }) .then(function (body) { // while the strategy was converting, our request was aborted if (esPromise === ABORTED) { diff --git a/src/kibana/components/courier/fetch/request/request.js b/src/kibana/components/courier/fetch/request/request.js index 61446941e6dc4a..61d940ca211455 100644 --- a/src/kibana/components/courier/fetch/request/request.js +++ b/src/kibana/components/courier/fetch/request/request.js @@ -37,7 +37,8 @@ define(function (require) { } if (source.history) { - source.history = _.first(source.history.concat(this), 20); + source.history.push(this); + source.history = _.last(source.history, 20); } }; diff --git a/src/kibana/components/courier/fetch/strategy/doc.js b/src/kibana/components/courier/fetch/strategy/doc.js index e222e6233238bf..005de6d252ab50 100644 --- a/src/kibana/components/courier/fetch/strategy/doc.js +++ b/src/kibana/components/courier/fetch/strategy/doc.js @@ -8,15 +8,10 @@ define(function (require) { * @param {array} requests - an array of flattened requests * @return {string} - the request body */ - convertReqsToBody: function (reqs) { - return Promise.map(reqs, function (req) { - return req.getFetchParams(); - }) - .then(function (reqsParams) { - return { - docs: reqsParams - }; - }); + reqsFetchParamsToBody: function (reqsFetchParams) { + return { + docs: reqsFetchParams + }; }, /** diff --git a/src/kibana/components/courier/fetch/strategy/search.js b/src/kibana/components/courier/fetch/strategy/search.js index 0e7df87a138bc8..384adca809ce3d 100644 --- a/src/kibana/components/courier/fetch/strategy/search.js +++ b/src/kibana/components/courier/fetch/strategy/search.js @@ -11,29 +11,23 @@ define(function (require) { * @param {array} requests - the requests to serialize * @return {string} - the request body */ - convertReqsToBody: function (reqs) { - return Promise.map(reqs, function (req) { - return req.getFetchParams(); - }) - .then(function (reqsParams) { - return reqsParams.map(function (reqParams) { - var indexList = reqParams.index; + reqsFetchParamsToBody: function (reqsFetchParams) { + return reqsFetchParams.map(function (fetchParams) { + var indexList = fetchParams.index; - if (_.isFunction(_.deepGet(indexList, 'toIndexList'))) { - var timeBounds = timefilter.getBounds(); - indexList = indexList.toIndexList(timeBounds.min, timeBounds.max); - } + if (_.isFunction(_.deepGet(indexList, 'toIndexList'))) { + var timeBounds = timefilter.getBounds(); + indexList = indexList.toIndexList(timeBounds.min, timeBounds.max); + } - return JSON.stringify({ - index: indexList, - type: reqParams.type, - ignore_unavailable: true - }) - + '\n' - + JSON.stringify(reqParams.body || {}); - - }).join('\n') + '\n'; - }); + return JSON.stringify({ + index: indexList, + type: fetchParams.type, + ignore_unavailable: true + }) + + '\n' + + JSON.stringify(fetchParams.body || {}); + }).join('\n') + '\n'; }, /** From 44f0dac2b60f88eb6dbe8356ee69179e66d72c0f Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Sat, 3 Jan 2015 20:52:42 -0700 Subject: [PATCH 42/50] [mixins] added behavior support --- src/kibana/utils/_mixins_chainable.js | 31 ++++++++++++++++++++++++ src/kibana/utils/_mixins_notchainable.js | 17 ++++++------- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/kibana/utils/_mixins_chainable.js b/src/kibana/utils/_mixins_chainable.js index 01404ce87d4352..6ed63550a6fddf 100644 --- a/src/kibana/utils/_mixins_chainable.js +++ b/src/kibana/utils/_mixins_chainable.js @@ -12,6 +12,37 @@ define(function (require) { var _ = require('lodash_src'); return { + /** + * Setup Class-like inheritance between two constructors. + * Exposes the Super class at SubClass.Super; + * + * @param {Constructor} Sub - The "Class" that should be extended + * @param {Constructor} Super - The parent "Class" + * @return {Constructor} - the sub argument; + */ + inherits: function (Sub, Super) { + Sub.prototype = Object.create(Super.prototype, { + constructor: { + value: Sub + }, + superConstructor: Sub.Super = Super + }); + return Sub; + }, + + /** + * Add a behavior to a Class, and track the behavior to enable _.hasBehavior + * + * @param {Constructor} Class - The "Class" that should be extended + * @param {object} behavior - The behavior that should be mixed into to the Class + * @return {Constructor} - Class; + */ + addBehavior: function (Class, behavior) { + Class.$$_behaviors = (Class.$$_behaviors || []).concat(behavior); + _.merge(Class.prototype, behavior); + return Class; + }, + /** * Remove an element at a specific index from an array * diff --git a/src/kibana/utils/_mixins_notchainable.js b/src/kibana/utils/_mixins_notchainable.js index 893e0f0884bb89..627b735eba4979 100644 --- a/src/kibana/utils/_mixins_notchainable.js +++ b/src/kibana/utils/_mixins_notchainable.js @@ -13,17 +13,16 @@ define(function (require) { return { /** - * Setup Class-like inheritance between two constructors. - * Exposes the Super class at SubClass.Super; + * Check if an object or class implements a behavior * - * @param {Constructor} Sub - The "Class" that should be extended - * @param {Constructor} Super - The parent "Class" - * @return {Constructor} - the sub argument; + * @param {Class|obj} instClass - Class or instance to test + * @param {behavior} behavior - behavior to test for + * @return {Boolean} */ - inherits: function (Sub, Super) { - Sub.prototype = _.create(Super.prototype, { 'constructor': Sub }); - Sub.Super = Super; - return Sub; + hasBehavior: function (instClass, behavior) { + if (_.isObject(instClass)) instClass = instClass.constructor; + if (!_.isFunction(instClass) || !behavior) return; + return _.contains(instClass.$$_behaviors, behavior); }, /** From 2965f3531286e93ec51406c5c15ca75112750a7b Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Sat, 3 Jan 2015 19:27:27 -0700 Subject: [PATCH 43/50] [visualize/spy] fix the req and resp spies --- .../visualize/spy/_req_resp_stats.js | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/kibana/components/visualize/spy/_req_resp_stats.js b/src/kibana/components/visualize/spy/_req_resp_stats.js index 52d066c0f177fc..2f89221d76dfe2 100644 --- a/src/kibana/components/visualize/spy/_req_resp_stats.js +++ b/src/kibana/components/visualize/spy/_req_resp_stats.js @@ -19,29 +19,25 @@ define(function (require) { if (!$scope.searchSource) return; - var searchHistory = $scope.searchSource.history; - if (!searchHistory) return; + var req = $scope.entry = _.last(_.deepGet($scope, 'searchSource.history')); + if (!req) return; - var entry = $scope.entry = _.find(searchHistory, 'state'); - if (!entry) return; - - var state = entry.state; - var resp = entry.resp; + var resp = req.resp; var meta = []; if (resp && resp.took != null) meta.push(['Query Duration', resp.took + 'ms']); - if (entry && entry.ms != null) meta.push(['Request Duration', entry.ms + 'ms']); + if (req && req.ms != null) meta.push(['Request Duration', req.ms + 'ms']); if (resp && resp.hits) meta.push(['Hits', resp.hits.total]); - if (state.index) meta.push(['Index', state.index]); - if (state.type) meta.push(['Type', state.type]); - if (state.id) meta.push(['Id', state.id]); + if (req.fetchParams.index) meta.push(['Index', req.fetchParams.index]); + if (req.fetchParams.type) meta.push(['Type', req.fetchParams.type]); + if (req.fetchParams.id) meta.push(['Id', req.fetchParams.id]); $scope.history = { meta: meta, - req: state.body, - resp: entry.resp, - complete: entry.complete + req: req.fetchParams.body, + resp: req.resp, + complete: req.complete }; }); }; From 33f4bbc70a5fdad79e1ec3210385a722db194ffe Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Sat, 3 Jan 2015 23:42:19 -0700 Subject: [PATCH 44/50] [visualize/termsAgg] order by either document count or a mertic agg --- .../components/agg_types/_agg_params.js | 2 +- .../components/agg_types/buckets/terms.js | 96 +++++++----- .../agg_types/controls/order_agg.html | 18 +++ src/kibana/components/vis/_agg_config.js | 10 +- src/kibana/components/vis/_agg_configs.js | 2 + src/kibana/plugins/visualize/editor/agg.html | 21 +-- src/kibana/plugins/visualize/editor/agg.js | 120 +-------------- .../plugins/visualize/editor/agg_params.html | 13 ++ .../plugins/visualize/editor/agg_params.js | 144 ++++++++++++++++++ .../visualize/editor/styles/editor.less | 6 + 10 files changed, 257 insertions(+), 175 deletions(-) create mode 100644 src/kibana/components/agg_types/controls/order_agg.html create mode 100644 src/kibana/plugins/visualize/editor/agg_params.html create mode 100644 src/kibana/plugins/visualize/editor/agg_params.js diff --git a/src/kibana/components/agg_types/_agg_params.js b/src/kibana/components/agg_types/_agg_params.js index 79ae567ff5ea9d..2a8c21d1a31098 100644 --- a/src/kibana/components/agg_types/_agg_params.js +++ b/src/kibana/components/agg_types/_agg_params.js @@ -9,7 +9,7 @@ define(function (require) { optioned: Private(require('components/agg_types/param_types/optioned')), regex: Private(require('components/agg_types/param_types/regex')), string: Private(require('components/agg_types/param_types/string')), - raw_json: Private(require('components/agg_types/param_types/raw_json')), + json: Private(require('components/agg_types/param_types/raw_json')), _default: Private(require('components/agg_types/param_types/base')) }; diff --git a/src/kibana/components/agg_types/buckets/terms.js b/src/kibana/components/agg_types/buckets/terms.js index af7d182a04df21..1a0909bba9bff1 100644 --- a/src/kibana/components/agg_types/buckets/terms.js +++ b/src/kibana/components/agg_types/buckets/terms.js @@ -2,14 +2,14 @@ define(function (require) { return function TermsAggDefinition(Private) { var _ = require('lodash'); var AggType = Private(require('components/agg_types/_agg_type')); - var bucketCountBetween = Private(require('components/agg_types/buckets/_bucket_count_between')); + var AggConfig = Private(require('components/vis/_agg_config')); var createFilter = Private(require('components/agg_types/buckets/create_filter/terms')); return new AggType({ name: 'terms', title: 'Terms', - makeLabel: function (aggConfig) { - var params = aggConfig.params; + makeLabel: function (agg) { + var params = agg.params; return params.order.display + ' ' + params.size + ' ' + params.field.displayName; }, createFilter: createFilter, @@ -19,54 +19,78 @@ define(function (require) { scriptable: true, filterFieldTypes: ['number', 'boolean', 'date', 'ip', 'string'] }, + { + name: 'exclude', + type: 'regex', + advanced: true + }, + { + name: 'include', + type: 'regex', + advanced: true + }, { name: 'size', default: 5 - // editor: batched with order }, { name: 'order', type: 'optioned', + default: 'desc', + editor: require('text!components/agg_types/controls/order_and_size.html'), options: [ { display: 'Top', val: 'desc' }, { display: 'Bottom', val: 'asc' } ], - editor: require('text!components/agg_types/controls/order_and_size.html'), - default: 'desc', - write: function (aggConfig, output) { - var sort = output.params.order = {}; - var order = aggConfig.params.order.val; - - var metricAggConfig = _.first(aggConfig.vis.aggs.bySchemaGroup.metrics); - - if (metricAggConfig.type.name === 'count') { - sort._count = order; - return; - } - - sort[metricAggConfig.id] = order; - - var visNotHierarchical = !aggConfig.vis.isHierarchical(); - - // if the vis is hierarchical, then the metric will always be copied - // if it's not, then we need to make sure the number of buckets is 0, else wise copy it - var metricNotChild = visNotHierarchical && bucketCountBetween(aggConfig, metricAggConfig) !== 0; - - if (metricNotChild) { - output.subAggs = output.subAggs || []; - output.subAggs.push(metricAggConfig); - } - } + write: _.noop // prevent default write, it's handled by orderAgg }, { - name: 'exclude', - type: 'regex', - advanced: true + name: 'orderBy', + type: 'optioned', + default: 'count', + options: [ + { display: 'Document Count', val: 'count' }, + { display: 'Custom Metric', val: 'agg' } + ], + write: _.noop // prevent default write, it's handled by orderAgg }, { - name: 'include', - type: 'regex', - advanced: true + name: 'orderAgg', + type: AggConfig, + default: null, + editor: require('text!components/agg_types/controls/order_agg.html'), + serialize: function (orderAgg) { + return orderAgg.toJSON(); + }, + deserialize: function (stateJSON, aggConfig) { + return new AggConfig(aggConfig.vis, stateJSON); + }, + controller: function ($scope) { + $scope.$watch('params.orderBy', function (orderBy) { + if (!orderBy) return; + if (orderBy.val !== 'agg') { + $scope.params.orderAgg = null; + return; + } + if ($scope.params.orderAgg) return; + + var agg = $scope.aggConfig; + $scope.params.orderAgg = new AggConfig(agg.vis, { + schema: _.first(agg.vis.type.schemas.metrics) + }); + }); + }, + write: function (agg, output) { + var dir = agg.params.order.val; + var order = output.params.order = {}; + + if (agg.params.orderAgg) { + output.subAggs = (output.subAggs || []).concat(agg.params.orderAgg); + order[agg.params.orderAgg.id] = dir; + } else { + order._count = dir; + } + } } ] }); diff --git a/src/kibana/components/agg_types/controls/order_agg.html b/src/kibana/components/agg_types/controls/order_agg.html new file mode 100644 index 00000000000000..e78934bade3755 --- /dev/null +++ b/src/kibana/components/agg_types/controls/order_agg.html @@ -0,0 +1,18 @@ +
+ + +
+
+ + + +
\ No newline at end of file diff --git a/src/kibana/components/vis/_agg_config.js b/src/kibana/components/vis/_agg_config.js index c090014d5ff881..410e3f0c6f8472 100644 --- a/src/kibana/components/vis/_agg_config.js +++ b/src/kibana/components/vis/_agg_config.js @@ -1,7 +1,6 @@ define(function (require) { return function AggConfigFactory(Private) { var _ = require('lodash'); - var aggTypes = Private(require('components/agg_types/index')); var fieldFormats = Private(require('components/index_patterns/_field_formats')); function AggConfig(vis, opts) { @@ -14,7 +13,7 @@ define(function (require) { // get the config type self.type = opts.type; if (_.isString(self.type)) { - self.type = aggTypes.byName[self.type]; + self.type = AggConfig.aggTypes.byName[self.type]; } // get the config schema @@ -81,8 +80,11 @@ define(function (require) { } if (aggParam.deserialize) { - if (!_.isObject(val)) { - // only deserialize if we have a scalar value + var isType = _.isFunction(aggParam.type) && (val instanceof aggParam.type); + var isObject = !isType && _.isObject(val); + var isDeserialized = (isType || isObject); + + if (!isDeserialized) { val = aggParam.deserialize(val, self); } diff --git a/src/kibana/components/vis/_agg_configs.js b/src/kibana/components/vis/_agg_configs.js index 24a244e533ae23..54fa834c69ee59 100644 --- a/src/kibana/components/vis/_agg_configs.js +++ b/src/kibana/components/vis/_agg_configs.js @@ -4,6 +4,8 @@ define(function (require) { var AggConfig = Private(require('components/vis/_agg_config')); var IndexedArray = require('utils/indexed_array/index'); + AggConfig.aggTypes = Private(require('components/agg_types/index')); + _(AggConfigs).inherits(IndexedArray); function AggConfigs(vis, configStates) { var self = this; diff --git a/src/kibana/plugins/visualize/editor/agg.html b/src/kibana/plugins/visualize/editor/agg.html index 80d0da93534cc9..1fea05ee55f63d 100644 --- a/src/kibana/plugins/visualize/editor/agg.html +++ b/src/kibana/plugins/visualize/editor/agg.html @@ -65,19 +65,10 @@ -
-
-

- "{{ agg.schema.title }}" aggs must run before all other buckets! -

- -
- - -
+ + \ No newline at end of file diff --git a/src/kibana/plugins/visualize/editor/agg.js b/src/kibana/plugins/visualize/editor/agg.js index 62cf305b74643c..beac1560cfd052 100644 --- a/src/kibana/plugins/visualize/editor/agg.js +++ b/src/kibana/plugins/visualize/editor/agg.js @@ -9,7 +9,7 @@ define(function (require) { var advancedToggleHtml = require('text!plugins/visualize/editor/advanced_toggle.html'); require('angular-ui-select'); - require('plugins/visualize/editor/agg_param'); + require('plugins/visualize/editor/agg_params'); require('filters/match_any'); var notify = new Notifier({ @@ -29,9 +29,7 @@ define(function (require) { groupMin: '=' }, link: function ($scope, $el) { - $scope.aggTypeOptions = aggTypes.byType[$scope.groupName]; $scope.editorOpen = $scope.agg.brandNew; - $scope.advancedToggled = false; $scope.$watchMulti([ '$index', @@ -43,122 +41,6 @@ define(function (require) { $scope.aggIsTooLow = calcAggIsTooLow(); }); - (function setupControlManagement() { - var $editorContainer = $el.find('.vis-editor-agg-editor'); - - // this will contain the controls for the schema (rows or columns?), which are unrelated to - // controls for the agg, which is why they are first - var $schemaEditor = $('
').addClass('schemaEditors').appendTo($editorContainer); - - if ($scope.agg.schema.editor) { - $schemaEditor.append($scope.agg.schema.editor); - $compile($schemaEditor)(editorScope()); - } - - // allow selection of an aggregation - var $aggSelect = $(aggSelectHtml).appendTo($editorContainer); - $compile($aggSelect)($scope); - - // params for the selected agg, these are rebuilt every time the agg in $aggSelect changes - var $aggParamEditors; // container for agg type param editors - var $aggParamEditorsScope; - $scope.$watch('agg.type', function updateAggParamEditor(newType, oldType) { - if ($aggParamEditors) { - $aggParamEditors.remove(); - $aggParamEditors = null; - } - - // if there's an old scope, destroy it - if ($aggParamEditorsScope) { - $aggParamEditorsScope.$destroy(); - $aggParamEditorsScope = null; - } - - var agg = $scope.agg; - var type = $scope.agg.type; - - if (!agg) return; - - if (newType !== oldType) { - // don't reset on initial load, the - // saved params should persist - agg.resetParams(); - } - - if (!type) return; - - var aggParamHTML = { - basic: [], - advanced: [] - }; - - // build collection of agg params html - type.params.forEach(function (param, i) { - var aggParam; - var type = 'basic'; - if (param.advanced) type = 'advanced'; - - if (aggParam = getAggParamHTML(param, i)) { - aggParamHTML[type].push(aggParam); - } - }); - - // compile the paramEditors html elements - var paramEditors = aggParamHTML.basic; - - if (aggParamHTML.advanced.length) { - paramEditors.push($(advancedToggleHtml).get(0)); - paramEditors = paramEditors.concat(aggParamHTML.advanced); - } - - $aggParamEditorsScope = $scope.$new(); - $aggParamEditors = $(paramEditors).appendTo($editorContainer); - $compile($aggParamEditors)($aggParamEditorsScope); - }); - - // build HTML editor given an aggParam and index - function getAggParamHTML(param, idx) { - // don't show params without an editor - if (!param.editor) { - return; - } - - var attrs = { - 'agg-type': 'agg.type', - 'agg-config': 'agg', - 'params': 'agg.params' - }; - - attrs['agg-param'] = 'agg.type.params[' + idx + ']'; - if (param.advanced) { - attrs['ng-show'] = 'advancedToggled'; - } - - return $('') - .attr(attrs) - .append(param.editor) - .get(0); - } - - // generic child scope creation, for both schema and agg - function editorScope() { - var $editorScope = $scope.$new(); - - setupBoundProp($editorScope, 'agg.type', 'aggType'); - setupBoundProp($editorScope, 'agg', 'aggConfig'); - setupBoundProp($editorScope, 'agg.params', 'params'); - - return $editorScope; - } - - // bind a property from our scope a child scope, with one-way binding - function setupBoundProp($child, get, set) { - var getter = _.partial($parse(get), $scope); - var setter = _.partial($parse(set).assign, $child); - $scope.$watch(getter, setter); - } - }()); - /** * Describe the aggregation, for display in the collapsed agg header * @return {[type]} [description] diff --git a/src/kibana/plugins/visualize/editor/agg_params.html b/src/kibana/plugins/visualize/editor/agg_params.html new file mode 100644 index 00000000000000..e4473e73a8b2ed --- /dev/null +++ b/src/kibana/plugins/visualize/editor/agg_params.html @@ -0,0 +1,13 @@ +
+

+ "{{ agg.schema.title }}" aggs must run before all other buckets! +

+ +
+ + \ No newline at end of file diff --git a/src/kibana/plugins/visualize/editor/agg_params.js b/src/kibana/plugins/visualize/editor/agg_params.js new file mode 100644 index 00000000000000..4f9a613e322c47 --- /dev/null +++ b/src/kibana/plugins/visualize/editor/agg_params.js @@ -0,0 +1,144 @@ +define(function (require) { + require('modules') + .get('app/visualize', ['ui.select']) + .directive('visEditorAggParams', function ($compile, $parse, Private, Notifier) { + var _ = require('lodash'); + var $ = require('jquery'); + var aggTypes = Private(require('components/agg_types/index')); + var aggSelectHtml = require('text!plugins/visualize/editor/agg_select.html'); + var advancedToggleHtml = require('text!plugins/visualize/editor/advanced_toggle.html'); + require('angular-ui-select'); + + require('plugins/visualize/editor/agg_param'); + require('filters/match_any'); + + var notify = new Notifier({ + location: 'visAggGroup' + }); + + return { + restrict: 'E', + template: require('text!plugins/visualize/editor/agg_params.html'), + scope: { + agg: '=', + groupName: '=' + }, + link: function ($scope, $el) { + $scope.aggTypeOptions = aggTypes.byType[$scope.groupName]; + $scope.advancedToggled = false; + + // this will contain the controls for the schema (rows or columns?), which are unrelated to + // controls for the agg, which is why they are first + var $schemaEditor = $('
').addClass('schemaEditors').appendTo($el); + + if ($scope.agg.schema.editor) { + $schemaEditor.append($scope.agg.schema.editor); + $compile($schemaEditor)(editorScope()); + } + + // allow selection of an aggregation + var $aggSelect = $(aggSelectHtml).appendTo($el); + $compile($aggSelect)($scope); + + // params for the selected agg, these are rebuilt every time the agg in $aggSelect changes + var $aggParamEditors; // container for agg type param editors + var $aggParamEditorsScope; + $scope.$watch('agg.type', function updateAggParamEditor(newType, oldType) { + if ($aggParamEditors) { + $aggParamEditors.remove(); + $aggParamEditors = null; + } + + // if there's an old scope, destroy it + if ($aggParamEditorsScope) { + $aggParamEditorsScope.$destroy(); + $aggParamEditorsScope = null; + } + + var agg = $scope.agg; + if (!agg) return; + + var type = $scope.agg.type; + + if (newType !== oldType) { + // don't reset on initial load, the + // saved params should persist + agg.resetParams(); + } + + if (!type) return; + + var aggParamHTML = { + basic: [], + advanced: [] + }; + + // build collection of agg params html + type.params.forEach(function (param, i) { + var aggParam; + var type = 'basic'; + if (param.advanced) type = 'advanced'; + + if (aggParam = getAggParamHTML(param, i)) { + aggParamHTML[type].push(aggParam); + } + }); + + // compile the paramEditors html elements + var paramEditors = aggParamHTML.basic; + + if (aggParamHTML.advanced.length) { + paramEditors.push($(advancedToggleHtml).get(0)); + paramEditors = paramEditors.concat(aggParamHTML.advanced); + } + + $aggParamEditorsScope = $scope.$new(); + $aggParamEditors = $(paramEditors).appendTo($el); + $compile($aggParamEditors)($aggParamEditorsScope); + }); + + // build HTML editor given an aggParam and index + function getAggParamHTML(param, idx) { + // don't show params without an editor + if (!param.editor) { + return; + } + + var attrs = { + 'agg-type': 'agg.type', + 'agg-config': 'agg', + 'params': 'agg.params' + }; + + attrs['agg-param'] = 'agg.type.params[' + idx + ']'; + if (param.advanced) { + attrs['ng-show'] = 'advancedToggled'; + } + + return $('') + .attr(attrs) + .append(param.editor) + .get(0); + } + + // generic child scope creation, for both schema and agg + function editorScope() { + var $editorScope = $scope.$new(); + + setupBoundProp($editorScope, 'agg.type', 'aggType'); + setupBoundProp($editorScope, 'agg', 'aggConfig'); + setupBoundProp($editorScope, 'agg.params', 'params'); + + return $editorScope; + } + + // bind a property from our scope a child scope, with one-way binding + function setupBoundProp($child, get, set) { + var getter = _.partial($parse(get), $scope); + var setter = _.partial($parse(set).assign, $child); + $scope.$watch(getter, setter); + } + } + }; + }); +}); \ No newline at end of file diff --git a/src/kibana/plugins/visualize/editor/styles/editor.less b/src/kibana/plugins/visualize/editor/styles/editor.less index 500881273c9f4e..f9ff1bb98fb482 100644 --- a/src/kibana/plugins/visualize/editor/styles/editor.less +++ b/src/kibana/plugins/visualize/editor/styles/editor.less @@ -227,6 +227,12 @@ margin: 0 0 5px 0; } } + + &-order-agg { + border-left: 2px solid @gray-lighter; + margin-left: @vis-editor-agg-editor-spacing; + padding-left: @vis-editor-agg-editor-spacing; + } } &-canvas { From 2765863a4fd3074e6e090a05a643aaf35322ae11 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Sun, 4 Jan 2015 12:57:54 -0700 Subject: [PATCH 45/50] [visualize/editor] allow ordering by existing metric aggs --- .../components/agg_types/buckets/terms.js | 45 ++++++++++++------- .../agg_types/controls/order_agg.html | 44 +++++++++++------- src/kibana/components/vis/_agg_config.js | 6 ++- 3 files changed, 59 insertions(+), 36 deletions(-) diff --git a/src/kibana/components/agg_types/buckets/terms.js b/src/kibana/components/agg_types/buckets/terms.js index 1a0909bba9bff1..e639059e1c4fc6 100644 --- a/src/kibana/components/agg_types/buckets/terms.js +++ b/src/kibana/components/agg_types/buckets/terms.js @@ -46,12 +46,6 @@ define(function (require) { }, { name: 'orderBy', - type: 'optioned', - default: 'count', - options: [ - { display: 'Document Count', val: 'count' }, - { display: 'Custom Metric', val: 'agg' } - ], write: _.noop // prevent default write, it's handled by orderAgg }, { @@ -66,16 +60,32 @@ define(function (require) { return new AggConfig(aggConfig.vis, stateJSON); }, controller: function ($scope) { - $scope.$watch('params.orderBy', function (orderBy) { + $scope.safeMakeLabel = function (agg) { + try { + return agg.makeLabel(); + } catch (e) { + return '- agg not valid -'; + } + }; + + $scope.$watch('params.orderBy', function (orderBy, prevOrderBy) { + var agg = $scope.aggConfig; + var aggs = agg.vis.aggs; + var params = $scope.params; + + if (orderBy === prevOrderBy && !orderBy) { + params.orderBy = (_.first(aggs.bySchemaGroup.metrics) || { id: 'custom' }).id; + return; + } + if (!orderBy) return; - if (orderBy.val !== 'agg') { - $scope.params.orderAgg = null; + if (orderBy !== 'custom') { + params.orderAgg = null; return; } - if ($scope.params.orderAgg) return; + if (params.orderAgg) return; - var agg = $scope.aggConfig; - $scope.params.orderAgg = new AggConfig(agg.vis, { + params.orderAgg = new AggConfig(agg.vis, { schema: _.first(agg.vis.type.schemas.metrics) }); }); @@ -84,12 +94,13 @@ define(function (require) { var dir = agg.params.order.val; var order = output.params.order = {}; - if (agg.params.orderAgg) { - output.subAggs = (output.subAggs || []).concat(agg.params.orderAgg); - order[agg.params.orderAgg.id] = dir; - } else { - order._count = dir; + var orderAgg = agg.params.orderAgg; + if (!orderAgg) { + orderAgg = agg.vis.aggs.byId[agg.params.orderBy]; } + + output.subAggs = (output.subAggs || []).concat(orderAgg); + order[orderAgg.id] = dir; } } ] diff --git a/src/kibana/components/agg_types/controls/order_agg.html b/src/kibana/components/agg_types/controls/order_agg.html index e78934bade3755..095031349755c9 100644 --- a/src/kibana/components/agg_types/controls/order_agg.html +++ b/src/kibana/components/agg_types/controls/order_agg.html @@ -1,18 +1,28 @@ -
- - -
-
- - - +
+
+ + +
+
+ + + +
\ No newline at end of file diff --git a/src/kibana/components/vis/_agg_config.js b/src/kibana/components/vis/_agg_config.js index 410e3f0c6f8472..386f5c45aa73af 100644 --- a/src/kibana/components/vis/_agg_config.js +++ b/src/kibana/components/vis/_agg_config.js @@ -80,8 +80,10 @@ define(function (require) { } if (aggParam.deserialize) { - var isType = _.isFunction(aggParam.type) && (val instanceof aggParam.type); - var isObject = !isType && _.isObject(val); + var isTyped = _.isFunction(aggParam.type); + + var isType = isTyped && (val instanceof aggParam.type); + var isObject = !isTyped && _.isObject(val); var isDeserialized = (isType || isObject); if (!isDeserialized) { From 0111a0d0ab44fbf3c300db6b8adf45ff5615a2c6 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Sun, 4 Jan 2015 13:08:21 -0700 Subject: [PATCH 46/50] [visualize/editor] prevent stacking advanced toggles --- src/kibana/plugins/visualize/editor/advanced_toggle.html | 2 +- src/kibana/plugins/visualize/editor/styles/editor.less | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/kibana/plugins/visualize/editor/advanced_toggle.html b/src/kibana/plugins/visualize/editor/advanced_toggle.html index 321d5ef7d78856..040a155cd45912 100644 --- a/src/kibana/plugins/visualize/editor/advanced_toggle.html +++ b/src/kibana/plugins/visualize/editor/advanced_toggle.html @@ -1,4 +1,4 @@ -
+
Advanced diff --git a/src/kibana/plugins/visualize/editor/styles/editor.less b/src/kibana/plugins/visualize/editor/styles/editor.less index f9ff1bb98fb482..fa17829122b252 100644 --- a/src/kibana/plugins/visualize/editor/styles/editor.less +++ b/src/kibana/plugins/visualize/editor/styles/editor.less @@ -188,6 +188,10 @@ color: @link-color; } } + + &-advanced-toggle { + text-align: right; + } } &-form-row { @@ -230,8 +234,9 @@ &-order-agg { border-left: 2px solid @gray-lighter; - margin-left: @vis-editor-agg-editor-spacing; - padding-left: @vis-editor-agg-editor-spacing; + border-bottom: 2px solid @gray-lighter; + margin: 0 0 @vis-editor-agg-editor-spacing @vis-editor-agg-editor-spacing; + padding: 0 0 @vis-editor-agg-editor-spacing @vis-editor-agg-editor-spacing; } } From 7d661fb12f7fa4e03cd2355e1cad55548a7853be Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Sun, 4 Jan 2015 13:18:11 -0700 Subject: [PATCH 47/50] [vis/aggConfigs/tests] proxy the aggTypes to AggConfig spy --- test/unit/specs/components/vis/_agg_configs.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/unit/specs/components/vis/_agg_configs.js b/test/unit/specs/components/vis/_agg_configs.js index 5e984098bd73c5..04503641e9dc75 100644 --- a/test/unit/specs/components/vis/_agg_configs.js +++ b/test/unit/specs/components/vis/_agg_configs.js @@ -16,7 +16,13 @@ define(function (require) { // replace the AggConfig module with a spy var RealAggConfigPM = require('components/vis/_agg_config'); AggConfig = Private(RealAggConfigPM); - Private.stub(RealAggConfigPM, sinon.spy(AggConfig)); + var spy = sinon.spy(AggConfig); + Object.defineProperty(spy, 'aggTypes', { + get: function () { return AggConfig.aggTypes; }, + set: function (val) { AggConfig.aggTypes = val; } + }); + + Private.stub(RealAggConfigPM, spy); // load main deps Vis = Private(require('components/vis/vis')); From 80a807137c2173aeb589a25f01a4b7b248be44da Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Sun, 4 Jan 2015 13:18:38 -0700 Subject: [PATCH 48/50] [vis/aggConfigs/tests] specify the required orderBy param --- test/unit/specs/components/vis/_agg_configs.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/specs/components/vis/_agg_configs.js b/test/unit/specs/components/vis/_agg_configs.js index 04503641e9dc75..f99d9ab410dc45 100644 --- a/test/unit/specs/components/vis/_agg_configs.js +++ b/test/unit/specs/components/vis/_agg_configs.js @@ -276,9 +276,9 @@ define(function (require) { var vis = new Vis(indexPattern, { type: 'histogram', aggs: [ - { type: 'terms', schema: 'segment', params: { field: 'ip' } }, - { type: 'terms', schema: 'segment', params: { field: 'extension' } }, - { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, + { type: 'terms', schema: 'segment', params: { field: 'ip', orderBy: 1 } }, + { type: 'terms', schema: 'segment', params: { field: 'extension', orderBy: 1 } }, + { id: 1, type: 'avg', schema: 'metric', params: { field: 'bytes' } }, { type: 'sum', schema: 'metric', params: { field: 'bytes' } }, { type: 'min', schema: 'metric', params: { field: 'bytes' } }, { type: 'max', schema: 'metric', params: { field: 'bytes' } } From 904968c7730d0c6418585c082da5ea64f255c5ab Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Sun, 4 Jan 2015 13:20:22 -0700 Subject: [PATCH 49/50] [aggTypes/aggParams/test] removed this unused behavior --- test/unit/specs/components/agg_types/_agg_params.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/test/unit/specs/components/agg_types/_agg_params.js b/test/unit/specs/components/agg_types/_agg_params.js index a0ddc271198794..ecf7f86f59d34e 100644 --- a/test/unit/specs/components/agg_types/_agg_params.js +++ b/test/unit/specs/components/agg_types/_agg_params.js @@ -21,19 +21,6 @@ define(function (require) { })); describe('constructor args', function () { - it('accepts an object of params defs', function () { - var params = { - one: {}, - two: {} - }; - var paramLength = Object.keys(params).length + 1; // json is appended - var aggParams = new AggParams(params); - - expect(aggParams).to.have.length(paramLength); - expect(aggParams).to.be.an(Array); - expect(aggParams.byName).to.have.keys(['one', 'two']); - }); - it('accepts an array of param defs', function () { var params = [ { name: 'one' }, From 9255c3d3d50b0a718b867da596f610f4a898ba79 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Sun, 4 Jan 2015 13:22:56 -0700 Subject: [PATCH 50/50] [aggTypes/aggParams/tests] json agg param is now appended by AggType so that AggParams is more reusable --- .../specs/components/agg_types/_agg_params.js | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/test/unit/specs/components/agg_types/_agg_params.js b/test/unit/specs/components/agg_types/_agg_params.js index ecf7f86f59d34e..d95af688668554 100644 --- a/test/unit/specs/components/agg_types/_agg_params.js +++ b/test/unit/specs/components/agg_types/_agg_params.js @@ -26,10 +26,9 @@ define(function (require) { { name: 'one' }, { name: 'two' } ]; - var paramLength = params.length + 1; // json is appended var aggParams = new AggParams(params); - expect(aggParams).to.have.length(paramLength); + expect(aggParams).to.have.length(params.length); expect(aggParams).to.be.an(Array); expect(aggParams.byName).to.have.keys(['one', 'two']); }); @@ -40,10 +39,9 @@ define(function (require) { var params = [ { name: 'field' } ]; - var paramLength = params.length + 1; // json is appended var aggParams = new AggParams(params); - expect(aggParams).to.have.length(paramLength); + expect(aggParams).to.have.length(params.length); expect(aggParams[0]).to.be.a(FieldAggParam); }); @@ -54,10 +52,9 @@ define(function (require) { type: 'optioned' } ]; - var paramLength = params.length + 1; // json is appended var aggParams = new AggParams(params); - expect(aggParams).to.have.length(paramLength); + expect(aggParams).to.have.length(params.length); expect(aggParams[0]).to.be.a(OptionedAggParam); }); @@ -68,10 +65,9 @@ define(function (require) { type: 'regex' } ]; - var paramLength = params.length + 1; // json is appended var aggParams = new AggParams(params); - expect(aggParams).to.have.length(paramLength); + expect(aggParams).to.have.length(params.length); expect(aggParams[0]).to.be.a(RegexAggParam); }); @@ -90,14 +86,13 @@ define(function (require) { editor: 'small' } ]; - var paramLength = params.length + 1; // json is appended var aggParams = new AggParams(params); - expect(BaseAggParam).to.have.property('callCount', paramLength); + expect(BaseAggParam).to.have.property('callCount', params.length); expect(FieldAggParam).to.have.property('callCount', 0); expect(OptionedAggParam).to.have.property('callCount', 0); - expect(aggParams).to.have.length(paramLength); + expect(aggParams).to.have.length(params.length); aggParams.forEach(function (aggParam) { expect(aggParam).to.be.a(BaseAggParam); });