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 @@