diff --git a/package.json b/package.json index c4fdd63b148670..17f0e72bad9c65 100644 --- a/package.json +++ b/package.json @@ -54,12 +54,12 @@ }, "repository": { "type": "git", - "url": "git@github.com:elasticsearch/kibana4.git" + "url": "git@github.com:elasticsearch/kibana.git" }, "author": "", "license": "Apache 2.0", "bugs": { - "url": "https://github.com/elasticsearch/kibana4/issues" + "url": "https://github.com/elasticsearch/kibana/issues" }, - "homepage": "https://github.com/elasticsearch/kibana4" + "homepage": "https://www.elasticsearch.org/overview/kibana" } diff --git a/src/kibana/components/agg_response/point_series/_get_point.js b/src/kibana/components/agg_response/point_series/_get_point.js index fe63ab4daa8212..c3ae30bcd2a41a 100644 --- a/src/kibana/components/agg_response/point_series/_get_point.js +++ b/src/kibana/components/agg_response/point_series/_get_point.js @@ -12,6 +12,10 @@ define(function (require) { yScale: yScale }; + if (point.y === 'NaN') { + return; + } + if (series) { point.series = unwrap(row[series.i]); } diff --git a/src/kibana/components/agg_response/point_series/_get_series.js b/src/kibana/components/agg_response/point_series/_get_series.js index 809352d0351635..acea356eef5279 100644 --- a/src/kibana/components/agg_response/point_series/_get_series.js +++ b/src/kibana/components/agg_response/point_series/_get_series.js @@ -15,12 +15,14 @@ define(function (require) { if (!multiY) { var point = partGetPoint(row, aspects.y); - addToSiri(series, point, point.series); + if (point) addToSiri(series, point, point.series); return; } aspects.y.forEach(function (y) { var point = partGetPoint(row, y); + if (!point) return; + var prefix = point.series ? point.series + ': ' : ''; var seriesId = prefix + y.agg.id; var seriesLabel = prefix + y.col.title; diff --git a/src/kibana/components/agg_types/controls/extended_stats.html b/src/kibana/components/agg_types/controls/extended_stats.html new file mode 100644 index 00000000000000..cad296ce4343a7 --- /dev/null +++ b/src/kibana/components/agg_types/controls/extended_stats.html @@ -0,0 +1,17 @@ +
+ + + + +

+ select at least one stat +

+ +
+ +
+
\ No newline at end of file diff --git a/src/kibana/components/agg_types/controls/ranges.html b/src/kibana/components/agg_types/controls/ranges.html index 4268b278c08375..20084cc1319f5a 100644 --- a/src/kibana/components/agg_types/controls/ranges.html +++ b/src/kibana/components/agg_types/controls/ranges.html @@ -25,7 +25,9 @@ name="range.to" /> - diff --git a/src/kibana/components/agg_types/index.js b/src/kibana/components/agg_types/index.js index 82d4703aae2716..6dcea38ea64d7f 100644 --- a/src/kibana/components/agg_types/index.js +++ b/src/kibana/components/agg_types/index.js @@ -9,6 +9,7 @@ define(function (require) { Private(require('components/agg_types/metrics/sum')), Private(require('components/agg_types/metrics/min')), Private(require('components/agg_types/metrics/max')), + Private(require('components/agg_types/metrics/extended_stats')), Private(require('components/agg_types/metrics/cardinality')), Private(require('components/agg_types/metrics/percentiles')) ], diff --git a/src/kibana/components/agg_types/metrics/extended_stats.js b/src/kibana/components/agg_types/metrics/extended_stats.js new file mode 100644 index 00000000000000..5d55a48d84990a --- /dev/null +++ b/src/kibana/components/agg_types/metrics/extended_stats.js @@ -0,0 +1,84 @@ +define(function (require) { + return function AggTypeMetricExtendedStatsProvider(Private) { + var _ = require('lodash'); + var MetricAggType = Private(require('components/agg_types/metrics/_metric_agg_type')); + var getResponseAggConfig = Private(require('components/agg_types/metrics/_get_response_agg_config')); + + var valueProps = { + makeLabel: function () { + return this.key + ' of ' + this.fieldDisplayName(); + } + }; + + var statNames = [ + 'count', + 'min', + 'max', + 'avg', + 'sum', + 'sum_of_squares', + 'variance', + 'std_deviation' + ]; + + var exStatsType = new MetricAggType({ + name: 'extended_stats', + title: 'Extended Stats', + makeLabel: function (agg) { + return 'Extended Stats on ' + agg.fieldDisplayName(); + }, + params: [ + { + name: 'field', + filterFieldTypes: 'number' + }, + { + name: 'names', + editor: require('text!components/agg_types/controls/extended_stats.html'), + default: statNames.slice(), + write: _.noop, + controller: function ($scope) { + $scope.map = mapList(); + $scope.names = listMap(); + $scope.statNames = statNames; + + $scope.$watchCollection('agg.params.names', function (names) { + if (names === $scope.names) return; + + $scope.names = _.intersection(statNames, names || []); + $scope.map = mapList(); + }); + + $scope.$watchCollection('map', function () { + $scope.names = $scope.agg.params.names = listMap(); + }); + + function mapList() { + return _.transform($scope.names, function (map, key) { + map[key] = true; + }, {}); + } + + function listMap() { + return _.transform(statNames, function (list, stat) { + if ($scope.map[stat]) list.push(stat); + }, []); + } + } + } + ], + getResponseAggs: function (agg) { + var ValueAggConfig = getResponseAggConfig(agg, valueProps); + return _.map(agg.params.names, function (name) { + return new ValueAggConfig(name); + }); + }, + getValue: function (agg, bucket) { + return bucket[agg.parentId][agg.key]; + } + }); + + exStatsType.statNames = statNames; + return exStatsType; + }; +}); \ No newline at end of file diff --git a/src/kibana/components/agg_types/metrics/percentiles.js b/src/kibana/components/agg_types/metrics/percentiles.js index f5958e8728ae34..f7f0a3ff822a97 100644 --- a/src/kibana/components/agg_types/metrics/percentiles.js +++ b/src/kibana/components/agg_types/metrics/percentiles.js @@ -53,6 +53,8 @@ define(function (require) { }); }, getValue: function (agg, bucket) { + // percentiles for 1, 5, and 10 will come back as 1.0, 5.0, and 10.0 so we + // parse the keys and respond with the value that matches return _.find(bucket[agg.parentId].values, function (value, key) { return agg.key === parseFloat(key); }); diff --git a/src/kibana/components/fancy_forms/_set_view_value.checksum b/src/kibana/components/fancy_forms/_set_view_value.checksum new file mode 100644 index 00000000000000..fb40f7f6150699 --- /dev/null +++ b/src/kibana/components/fancy_forms/_set_view_value.checksum @@ -0,0 +1 @@ +function(value){this.$viewValue=value;//changetodirtyif(this.$pristine){this.$dirty=true;this.$pristine=false;$animate.removeClass($element,PRISTINE_CLASS);$animate.addClass($element,DIRTY_CLASS);parentForm.$setDirty();}forEach(this.$parsers,function(fn){value=fn(value);});if(this.$modelValue!==value){this.$modelValue=value;ngModelSet($scope,value);forEach(this.$viewChangeListeners,function(listener){try{listener();}catch(e){$exceptionHandler(e);}});}} \ No newline at end of file diff --git a/src/kibana/components/fancy_forms/fancy_forms.js b/src/kibana/components/fancy_forms/fancy_forms.js new file mode 100644 index 00000000000000..aa095e29374309 --- /dev/null +++ b/src/kibana/components/fancy_forms/fancy_forms.js @@ -0,0 +1,45 @@ +define(function (require) { + var _ = require('lodash'); + var $ = require('jquery'); + + var KbnFormController = require('components/fancy_forms/kbn_form'); + var KbnModelController = require('components/fancy_forms/kbn_model'); + + require('modules') + .get('kibana') + .config(function ($provide) { + function decorateDirectiveController(DecorativeController) { + return function ($delegate, $injector) { + // directive providers are arrays + $delegate.forEach(function (directive) { + // get metadata about all init fns + var chain = [directive.controller, DecorativeController].map(function (fn) { + var deps = $injector.annotate(fn); + return { deps: deps, fn: _.isArray(fn) ? _.last(fn) : fn }; + }); + + // replace the controller with one that will setup the actual controller + directive.controller = function stub() { + var allDeps = _.toArray(arguments); + return chain.reduce(function (controller, link, i) { + var deps = allDeps.splice(0, link.deps.length); + return link.fn.apply(controller, deps) || controller; + }, this); + }; + + // set the deps of our new controller to be the merged deps of every fn + directive.controller.$inject = chain.reduce(function (deps, link) { + return deps.concat(link.deps); + }, []); + }); + + return $delegate; + }; + } + + + $provide.decorator('formDirective', decorateDirectiveController(KbnFormController)); + $provide.decorator('ngFormDirective', decorateDirectiveController(KbnFormController)); + $provide.decorator('ngModelDirective', decorateDirectiveController(KbnModelController)); + }); +}); \ No newline at end of file diff --git a/src/kibana/components/fancy_forms/kbn_form.js b/src/kibana/components/fancy_forms/kbn_form.js new file mode 100644 index 00000000000000..e1312016a0d633 --- /dev/null +++ b/src/kibana/components/fancy_forms/kbn_form.js @@ -0,0 +1,26 @@ +define(function (require) { + var _ = require('lodash'); + + /** + * Extension of Angular's FormController class + * that provides helpers for error handling/validation. + * + * @param {$scope} $scope + */ + function KbnFormController($scope, $element) { + var self = this; + + self.errorCount = function () { + return _.reduce(self.$error, function (count, controls, errorType) { + return count + _.size(controls); + }, 0); + }; + + self.describeErrors = function () { + var count = self.errorCount(); + return count + ' Error' + (count === 1 ? '' : 's'); + }; + } + + return KbnFormController; +}); \ No newline at end of file diff --git a/src/kibana/components/fancy_forms/kbn_model.js b/src/kibana/components/fancy_forms/kbn_model.js new file mode 100644 index 00000000000000..cef5bcbd921595 --- /dev/null +++ b/src/kibana/components/fancy_forms/kbn_model.js @@ -0,0 +1,102 @@ +define(function (require) { + var _ = require('lodash'); + var SVV_CHECKSUM = require('text!components/fancy_forms/_set_view_value.checksum'); + var PRISTINE_CLASS = 'ng-pristine'; + var DIRTY_CLASS = 'ng-dirty'; + + // http://goo.gl/eJofve + var nullFormCtrl = { + $addControl: _.noop, + $removeControl: _.noop, + $setValidity: _.noop, + $setDirty: _.noop, + $setPristine: _.noop + }; + + /** + * Extension of Angular's NgModelController class + * that ensures models are marked "dirty" after + * they move from an invalid state to valid. + * + * @param {$scope} $scope + */ + function KbnModelController($scope, $element, $animate) { + var ngModel = this; + + // verify that angular works the way we are assuming it does + if (String(ngModel.$setViewValue).replace(/\s+/g, '') !== SVV_CHECKSUM) { + throw new Error('ngModelController.$setViewValue has updated but KbnModelController has not!'); + } + + /** + * Get the form a model belongs to + * + * @return {NgFormController} - the parent controller of a noop controller + */ + ngModel.$getForm = function () { + return $element.inheritedData('$formController') || nullFormCtrl; + }; + + /** + * Update the ngModel to be "dirty" if it is pristine. + * + * @return {undefined} + */ + ngModel.$setDirty = function () { + if (ngModel.$dirty) return; + ngModel.$dirty = true; + ngModel.$pristine = false; + $animate.removeClass($element, PRISTINE_CLASS); + $animate.addClass($element, DIRTY_CLASS); + ngModel.$getForm().$setDirty(); + }; + + /** + * While the model is pristine, ensure that the model + * gets set to dirty if it becomes invalid. If the model + * becomes dirty of other reasons stop watching and + * waitForPristine() + * + * @return {undefined} + */ + function watchForDirtyOrInvalid() { + var unwatch = $scope.$watch(get, react); + + function get() { + return ngModel.$dirty || ngModel.$invalid; + } + + function react(is, was) { + if (is === was) return; + unwatch(); + waitForPristine(); + ngModel.$setDirty(); + } + } + + /** + * Once a model becomes dirty, there is no longer a need + * for a watcher. Instead, we will react to the $setPristine + * method being called. This is the only way for a model to go + * from dirty -> pristine. + * + * @return {[type]} [description] + */ + function waitForPristine() { + var fn = ngModel.$setPristine; + ngModel.$setPristine = function () { + var ret = fn.apply(this, arguments); + if (ngModel.$pristine) { + ngModel.$setPristine = fn; + watchForDirtyOrInvalid(); + } + return ret; + }; + } + + if (ngModel.$dirty) waitForPristine(); + else watchForDirtyOrInvalid(); + } + + return KbnModelController; +}); \ No newline at end of file diff --git a/src/kibana/components/vislib/styles/_layout.less b/src/kibana/components/vislib/styles/_layout.less index 80bc2f41641512..e30bcc61327ce4 100644 --- a/src/kibana/components/vislib/styles/_layout.less +++ b/src/kibana/components/vislib/styles/_layout.less @@ -92,7 +92,7 @@ .display(flex); .flex(1 0 20px); overflow: visible; - margin: 0 5px 0 0; + margin: 0; min-height: 0; min-width: 0; } diff --git a/src/kibana/components/vislib/styles/_svg.less b/src/kibana/components/vislib/styles/_svg.less index 2422a4d145dd4c..01fa278fe879bb 100644 --- a/src/kibana/components/vislib/styles/_svg.less +++ b/src/kibana/components/vislib/styles/_svg.less @@ -15,7 +15,7 @@ } .tick text { - font-size: 9pt; + font-size: 8pt; fill: #848e96; } diff --git a/src/kibana/plugins/dashboard/services/_saved_dashboard.js b/src/kibana/plugins/dashboard/services/_saved_dashboard.js index 70b7abdc0bdd22..c38d07fc012c18 100644 --- a/src/kibana/plugins/dashboard/services/_saved_dashboard.js +++ b/src/kibana/plugins/dashboard/services/_saved_dashboard.js @@ -22,7 +22,8 @@ define(function (require) { title: 'string', hits: 'integer', description: 'string', - panelsJSON: 'string' + panelsJSON: 'string', + version: 'integer' }, // defeult values to assign to the doc @@ -30,7 +31,8 @@ define(function (require) { title: 'New Dashboard', hits: 0, description: '', - panelsJSON: '[]' + panelsJSON: '[]', + version: 1 }, searchSource: true, diff --git a/src/kibana/plugins/discover/components/field_chooser/discover_field_details.html b/src/kibana/plugins/discover/components/field_chooser/discover_field_details.html index 29c5d374f57795..4c83364f221d94 100644 --- a/src/kibana/plugins/discover/components/field_chooser/discover_field_details.html +++ b/src/kibana/plugins/discover/components/field_chooser/discover_field_details.html @@ -24,7 +24,7 @@
Quick Count ng-click="updateFilterInQuery(field, bucket.value, '+')">
- {{bucket.value}} + {{bucket.display}}
{{bucket.percent}}% diff --git a/src/kibana/plugins/discover/components/field_chooser/field_chooser.js b/src/kibana/plugins/discover/components/field_chooser/field_chooser.js index 500ffe5a86a367..dbb8198033e542 100644 --- a/src/kibana/plugins/discover/components/field_chooser/field_chooser.js +++ b/src/kibana/plugins/discover/components/field_chooser/field_chooser.js @@ -169,6 +169,9 @@ define(function (require) { count: 5, grouped: false }); + _.each(field.details.buckets, function (bucket) { + bucket.display = field.format.convert(bucket.value); + }); $scope.increaseFieldCounter(field, 1); } else { delete field.details; diff --git a/src/kibana/plugins/discover/components/field_chooser/lib/field_calculator.js b/src/kibana/plugins/discover/components/field_chooser/lib/field_calculator.js index 38da8422e65973..1925b73e75f478 100644 --- a/src/kibana/plugins/discover/components/field_chooser/lib/field_calculator.js +++ b/src/kibana/plugins/discover/components/field_chooser/lib/field_calculator.js @@ -82,7 +82,7 @@ define(function (require) { if (_.isArray(value) && !params.grouped) { k = value; } else { - k = _.isUndefined(value) || _.isNull(value) ? undefined : [value.toString()]; + k = value == null ? undefined : [value]; } /* jshint -W083 */ diff --git a/src/kibana/plugins/discover/saved_searches/_saved_search.js b/src/kibana/plugins/discover/saved_searches/_saved_search.js index 351ad4e625d85d..954882126434bd 100644 --- a/src/kibana/plugins/discover/saved_searches/_saved_search.js +++ b/src/kibana/plugins/discover/saved_searches/_saved_search.js @@ -21,7 +21,8 @@ define(function (require) { description: 'string', hits: 'integer', columns: 'string', - sort: 'string' + sort: 'string', + version: 'integer' }, defaults: { @@ -29,7 +30,8 @@ define(function (require) { description: '', columns: [], hits: 0, - sort: [] + sort: [], + version: 1 }, searchSource: true diff --git a/src/kibana/plugins/kibana/index.js b/src/kibana/plugins/kibana/index.js index e20f743dfa04f5..402626389bbca7 100644 --- a/src/kibana/plugins/kibana/index.js +++ b/src/kibana/plugins/kibana/index.js @@ -16,6 +16,7 @@ define(function (require) { require('components/watch_multi'); require('components/bind'); require('components/listen'); + require('components/fancy_forms/fancy_forms'); require('directives/click_focus'); require('directives/info'); require('directives/spinner'); diff --git a/src/kibana/plugins/visualize/editor/agg.html b/src/kibana/plugins/visualize/editor/agg.html index 56a1c0e732d49d..ef829e3be00104 100644 --- a/src/kibana/plugins/visualize/editor/agg.html +++ b/src/kibana/plugins/visualize/editor/agg.html @@ -23,7 +23,7 @@ - {{ describeError() }} + {{ aggForm.describeErrors() }} diff --git a/src/kibana/plugins/visualize/editor/agg.js b/src/kibana/plugins/visualize/editor/agg.js index a509a17218dd52..875eca1d4e1185 100644 --- a/src/kibana/plugins/visualize/editor/agg.js +++ b/src/kibana/plugins/visualize/editor/agg.js @@ -38,18 +38,6 @@ define(function (require) { return label ? label : ''; }; - /** - * Describe the errors in this agg - * @return {[type]} [description] - */ - $scope.describeError = function () { - var count = _.reduce($scope.aggForm.$error, function (count, controls, errorType) { - return count + _.size(controls); - }, 0); - - return count + ' Error' + (count > 1 ? 's' : ''); - }; - function move(below, agg) { _.move($scope.vis.aggs, agg, below, function (otherAgg) { return otherAgg.schema.group === agg.schema.group; diff --git a/src/kibana/plugins/visualize/editor/sidebar.html b/src/kibana/plugins/visualize/editor/sidebar.html index 99200a17848949..977b3cf7fbf257 100644 --- a/src/kibana/plugins/visualize/editor/sidebar.html +++ b/src/kibana/plugins/visualize/editor/sidebar.html @@ -18,6 +18,11 @@ diff --git a/src/kibana/plugins/visualize/saved_visualizations/_saved_vis.js b/src/kibana/plugins/visualize/saved_visualizations/_saved_vis.js index 4205e4024196db..bad341e7950431 100644 --- a/src/kibana/plugins/visualize/saved_visualizations/_saved_vis.js +++ b/src/kibana/plugins/visualize/saved_visualizations/_saved_vis.js @@ -24,7 +24,8 @@ define(function (require) { title: 'string', visState: 'json', description: 'string', - savedSearchId: 'string' + savedSearchId: 'string', + version: 'integer' }, defaults: { @@ -36,7 +37,8 @@ define(function (require) { return def; }()), description: '', - savedSearchId: opts.savedSearchId + savedSearchId: opts.savedSearchId, + version: 1 }, searchSource: true, diff --git a/src/kibana/styles/_sidebar.less b/src/kibana/styles/_sidebar.less index 10a98b30ea151a..5ebd7222b4c541 100644 --- a/src/kibana/styles/_sidebar.less +++ b/src/kibana/styles/_sidebar.less @@ -15,7 +15,7 @@ margin-bottom: 0px; } - .sidebar-list-header { + &-header { padding-left: 10px; padding-right: 10px; color: @sidebar-header-color; @@ -35,8 +35,19 @@ } } - .sidebar-item-title { + &-title, + &-text, + &-button { + margin: 0; padding: 5px 10px; + text-align: center; + width: 100%; + border: none; + border-radius: 0; + } + + &-title { + text-align: left; white-space: nowrap; .ellipsis(); @@ -51,11 +62,11 @@ } } - .sidebar-item-button { - padding: 5px 10px; - text-align: center; - width: 100%; - border-radius: 0; + &-text { + background: white; + } + + &-button { font-size: inherit; &[disabled] { @@ -87,6 +98,10 @@ background-color: @btn-danger-bg; color: @btn-danger-color } + + &.default { + .btn-default(); + } } .active { diff --git a/src/kibana/styles/main.less b/src/kibana/styles/main.less index 70496d1f50c4c8..bcc705550c5b8e 100644 --- a/src/kibana/styles/main.less +++ b/src/kibana/styles/main.less @@ -114,12 +114,20 @@ ul.navbar-inline li { } .top-fixed { - position:fixed; - bottom:0px; + position: fixed; + bottom: 0px; } -.checkbox input[type="checkbox"] { - float: none; +.checkbox label { + .display(flex); + .align-items(center); + padding-left: 0; + + input[type="checkbox"] { + float: none; + margin: 0 4px; + position: static; + } } notifications { diff --git a/test/unit/fixtures/hits.js b/test/unit/fixtures/hits.js index 09c3a5940642dd..61fef38a06817e 100644 --- a/test/unit/fixtures/hits.js +++ b/test/unit/fixtures/hits.js @@ -7,10 +7,10 @@ define(function (require) { {_source: {timestamp: 3, bytes: 30, request: 'baz'}}, {_source: {timestamp: 4, bytes: 30, request: 'baz'}}, {_source: {timestamp: 5, bytes: 30, request: 'baz'}}, - {_source: {timestamp: 6, bytes: 40, request: 'bat'}}, - {_source: {timestamp: 7, bytes: 40, request: 'bat'}}, - {_source: {timestamp: 8, bytes: 40, request: 'bat'}}, - {_source: {timestamp: 9, bytes: 40, request: 'bat'}}, + {_source: {timestamp: 6, bytes: 40.1415926535, request: 'bat'}}, + {_source: {timestamp: 7, bytes: 40.1415926535, request: 'bat'}}, + {_source: {timestamp: 8, bytes: 40.1415926535, request: 'bat'}}, + {_source: {timestamp: 9, bytes: 40.1415926535, request: 'bat'}}, ], function (p, i) { return _.merge({}, p, { _score: 1, diff --git a/test/unit/specs/apps/discover/directives/field_chooser.js b/test/unit/specs/apps/discover/directives/field_chooser.js index e2b4d3bd00c7da..78d57d51633986 100644 --- a/test/unit/specs/apps/discover/directives/field_chooser.js +++ b/test/unit/specs/apps/discover/directives/field_chooser.js @@ -188,6 +188,15 @@ define(function (require) { done(); }); + it('should create buckets with formatted and raw values', function (done) { + $scope.details(field); + expect(field.details.buckets).to.not.be(undefined); + expect(field.details.buckets[0].value).to.be(40.1415926535); + expect(field.details.buckets[0].display).to.be(40.142); + done(); + }); + + it('should recalculate the details on open fields if the data changes', function () { $scope.details(field); sinon.stub($scope, 'details'); diff --git a/test/unit/specs/components/agg_response/point_series/_get_points.js b/test/unit/specs/components/agg_response/point_series/_get_point.js similarity index 76% rename from test/unit/specs/components/agg_response/point_series/_get_points.js rename to test/unit/specs/components/agg_response/point_series/_get_point.js index 88fadfc980fbf0..e4e413eee0325c 100644 --- a/test/unit/specs/components/agg_response/point_series/_get_points.js +++ b/test/unit/specs/components/agg_response/point_series/_get_point.js @@ -8,7 +8,7 @@ define(function (require) { getPoint = Private(require('components/agg_response/point_series/_get_point')); })); - it('properly unwraps and scales values without the a series', function () { + it('properly unwraps and scales values without a series', function () { var row = [ { value: 1 }, { value: 2 }]; var point = getPoint({ i: 0 }, null, 5, row, { i: 1 }); @@ -29,5 +29,11 @@ define(function (require) { .and.have.property('y', 3) .and.have.property('aggConfigResult', row[2]); }); + + it('ignores points with a y value of NaN', function () { + var row = [ { value: 1 }, { value: 'NaN' }]; + var point = getPoint({ i: 0 }, null, 5, row, { i: 1 }); + expect(point).to.be(void 0); + }); }]; }); \ No newline at end of file diff --git a/test/unit/specs/components/agg_response/point_series/_get_series.js b/test/unit/specs/components/agg_response/point_series/_get_series.js index 8c46b64e96746a..4b5cb9909e69a6 100644 --- a/test/unit/specs/components/agg_response/point_series/_get_series.js +++ b/test/unit/specs/components/agg_response/point_series/_get_series.js @@ -98,7 +98,9 @@ define(function (require) { var rows = [ ['0', 3], ['1', 3], + ['1', 'NaN'], ['0', 3], + ['0', 'NaN'], ['1', 3], ['0', 3], ['1', 3] diff --git a/test/unit/specs/components/agg_response/point_series/point_series.js b/test/unit/specs/components/agg_response/point_series/point_series.js index b4c9f9b030feb7..000c3868268d18 100644 --- a/test/unit/specs/components/agg_response/point_series/point_series.js +++ b/test/unit/specs/components/agg_response/point_series/point_series.js @@ -4,7 +4,7 @@ define(function (require) { run(require('specs/components/agg_response/point_series/_add_to_siri')); run(require('specs/components/agg_response/point_series/_fake_x_aspect')); run(require('specs/components/agg_response/point_series/_get_aspects')); - run(require('specs/components/agg_response/point_series/_get_points')); + run(require('specs/components/agg_response/point_series/_get_point')); run(require('specs/components/agg_response/point_series/_get_series')); run(require('specs/components/agg_response/point_series/_init_x_axis')); run(require('specs/components/agg_response/point_series/_init_y_axis')); diff --git a/test/unit/specs/components/agg_types/_metric_aggs.js b/test/unit/specs/components/agg_types/_metric_aggs.js deleted file mode 100644 index c4ccf12f75019d..00000000000000 --- a/test/unit/specs/components/agg_types/_metric_aggs.js +++ /dev/null @@ -1,5 +0,0 @@ -define(function (require) { - return ['AggParams', function () { - - }]; -}); \ No newline at end of file diff --git a/test/unit/specs/components/agg_types/index.js b/test/unit/specs/components/agg_types/index.js index c86cd2a25fbd3a..9cc84e3438e598 100644 --- a/test/unit/specs/components/agg_types/index.js +++ b/test/unit/specs/components/agg_types/index.js @@ -6,7 +6,7 @@ define(function (require) { require('specs/components/agg_types/_bucket_count_between'), require('specs/components/agg_types/buckets/_histogram'), require('specs/components/agg_types/buckets/_date_histogram'), - require('specs/components/agg_types/_metric_aggs') + require('specs/components/agg_types/metrics/_extended_stats') ].forEach(function (s) { describe(s[0], s[1]); }); diff --git a/test/unit/specs/components/agg_types/metrics/_extended_stats.js b/test/unit/specs/components/agg_types/metrics/_extended_stats.js new file mode 100644 index 00000000000000..7b26166a5c42d6 --- /dev/null +++ b/test/unit/specs/components/agg_types/metrics/_extended_stats.js @@ -0,0 +1,104 @@ +define(function (require) { + return ['¡Extended Stats!', function () { + var _ = require('lodash'); + var $ = require('jquery'); + + var vis; + var agg; + + beforeEach(module('kibana')); + beforeEach(inject(function (Private) { + var Vis = Private(require('components/vis/vis')); + var indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern')); + + // the vis which wraps the agg + vis = new Vis(indexPattern, { + type: 'histogram', + aggs: [ + { type: 'extended_stats', params: { field: 'bytes' } } + ] + }); + + // the extended_stats agg + agg = vis.aggs[0]; + + })); + + describe('#makeLabel', function () { + it('makes pretty labels', function () { + expect(agg.makeLabel()).to.be('Extended Stats on bytes'); + }); + }); + + describe('names param', function () { + it('defaults to the full metric list', function () { + expect(_.size(agg.params.names)).to.be.greaterThan(0); + expect(agg.params.names).to.eql(agg.type.statNames); + }); + + describe('editor controller', function () { + var $el; + var $scope; + var $rootScope; + + beforeEach(inject(function ($injector, $compile) { + $rootScope = $injector.get('$rootScope'); + + $scope = $rootScope.$new(); + $scope.agg = agg; + $scope.aggParam = agg.type.params.byName.names; + + $el = $($scope.aggParam.editor); + $compile($el)($scope); + })); + + afterEach(function () { + $el.remove(); + $scope.$destroy(); + }); + + it('reflects the selected names as selected checkboxes', function () { + agg.params.names = _.sample(agg.type.statNames, 3); + $rootScope.$apply(); + + var $checks = $el.find('input[type=checkbox]'); + expect($checks).to.have.length(agg.type.statNames.length); + + $checks.each(function () { + var $check = $(this); + var name = $check.parent().text().trim(); + var index = agg.params.names.indexOf(name); + + if (!$check.is(':checked')) expect(index).to.be(-1); + else expect(index).to.be.greaterThan(-1); + }); + }); + + it('syncs the checked boxes with the name list', function () { + agg.params.names = []; + $rootScope.$apply(); + + var $checks = $el.find('input[type=checkbox]'); + expect($checks).to.have.length(agg.type.statNames.length); + + $checks.each(function (i) { + var $check = $(this).click(); + $rootScope.$apply(); + + var name = $check.parent().text().trim(); + var index = agg.params.names.indexOf(name); + expect(index).to.be(i); + expect(agg.params.names).to.have.length(i + 1); + }); + }); + }); + }); + + describe('#getResponseAggs', function () { + it('creates a response agg for each name', function () { + var aggs = agg.type.getResponseAggs(agg); + expect(agg.params.names).to.eql(_.pluck(aggs, 'key')); + }); + }); + }]; +}); \ No newline at end of file diff --git a/test/unit/specs/components/fancy_forms/fancy_forms.js b/test/unit/specs/components/fancy_forms/fancy_forms.js new file mode 100644 index 00000000000000..8e4c7d4134eeb3 --- /dev/null +++ b/test/unit/specs/components/fancy_forms/fancy_forms.js @@ -0,0 +1,94 @@ +define(function (require) { + var $ = require('jquery'); + + describe('fancy forms', function () { + var $baseEl = $('
').append( + $('') + ); + + var $el; + var $scope; + var $compile; + var $rootScope; + var ngForm; + var ngModel; + + beforeEach(module('kibana')); + beforeEach(inject(function ($injector) { + $rootScope = $injector.get('$rootScope'); + $compile = $injector.get('$compile'); + + $scope = $rootScope.$new(); + $el = $baseEl.clone(); + + $compile($el)($scope); + $scope.$apply(); + + ngForm = $el.controller('form'); + ngModel = $el.find('input').controller('ngModel'); + })); + + describe('ngFormController', function () { + it('counts errors', function () { + expect(ngForm.errorCount()).to.be(1); + }); + + it('clears errors', function () { + $scope.val = 'someting'; + $scope.$apply(); + expect(ngForm.errorCount()).to.be(0); + }); + + it('describes 0 errors', function () { + $scope.val = 'someting'; + $scope.$apply(); + expect(ngForm.describeErrors()).to.be('0 Errors'); + }); + + it('describes 1 error', function () { + $scope.$apply(); + expect(ngForm.describeErrors()).to.be('1 Error'); + }); + }); + + describe('ngModelController', function () { + it('gives access to the ngFormController', function () { + expect(ngModel.$getForm()).to.be(ngForm); + }); + + it('allows setting the model dirty', function () { + expect($el.find('input.ng-dirty')).to.have.length(0); + ngModel.$setDirty(); + expect($el.find('input.ng-dirty')).to.have.length(1); + }); + + it('sets the model dirty when it moves from valid to invalid', function () { + // clear out the old scope/el + $scope.$destroy(); + $el = $baseEl.clone(); + $scope = $rootScope.$new(); + + // start with a valid value + $scope.val = 'something'; + $compile($el)($scope); + $rootScope.$apply(); + + // ensure that the field is valid and pristinve + var $valid = $el.find('input.ng-valid'); + expect($valid).to.have.length(1); + expect($valid.hasClass('ng-pristine')).to.be(true); + expect($valid.hasClass('ng-dirty')).to.be(false); + + // remove the value without actually setting the view model + $scope.val = null; + $rootScope.$apply(); + + // ensure that the field is now invalid and dirty + var $invalid = $el.find('input.ng-invalid'); + expect($invalid).to.have.length(1); + expect($valid.hasClass('ng-pristine')).to.be(false); + expect($valid.hasClass('ng-dirty')).to.be(true); + }); + }); + }); +}); \ No newline at end of file