mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Merge remote-tracking branch 'upstream/master' into tweat/visualize_button_position
This commit is contained in:
commit
94085f9e02
34 changed files with 833 additions and 398 deletions
|
@ -0,0 +1,9 @@
|
|||
<div class="form-group">
|
||||
<label>Values</label>
|
||||
<kbn-number-list
|
||||
ng-model="agg.params.values"
|
||||
unit-name="value"
|
||||
range="[-Infinity,Infinity]"
|
||||
>
|
||||
</kbn-number-list>
|
||||
</div>
|
|
@ -0,0 +1,9 @@
|
|||
<div class="form-group">
|
||||
<label>Percents</label>
|
||||
<kbn-number-list
|
||||
ng-model="agg.params.percents"
|
||||
unit-name="percent"
|
||||
range="[0,100]"
|
||||
>
|
||||
</kbn-number-list>
|
||||
</div>
|
|
@ -1,34 +0,0 @@
|
|||
<div class="form-group" ng-controller="agg.type.params.byName.percents.controller">
|
||||
<label>Percentiles</label>
|
||||
<div
|
||||
ng-repeat="value in agg.params.percents track by $index"
|
||||
class="form-group vis-editor-agg-form-row vis-editor-agg-form-row">
|
||||
|
||||
<input
|
||||
ng-model="agg.params.percents[$index]"
|
||||
values-list="agg.params.percents"
|
||||
values-list-min="0"
|
||||
values-list-max="100"
|
||||
input-focus
|
||||
class="form-control">
|
||||
|
||||
<button type="button" ng-click="remove($index, 1)" class="btn btn-danger btn-xs">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<input ng-model="validLength" name="validLength" required type="hidden">
|
||||
<div class="hintbox" ng-show="aggForm.validLength.$invalid">
|
||||
<p>
|
||||
<i class="fa fa-danger text-danger"></i>
|
||||
<strong>Required:</strong> You mush specify at least one percentile
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
ng-click="add()"
|
||||
type="button"
|
||||
class="sidebar-item-button primary">
|
||||
<i class="fa fa-plus"></i> Add Percent
|
||||
</button>
|
||||
</div>
|
|
@ -1,33 +0,0 @@
|
|||
<div class="form-group" ng-controller="agg.type.params.byName.values.controller">
|
||||
<label>Values</label>
|
||||
<div
|
||||
ng-repeat="value in agg.params.values track by $index"
|
||||
class="form-group vis-editor-agg-form-row vis-editor-agg-form-row">
|
||||
|
||||
<input
|
||||
ng-model="agg.params.values[$index]"
|
||||
values-list="agg.params.values"
|
||||
values-list-min="0"
|
||||
input-focus
|
||||
class="form-control">
|
||||
|
||||
<button type="button" ng-click="remove($index, 1)" class="btn btn-danger btn-xs">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<input ng-model="validLength" name="validLength" required type="hidden">
|
||||
<div class="hintbox" ng-show="aggForm.validLength.$invalid">
|
||||
<p>
|
||||
<i class="fa fa-danger text-danger"></i>
|
||||
<strong>Required:</strong> You must specify at least one value
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
ng-click="add()"
|
||||
type="button"
|
||||
class="sidebar-item-button primary">
|
||||
<i class="fa fa-plus"></i> Add value
|
||||
</button>
|
||||
</div>
|
|
@ -5,8 +5,9 @@ define(function (require) {
|
|||
var MetricAggType = Private(require('components/agg_types/metrics/_metric_agg_type'));
|
||||
var getResponseAggConfig = Private(require('components/agg_types/metrics/_get_response_agg_config'));
|
||||
|
||||
require('components/agg_types/controls/_values_list');
|
||||
var valuesEditor = require('text!components/agg_types/controls/values.html');
|
||||
var valuesEditor = require('text!components/agg_types/controls/percentile_ranks.html');
|
||||
// required by the values editor
|
||||
require('components/number_list/number_list');
|
||||
|
||||
var valueProps = {
|
||||
makeLabel: function () {
|
||||
|
@ -28,20 +29,7 @@ define(function (require) {
|
|||
{
|
||||
name: 'values',
|
||||
editor: valuesEditor,
|
||||
default: [],
|
||||
controller: function ($scope) {
|
||||
$scope.remove = function (index) {
|
||||
$scope.agg.params.values.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.add = function () {
|
||||
$scope.agg.params.values.push(_.last($scope.agg.params.values) + 1);
|
||||
};
|
||||
|
||||
$scope.$watchCollection('agg.params.values', function (values) {
|
||||
$scope.validLength = _.size(values) || null;
|
||||
});
|
||||
}
|
||||
default: []
|
||||
}
|
||||
],
|
||||
getResponseAggs: function (agg) {
|
||||
|
@ -60,4 +48,4 @@ define(function (require) {
|
|||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,8 +6,9 @@ define(function (require) {
|
|||
var getResponseAggConfig = Private(require('components/agg_types/metrics/_get_response_agg_config'));
|
||||
var ordinalSuffix = require('utils/ordinal_suffix');
|
||||
|
||||
require('components/agg_types/controls/_values_list');
|
||||
var percentEditor = require('text!components/agg_types/controls/percents.html');
|
||||
var percentsEditor = require('text!components/agg_types/controls/percentiles.html');
|
||||
// required by the percentiles editor
|
||||
require('components/number_list/number_list');
|
||||
|
||||
var valueProps = {
|
||||
makeLabel: function () {
|
||||
|
@ -28,21 +29,8 @@ define(function (require) {
|
|||
},
|
||||
{
|
||||
name: 'percents',
|
||||
editor: percentEditor,
|
||||
default: [1, 5, 25, 50, 75, 95, 99],
|
||||
controller: function ($scope) {
|
||||
$scope.remove = function (index) {
|
||||
$scope.agg.params.percents.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.add = function () {
|
||||
$scope.agg.params.percents.push(_.last($scope.agg.params.percents) + 1);
|
||||
};
|
||||
|
||||
$scope.$watchCollection('agg.params.percents', function (percents) {
|
||||
$scope.validLength = _.size(percents) || null;
|
||||
});
|
||||
}
|
||||
editor: percentsEditor,
|
||||
default: [1, 5, 25, 50, 75, 95, 99]
|
||||
}
|
||||
],
|
||||
getResponseAggs: function (agg) {
|
||||
|
@ -61,4 +49,4 @@ define(function (require) {
|
|||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -43,6 +43,7 @@ define(function (require) {
|
|||
|
||||
// the id of the document
|
||||
self.id = config.id || void 0;
|
||||
self.defaults = config.defaults;
|
||||
|
||||
/**
|
||||
* Asynchronously initialize this object - will only run
|
||||
|
@ -119,20 +120,7 @@ define(function (require) {
|
|||
_.assign(self, self._source);
|
||||
|
||||
return Promise.try(function () {
|
||||
// if we have a searchSource, set it's state based on the searchSourceJSON field
|
||||
if (self.searchSource) {
|
||||
var state = {};
|
||||
try {
|
||||
state = JSON.parse(meta.searchSourceJSON);
|
||||
} catch (e) {}
|
||||
|
||||
var oldState = self.searchSource.toJSON();
|
||||
var fnProps = _.transform(oldState, function (dynamic, val, name) {
|
||||
if (_.isFunction(val)) dynamic[name] = val;
|
||||
}, {});
|
||||
|
||||
self.searchSource.set(_.defaults(state, fnProps));
|
||||
}
|
||||
parseSearchSource(meta.searchSourceJSON);
|
||||
})
|
||||
.then(hydrateIndexPattern)
|
||||
.then(function () {
|
||||
|
@ -153,6 +141,23 @@ define(function (require) {
|
|||
});
|
||||
});
|
||||
|
||||
function parseSearchSource(searchSourceJson) {
|
||||
if (!self.searchSource) return;
|
||||
|
||||
// if we have a searchSource, set its state based on the searchSourceJSON field
|
||||
var state = {};
|
||||
try {
|
||||
state = JSON.parse(searchSourceJson);
|
||||
} catch (e) {}
|
||||
|
||||
var oldState = self.searchSource.toJSON();
|
||||
var fnProps = _.transform(oldState, function (dynamic, val, name) {
|
||||
if (_.isFunction(val)) dynamic[name] = val;
|
||||
}, {});
|
||||
|
||||
self.searchSource.set(_.defaults(state, fnProps));
|
||||
}
|
||||
|
||||
/**
|
||||
* After creation or fetching from ES, ensure that the searchSources index indexPattern
|
||||
* is an bonafide IndexPattern object.
|
||||
|
@ -181,14 +186,12 @@ define(function (require) {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save this object
|
||||
* Serialize this object
|
||||
*
|
||||
* @return {Promise}
|
||||
* @resolved {String} - The id of the doc
|
||||
* @return {Object}
|
||||
*/
|
||||
self.save = function () {
|
||||
self.serialize = function () {
|
||||
var body = {};
|
||||
|
||||
_.forOwn(mapping, function (fieldMapping, fieldName) {
|
||||
|
@ -205,6 +208,18 @@ define(function (require) {
|
|||
};
|
||||
}
|
||||
|
||||
return body;
|
||||
};
|
||||
|
||||
/**
|
||||
* Save this object
|
||||
*
|
||||
* @return {Promise}
|
||||
* @resolved {String} - The id of the doc
|
||||
*/
|
||||
self.save = function () {
|
||||
|
||||
var body = self.serialize();
|
||||
|
||||
// Slugify the object id
|
||||
self.id = slugifyId(self.id);
|
||||
|
@ -229,7 +244,7 @@ define(function (require) {
|
|||
return docSource.doCreate(source)
|
||||
.then(finish)
|
||||
.catch(function (err) {
|
||||
var confirmMessage = 'Are you sure you want to overwrite this?';
|
||||
var confirmMessage = 'Are you sure you want to overwrite ' + self.title + '?';
|
||||
if (_.deepGet(err, 'origError.status') === 409 && window.confirm(confirmMessage)) {
|
||||
return docSource.doIndex(source).then(finish);
|
||||
}
|
||||
|
@ -264,7 +279,6 @@ define(function (require) {
|
|||
});
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
return SavedObject;
|
||||
|
|
34
src/kibana/components/number_list/number_list.html
Normal file
34
src/kibana/components/number_list/number_list.html
Normal file
|
@ -0,0 +1,34 @@
|
|||
<div
|
||||
ng-repeat="value in numberListCntr.getList() track by $index"
|
||||
class="form-group vis-editor-agg-form-row vis-editor-agg-form-row">
|
||||
|
||||
<input
|
||||
ng-model="numberListCntr.getList()[$index]"
|
||||
kbn-number-list-input
|
||||
input-focus
|
||||
class="form-control">
|
||||
|
||||
<button
|
||||
ng-click="numberListCntr.remove($index, 1)"
|
||||
class="btn btn-danger btn-xs"
|
||||
type="button">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<p ng-show="numberListCntr.invalidLength()" class="text-danger text-center">
|
||||
You must specify at least one {{numberListCntr.getUnitName()}}
|
||||
</p>
|
||||
|
||||
<p ng-show="numberListCntr.undefinedLength()" class="text-primary text-center">
|
||||
<!-- be a bit more polite when the form is first init'd -->
|
||||
Please specify at least one {{numberListCntr.getUnitName()}}
|
||||
</p>
|
||||
|
||||
<button
|
||||
ng-click="numberListCntr.add()"
|
||||
type="button"
|
||||
class="sidebar-item-button primary">
|
||||
<i class="fa fa-plus"></i> Add {{numberListCntr.getUnitName()}}
|
||||
</button>
|
108
src/kibana/components/number_list/number_list.js
Normal file
108
src/kibana/components/number_list/number_list.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var parseRange = require('utils/range');
|
||||
|
||||
require('components/number_list/number_list_input');
|
||||
require('modules')
|
||||
.get('kibana')
|
||||
.directive('kbnNumberList', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: require('text!components/number_list/number_list.html'),
|
||||
controllerAs: 'numberListCntr',
|
||||
require: 'ngModel',
|
||||
controller: function ($scope, $attrs, $parse) {
|
||||
var self = this;
|
||||
|
||||
// Called from the pre-link function once we have the controllers
|
||||
self.init = function (modelCntr) {
|
||||
self.modelCntr = modelCntr;
|
||||
|
||||
self.getList = function () {
|
||||
return self.modelCntr.$modelValue;
|
||||
};
|
||||
|
||||
self.getUnitName = _.partial($parse($attrs.unit), $scope);
|
||||
|
||||
var defaultRange = self.range = parseRange('[0,Infinity)');
|
||||
|
||||
$scope.$watch(function () {
|
||||
return $attrs.range;
|
||||
}, function (range, prev) {
|
||||
if (!range) {
|
||||
self.range = defaultRange;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
self.range = parseRange(range);
|
||||
} catch (e) {
|
||||
throw new TypeError('Unable to parse range: ' + e.message);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Remove an item from list by index
|
||||
* @param {number} index
|
||||
* @return {undefined}
|
||||
*/
|
||||
self.remove = function (index) {
|
||||
var list = self.getList();
|
||||
if (!list) return;
|
||||
|
||||
list.splice(index, 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an item to the end of the list
|
||||
* @return {undefined}
|
||||
*/
|
||||
self.add = function () {
|
||||
var list = self.getList();
|
||||
if (!list) return;
|
||||
|
||||
list.push(_.last(list) + 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check to see if the list is too short.
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
self.tooShort = function () {
|
||||
return _.size(self.getList()) < 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check to see if the list is too short, but simply
|
||||
* because the user hasn't interacted with it yet
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
self.undefinedLength = function () {
|
||||
return self.tooShort() && (self.modelCntr.$untouched && self.modelCntr.$pristine);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check to see if the list is too short
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
self.invalidLength = function () {
|
||||
return self.tooShort() && !self.undefinedLength();
|
||||
};
|
||||
|
||||
$scope.$watchCollection(self.getList, function () {
|
||||
self.modelCntr.$setValidity('numberListLength', !self.tooShort());
|
||||
});
|
||||
};
|
||||
},
|
||||
link: {
|
||||
pre: function ($scope, $el, attrs, ngModelCntr) {
|
||||
$scope.numberListCntr.init(ngModelCntr);
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
});
|
|
@ -6,18 +6,21 @@ define(function (require) {
|
|||
var INVALID = {}; // invalid flag
|
||||
var FLOATABLE = /^[\d\.e\-\+]+$/i;
|
||||
|
||||
var VALIDATION_ERROR = 'numberListRangeAndOrder';
|
||||
var DIRECTIVE_ATTR = 'kbn-number-list-input';
|
||||
|
||||
require('modules')
|
||||
.get('kibana')
|
||||
.directive('valuesList', function ($parse) {
|
||||
.directive('kbnNumberListInput', function ($parse) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function ($scope, $el, attrs, ngModelController) {
|
||||
require: ['ngModel', '^kbnNumberList'],
|
||||
link: function ($scope, $el, attrs, controllers) {
|
||||
var ngModelCntr = controllers[0];
|
||||
var numberListCntr = controllers[1];
|
||||
|
||||
var $setModel = $parse(attrs.ngModel).assign;
|
||||
var $repeater = $el.closest('[ng-repeat]');
|
||||
var $listGetter = $parse(attrs.valuesList);
|
||||
var $minValue = $parse(attrs.valuesListMin);
|
||||
var $maxValue = $parse(attrs.valuesListMax);
|
||||
|
||||
var handlers = {
|
||||
up: change(add, 1),
|
||||
|
@ -29,14 +32,16 @@ define(function (require) {
|
|||
tab: go('next'),
|
||||
'shift-tab': go('prev'),
|
||||
|
||||
'shift-enter': numberListCntr.add,
|
||||
|
||||
backspace: removeIfEmpty,
|
||||
delete: removeIfEmpty
|
||||
};
|
||||
|
||||
function removeIfEmpty(event) {
|
||||
if ($el.val() === '') {
|
||||
if (!ngModelCntr.$viewValue) {
|
||||
$get('prev').focus();
|
||||
$scope.remove($scope.$index);
|
||||
numberListCntr.remove($scope.$index);
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
|
@ -44,7 +49,7 @@ define(function (require) {
|
|||
}
|
||||
|
||||
function $get(dir) {
|
||||
return $repeater[dir]().find('[values-list]');
|
||||
return $repeater[dir]().find('[' + DIRECTIVE_ATTR + ']');
|
||||
}
|
||||
|
||||
function go(dir) {
|
||||
|
@ -88,7 +93,7 @@ define(function (require) {
|
|||
|
||||
function change(using, mod) {
|
||||
return function () {
|
||||
var str = String(ngModelController.$viewValue);
|
||||
var str = String(ngModelCntr.$viewValue);
|
||||
var val = parse(str);
|
||||
if (val === INVALID) return;
|
||||
|
||||
|
@ -96,7 +101,7 @@ define(function (require) {
|
|||
if (next === INVALID) return;
|
||||
|
||||
$el.val(next);
|
||||
ngModelController.$setViewValue(next);
|
||||
ngModelCntr.$setViewValue(next);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -117,17 +122,26 @@ define(function (require) {
|
|||
});
|
||||
|
||||
function parse(viewValue) {
|
||||
viewValue = String(viewValue || 0);
|
||||
var num = viewValue.trim();
|
||||
if (!FLOATABLE.test(num)) return INVALID;
|
||||
num = parseFloat(num);
|
||||
if (isNaN(num)) return INVALID;
|
||||
var num = viewValue;
|
||||
|
||||
var list = $listGetter($scope);
|
||||
var min = list[$scope.$index - 1] || $minValue($scope);
|
||||
var max = list[$scope.$index + 1] || $maxValue($scope);
|
||||
if (typeof num !== 'number' || isNaN(num)) {
|
||||
// parse non-numbers
|
||||
num = String(viewValue || 0).trim();
|
||||
if (!FLOATABLE.test(num)) return INVALID;
|
||||
|
||||
if (num <= min || num >= max) return INVALID;
|
||||
num = parseFloat(num);
|
||||
if (isNaN(num)) return INVALID;
|
||||
}
|
||||
|
||||
var range = numberListCntr.range;
|
||||
if (!range.within(num)) return INVALID;
|
||||
|
||||
if ($scope.$index > 0) {
|
||||
var i = $scope.$index - 1;
|
||||
var list = numberListCntr.getList();
|
||||
var prev = list[i];
|
||||
if (num <= prev) return INVALID;
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
@ -137,31 +151,35 @@ define(function (require) {
|
|||
{
|
||||
fn: $scope.$watchCollection,
|
||||
get: function () {
|
||||
return $listGetter($scope);
|
||||
return numberListCntr.getList();
|
||||
}
|
||||
}
|
||||
], function () {
|
||||
var valid = parse(ngModelController.$viewValue) !== INVALID;
|
||||
ngModelController.$setValidity('valuesList', valid);
|
||||
var valid = parse(ngModelCntr.$viewValue) !== INVALID;
|
||||
ngModelCntr.$setValidity(VALIDATION_ERROR, valid);
|
||||
});
|
||||
|
||||
function validate(then) {
|
||||
return function (input) {
|
||||
var value = parse(input);
|
||||
var valid = value !== INVALID;
|
||||
value = valid ? value : void 0;
|
||||
ngModelController.$setValidity('valuesList', valid);
|
||||
value = valid ? value : input;
|
||||
ngModelCntr.$setValidity(VALIDATION_ERROR, valid);
|
||||
then && then(input, value);
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
ngModelController.$parsers.push(validate());
|
||||
ngModelController.$formatters.push(validate(function (input, value) {
|
||||
ngModelCntr.$parsers.push(validate());
|
||||
ngModelCntr.$formatters.push(validate(function (input, value) {
|
||||
if (input !== value) $setModel($scope, value);
|
||||
}));
|
||||
|
||||
if (parse(ngModelCntr.$viewValue) === INVALID) {
|
||||
ngModelCntr.$setTouched();
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
});
|
|
@ -1,19 +1,23 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var modules = require('modules');
|
||||
var urlParam = '_a';
|
||||
|
||||
|
||||
function AppStateProvider(Private, $rootScope, getAppState) {
|
||||
var State = Private(require('components/state_management/state'));
|
||||
|
||||
|
||||
_(AppState).inherits(State);
|
||||
function AppState(defaults) {
|
||||
AppState.Super.call(this, '_a', defaults);
|
||||
AppState.Super.call(this, urlParam, defaults);
|
||||
getAppState._set(this);
|
||||
}
|
||||
|
||||
// if the url param is missing, write it back
|
||||
AppState.prototype._persistAcrossApps = false;
|
||||
|
||||
|
||||
AppState.prototype.destroy = function () {
|
||||
AppState.Super.prototype.destroy.call(this);
|
||||
getAppState._set(null);
|
||||
|
@ -26,13 +30,19 @@ define(function (require) {
|
|||
.factory('AppState', function (Private) {
|
||||
return Private(AppStateProvider);
|
||||
})
|
||||
.service('getAppState', function () {
|
||||
.service('getAppState', function ($location) {
|
||||
var currentAppState;
|
||||
|
||||
function get() {
|
||||
return currentAppState;
|
||||
}
|
||||
|
||||
// Checks to see if the appState might already exist, even if it hasn't been newed up
|
||||
get.previouslyStored = function () {
|
||||
var search = $location.search();
|
||||
return search[urlParam] ? true : false;
|
||||
};
|
||||
|
||||
get._set = function (current) {
|
||||
currentAppState = current;
|
||||
};
|
||||
|
|
|
@ -49,6 +49,11 @@ define(function (require) {
|
|||
throw new Error('No valid data!');
|
||||
}
|
||||
|
||||
if (this.handler) {
|
||||
this.data = null;
|
||||
this._runOnHandler('destroy');
|
||||
}
|
||||
|
||||
this.data = data;
|
||||
this.handler = handlerTypes[chartType](this) || handlerTypes.column(this);
|
||||
this._runOnHandler('render');
|
||||
|
|
32
src/kibana/directives/file_upload.js
Normal file
32
src/kibana/directives/file_upload.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
define(function (require) {
|
||||
var module = require('modules').get('kibana');
|
||||
var $ = require('jquery');
|
||||
|
||||
module.directive('fileUpload', function ($parse) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function ($scope, $elem, attrs) {
|
||||
var onUpload = $parse(attrs.fileUpload);
|
||||
|
||||
var $fileInput = $('<input type="file" style="opacity: 0" id="testfile" />');
|
||||
$elem.after($fileInput);
|
||||
|
||||
$fileInput.on('change', function (e) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
$scope.$apply(function () {
|
||||
onUpload($scope, {fileContents: e.target.result});
|
||||
});
|
||||
};
|
||||
|
||||
var target = e.srcElement || e.target;
|
||||
if (target && target.files && target.files.length) reader.readAsText(target.files[0]);
|
||||
});
|
||||
|
||||
$elem.on('click', function (e) {
|
||||
$fileInput.trigger('click');
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
|
@ -49,7 +49,7 @@ define(function (require) {
|
|||
|
||||
app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter, kbnUrl) {
|
||||
return {
|
||||
controller: function ($scope, $route, $routeParams, $location, configFile, Private) {
|
||||
controller: function ($scope, $route, $routeParams, $location, configFile, Private, getAppState) {
|
||||
var filterBarWatchFilters = Private(require('components/filter_bar/lib/watchFilters'));
|
||||
|
||||
var notify = new Notifier({
|
||||
|
@ -57,6 +57,12 @@ define(function (require) {
|
|||
});
|
||||
|
||||
var dash = $scope.dash = $route.current.locals.dash;
|
||||
|
||||
if (dash.timeRestore && dash.timeTo && dash.timeFrom && !getAppState.previouslyStored()) {
|
||||
timefilter.time.to = dash.timeTo;
|
||||
timefilter.time.from = dash.timeFrom;
|
||||
}
|
||||
|
||||
$scope.$on('$destroy', dash.destroy);
|
||||
|
||||
var matchQueryFilter = function (filter) {
|
||||
|
@ -135,6 +141,8 @@ define(function (require) {
|
|||
$state.title = dash.id = dash.title;
|
||||
$state.save();
|
||||
dash.panelsJSON = angular.toJson($state.panels);
|
||||
dash.timeFrom = dash.timeRestore ? timefilter.time.from : undefined;
|
||||
dash.timeTo = dash.timeRestore ? timefilter.time.to : undefined;
|
||||
|
||||
dash.save()
|
||||
.then(function (id) {
|
||||
|
|
|
@ -3,5 +3,11 @@
|
|||
<label for="dashboardTitle">Save As</label>
|
||||
<input id="dashboardTitle" type="text" ng-model="opts.dashboard.title" class="form-control" placeholder="Dashboard title" input-focus="select">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="opts.dashboard.timeRestore" ng-checked="opts.dashboard.timeRestore">
|
||||
Store time with dashboard <i class="fa fa-info-circle ng-scope" tooltip="Change the time filter to the currently selected time each time this dashboard is loaded" tooltip-placement="" tooltip-popup-delay="250"></i>
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" ng-disabled="!opts.dashboard.title" class="btn btn-primary" aria-label="Save dashboard">Save</button>
|
||||
</form>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
define(function (require) {
|
||||
var module = require('modules').get('app/dashboard');
|
||||
var _ = require('lodash');
|
||||
var moment = require('moment');
|
||||
|
||||
// Used only by the savedDashboards service, usually no reason to change this
|
||||
module.factory('SavedDashboard', function (courier) {
|
||||
|
@ -23,7 +24,10 @@ define(function (require) {
|
|||
hits: 'integer',
|
||||
description: 'string',
|
||||
panelsJSON: 'string',
|
||||
version: 'integer'
|
||||
version: 'integer',
|
||||
timeRestore: 'boolean',
|
||||
timeTo: 'string',
|
||||
timeFrom: 'string'
|
||||
},
|
||||
|
||||
// defeult values to assign to the doc
|
||||
|
@ -32,7 +36,10 @@ define(function (require) {
|
|||
hits: 0,
|
||||
description: '',
|
||||
panelsJSON: '[]',
|
||||
version: 1
|
||||
version: 1,
|
||||
timeRestore: false,
|
||||
timeTo: undefined,
|
||||
timeFrom: undefined
|
||||
},
|
||||
|
||||
searchSource: true,
|
||||
|
|
|
@ -8,11 +8,15 @@
|
|||
<span class="smaller">{{conf.description}}</span>
|
||||
</td>
|
||||
<td class="value">
|
||||
|
||||
|
||||
<!-- Settings editors -->
|
||||
<form
|
||||
name="forms.configEdit"
|
||||
ng-if="conf.editting"
|
||||
ng-submit="save(conf)"
|
||||
role="form">
|
||||
|
||||
<input
|
||||
ng-show="conf.normal"
|
||||
ng-model="conf.unsavedValue"
|
||||
|
@ -20,14 +24,7 @@
|
|||
placeholder="{{conf.value || conf.defVal}}"
|
||||
type="text"
|
||||
class="form-control">
|
||||
<input
|
||||
ng-show="conf.array"
|
||||
ng-list=","
|
||||
ng-model="conf.unsavedValue"
|
||||
ng-keyup="maybeCancel($event, conf)"
|
||||
placeholder="{{(conf.value || conf.defVal).join(', ')}}"
|
||||
type="text"
|
||||
class="form-control">
|
||||
|
||||
<textarea
|
||||
ng-if="conf.json"
|
||||
type="text"
|
||||
|
@ -36,16 +33,38 @@
|
|||
ng-keyup="maybeCancel($event, conf)"
|
||||
validate-json
|
||||
></textarea>
|
||||
<small ng-show="forms.configEdit.$error.jsonInput">Invalid JSON syntax</small>
|
||||
<small ng-show="forms.configEdit.$error.jsonInput">Invalid JSON syntax</small>
|
||||
|
||||
<input
|
||||
ng-show="conf.array"
|
||||
ng-list=","
|
||||
ng-model="conf.unsavedValue"
|
||||
ng-keyup="maybeCancel($event, conf)"
|
||||
placeholder="{{(conf.value || conf.defVal).join(', ')}}"
|
||||
type="text"
|
||||
class="form-control">
|
||||
|
||||
<input
|
||||
ng-show="conf.bool"
|
||||
ng-model="conf.unsavedValue"
|
||||
type="checkbox"
|
||||
class="form-control">
|
||||
|
||||
<select
|
||||
ng-show="conf.select"
|
||||
name="conf.name"
|
||||
ng-model="conf.unsavedValue"
|
||||
ng-options="option as option for option in conf.options"
|
||||
class="form-control">
|
||||
</select>
|
||||
</form>
|
||||
<span ng-if="!conf.editting && (conf.normal || conf.json)">{{conf.value || conf.defVal}}</span>
|
||||
<span ng-if="!conf.editting && conf.bool">{{conf.value === undefined ? conf.defVal : conf.value}}</span>
|
||||
<span ng-if="!conf.editting && conf.array">{{(conf.value || conf.defVal).join(', ')}}</span>
|
||||
|
||||
<!-- Setting display formats -->
|
||||
<span ng-if="!conf.editting">
|
||||
<span ng-show="(conf.normal || conf.json || conf.select)">{{conf.value || conf.defVal}}</span>
|
||||
<span ng-show="conf.array">{{(conf.value || conf.defVal).join(', ')}}</span>
|
||||
<span ng-show="conf.bool">{{conf.value === undefined ? conf.defVal : conf.value}}</span>
|
||||
</span>
|
||||
|
||||
</td>
|
||||
<td class="actions">
|
||||
|
|
|
@ -20,7 +20,7 @@ define(function (require) {
|
|||
ESC: 27
|
||||
};
|
||||
|
||||
var NAMED_EDITORS = ['json', 'array', 'boolean'];
|
||||
var NAMED_EDITORS = ['json', 'array', 'boolean', 'select'];
|
||||
var NORMAL_EDITOR = ['number', 'string', 'null', 'undefined'];
|
||||
|
||||
function getEditorType(conf) {
|
||||
|
@ -42,11 +42,13 @@ define(function (require) {
|
|||
defVal: def.value,
|
||||
type: getValType(def, val),
|
||||
description: def.description,
|
||||
options: def.options,
|
||||
value: val,
|
||||
};
|
||||
|
||||
var editor = getEditorType(conf);
|
||||
conf.json = editor === 'json';
|
||||
conf.select = editor === 'select';
|
||||
conf.bool = editor === 'boolean';
|
||||
conf.array = editor === 'array';
|
||||
conf.normal = editor === 'normal';
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<kbn-settings-app section="objects">
|
||||
<kbn-settings-objects class="container">
|
||||
<h2>Edit Saved Objects</h2>
|
||||
<div class="header">
|
||||
<h2 class="title">Edit Saved Objects</h2>
|
||||
<button class="btn btn-default controls" ng-click="exportAll()"><i aria-hidden="true" class="fa fa-download"></i> Export</button>
|
||||
<button file-upload="importAll(fileContents)" class="btn btn-default controls" ng-click><i aria-hidden="true" class="fa fa-upload"></i> Import</button>
|
||||
</div>
|
||||
<p>
|
||||
From here you can delete saved objects, such as saved searches. You can also edit the raw data of saved objects. Typically objects are only modified via their associated application, which is probably what you should use instead of this screen. Each tab is limited to 100 results. You can use the filter to find objects not in the default list.
|
||||
</p>
|
||||
|
@ -20,13 +24,16 @@
|
|||
<div class="tab-content">
|
||||
<div class="action-bar">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="deleteAll">
|
||||
<input type="checkbox" ng-checked="currentTab.data.length > 0 && selectedItems.length == currentTab.data.length" ng-click="toggleAll()" />
|
||||
Select All
|
||||
</label>
|
||||
<a ng-disabled="!deleteAllBtn"
|
||||
<a ng-disabled="selectedItems.length == 0"
|
||||
confirm-click="bulkDelete()"
|
||||
confirmation="Are you sure want to delete the selected {{service.title}}? This action is irreversible!"
|
||||
class="delete-all btn btn-danger btn-xs" aria-label="Delete Selected"><i aria-hidden="true" class="fa fa-trash"></i> Delete Selected</a>
|
||||
confirmation="Are you sure want to delete the selected {{currentTab.title}}? This action is irreversible!"
|
||||
class="btn btn-xs btn-danger" aria-label="Delete"><i aria-hidden="true" class="fa fa-trash"></i> Delete</a>
|
||||
<a ng-disabled="selectedItems.length == 0"
|
||||
ng-click="bulkExport()"
|
||||
class="btn btn-xs btn-default" aria-label="Export"><i aria-hidden="true" class="fa fa-download"></i> Export</a>
|
||||
</div>
|
||||
<div ng-repeat="service in services" ng-class="{ active: state.tab === service.title }" class="tab-pane">
|
||||
<ul class="list-unstyled">
|
||||
|
@ -51,8 +58,8 @@
|
|||
|
||||
<div class="pull-left">
|
||||
<input
|
||||
ng-click="item.checked = !item.checked; toggleDeleteBtn(service)"
|
||||
ng-checked="item.checked"
|
||||
ng-click="toggleItem(item)"
|
||||
ng-checked="selectedItems.indexOf(item) >= 0"
|
||||
type="checkbox" >
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var angular = require('angular');
|
||||
var saveAs = require('file_saver');
|
||||
var registry = require('plugins/settings/saved_object_registry');
|
||||
var objectIndexHTML = require('text!plugins/settings/sections/objects/_objects.html');
|
||||
|
||||
require('directives/file_upload');
|
||||
|
||||
require('routes')
|
||||
.when('/settings/objects', {
|
||||
template: objectIndexHTML
|
||||
|
@ -12,42 +16,52 @@ define(function (require) {
|
|||
.directive('kbnSettingsObjects', function (config, Notifier, Private, kbnUrl) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
controller: function ($scope, $injector, $q, AppState) {
|
||||
controller: function ($scope, $injector, $q, AppState, es) {
|
||||
var notify = new Notifier({ location: 'Saved Objects' });
|
||||
|
||||
var $state = $scope.state = new AppState();
|
||||
|
||||
var resetCheckBoxes = function () {
|
||||
$scope.deleteAll = false;
|
||||
_.each($scope.services, function (service) {
|
||||
_.each(service.data, function (item) {
|
||||
item.checked = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
$scope.currentTab = null;
|
||||
$scope.selectedItems = [];
|
||||
|
||||
var getData = function (filter) {
|
||||
var services = registry.all().map(function (obj) {
|
||||
var service = $injector.get(obj.service);
|
||||
return service.find(filter).then(function (data) {
|
||||
return { service: obj.service, title: obj.title, data: data.hits, total: data.total };
|
||||
return {
|
||||
service: service,
|
||||
serviceName: obj.service,
|
||||
title: obj.title,
|
||||
type: service.type,
|
||||
data: data.hits,
|
||||
total: data.total
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
$q.all(services).then(function (data) {
|
||||
$scope.services = _.sortBy(data, 'title');
|
||||
if (!$state.tab) {
|
||||
$scope.changeTab($scope.services[0]);
|
||||
}
|
||||
var tab = $scope.services[0];
|
||||
if ($state.tab) tab = _.find($scope.services, {title: $state.tab});
|
||||
$scope.changeTab(tab);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$watch('deleteAll', function (checked) {
|
||||
var service = _.find($scope.services, { title: $state.tab });
|
||||
if (!service) return;
|
||||
_.each(service.data, function (item) {
|
||||
item.checked = checked;
|
||||
});
|
||||
$scope.toggleDeleteBtn(service);
|
||||
});
|
||||
$scope.toggleAll = function () {
|
||||
if ($scope.selectedItems.length === $scope.currentTab.data.length) {
|
||||
$scope.selectedItems.length = 0;
|
||||
} else {
|
||||
$scope.selectedItems = [].concat($scope.currentTab.data);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleItem = function (item) {
|
||||
var i = $scope.selectedItems.indexOf(item);
|
||||
if (i >= 0) {
|
||||
$scope.selectedItems.splice(i, 1);
|
||||
} else {
|
||||
$scope.selectedItems.push(item);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.open = function (item) {
|
||||
kbnUrl.change(item.url.substr(1));
|
||||
|
@ -55,43 +69,103 @@ define(function (require) {
|
|||
|
||||
$scope.edit = function (service, item) {
|
||||
var params = {
|
||||
service: service.service,
|
||||
service: service.serviceName,
|
||||
id: item.id
|
||||
};
|
||||
|
||||
kbnUrl.change('/settings/objects/{{ service }}/{{ id }}', params);
|
||||
};
|
||||
|
||||
$scope.toggleDeleteBtn = function (service) {
|
||||
$scope.deleteAllBtn = _.some(service.data, { checked: true});
|
||||
$scope.bulkDelete = function () {
|
||||
$scope.currentTab.service.delete(_.pluck($scope.selectedItems, 'id')).then(refreshData);
|
||||
};
|
||||
|
||||
$scope.bulkDelete = function () {
|
||||
var serviceObj = _.find($scope.services, { title: $state.tab });
|
||||
if (!serviceObj) return;
|
||||
var service = $injector.get(serviceObj.service);
|
||||
var ids = _(serviceObj.data)
|
||||
.filter({ checked: true})
|
||||
.pluck('id')
|
||||
.value();
|
||||
service.delete(ids).then(function (resp) {
|
||||
serviceObj.data = _.filter(serviceObj.data, function (obj) {
|
||||
return !obj.checked;
|
||||
});
|
||||
resetCheckBoxes();
|
||||
$scope.bulkExport = function () {
|
||||
var objs = $scope.selectedItems.map(_.partialRight(_.extend, {type: $scope.currentTab.type}));
|
||||
retrieveAndExportDocs(objs);
|
||||
};
|
||||
|
||||
$scope.exportAll = function () {
|
||||
var objs = $scope.services.map(function (service) {
|
||||
return service.data.map(_.partialRight(_.extend, {type: service.type}));
|
||||
});
|
||||
retrieveAndExportDocs(_.flatten(objs));
|
||||
};
|
||||
|
||||
function retrieveAndExportDocs(objs) {
|
||||
es.mget({
|
||||
index: config.file.kibana_index,
|
||||
body: {docs: objs.map(transformToMget)}
|
||||
})
|
||||
.then(function (response) {
|
||||
saveToFile(response.docs.map(_.partialRight(_.pick, '_id', '_type', '_source')));
|
||||
});
|
||||
}
|
||||
|
||||
// Takes an object and returns the associated data needed for an mget API request
|
||||
function transformToMget(obj) {
|
||||
return {_id: obj.id, _type: obj.type};
|
||||
}
|
||||
|
||||
function saveToFile(results) {
|
||||
var blob = new Blob([angular.toJson(results, true)], {type: 'application/json'});
|
||||
saveAs(blob, 'export.json');
|
||||
}
|
||||
|
||||
$scope.importAll = function (fileContents) {
|
||||
var docs;
|
||||
try {
|
||||
docs = JSON.parse(fileContents);
|
||||
} catch (e) {
|
||||
notify.error('The file could not be processed.');
|
||||
}
|
||||
|
||||
return es.mget({
|
||||
index: config.file.kibana_index,
|
||||
body: {docs: docs.map(_.partialRight(_.pick, '_id', '_type'))}
|
||||
})
|
||||
.then(function (response) {
|
||||
var existingDocs = _.where(response.docs, {found: true});
|
||||
var confirmMessage = 'The following objects will be overwritten:\n\n';
|
||||
if (existingDocs.length === 0 || window.confirm(confirmMessage + _.pluck(existingDocs, '_id').join('\n'))) {
|
||||
return es.bulk({
|
||||
index: config.file.kibana_index,
|
||||
body: _.flatten(docs.map(transformToBulk))
|
||||
})
|
||||
.then(refreshIndex)
|
||||
.then(refreshData, notify.error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.changeTab = function (obj) {
|
||||
$state.tab = obj.title;
|
||||
// Takes a doc and returns the associated two entries for an index bulk API request
|
||||
function transformToBulk(doc) {
|
||||
return [
|
||||
{index: _.pick(doc, '_id', '_type')},
|
||||
doc._source
|
||||
];
|
||||
}
|
||||
|
||||
function refreshIndex() {
|
||||
return es.indices.refresh({
|
||||
index: config.file.kibana_index
|
||||
});
|
||||
}
|
||||
|
||||
function refreshData() {
|
||||
return getData($scope.advancedFilter);
|
||||
}
|
||||
|
||||
$scope.changeTab = function (tab) {
|
||||
$scope.currentTab = tab;
|
||||
$scope.selectedItems.length = 0;
|
||||
$state.tab = tab.title;
|
||||
$state.save();
|
||||
resetCheckBoxes();
|
||||
};
|
||||
|
||||
$scope.$watch('advancedFilter', function (filter) {
|
||||
getData(filter);
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
<textarea rows="1" msd-elastic ng-if="field.type === 'text'" ng-model="field.value" class="form-control span12"/>
|
||||
<input ng-if="field.type === 'number'" type="number" ng-model="field.value" class="form-control span12"/>
|
||||
<div ng-if="field.type === 'json' || field.type === 'array'" ui-ace="{ onLoad: aceLoaded, mode: 'json' }" id="{{field.name}}" ng-model="field.value" class="form-control"></div>
|
||||
<input ng-if="field.type === 'boolean'" type="checkbox" ng-model="field.value" ng-checked="field.value">
|
||||
</div>
|
||||
</form>
|
||||
<div class="form-group">
|
||||
|
|
|
@ -29,7 +29,7 @@ define(function (require) {
|
|||
*
|
||||
* @param {array} memo The stack of fields
|
||||
* @param {mixed} value The value of the field
|
||||
* @param {stirng} key The key of the field
|
||||
* @param {string} key The key of the field
|
||||
* @param {object} collection This is a reference the collection being reduced
|
||||
* @param {array} parents The parent keys to the field
|
||||
* @returns {array}
|
||||
|
@ -55,11 +55,12 @@ define(function (require) {
|
|||
} else if (_.isArray(field.value)) {
|
||||
field.type = 'array';
|
||||
field.value = angular.toJson(field.value, true);
|
||||
} else if (_.isBoolean(field.value)) {
|
||||
field.type = 'boolean';
|
||||
field.value = field.value;
|
||||
} else if (_.isPlainObject(field.value)) {
|
||||
// do something recursive
|
||||
return _.reduce(field.value, _.partialRight(createField, parents), memo);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
memo.push(field);
|
||||
|
@ -74,15 +75,10 @@ define(function (require) {
|
|||
|
||||
$scope.title = inflection.singularize(serviceObj.title);
|
||||
|
||||
es.get({
|
||||
index: config.file.kibana_index,
|
||||
type: service.type,
|
||||
id: $routeParams.id
|
||||
})
|
||||
.then(function (obj) {
|
||||
service.get($routeParams.id).then(function (obj) {
|
||||
$scope.obj = obj;
|
||||
$scope.link = service.urlFor(obj._id);
|
||||
$scope.fields = _.reduce(obj._source, createField, []);
|
||||
$scope.link = service.urlFor(obj.id);
|
||||
$scope.fields = _.reduce(_.defaults(obj.serialize(), obj.defaults), createField, []);
|
||||
})
|
||||
.catch(notify.fatal);
|
||||
|
||||
|
|
|
@ -48,12 +48,18 @@ kbn-settings-objects {
|
|||
font-weight: normal;
|
||||
}
|
||||
|
||||
.delete-all {
|
||||
.btn {
|
||||
font-size: 10px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
.title, .controls {
|
||||
padding-right: 1em;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kbn-settings-advanced {
|
||||
|
|
|
@ -1,84 +1,81 @@
|
|||
<ng-form name="aggForm">
|
||||
<!-- header -->
|
||||
<div class="vis-editor-agg-header">
|
||||
|
||||
<!-- header -->
|
||||
<div class="vis-editor-agg-header">
|
||||
<!-- open/close editor -->
|
||||
<button
|
||||
aria-label="{{ editorOpen ? 'Close Editor' : 'Open Editor' }}"
|
||||
ng-click="editorOpen = !editorOpen"
|
||||
type="button"
|
||||
class="btn btn-xs vis-editor-agg-header-toggle">
|
||||
<i aria-hidden="true" ng-class="{ 'fa-caret-down': editorOpen, 'fa-caret-right': !editorOpen }" class="fa"></i>
|
||||
</button>
|
||||
|
||||
<!-- open/close editor -->
|
||||
<!-- title -->
|
||||
<span class="vis-editor-agg-header-title">
|
||||
{{ agg.schema.title }}
|
||||
</span>
|
||||
|
||||
<!-- description -->
|
||||
<span ng-if="!editorOpen && aggForm.$valid" class="vis-editor-agg-header-description">
|
||||
{{ describe() }}
|
||||
</span>
|
||||
|
||||
<!-- error -->
|
||||
<span ng-if="!editorOpen && aggForm.$invalid" class="vis-editor-agg-header-description danger">
|
||||
{{ aggForm.describeErrors() }}
|
||||
</span>
|
||||
|
||||
<!-- controls !!!actually disabling buttons will break tooltips¡¡¡ -->
|
||||
<div class="vis-editor-agg-header-controls btn-group">
|
||||
<!-- up button -->
|
||||
<button
|
||||
aria-label="{{ editorOpen ? 'Close Editor' : 'Open Editor' }}"
|
||||
ng-click="editorOpen = !editorOpen"
|
||||
aria-label="Increase Priority"
|
||||
ng-if="stats.count > 1"
|
||||
ng-class="{ disabled: $first }"
|
||||
ng-click="moveUp(agg)"
|
||||
tooltip="Increase Priority"
|
||||
tooltip-append-to-body="true"
|
||||
type="button"
|
||||
class="btn btn-xs vis-editor-agg-header-toggle">
|
||||
<i aria-hidden="true" ng-class="{ 'fa-caret-down': editorOpen, 'fa-caret-right': !editorOpen }" class="fa"></i>
|
||||
class="btn btn-xs btn-default">
|
||||
<i aria-hidden="true" class="fa fa-caret-up"></i>
|
||||
</button>
|
||||
|
||||
<!-- title -->
|
||||
<span class="vis-editor-agg-header-title">
|
||||
{{ agg.schema.title }}
|
||||
</span>
|
||||
<!-- down button -->
|
||||
<button
|
||||
aria-lebl="Decrease Priority"
|
||||
ng-if="stats.count > 1"
|
||||
ng-class="{ disabled: $last }"
|
||||
ng-click="moveDown(agg)"
|
||||
tooltip="Decrease Priority"
|
||||
tooltip-append-to-body="true"
|
||||
type="button"
|
||||
class="btn btn-xs btn-default">
|
||||
<i aria-hidden="true" class="fa fa-caret-down"></i>
|
||||
</button>
|
||||
|
||||
<!-- description -->
|
||||
<span ng-if="!editorOpen && aggForm.$valid" class="vis-editor-agg-header-description">
|
||||
{{ describe() }}
|
||||
</span>
|
||||
|
||||
<!-- error -->
|
||||
<span ng-if="!editorOpen && aggForm.$invalid" class="vis-editor-agg-header-description danger">
|
||||
{{ aggForm.describeErrors() }}
|
||||
</span>
|
||||
|
||||
<!-- controls !!!actually disabling buttons will break tooltips¡¡¡ -->
|
||||
<div class="vis-editor-agg-header-controls btn-group">
|
||||
<!-- up button -->
|
||||
<button
|
||||
aria-label="Increase Priority"
|
||||
ng-if="stats.count > 1"
|
||||
ng-class="{ disabled: $first }"
|
||||
ng-click="moveUp(agg)"
|
||||
tooltip="Increase Priority"
|
||||
tooltip-append-to-body="true"
|
||||
type="button"
|
||||
class="btn btn-xs btn-default">
|
||||
<i aria-hidden="true" class="fa fa-caret-up"></i>
|
||||
</button>
|
||||
|
||||
<!-- down button -->
|
||||
<button
|
||||
aria-lebl="Decrease Priority"
|
||||
ng-if="stats.count > 1"
|
||||
ng-class="{ disabled: $last }"
|
||||
ng-click="moveDown(agg)"
|
||||
tooltip="Decrease Priority"
|
||||
tooltip-append-to-body="true"
|
||||
type="button"
|
||||
class="btn btn-xs btn-default">
|
||||
<i aria-hidden="true" class="fa fa-caret-down"></i>
|
||||
</button>
|
||||
|
||||
<!-- remove button -->
|
||||
<button
|
||||
ng-if="canRemove(agg)"
|
||||
aria-label="Remove Dimension"
|
||||
ng-if="stats.count > stats.min"
|
||||
ng-click="remove(agg)"
|
||||
tooltip="Remove Dimension"
|
||||
tooltip-append-to-body="true"
|
||||
type="button"
|
||||
class="btn btn-xs btn-danger">
|
||||
<i aria-hidden="true" class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- remove button -->
|
||||
<button
|
||||
ng-if="canRemove(agg)"
|
||||
aria-label="Remove Dimension"
|
||||
ng-if="stats.count > stats.min"
|
||||
ng-click="remove(agg)"
|
||||
tooltip="Remove Dimension"
|
||||
tooltip-append-to-body="true"
|
||||
type="button"
|
||||
class="btn btn-xs btn-danger">
|
||||
<i aria-hidden="true" class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<vis-editor-agg-params
|
||||
agg="agg"
|
||||
group-name="groupName"
|
||||
ng-show="editorOpen"
|
||||
class="vis-editor-agg-editor">
|
||||
</vis-editor-agg-params>
|
||||
<vis-editor-agg-params
|
||||
agg="agg"
|
||||
group-name="groupName"
|
||||
ng-show="editorOpen"
|
||||
class="vis-editor-agg-editor">
|
||||
</vis-editor-agg-params>
|
||||
|
||||
<vis-editor-agg-add
|
||||
ng-if="$index + 1 === stats.count"
|
||||
class="vis-editor-agg-add vis-editor-agg-add-subagg">
|
||||
</vis-editor-agg-add>
|
||||
</ng-form>
|
||||
<vis-editor-agg-add
|
||||
ng-if="$index + 1 === stats.count"
|
||||
class="vis-editor-agg-add vis-editor-agg-add-subagg">
|
||||
</vis-editor-agg-add>
|
||||
|
|
|
@ -15,10 +15,14 @@ define(function (require) {
|
|||
});
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
restrict: 'A',
|
||||
template: require('text!plugins/visualize/editor/agg.html'),
|
||||
link: function ($scope, $el) {
|
||||
$scope.editorOpen = $scope.agg.brandNew;
|
||||
require: 'form',
|
||||
link: function ($scope, $el, attrs, kbnForm) {
|
||||
$scope.editorOpen = !!$scope.agg.brandNew;
|
||||
if (!$scope.editorOpen) {
|
||||
$scope.$evalAsync(kbnForm.$setTouched);
|
||||
}
|
||||
|
||||
$scope.$watchMulti([
|
||||
'$index',
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
</nesting-indicator>
|
||||
|
||||
<!-- agg.html - controls for aggregation -->
|
||||
<vis-editor-agg class="vis-editor-agg"></vis-editor-agg>
|
||||
<ng-form vis-editor-agg name="aggForm" class="vis-editor-agg"></ng-form>
|
||||
</div>
|
||||
|
||||
<vis-editor-agg-add ng-if="stats.count === 0" class="vis-editor-agg-add"></vis-editor-agg-add>
|
||||
</div>
|
||||
</li>
|
||||
</li>
|
||||
|
|
|
@ -9,18 +9,22 @@
|
|||
|
||||
<nav class="navbar navbar-default navbar-static-top subnav">
|
||||
<div class="container-fluid">
|
||||
<ul class="nav navbar-nav" ng-init="visConfigSection = vis.type.requiresSearch ? 'data' : 'options'">
|
||||
<li ng-class="{active: visConfigSection == 'data'}" ng-show="vis.type.schemas.metrics">
|
||||
<a class="navbar-link active" ng-click="visConfigSection='data'">Data</a>
|
||||
|
||||
<!-- tabs -->
|
||||
<ul class="nav navbar-nav">
|
||||
<li ng-class="{active: sidebar.section == 'data'}" ng-show="vis.type.schemas.metrics">
|
||||
<a class="navbar-link active" ng-click="sidebar.section='data'">Data</a>
|
||||
</li>
|
||||
<li ng-class="{active: visConfigSection == 'options'}">
|
||||
<a class="navbar-link" ng-click="visConfigSection='options'">Options</a>
|
||||
<li ng-class="{active: sidebar.section == 'options'}">
|
||||
<a class="navbar-link" ng-click="sidebar.section='options'">Options</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- controls -->
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li ng-if="visualizeEditor.softErrorCount() > 0"
|
||||
disabled
|
||||
tooltip="{{visualizeEditor.describeErrors()}}" tooltip-placement="bottom" tooltip-popup-delay="400" tooltip-append-to-body="1">
|
||||
tooltip="{{ visualizeEditor.describeErrors() }}" tooltip-placement="bottom" tooltip-popup-delay="400" tooltip-append-to-body="1">
|
||||
<a class="danger navbar-link">
|
||||
<i class="fa fa-warning"></i>
|
||||
</a>
|
||||
|
@ -45,7 +49,7 @@
|
|||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="vis-editor-config" ng-show="visConfigSection == 'data'">
|
||||
<div class="vis-editor-config" ng-show="sidebar.section == 'data'">
|
||||
<ul class="list-unstyled">
|
||||
<!-- metrics -->
|
||||
<vis-editor-agg-group ng-if="vis.type.schemas.metrics" group-name="metrics"></vis-editor-agg-group>
|
||||
|
@ -55,16 +59,12 @@
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="vis-editor-config" ng-show="visConfigSection == 'options'">
|
||||
<div class="vis-editor-config" ng-show="sidebar.section == 'options'">
|
||||
<ul class="list-unstyled">
|
||||
|
||||
<!-- vis options -->
|
||||
<vis-editor-vis-options
|
||||
vis="vis"
|
||||
>
|
||||
</vis-editor-vis-options>
|
||||
<vis-editor-vis-options vis="vis"></vis-editor-vis-options>
|
||||
|
||||
<!-- apply/discard -->
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -11,9 +11,11 @@ define(function (require) {
|
|||
restrict: 'E',
|
||||
template: require('text!plugins/visualize/editor/sidebar.html'),
|
||||
scope: true,
|
||||
link: function ($scope) {
|
||||
controllerAs: 'sidebar',
|
||||
controller: function ($scope) {
|
||||
$scope.$bind('vis', 'editableVis');
|
||||
this.section = _.get($scope, 'vis.type.requiresSearch') ? 'data' : 'options';
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,29 +3,36 @@ define(function () {
|
|||
var lineSize = 0;
|
||||
var newText = '';
|
||||
var inHtmlTag = false;
|
||||
var inHtmlChar = false;
|
||||
|
||||
for (var i = 0, len = text.length; i < len; i++) {
|
||||
var chr = text.charAt(i);
|
||||
newText += chr;
|
||||
|
||||
switch (chr) {
|
||||
case ' ':
|
||||
case ';':
|
||||
case ':':
|
||||
case ',':
|
||||
// natural line break, reset line size
|
||||
lineSize = 0;
|
||||
break;
|
||||
case '<':
|
||||
inHtmlTag = true;
|
||||
break;
|
||||
case '>':
|
||||
inHtmlTag = false;
|
||||
lineSize = 0;
|
||||
break;
|
||||
default:
|
||||
if (!inHtmlTag) lineSize++;
|
||||
break;
|
||||
case ' ':
|
||||
case ':':
|
||||
case ',':
|
||||
// natural line break, reset line size
|
||||
lineSize = 0;
|
||||
break;
|
||||
case '<':
|
||||
inHtmlTag = true;
|
||||
break;
|
||||
case '>':
|
||||
inHtmlTag = false;
|
||||
lineSize = 0;
|
||||
break;
|
||||
case '&':
|
||||
inHtmlChar = true;
|
||||
break;
|
||||
case ';':
|
||||
inHtmlChar = false;
|
||||
lineSize = 0;
|
||||
break;
|
||||
default:
|
||||
if (!inHtmlTag && !inHtmlChar) lineSize++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (lineSize > minLineLength) {
|
||||
|
@ -38,4 +45,4 @@ define(function () {
|
|||
|
||||
return newText;
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
68
src/kibana/utils/range.js
Normal file
68
src/kibana/utils/range.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
|
||||
/**
|
||||
* Regexp portion that matches our number
|
||||
*
|
||||
* supports:
|
||||
* -100
|
||||
* -100.0
|
||||
* 0
|
||||
* 0.10
|
||||
* Infinity
|
||||
* -Infinity
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
var _RE_NUMBER = '(\\-?(?:\\d+(?:\\.\\d+)?|Infinity))';
|
||||
|
||||
/**
|
||||
* Regexp for the interval notation
|
||||
*
|
||||
* supports:
|
||||
* [num, num]
|
||||
* ( num , num ]
|
||||
* [Infinity,num)
|
||||
*
|
||||
* @type {RegExp}
|
||||
*/
|
||||
var RANGE_RE = new RegExp('^\\s*([\\[|\\(])\\s*' + _RE_NUMBER + '\\s*,\\s*' + _RE_NUMBER + '\\s*([\\]|\\)])\\s*$');
|
||||
|
||||
function parse(input) {
|
||||
|
||||
var match = String(input).match(RANGE_RE);
|
||||
if (!match) {
|
||||
throw new TypeError('expected input to be in interval notation eg. (100, 200]');
|
||||
}
|
||||
|
||||
return new Range(
|
||||
match[1] === '[',
|
||||
parseFloat(match[2]),
|
||||
parseFloat(match[3]),
|
||||
match[4] === ']'
|
||||
);
|
||||
}
|
||||
|
||||
function Range(/* minIncl, min, max, maxIncl */) {
|
||||
var args = _.toArray(arguments);
|
||||
if (args[1] > args[2]) args.reverse();
|
||||
|
||||
this.minInclusive = args[0];
|
||||
this.min = args[1];
|
||||
this.max = args[2];
|
||||
this.maxInclusive = args[3];
|
||||
}
|
||||
|
||||
Range.prototype.within = function (n) {
|
||||
if (this.min === n && !this.minInclusive) return false;
|
||||
if (this.min > n) return false;
|
||||
|
||||
if (this.max === n && !this.maxInclusive) return false;
|
||||
if (this.max < n) return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return parse;
|
||||
|
||||
});
|
|
@ -1,31 +1,33 @@
|
|||
define(function (require) {
|
||||
describe('PercentList directive', function () {
|
||||
describe('NumberList directive', function () {
|
||||
var $ = require('jquery');
|
||||
var _ = require('lodash');
|
||||
var simulateKeys = require('test_utils/simulate_keys');
|
||||
|
||||
require('components/agg_types/controls/_values_list');
|
||||
require('components/number_list/number_list');
|
||||
|
||||
var $el;
|
||||
var $scope;
|
||||
var compile;
|
||||
|
||||
function onlyValidValues() {
|
||||
return $el.find('[ng-model]').toArray().map(function (el) {
|
||||
var ngModel = $(el).controller('ngModel');
|
||||
return ngModel.$valid ? ngModel.$modelValue : undefined;
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(module('kibana'));
|
||||
beforeEach(inject(function ($injector) {
|
||||
var $compile = $injector.get('$compile');
|
||||
var $rootScope = $injector.get('$rootScope');
|
||||
|
||||
$scope = $rootScope.$new();
|
||||
$el = $('<div>').append(
|
||||
$('<input>')
|
||||
.attr('ng-model', 'vals[$index]')
|
||||
.attr('ng-repeat', 'val in vals')
|
||||
.attr('values-list', 'vals')
|
||||
.attr('values-list-min', '0')
|
||||
.attr('values-list-max', '100')
|
||||
);
|
||||
compile = function (vals) {
|
||||
$el = $('<kbn-number-list ng-model="vals">');
|
||||
compile = function (vals, range) {
|
||||
$scope.vals = vals || [];
|
||||
$el.attr('range', range);
|
||||
|
||||
$compile($el)($scope);
|
||||
$scope.$apply();
|
||||
};
|
||||
|
@ -38,51 +40,22 @@ define(function (require) {
|
|||
|
||||
it('fails on invalid numbers', function () {
|
||||
compile([1, 'foo']);
|
||||
expect($scope.vals).to.eql([1, undefined]);
|
||||
expect($el.find('.ng-invalid').size()).to.be(1);
|
||||
expect(onlyValidValues()).to.eql([1, undefined]);
|
||||
});
|
||||
|
||||
it('supports decimals', function () {
|
||||
compile(['1.2', '000001.6', '99.10']);
|
||||
expect($scope.vals).to.eql([1.2, 1.6, 99.1]);
|
||||
expect(onlyValidValues()).to.eql([1.2, 1.6, 99.1]);
|
||||
});
|
||||
|
||||
it('ensures that the values are in order', function () {
|
||||
compile([1, 2, 3, 10, 4, 5]);
|
||||
expect($scope.vals).to.eql([1, 2, 3, undefined, 4, 5]);
|
||||
expect($el.find('.ng-invalid').size()).to.be(1);
|
||||
expect(onlyValidValues()).to.eql([1, 2, 3, 10, undefined, 5]);
|
||||
});
|
||||
|
||||
describe('ensures that the values are between 0 and 100', function () {
|
||||
it(': -1', function () {
|
||||
compile([-1, 1]);
|
||||
expect($scope.vals).to.eql([undefined, 1]);
|
||||
expect($el.find('.ng-invalid').size()).to.be(1);
|
||||
});
|
||||
|
||||
it(': 0', function () {
|
||||
compile([0, 1]);
|
||||
expect($scope.vals).to.eql([undefined, 1]);
|
||||
expect($el.find('.ng-invalid').size()).to.be(1);
|
||||
});
|
||||
|
||||
it(': 0.0001', function () {
|
||||
compile([0.0001, 1]);
|
||||
expect($scope.vals).to.eql([0.0001, 1]);
|
||||
expect($el.find('.ng-invalid').size()).to.be(0);
|
||||
});
|
||||
|
||||
it(': 99.9999999', function () {
|
||||
compile([1, 99.9999999]);
|
||||
expect($scope.vals).to.eql([1, 99.9999999]);
|
||||
expect($el.find('.ng-invalid').size()).to.be(0);
|
||||
});
|
||||
|
||||
it(': 101', function () {
|
||||
compile([1, 101]);
|
||||
expect($scope.vals).to.eql([1, undefined]);
|
||||
expect($el.find('.ng-invalid').size()).to.be(1);
|
||||
});
|
||||
it('requires that values are within a range', function () {
|
||||
compile([50, 100, 200, 250], '[100, 200)');
|
||||
expect(onlyValidValues()).to.eql([undefined, 100, undefined, undefined]);
|
||||
});
|
||||
|
||||
describe('listens for keyboard events', function () {
|
||||
|
@ -94,7 +67,7 @@ define(function (require) {
|
|||
['up', 'up', 'up']
|
||||
)
|
||||
.then(function () {
|
||||
expect($scope.vals).to.eql([4]);
|
||||
expect(onlyValidValues()).to.eql([4]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -118,7 +91,7 @@ define(function (require) {
|
|||
seq
|
||||
)
|
||||
.then(function () {
|
||||
expect($scope.vals).to.eql([5.1]);
|
||||
expect(onlyValidValues()).to.eql([5.1]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -130,7 +103,7 @@ define(function (require) {
|
|||
['down', 'down', 'down']
|
||||
)
|
||||
.then(function () {
|
||||
expect($scope.vals).to.eql([2]);
|
||||
expect(onlyValidValues()).to.eql([2]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -154,7 +127,7 @@ define(function (require) {
|
|||
seq
|
||||
)
|
||||
.then(function () {
|
||||
expect($scope.vals).to.eql([4.8]);
|
||||
expect(onlyValidValues()).to.eql([4.8]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -170,9 +143,9 @@ define(function (require) {
|
|||
|
||||
return simulateKeys(getEl, seq)
|
||||
.then(function () {
|
||||
expect($scope.vals).to.eql([9, 10, 13]);
|
||||
expect(onlyValidValues()).to.eql([9, 10, 13]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -64,7 +64,7 @@ define(function (require) {
|
|||
|
||||
// make the element
|
||||
$elem = angular.element(
|
||||
'<vis-editor-agg></vis-editor-agg>'
|
||||
'<ng-form vis-editor-agg></ng-form>'
|
||||
);
|
||||
|
||||
// compile the html
|
||||
|
@ -77,7 +77,7 @@ define(function (require) {
|
|||
$scope = $elem.isolateScope();
|
||||
}));
|
||||
|
||||
it('should only add the close button only if there is more than the minimum', function () {
|
||||
it('should only add the close button if there is more than the minimum', function () {
|
||||
expect($parentScope.canRemove($parentScope.agg)).to.be(false);
|
||||
$parentScope.group.push({
|
||||
id: '3',
|
||||
|
|
|
@ -8,11 +8,11 @@ define(function (require) {
|
|||
['aaaaaaaaaaaaaaaaaaaa', 'aaaaaaaaaaa<wbr>aaaaaaaaa'],
|
||||
['aaaa aaaaaaaaaaaaaaa', 'aaaa aaaaaaaaaaa<wbr>aaaa'],
|
||||
['aaaa;aaaaaaaaaaaaaaa', 'aaaa;aaaaaaaaaaa<wbr>aaaa'],
|
||||
['aaaa&aaaaaaaaaaaaaaa', 'aaaa&aaaaaa<wbr>aaaaaaaaa'],
|
||||
['aaaa:aaaaaaaaaaaaaaa', 'aaaa:aaaaaaaaaaa<wbr>aaaa'],
|
||||
['aaaa,aaaaaaaaaaaaaaa', 'aaaa,aaaaaaaaaaa<wbr>aaaa'],
|
||||
['aaaa aaaa', 'aaaa aaaa'],
|
||||
['aaaa <mark>aaaa</mark>aaaaaaaaaaaa', 'aaaa <mark>aaaa</mark>aaaaaaaaaaa<wbr>a']
|
||||
['aaaa <mark>aaaa</mark>aaaaaaaaaaaa', 'aaaa <mark>aaaa</mark>aaaaaaaaaaa<wbr>a'],
|
||||
['aaaa"aaaaaaaaaaaa', 'aaaa"aaaaaaaaaaa<wbr>a']
|
||||
];
|
||||
|
||||
_.each(fixtures, function (fixture) {
|
||||
|
|
110
test/unit/specs/utils/range.js
Normal file
110
test/unit/specs/utils/range.js
Normal file
|
@ -0,0 +1,110 @@
|
|||
define(function (require) {
|
||||
describe('Range parsing utility', function () {
|
||||
var _ = require('lodash');
|
||||
var parse = require('utils/range');
|
||||
|
||||
it('throws an error for inputs that are not formatted properly', function () {
|
||||
expect(function () {
|
||||
parse('');
|
||||
}).to.throwException(TypeError);
|
||||
|
||||
expect(function () {
|
||||
parse('p10202');
|
||||
}).to.throwException(TypeError);
|
||||
|
||||
expect(function () {
|
||||
parse('{0,100}');
|
||||
}).to.throwException(TypeError);
|
||||
|
||||
expect(function () {
|
||||
parse('[0,100');
|
||||
}).to.throwException(TypeError);
|
||||
|
||||
expect(function () {
|
||||
parse(')0,100(');
|
||||
}).to.throwException(TypeError);
|
||||
});
|
||||
|
||||
var tests = {
|
||||
'[ 0 , 100 ]': {
|
||||
props: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
minInclusive: true,
|
||||
maxInclusive: true
|
||||
},
|
||||
within: [
|
||||
[0, true],
|
||||
[0.0000001, true],
|
||||
[1, true],
|
||||
[99.99999, true],
|
||||
[100, true]
|
||||
]
|
||||
},
|
||||
'(26.3 , 42]': {
|
||||
props: {
|
||||
min: 26.3,
|
||||
max: 42,
|
||||
minInclusive: false,
|
||||
maxInclusive: true
|
||||
},
|
||||
within: [
|
||||
[26.2999999, false],
|
||||
[26.3000001, true],
|
||||
[30, true],
|
||||
[41, true],
|
||||
[42, true]
|
||||
]
|
||||
},
|
||||
'(-50,50)': {
|
||||
props: {
|
||||
min: -50,
|
||||
max: 50,
|
||||
minInclusive: false,
|
||||
maxInclusive: false
|
||||
},
|
||||
within: [
|
||||
[-50, false],
|
||||
[-49.99999, true],
|
||||
[0, true],
|
||||
[49.99999, true],
|
||||
[50, false]
|
||||
]
|
||||
},
|
||||
'(Infinity, -Infinity)': {
|
||||
props: {
|
||||
min: -Infinity,
|
||||
max: Infinity,
|
||||
minInclusive: false,
|
||||
maxInclusive: false
|
||||
},
|
||||
within: [
|
||||
[0, true],
|
||||
[-0.0000001, true],
|
||||
[-1, true],
|
||||
[-10000000000, true],
|
||||
[-Infinity, false],
|
||||
[Infinity, false],
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
_.forOwn(tests, function (spec, str) {
|
||||
|
||||
describe(str, function () {
|
||||
var range = parse(str);
|
||||
|
||||
it('creation', function () {
|
||||
expect(range).to.eql(spec.props);
|
||||
});
|
||||
|
||||
spec.within.forEach(function (tup) {
|
||||
it('#within(' + tup[0] + ')', function () {
|
||||
expect(range.within(tup[0])).to.be(tup[1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue