mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Merge branch 'master' into fixed_scales
Conflicts: src/kibana/plugins/vis_types/controls/vislib_basic_options.html src/kibana/plugins/vis_types/vislib/area.js src/kibana/plugins/vis_types/vislib/histogram.js src/kibana/plugins/vis_types/vislib/line.js
This commit is contained in:
commit
0cbcb99be6
79 changed files with 1585 additions and 641 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) {
|
|||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -45,7 +45,7 @@ define(function (require) {
|
|||
queue.forEach(function (q) { q.reject(err); });
|
||||
})
|
||||
.finally(function () {
|
||||
$rootScope.$emit('change:config', updated.concat(deleted));
|
||||
$rootScope.$broadcast('change:config', updated.concat(deleted));
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -70,7 +70,7 @@ define(function (require) {
|
|||
var defer = Promise.defer();
|
||||
queue.push(defer);
|
||||
notify.log('config change: ' + key + ': ' + oldVal + ' -> ' + newVal);
|
||||
$rootScope.$emit('change:config.' + key, newVal, oldVal);
|
||||
$rootScope.$broadcast('change:config.' + key, newVal, oldVal);
|
||||
|
||||
// reset the fire timer
|
||||
clearTimeout(timer);
|
||||
|
@ -80,4 +80,4 @@ define(function (require) {
|
|||
};
|
||||
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,7 +19,7 @@ define(function (require) {
|
|||
|
||||
var angular = require('angular');
|
||||
var _ = require('lodash');
|
||||
var defaults = require('components/config/defaults');
|
||||
var defaults = Private(require('components/config/defaults'));
|
||||
var DelayedUpdater = Private(require('components/config/_delayed_updater'));
|
||||
var vals = Private(require('components/config/_vals'));
|
||||
|
||||
|
@ -123,6 +123,29 @@ define(function (require) {
|
|||
if (updater) updater.fire();
|
||||
};
|
||||
|
||||
/**
|
||||
* A little helper for binding config variables to $scopes
|
||||
*
|
||||
* @param {Scope} $scope - an angular $scope object
|
||||
* @param {string} key - the config key to bind to
|
||||
* @param {string} [property] - optional property name where the value should
|
||||
* be stored. Defaults to the config key
|
||||
* @return {function} - an unbind function
|
||||
*/
|
||||
config.$bind = function ($scope, key, property) {
|
||||
if (!property) property = key;
|
||||
|
||||
var update = function () {
|
||||
$scope[property] = config.get(key);
|
||||
};
|
||||
|
||||
update();
|
||||
return _.partial(_.invoke, [
|
||||
$scope.$on('change:config.' + key, update),
|
||||
$scope.$on('init:config', update)
|
||||
], 'call');
|
||||
};
|
||||
|
||||
/*****
|
||||
* PRIVATE API
|
||||
*****/
|
||||
|
|
|
@ -1,86 +1,93 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
return function () {
|
||||
var _ = require('lodash');
|
||||
|
||||
return {
|
||||
'query:queryString:options': {
|
||||
value: '{ "analyze_wildcard": true }',
|
||||
description: 'Options for the lucene query string parser',
|
||||
type: 'json'
|
||||
},
|
||||
'dateFormat': {
|
||||
value: 'MMMM Do YYYY, HH:mm:ss.SSS',
|
||||
description: 'When displaying a pretty formatted date, use this format',
|
||||
},
|
||||
'dateFormat:scaled': {
|
||||
type: 'json',
|
||||
value:
|
||||
'[\n' +
|
||||
' ["", "hh:mm:ss.SSS"],\n' +
|
||||
' ["PT1S", "HH:mm:ss"],\n' +
|
||||
' ["PT1M", "HH:mm"],\n' +
|
||||
' ["PT1H",\n' +
|
||||
' "YYYY-MM-DD HH:mm"],\n' +
|
||||
' ["P1DT", "YYYY-MM-DD"],\n' +
|
||||
' ["P1YT", "YYYY"]\n' +
|
||||
']',
|
||||
description: 'Values that define the format used in situations where timebased' +
|
||||
' data is rendered in order, and formatted timestamps should adapt to the' +
|
||||
' interval between measurements. Keys are ISO 8601 intervals:' +
|
||||
' http://en.wikipedia.org/wiki/ISO_8601#Time_intervals'
|
||||
},
|
||||
'defaultIndex': {
|
||||
value: null,
|
||||
description: 'The index to access if no index is set',
|
||||
},
|
||||
'metaFields': {
|
||||
value: ['_source', '_id', '_type', '_index'],
|
||||
description: 'Fields that exist outside of _source to merge into our document when displaying it',
|
||||
},
|
||||
'discover:sampleSize': {
|
||||
value: 500,
|
||||
description: 'The number of rows to show in the table',
|
||||
},
|
||||
'fields:popularLimit': {
|
||||
value: 10,
|
||||
description: 'The top N most popular fields to show',
|
||||
},
|
||||
'format:numberPrecision': {
|
||||
value: 3,
|
||||
description: 'Round numbers to this many decimal places',
|
||||
},
|
||||
'histogram:barTarget': {
|
||||
value: 50,
|
||||
description: 'Attempt to generate around this many bar when using "auto" interval in date histograms',
|
||||
},
|
||||
'histogram:maxBars': {
|
||||
value: 100,
|
||||
description: 'Never show more than this many bar in date histograms, scale values if needed',
|
||||
},
|
||||
'visualization:tileMap:maxPrecision': {
|
||||
value: 7,
|
||||
description: 'The maximum geoHash precision displayed on tile maps: 7 is high, 10 is very high, ' +
|
||||
'12 is the max. Explanation of cell dimensions: http://www.elastic.co/guide/en/elasticsearch/reference/current/' +
|
||||
'search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator',
|
||||
},
|
||||
'csv:separator': {
|
||||
value: ',',
|
||||
description: 'Separate exported values with this string',
|
||||
},
|
||||
'csv:quoteValues': {
|
||||
value: true,
|
||||
description: 'Should values be quoted in csv exports?',
|
||||
},
|
||||
'history:limit': {
|
||||
value: 10,
|
||||
description: 'In fields that have history (e.g. query inputs), show this many recent values',
|
||||
},
|
||||
'shortDots:enable': {
|
||||
value: false,
|
||||
description: 'Shorten long fields, for example, instead of foo.bar.baz, show f.b.baz',
|
||||
},
|
||||
'truncate:maxHeight': {
|
||||
value: 115,
|
||||
description: 'The maximum height that a cell in a table should occupy. Set to 0 to disable truncation.'
|
||||
}
|
||||
return {
|
||||
'query:queryString:options': {
|
||||
value: '{ "analyze_wildcard": true }',
|
||||
description: 'Options for the lucene query string parser',
|
||||
type: 'json'
|
||||
},
|
||||
'dateFormat': {
|
||||
value: 'MMMM Do YYYY, HH:mm:ss.SSS',
|
||||
description: 'When displaying a pretty formatted date, use this format',
|
||||
},
|
||||
'dateFormat:scaled': {
|
||||
type: 'json',
|
||||
value:
|
||||
'[\n' +
|
||||
' ["", "hh:mm:ss.SSS"],\n' +
|
||||
' ["PT1S", "HH:mm:ss"],\n' +
|
||||
' ["PT1M", "HH:mm"],\n' +
|
||||
' ["PT1H",\n' +
|
||||
' "YYYY-MM-DD HH:mm"],\n' +
|
||||
' ["P1DT", "YYYY-MM-DD"],\n' +
|
||||
' ["P1YT", "YYYY"]\n' +
|
||||
']',
|
||||
description: 'Values that define the format used in situations where timebased' +
|
||||
' data is rendered in order, and formatted timestamps should adapt to the' +
|
||||
' interval between measurements. Keys are ISO 8601 intervals:' +
|
||||
' http://en.wikipedia.org/wiki/ISO_8601#Time_intervals'
|
||||
},
|
||||
'defaultIndex': {
|
||||
value: null,
|
||||
description: 'The index to access if no index is set',
|
||||
},
|
||||
'metaFields': {
|
||||
value: ['_source', '_id', '_type', '_index'],
|
||||
description: 'Fields that exist outside of _source to merge into our document when displaying it',
|
||||
},
|
||||
'discover:sampleSize': {
|
||||
value: 500,
|
||||
description: 'The number of rows to show in the table',
|
||||
},
|
||||
'fields:popularLimit': {
|
||||
value: 10,
|
||||
description: 'The top N most popular fields to show',
|
||||
},
|
||||
'format:numberPrecision': {
|
||||
value: 3,
|
||||
description: 'Round numbers to this many decimal places',
|
||||
},
|
||||
'histogram:barTarget': {
|
||||
value: 50,
|
||||
description: 'Attempt to generate around this many bar when using "auto" interval in date histograms',
|
||||
},
|
||||
'histogram:maxBars': {
|
||||
value: 100,
|
||||
description: 'Never show more than this many bar in date histograms, scale values if needed',
|
||||
},
|
||||
'visualization:tileMap:maxPrecision': {
|
||||
value: 7,
|
||||
description: 'The maximum geoHash precision displayed on tile maps: 7 is high, 10 is very high, ' +
|
||||
'12 is the max. Explanation of cell dimensions: http://www.elastic.co/guide/en/elasticsearch/reference/current/' +
|
||||
'search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator',
|
||||
},
|
||||
'csv:separator': {
|
||||
value: ',',
|
||||
description: 'Separate exported values with this string',
|
||||
},
|
||||
'csv:quoteValues': {
|
||||
value: true,
|
||||
description: 'Should values be quoted in csv exports?',
|
||||
},
|
||||
'history:limit': {
|
||||
value: 10,
|
||||
description: 'In fields that have history (e.g. query inputs), show this many recent values',
|
||||
},
|
||||
'shortDots:enable': {
|
||||
value: false,
|
||||
description: 'Shorten long fields, for example, instead of foo.bar.baz, show f.b.baz',
|
||||
},
|
||||
'truncate:maxHeight': {
|
||||
value: 115,
|
||||
description: 'The maximum height that a cell in a table should occupy. Set to 0 to disable truncation.'
|
||||
},
|
||||
'indexPattern:fieldMapping:lookBack': {
|
||||
value: 5,
|
||||
description: 'For index patterns containing timestamps in their names, look for this many recent matching ' +
|
||||
'patterns from which to query the field mapping.'
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<th width="1%"></th>
|
||||
<th ng-if="indexPattern.timeFieldName">
|
||||
<span ng-click="sort(indexPattern.timeFieldName)" tooltip="Sort by time">Time <i ng-class="headerClass(indexPattern.timeFieldName)"></i></span>
|
||||
<span>Time <i ng-class="headerClass(indexPattern.timeFieldName)" ng-click="sort(indexPattern.timeFieldName)" tooltip="Sort by time"></i></span>
|
||||
</th>
|
||||
<th ng-repeat="name in columns">
|
||||
<span class="table-header-name">
|
||||
|
@ -11,4 +11,4 @@
|
|||
<i ng-click="moveLeft(name)" class="fa fa-angle-double-left" ng-show="!$first" tooltip="Move column to the left" tooltip-append-to-body="1"></i>
|
||||
<i ng-click="moveRight(name)" class="fa fa-angle-double-right" ng-show="!$last" tooltip="Move column to the right" tooltip-append-to-body="1"></i>
|
||||
</span>
|
||||
</th>
|
||||
</th>
|
||||
|
|
|
@ -12,7 +12,7 @@ define(function (require) {
|
|||
require('filters/short_dots');
|
||||
|
||||
|
||||
// guestimate at the minimum number of chars wide cells in the table should be
|
||||
// guesstimate at the minimum number of chars wide cells in the table should be
|
||||
var MIN_LINE_LENGTH = 20;
|
||||
|
||||
/**
|
||||
|
@ -235,4 +235,4 @@ define(function (require) {
|
|||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -49,9 +49,7 @@
|
|||
tooltip-placement="top"
|
||||
tooltip="Objects in arrays are not well supported."
|
||||
class="fa fa-warning text-color-warning ng-scope doc-viewer-object-array"></i>
|
||||
<span class="doc-viewer-value" ng-bind-html="(typeof(formatted[field]) === 'undefined' ? hit[field] : formatted[field]) | highlight : hit.highlight[field] | trustAsHtml"></span>
|
||||
|
||||
|
||||
<div class="doc-viewer-value" ng-bind-html="(typeof(formatted[field]) === 'undefined' ? hit[field] : formatted[field]) | highlight : hit.highlight[field] | trustAsHtml"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
define(function (require) {
|
||||
return function MapperService(Private, Promise, es, configFile) {
|
||||
return function MapperService(Private, Promise, es, configFile, config) {
|
||||
var _ = require('lodash');
|
||||
var moment = require('moment');
|
||||
|
||||
|
@ -51,7 +51,7 @@ define(function (require) {
|
|||
promise = self.getIndicesForIndexPattern(indexPattern)
|
||||
.then(function (existing) {
|
||||
if (existing.matches.length === 0) throw new IndexPatternMissingIndices();
|
||||
return existing.matches.slice(-5); // Grab the most recent 5
|
||||
return existing.matches.slice(-config.get('indexPattern:fieldMapping:lookBack')); // Grab the most recent
|
||||
});
|
||||
}
|
||||
|
||||
|
|
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;
|
||||
};
|
||||
|
|
|
@ -43,7 +43,8 @@ define(function (require) {
|
|||
|
||||
if (self.shouldAutoReload(next, prev)) {
|
||||
var appState = getAppState();
|
||||
appState.destroy();
|
||||
if (appState) appState.destroy();
|
||||
|
||||
reloading = $rootScope.$on('$locationChangeSuccess', function () {
|
||||
// call the "unlisten" function returned by $on
|
||||
reloading();
|
||||
|
|
|
@ -102,6 +102,14 @@ define(function (require) {
|
|||
}
|
||||
};
|
||||
|
||||
Vis.prototype.hasSchemaAgg = function (schemaName, aggTypeName) {
|
||||
var aggs = this.aggs.bySchemaName[schemaName] || [];
|
||||
return aggs.some(function (agg) {
|
||||
if (!agg.type || !agg.type.name) return false;
|
||||
return agg.type.name === aggTypeName;
|
||||
});
|
||||
};
|
||||
|
||||
return Vis;
|
||||
};
|
||||
});
|
||||
|
|
|
@ -133,7 +133,7 @@ define(function (require) {
|
|||
var events = self.events ? self.events.eventResponse(d, i) : d;
|
||||
return render(tooltipFormatter(events));
|
||||
})
|
||||
.on('mouseout.tip', function () {
|
||||
.on('mouseleave.tip', function () {
|
||||
render();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -512,30 +512,24 @@ define(function (require) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Checks whether all pie slices have zero values.
|
||||
* If so, an error is thrown.
|
||||
* Removes zeros from pie chart data
|
||||
* @param slices
|
||||
* @returns {*}
|
||||
*/
|
||||
Data.prototype._validatePieData = function () {
|
||||
var visData = this.getVisData();
|
||||
Data.prototype._removeZeroSlices = function (slices) {
|
||||
var self = this;
|
||||
|
||||
visData.forEach(function (chartData) {
|
||||
chartData.slices = (function withoutZeroSlices(slices) {
|
||||
if (!slices.children) return slices;
|
||||
if (!slices.children) return slices;
|
||||
|
||||
slices = _.clone(slices);
|
||||
slices.children = slices.children.reduce(function (children, child) {
|
||||
if (child.size !== 0) {
|
||||
children.push(withoutZeroSlices(child));
|
||||
}
|
||||
return children;
|
||||
}, []);
|
||||
return slices;
|
||||
}(chartData.slices));
|
||||
|
||||
if (chartData.slices.children.length === 0) {
|
||||
throw new errors.PieContainsAllZeros();
|
||||
slices = _.clone(slices);
|
||||
slices.children = slices.children.reduce(function (children, child) {
|
||||
if (child.size !== 0) {
|
||||
children.push(self._removeZeroSlices(child));
|
||||
}
|
||||
});
|
||||
return children;
|
||||
}, []);
|
||||
|
||||
return slices;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -547,12 +541,12 @@ define(function (require) {
|
|||
*/
|
||||
Data.prototype.pieNames = function () {
|
||||
var self = this;
|
||||
var data = this.getVisData();
|
||||
var names = [];
|
||||
|
||||
this._validatePieData();
|
||||
|
||||
_.forEach(this.getVisData(), function (obj) {
|
||||
_.forEach(data, function (obj) {
|
||||
var columns = obj.raw ? obj.raw.columns : undefined;
|
||||
obj.slices = self._removeZeroSlices(obj.slices);
|
||||
|
||||
_.forEach(self.getNames(obj, columns), function (name) {
|
||||
names.push(name);
|
||||
|
|
|
@ -138,14 +138,14 @@ define(function (require) {
|
|||
// legend
|
||||
legendDiv.selectAll('li')
|
||||
.filter(function (d) {
|
||||
return this.getAttribute('data-label') !== label;
|
||||
return this.getAttribute('data-label') !== label.toString();
|
||||
})
|
||||
.classed('blur_shape', true);
|
||||
|
||||
// all data-label attribute
|
||||
charts.selectAll('[data-label]')
|
||||
.filter(function (d) {
|
||||
return this.getAttribute('data-label') !== label;
|
||||
return this.getAttribute('data-label') !== label.toString();
|
||||
})
|
||||
.classed('blur_shape', true);
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
define(function (require) {
|
||||
return function ChartBaseClass(d3, Private) {
|
||||
var _ = require('lodash');
|
||||
var errors = require('errors');
|
||||
|
||||
var Dispatch = Private(require('components/vislib/lib/dispatch'));
|
||||
var Tooltip = Private(require('components/vislib/components/tooltip/tooltip'));
|
||||
|
|
|
@ -4,6 +4,7 @@ define(function (require) {
|
|||
var $ = require('jquery');
|
||||
|
||||
var PointSeriesChart = Private(require('components/vislib/visualizations/_point_series_chart'));
|
||||
var TimeMarker = Private(require('components/vislib/visualizations/time_marker'));
|
||||
var errors = require('errors');
|
||||
require('css!components/vislib/styles/main');
|
||||
|
||||
|
@ -266,6 +267,9 @@ define(function (require) {
|
|||
var yScale = this.handler.yAxis.yScale;
|
||||
var minWidth = 20;
|
||||
var minHeight = 20;
|
||||
var addTimeMarker = this._attr.addTimeMarker;
|
||||
var times = this._attr.times || [];
|
||||
var timeMarker;
|
||||
var div;
|
||||
var svg;
|
||||
var width;
|
||||
|
@ -283,6 +287,10 @@ define(function (require) {
|
|||
width = elWidth;
|
||||
height = elHeight - margin.top - margin.bottom;
|
||||
|
||||
if (addTimeMarker) {
|
||||
timeMarker = new TimeMarker(times, xScale, height);
|
||||
}
|
||||
|
||||
if (width < minWidth || height < minHeight) {
|
||||
throw new errors.ContainerTooSmall();
|
||||
}
|
||||
|
@ -333,6 +341,10 @@ define(function (require) {
|
|||
.style('stroke', '#ddd')
|
||||
.style('stroke-width', 1);
|
||||
|
||||
if (addTimeMarker) {
|
||||
timeMarker.render(svg);
|
||||
}
|
||||
|
||||
return svg;
|
||||
});
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@ define(function (require) {
|
|||
var moment = require('moment');
|
||||
|
||||
var PointSeriesChart = Private(require('components/vislib/visualizations/_point_series_chart'));
|
||||
var TimeMarker = Private(require('components/vislib/visualizations/time_marker'));
|
||||
var errors = require('errors');
|
||||
require('css!components/vislib/styles/main');
|
||||
|
||||
|
@ -269,8 +270,12 @@ define(function (require) {
|
|||
var elHeight = this._attr.height = $elem.height();
|
||||
var yMin = this.handler.yAxis.yMin;
|
||||
var yScale = this.handler.yAxis.yScale;
|
||||
var xScale = this.handler.xAxis.xScale;
|
||||
var minWidth = 20;
|
||||
var minHeight = 20;
|
||||
var addTimeMarker = this._attr.addTimeMarker;
|
||||
var times = this._attr.times || [];
|
||||
var timeMarker;
|
||||
var div;
|
||||
var svg;
|
||||
var width;
|
||||
|
@ -285,6 +290,10 @@ define(function (require) {
|
|||
width = elWidth;
|
||||
height = elHeight - margin.top - margin.bottom;
|
||||
|
||||
if (addTimeMarker) {
|
||||
timeMarker = new TimeMarker(times, xScale, height);
|
||||
}
|
||||
|
||||
if (width < minWidth || height < minHeight) {
|
||||
throw new errors.ContainerTooSmall();
|
||||
}
|
||||
|
@ -325,6 +334,10 @@ define(function (require) {
|
|||
.style('stroke-width', 1);
|
||||
}
|
||||
|
||||
if (addTimeMarker) {
|
||||
timeMarker.render(svg);
|
||||
}
|
||||
|
||||
return svg;
|
||||
});
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@ define(function (require) {
|
|||
var errors = require('errors');
|
||||
|
||||
var PointSeriesChart = Private(require('components/vislib/visualizations/_point_series_chart'));
|
||||
var TimeMarker = Private(require('components/vislib/visualizations/time_marker'));
|
||||
require('css!components/vislib/styles/main');
|
||||
|
||||
/**
|
||||
|
@ -258,10 +259,14 @@ define(function (require) {
|
|||
var elHeight = this._attr.height = $elem.height();
|
||||
var yMin = this.handler.yAxis.yMin;
|
||||
var yScale = this.handler.yAxis.yScale;
|
||||
var xScale = this.handler.xAxis.xScale;
|
||||
var minWidth = 20;
|
||||
var minHeight = 20;
|
||||
var startLineX = 0;
|
||||
var lineStrokeWidth = 1;
|
||||
var addTimeMarker = this._attr.addTimeMarker;
|
||||
var times = this._attr.times || [];
|
||||
var timeMarker;
|
||||
var div;
|
||||
var svg;
|
||||
var width;
|
||||
|
@ -288,6 +293,10 @@ define(function (require) {
|
|||
width = elWidth - margin.left - margin.right;
|
||||
height = elHeight - margin.top - margin.bottom;
|
||||
|
||||
if (addTimeMarker) {
|
||||
timeMarker = new TimeMarker(times, xScale, height);
|
||||
}
|
||||
|
||||
if (width < minWidth || height < minHeight) {
|
||||
throw new errors.ContainerTooSmall();
|
||||
}
|
||||
|
@ -331,6 +340,10 @@ define(function (require) {
|
|||
.style('stroke', '#ddd')
|
||||
.style('stroke-width', lineStrokeWidth);
|
||||
|
||||
if (addTimeMarker) {
|
||||
timeMarker.render(svg);
|
||||
}
|
||||
|
||||
return svg;
|
||||
});
|
||||
};
|
||||
|
|
|
@ -24,11 +24,26 @@ define(function (require) {
|
|||
}
|
||||
PieChart.Super.apply(this, arguments);
|
||||
|
||||
var charts = this.handler.data.getVisData();
|
||||
this._validatePieData(charts);
|
||||
|
||||
this._attr = _.defaults(handler._attr || {}, {
|
||||
isDonut: handler._attr.isDonut || false
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether pie slices have all zero values.
|
||||
* If so, an error is thrown.
|
||||
*/
|
||||
PieChart.prototype._validatePieData = function (charts) {
|
||||
var isAllZeros = charts.every(function (chart) {
|
||||
return chart.slices.children.length === 0;
|
||||
});
|
||||
|
||||
if (isAllZeros) { throw new errors.PieContainsAllZeros(); }
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds Events to SVG paths
|
||||
*
|
||||
|
@ -150,6 +165,15 @@ define(function (require) {
|
|||
return path;
|
||||
};
|
||||
|
||||
PieChart.prototype._validateContainerSize = function (width, height) {
|
||||
var minWidth = 20;
|
||||
var minHeight = 20;
|
||||
|
||||
if (width <= minWidth || height <= minHeight) {
|
||||
throw new errors.ContainerTooSmall();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders d3 visualization
|
||||
*
|
||||
|
@ -162,17 +186,15 @@ define(function (require) {
|
|||
return function (selection) {
|
||||
selection.each(function (data) {
|
||||
var slices = data.slices;
|
||||
var el = this;
|
||||
var div = d3.select(el);
|
||||
var width = $(el).width();
|
||||
var height = $(el).height();
|
||||
var minWidth = 20;
|
||||
var minHeight = 20;
|
||||
var div = d3.select(this);
|
||||
var width = $(this).width();
|
||||
var height = $(this).height();
|
||||
var path;
|
||||
|
||||
if (width <= minWidth || height <= minHeight) {
|
||||
throw new errors.ContainerTooSmall();
|
||||
}
|
||||
if (!slices.children.length) return;
|
||||
|
||||
self.convertToPercentage(slices);
|
||||
self._validateContainerSize(width, height);
|
||||
|
||||
var svg = div.append('svg')
|
||||
.attr('width', width)
|
||||
|
@ -180,7 +202,6 @@ define(function (require) {
|
|||
.append('g')
|
||||
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
|
||||
|
||||
self.convertToPercentage(slices);
|
||||
path = self.addPath(width, height, svg, slices);
|
||||
self.addPathEvents(path);
|
||||
|
||||
|
|
|
@ -178,34 +178,13 @@ define(function (require) {
|
|||
} else if (this._attr.mapType === 'Shaded Geohash Grid') {
|
||||
featureLayer = this.shadedGeohashGrid(map, mapData);
|
||||
} else {
|
||||
featureLayer = this.pinMarkers(mapData);
|
||||
featureLayer = this.scaledCircleMarkers(map, mapData);
|
||||
}
|
||||
}
|
||||
|
||||
return featureLayer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type of data overlay for map:
|
||||
* creates featurelayer from mapData (geoJson)
|
||||
* with default leaflet pin markers
|
||||
*
|
||||
* @method pinMarkers
|
||||
* @param mapData {Object}
|
||||
* @return {Leaflet object} featureLayer
|
||||
*/
|
||||
TileMap.prototype.pinMarkers = function (mapData) {
|
||||
var self = this;
|
||||
|
||||
var featureLayer = L.geoJson(mapData, {
|
||||
onEachFeature: function (feature, layer) {
|
||||
self.bindPopup(feature, layer);
|
||||
}
|
||||
});
|
||||
|
||||
return featureLayer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type of data overlay for map:
|
||||
* creates featurelayer from mapData (geoJson)
|
||||
|
|
74
src/kibana/components/vislib/visualizations/time_marker.js
Normal file
74
src/kibana/components/vislib/visualizations/time_marker.js
Normal file
|
@ -0,0 +1,74 @@
|
|||
define(function (require) {
|
||||
var datemath = require('utils/datemath');
|
||||
|
||||
return function TimeMarkerFactory(d3) {
|
||||
function TimeMarker(times, xScale, height) {
|
||||
if (!(this instanceof TimeMarker)) {
|
||||
return new TimeMarker(times, xScale, height);
|
||||
}
|
||||
|
||||
var currentTimeArr = [{
|
||||
'time': new Date().getTime(),
|
||||
'class': 'time-marker',
|
||||
'color': '#c80000',
|
||||
'opacity': 0.3,
|
||||
'width': 2
|
||||
}];
|
||||
|
||||
this.xScale = xScale;
|
||||
this.height = height;
|
||||
this.times = (times.length) ? times.map(function (d) {
|
||||
return {
|
||||
'time': datemath.parse(d.time),
|
||||
'class': d.class || 'time-marker',
|
||||
'color': d.color || '#c80000',
|
||||
'opacity': d.opacity || 0.3,
|
||||
'width': d.width || 2
|
||||
};
|
||||
}) : currentTimeArr;
|
||||
}
|
||||
|
||||
TimeMarker.prototype._isTimeBasedChart = function (selection) {
|
||||
var data = selection.data();
|
||||
return data.every(function (datum) {
|
||||
return (datum.ordered && datum.ordered.date);
|
||||
});
|
||||
};
|
||||
|
||||
TimeMarker.prototype.render = function (selection) {
|
||||
var self = this;
|
||||
|
||||
// return if not time based chart
|
||||
if (!self._isTimeBasedChart(selection)) return;
|
||||
|
||||
selection.each(function () {
|
||||
d3.select(this).selectAll('time-marker')
|
||||
.data(self.times)
|
||||
.enter().append('line')
|
||||
.attr('class', function (d) {
|
||||
return d.class;
|
||||
})
|
||||
.attr('pointer-events', 'none')
|
||||
.attr('stroke', function (d) {
|
||||
return d.color;
|
||||
})
|
||||
.attr('stroke-width', function (d) {
|
||||
return d.width;
|
||||
})
|
||||
.attr('stroke-opacity', function (d) {
|
||||
return d.opacity;
|
||||
})
|
||||
.attr('x1', function (d) {
|
||||
return self.xScale(d.time);
|
||||
})
|
||||
.attr('x2', function (d) {
|
||||
return self.xScale(d.time);
|
||||
})
|
||||
.attr('y1', self.height)
|
||||
.attr('y2', self.xScale.range()[0]);
|
||||
});
|
||||
};
|
||||
|
||||
return TimeMarker;
|
||||
};
|
||||
});
|
|
@ -84,7 +84,6 @@ visualize-spy {
|
|||
&-tab {
|
||||
margin: 0px auto;
|
||||
margin-top: -1px;
|
||||
|
||||
border: 1px solid #ecf0f1;
|
||||
border-top: 0px;
|
||||
border-width: 0px 1px 1px 1px;
|
||||
|
@ -92,10 +91,14 @@ visualize-spy {
|
|||
border-bottom-right-radius: @border-radius-base;
|
||||
width: 50px;
|
||||
background: @body-bg;
|
||||
text-align: center;
|
||||
|
||||
text-align: center;
|
||||
&:hover {
|
||||
background-color: @gray-lighter;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
i {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
|
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,
|
||||
|
|
|
@ -484,6 +484,7 @@ define(function (require) {
|
|||
type: 'histogram',
|
||||
params: {
|
||||
addLegend: false,
|
||||
addTimeMarker: true
|
||||
},
|
||||
listeners: {
|
||||
click: function (e) {
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
</ul>
|
||||
|
||||
<ul class="nav navbar-nav navbar-right navbar-timepicker">
|
||||
<li ng-show="httpActive.length" class="navbar-text">
|
||||
<li ng-show="httpActive.length" class="navbar-text hidden-xs">
|
||||
<div class="spinner"></div>
|
||||
</li>
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var configDefaults = require('components/config/defaults');
|
||||
|
||||
require('modules').get('apps/settings')
|
||||
.directive('advancedRow', function (config, Notifier, Private) {
|
||||
|
@ -13,6 +12,7 @@ define(function (require) {
|
|||
configs: '='
|
||||
},
|
||||
link: function ($scope) {
|
||||
var configDefaults = Private(require('components/config/defaults'));
|
||||
var notify = new Notifier();
|
||||
var keyCodes = {
|
||||
ESC: 27
|
||||
|
@ -66,4 +66,4 @@ define(function (require) {
|
|||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var configDefaults = require('components/config/defaults');
|
||||
var getValType = require('plugins/settings/sections/advanced/lib/get_val_type');
|
||||
|
||||
|
||||
|
@ -16,11 +15,12 @@ define(function (require) {
|
|||
return {
|
||||
restrict: 'E',
|
||||
link: function ($scope) {
|
||||
var configDefaults = Private(require('components/config/defaults'));
|
||||
var keyCodes = {
|
||||
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';
|
||||
|
@ -68,4 +70,4 @@ define(function (require) {
|
|||
display: 'Advanced',
|
||||
url: '#/settings/advanced'
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,11 +22,8 @@ define(function (require) {
|
|||
template: require('text!plugins/settings/sections/indices/index.html'),
|
||||
link: function ($scope) {
|
||||
$scope.edittingId = $route.current.params.id;
|
||||
$scope.defaultIndex = config.get('defaultIndex');
|
||||
$rootScope.$on('change:config.defaultIndex', function () {
|
||||
$scope.defaultIndex = config.get('defaultIndex');
|
||||
});
|
||||
|
||||
config.$bind($scope, 'defaultIndex');
|
||||
$scope.$watch('defaultIndex', function (defaultIndex) {
|
||||
$scope.indexPatternList = _($route.current.locals.indexPatternIds)
|
||||
.map(function (id) {
|
||||
|
@ -50,4 +47,4 @@ define(function (require) {
|
|||
display: 'Indices',
|
||||
url: '#/settings/indices',
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,4 +1,10 @@
|
|||
<div>
|
||||
<div class="vis-option-item" ng-show="vis.hasSchemaAgg('segment', 'date_histogram')">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="vis.params.addTimeMarker" ng-checked="vis.params.addTimeMarker">
|
||||
Current time marker
|
||||
</label>
|
||||
</div>
|
||||
<div class="vis-option-item">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="vis.params.setYExtents">
|
||||
|
|
|
@ -20,6 +20,8 @@ define(function (require) {
|
|||
scale: 'linear',
|
||||
interpolate: 'linear',
|
||||
mode: 'stacked',
|
||||
times: [],
|
||||
addTimeMarker: false,
|
||||
defaultYExtents: false,
|
||||
setYExtents: false,
|
||||
yAxis: {}
|
||||
|
|
|
@ -16,6 +16,8 @@ define(function (require) {
|
|||
addLegend: true,
|
||||
scale: 'linear',
|
||||
mode: 'stacked',
|
||||
times: [],
|
||||
addTimeMarker: false,
|
||||
defaultYExtents: false,
|
||||
setYExtents: false,
|
||||
yAxis: {}
|
||||
|
|
|
@ -17,12 +17,14 @@ define(function (require) {
|
|||
showCircles: true,
|
||||
smoothLines: false,
|
||||
interpolate: 'linear',
|
||||
scale: 'linear',
|
||||
drawLinesBetweenPoints: true,
|
||||
radiusRatio: 9,
|
||||
times: [],
|
||||
addTimeMarker: false,
|
||||
defaultYExtents: false,
|
||||
setYExtents: false,
|
||||
yAxis: {},
|
||||
scale: 'linear'
|
||||
yAxis: {}
|
||||
},
|
||||
scales: ['linear', 'log', 'square root'],
|
||||
editor: require('text!plugins/vis_types/vislib/editors/line.html')
|
||||
|
|
|
@ -15,7 +15,7 @@ define(function (require) {
|
|||
mapType: 'Scaled Circle Markers',
|
||||
isDesaturated: true
|
||||
},
|
||||
mapTypes: ['Scaled Circle Markers', 'Shaded Circle Markers', 'Shaded Geohash Grid', 'Pins'],
|
||||
mapTypes: ['Scaled Circle Markers', 'Shaded Circle Markers', 'Shaded Geohash Grid'],
|
||||
editor: require('text!plugins/vis_types/vislib/editors/tile_map.html')
|
||||
},
|
||||
responseConverter: geoJsonConverter,
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -11,20 +11,21 @@
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
<button
|
||||
<div
|
||||
ng-if="stats.max > stats.count"
|
||||
type="button"
|
||||
ng-init="add.form = stats.count < 1 ? !add.form : add.form"
|
||||
ng-click="add.form = !add.form"
|
||||
class="vis-editor-agg-wide-btn btn btn-xs btn-default" >
|
||||
class="vis-editor-agg-wide-btn">
|
||||
|
||||
<div ng-if="!add.form">
|
||||
<span ng-if="groupName !== 'buckets' || !stats.count">
|
||||
<i aria-hidden="true" class="fa fa-plus"></i> Add Aggregation
|
||||
</span>
|
||||
<span ng-if="groupName === 'buckets' && stats.count > 0">
|
||||
<i aria-hidden="true" class="fa fa-code-fork"></i> Add Sub Aggregation
|
||||
</span>
|
||||
<div class="vis-editor-agg-wide-btn-add" ng-if="groupName !== 'buckets' || !stats.count">
|
||||
<i aria-hidden="true" class="fa fa-plus"></i> Add {{ groupName }}
|
||||
</div>
|
||||
<div class="vis-editor-agg-wide-btn-add" ng-if="groupName === 'buckets' && stats.count > 0">
|
||||
<i aria-hidden="true" class="fa fa-code-fork"></i> Add sub-{{ groupName }}
|
||||
</div>
|
||||
</div>
|
||||
<span ng-if="add.form">cancel</span>
|
||||
</button>
|
||||
<div class="vis-editor-agg-wide-btn-add" ng-if="add.form">
|
||||
Cancel
|
||||
</div>
|
||||
</div>
|
|
@ -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>
|
||||
|
|
|
@ -7,42 +7,68 @@
|
|||
{{ indexPattern.id }}
|
||||
</div>
|
||||
|
||||
<ul class="list-unstyled">
|
||||
<!-- metrics -->
|
||||
<vis-editor-agg-group ng-if="vis.type.schemas.metrics" group-name="metrics"></vis-editor-agg-group>
|
||||
<nav class="navbar navbar-default navbar-static-top subnav">
|
||||
<div class="container-fluid">
|
||||
|
||||
<!-- buckets -->
|
||||
<vis-editor-agg-group ng-if="vis.type.schemas.buckets" group-name="buckets"></vis-editor-agg-group>
|
||||
<!-- 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: sidebar.section == 'options'}">
|
||||
<a class="navbar-link" ng-click="sidebar.section='options'">Options</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- vis options -->
|
||||
<vis-editor-vis-options
|
||||
vis="vis"
|
||||
>
|
||||
</vis-editor-vis-options>
|
||||
<!-- 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">
|
||||
<a class="danger navbar-link">
|
||||
<i class="fa fa-warning"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li tooltip="Apply changes" tooltip-placement="bottom" tooltip-popup-delay="400" tooltip-append-to-body="1">
|
||||
<button class="btn-success navbar-btn-link"
|
||||
type="submit"
|
||||
ng-disabled="!vis.dirty">
|
||||
|
||||
<!-- apply/discard -->
|
||||
</ul>
|
||||
<div class="vis-editor-sidebar-buttons">
|
||||
<p
|
||||
ng-if="visualizeEditor.softErrorCount() > 0"
|
||||
class="text-center text-danger sidebar-item-text">
|
||||
<i aria-hidden="true" class="fa fa-warning"></i>
|
||||
{{ visualizeEditor.describeErrors() }}
|
||||
</p>
|
||||
<button
|
||||
ng-disabled="!vis.dirty"
|
||||
type="submit"
|
||||
class="sidebar-item-button success">
|
||||
Apply
|
||||
</button>
|
||||
<button
|
||||
ng-disabled="!vis.dirty"
|
||||
type="button"
|
||||
ng-click="resetEditableVis()"
|
||||
class="sidebar-item-button default">
|
||||
Discard
|
||||
</button>
|
||||
<i class="fa fa-play"></i>
|
||||
</button>
|
||||
</li>
|
||||
<li tooltip="Discard changes" tooltip-placement="bottom" tooltip-popup-delay="400" tooltip-append-to-body="1">
|
||||
<button class="btn-default navbar-btn-link"
|
||||
ng-disabled="!vis.dirty"
|
||||
ng-click="resetEditableVis()">
|
||||
|
||||
<i class="fa fa-close"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<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>
|
||||
|
||||
<!-- buckets -->
|
||||
<vis-editor-agg-group ng-if="vis.type.schemas.buckets" group-name="buckets"></vis-editor-agg-group>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
||||
</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';
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,6 +6,23 @@
|
|||
@vis-editor-nesting-width: 8px;
|
||||
@vis-editor-agg-editor-spacing: 5px;
|
||||
|
||||
// For the vis-editor sidebar nav
|
||||
.navbar-default .navbar-nav {
|
||||
&> .active > a:before {
|
||||
border: 7px solid @gray-lighter;
|
||||
border-color: transparent transparent @gray-lighter transparent;
|
||||
}
|
||||
|
||||
.danger {
|
||||
color: @btn-danger-color;
|
||||
background-color: @btn-danger-bg;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-xs {
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
navbar {
|
||||
.bitty-modal-container {
|
||||
position: relative;
|
||||
|
@ -73,7 +90,7 @@
|
|||
form {
|
||||
.flex-parent(1, 1, auto);
|
||||
|
||||
> ul {
|
||||
> div.vis-editor-config {
|
||||
.flex(1, 1, auto);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
@ -81,16 +98,25 @@
|
|||
> .vis-edit-sidebar-buttons {
|
||||
.flex(0, 0, auto)
|
||||
}
|
||||
|
||||
label {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-item-title {
|
||||
background: @sidebar-bg;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
border: inherit !important;
|
||||
background-color: @gray-lighter;
|
||||
margin-bottom: 5px;
|
||||
padding: 2px 5px !important;
|
||||
}
|
||||
|
||||
.sidebar-item-title:hover {
|
||||
color: @sidebar-header-color !important;
|
||||
background-color: @sidebar-bg !important;
|
||||
color: @text-color !important;
|
||||
background-color: @gray-lighter !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,7 +145,6 @@
|
|||
.flex-parent();
|
||||
|
||||
padding: @vis-editor-agg-editor-spacing;
|
||||
border-bottom: 1px solid @sidebar-bg;
|
||||
|
||||
// wraps the .vis-editor-agg and nesting-indicator ^^
|
||||
&-wrapper {
|
||||
|
@ -226,6 +251,24 @@
|
|||
|
||||
&-wide-btn {
|
||||
.border-radius(0);
|
||||
border-top: 2px solid @gray-lighter;
|
||||
|
||||
&-add {
|
||||
width: 140px;
|
||||
margin: -2px auto 5px auto;
|
||||
text-align: center;
|
||||
border: 2px solid @gray-lighter;
|
||||
border-top: 0px;
|
||||
padding: 3px;
|
||||
border-bottom-right-radius: @border-radius-base;
|
||||
border-bottom-left-radius: @border-radius-base;
|
||||
background-color: @body-bg;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&-add:hover {
|
||||
background-color: @gray-lighter;
|
||||
}
|
||||
}
|
||||
|
||||
&-add {
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
<li class="sidebar-item" ng-show="vis.type.params.editor">
|
||||
<div ng-hide="alwaysShowOptions" class="sidebar-item-title" ng-click="showVisOptions = !showVisOptions">
|
||||
<div class="sidebar-item-title">
|
||||
view options
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="fa fa-caret-down"
|
||||
ng-class="{'fa-caret-down': showVisOptions, 'fa-caret-right': !showVisOptions}">
|
||||
</i>
|
||||
</div>
|
||||
<div ng-show="alwaysShowOptions" class="sidebar-item-title">view options</div>
|
||||
|
||||
<div class="visualization-options" ng-show="alwaysShowOptions || showVisOptions"></div>
|
||||
<div class="visualization-options"></div>
|
||||
</li>
|
||||
|
|
|
@ -15,19 +15,19 @@ kbn-table, .kbn-table, tbody[kbn-rows] {
|
|||
dl.source {
|
||||
margin-bottom: 0;
|
||||
line-height: 2em;
|
||||
word-break: break-all;
|
||||
|
||||
dt, dd {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
dt {
|
||||
display: inline;
|
||||
background: @gray-lighter;
|
||||
padding: @padding-xs-vertical @padding-xs-horizontal;
|
||||
margin-right: @padding-xs-horizontal;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
dd {
|
||||
display: inline;
|
||||
word-break: break-all;
|
||||
word-break: normal;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,17 @@
|
|||
-moz-box-shadow: inset 0 -10px 10px -12px #333;
|
||||
-webkit-box-shadow: inset 0 -10px 10px -12px #333;
|
||||
|
||||
&-btn-link {
|
||||
height: @navbar-height;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
|
||||
@media (max-width: @screen-sm-min) {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
&-default {
|
||||
|
||||
.badge {
|
||||
|
|
|
@ -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;
|
||||
|
||||
});
|
|
@ -10,7 +10,7 @@ define(function (require) {
|
|||
'=' : '-equal-'
|
||||
};
|
||||
_.each(trans, function (val, key) {
|
||||
var regex = new RegExp(key);
|
||||
var regex = new RegExp(key, 'g');
|
||||
id = id.replace(regex, val);
|
||||
});
|
||||
id = id.replace(/[\s]+/g, '-');
|
||||
|
|
|
@ -16,6 +16,17 @@ function checkPath(path) {
|
|||
}
|
||||
}
|
||||
|
||||
// Set defaults for config file stuff
|
||||
kibana.port = kibana.port || 5601;
|
||||
kibana.host = kibana.host || '0.0.0.0';
|
||||
kibana.elasticsearch_url = kibana.elasticsearch_url || 'http://localhost:9200';
|
||||
kibana.maxSockets = kibana.maxSockets || Infinity;
|
||||
kibana.log_file = kibana.log_file || null;
|
||||
|
||||
kibana.request_timeout = kibana.startup_timeout == null ? 0 : kibana.request_timeout;
|
||||
kibana.ping_timeout = kibana.ping_timeout == null ? kibana.request_timeout : kibana.ping_timeout;
|
||||
kibana.startup_timeout = kibana.startup_timeout == null ? 5000 : kibana.startup_timeout;
|
||||
|
||||
// Check if the local public folder is present. This means we are running in
|
||||
// the NPM module. If it's not there then we are running in the git root.
|
||||
var public_folder = path.resolve(__dirname, '..', 'public');
|
||||
|
@ -33,13 +44,10 @@ try {
|
|||
packagePath = path.resolve(__dirname, '..', '..', '..', 'package.json');
|
||||
}
|
||||
|
||||
var requestTimeout = kibana.request_timeout || 0;
|
||||
var pingTimeout = kibana.ping_timeout == null ? requestTimeout : kibana.ping_timeout;
|
||||
|
||||
var config = module.exports = {
|
||||
port : kibana.port || 5601,
|
||||
host : kibana.host || '0.0.0.0',
|
||||
elasticsearch : kibana.elasticsearch_url || 'http://localhost:9200',
|
||||
port : kibana.port,
|
||||
host : kibana.host,
|
||||
elasticsearch : kibana.elasticsearch_url,
|
||||
root : path.normalize(path.join(__dirname, '..')),
|
||||
quiet : false,
|
||||
public_folder : public_folder,
|
||||
|
@ -49,10 +57,10 @@ var config = module.exports = {
|
|||
package : require(packagePath),
|
||||
htpasswd : htpasswdPath,
|
||||
buildNum : '@@buildNum',
|
||||
maxSockets : kibana.maxSockets || Infinity,
|
||||
log_file : kibana.log_file || null,
|
||||
request_timeout : requestTimeout,
|
||||
ping_timeout : pingTimeout
|
||||
maxSockets : kibana.maxSockets,
|
||||
log_file : kibana.log_file,
|
||||
request_timeout : kibana.request_timeout,
|
||||
ping_timeout : kibana.ping_timeout
|
||||
};
|
||||
|
||||
config.plugins = listPlugins(config);
|
||||
|
|
|
@ -45,6 +45,9 @@ request_timeout: 300000
|
|||
# Set to 0 to disable.
|
||||
shard_timeout: 0
|
||||
|
||||
# Time in milliseconds to wait for Elasticsearch at Kibana startup before retrying
|
||||
# startup_timeout: 5000
|
||||
|
||||
# Set to false to have a complete disregard for the validity of the SSL
|
||||
# certificate.
|
||||
verify_ssl: true
|
||||
|
|
|
@ -6,7 +6,7 @@ var logger = require('./logger');
|
|||
var config = require('../config');
|
||||
|
||||
function waitForPong() {
|
||||
return client.ping()
|
||||
return client.ping({requestTimeout: config.kibana.startup_timeout})
|
||||
.catch(function (err) {
|
||||
if (!(err instanceof NoConnections)) throw err;
|
||||
|
||||
|
|
|
@ -1,12 +1,5 @@
|
|||
module.exports = function (grunt) {
|
||||
var config = {
|
||||
test: {
|
||||
files: [
|
||||
'<%= unitTestDir %>/**/*.js'
|
||||
],
|
||||
tasks: ['mocha:unit']
|
||||
},
|
||||
|
||||
less: {
|
||||
files: [
|
||||
'<%= app %>/**/styles/**/*.less',
|
||||
|
|
|
@ -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]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
74
test/unit/specs/components/config.js
Normal file
74
test/unit/specs/components/config.js
Normal file
|
@ -0,0 +1,74 @@
|
|||
define(function (require) {
|
||||
describe('config component', function () {
|
||||
var $scope;
|
||||
var config;
|
||||
var defaults;
|
||||
var configFile;
|
||||
|
||||
beforeEach(module('kibana'));
|
||||
beforeEach(inject(function ($injector, Private) {
|
||||
config = $injector.get('config');
|
||||
$scope = $injector.get('$rootScope');
|
||||
configFile = $injector.get('configFile');
|
||||
defaults = Private(require('components/config/defaults'));
|
||||
}));
|
||||
|
||||
it('exposes the configFile', function () {
|
||||
expect(config.file).to.be(configFile);
|
||||
});
|
||||
|
||||
describe('#get', function () {
|
||||
|
||||
it('gives access to config values', function () {
|
||||
expect(config.get('dateFormat')).to.be.a('string');
|
||||
});
|
||||
|
||||
it('reads from the defaults', function () {
|
||||
var initial = config.get('dateFormat');
|
||||
var newDefault = initial + '- new';
|
||||
defaults.dateFormat.value = newDefault;
|
||||
expect(config.get('dateFormat')).to.be(newDefault);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#set', function () {
|
||||
|
||||
it('stores a value in the config val set', function () {
|
||||
var initial = config.get('dateFormat');
|
||||
config.set('dateFormat', 'notaformat');
|
||||
expect(config.get('dateFormat')).to.be('notaformat');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#$bind', function () {
|
||||
|
||||
it('binds a config key to a $scope property', function () {
|
||||
var dateFormat = config.get('dateFormat');
|
||||
config.$bind($scope, 'dateFormat');
|
||||
expect($scope).to.have.property('dateFormat', dateFormat);
|
||||
});
|
||||
|
||||
it('alows overriding the property name', function () {
|
||||
var dateFormat = config.get('dateFormat');
|
||||
config.$bind($scope, 'dateFormat', 'defaultDateFormat');
|
||||
expect($scope).to.not.have.property('dateFormat');
|
||||
expect($scope).to.have.property('defaultDateFormat', dateFormat);
|
||||
});
|
||||
|
||||
it('keeps the property up to date', function () {
|
||||
var dateFormat = config.get('dateFormat');
|
||||
var newDateFormat = dateFormat + ' NEW NEW NEW!';
|
||||
config.$bind($scope, 'dateFormat');
|
||||
|
||||
expect($scope).to.have.property('dateFormat', dateFormat);
|
||||
config.set('dateFormat', newDateFormat);
|
||||
expect($scope).to.have.property('dateFormat', newDateFormat);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
|
@ -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]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
|
@ -16,7 +16,11 @@ define(function (require) {
|
|||
['test / ^test', 'test-slash-^test'],
|
||||
['test ? test', 'test-questionmark-test'],
|
||||
['test = test', 'test-equal-test'],
|
||||
['test & test', 'test-ampersand-test']
|
||||
['test & test', 'test-ampersand-test'],
|
||||
['test/test/test', 'test-slash-test-slash-test'],
|
||||
['test?test?test', 'test-questionmark-test-questionmark-test'],
|
||||
['test&test&test', 'test-ampersand-test-ampersand-test'],
|
||||
['test=test=test', 'test-equal-test-equal-test']
|
||||
];
|
||||
|
||||
_.each(fixtures, function (fixture) {
|
||||
|
|
|
@ -205,6 +205,37 @@ define(function (require) {
|
|||
|
||||
});
|
||||
|
||||
describe('_removeZeroSlices', function () {
|
||||
var pieData = {
|
||||
slices: {
|
||||
children: [
|
||||
{size: 30},
|
||||
{size: 20},
|
||||
{size: 0}
|
||||
]
|
||||
}
|
||||
};
|
||||
var DataFactory;
|
||||
var data;
|
||||
|
||||
beforeEach(function () {
|
||||
module('DataFactory');
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
inject(function (Private) {
|
||||
DataFactory = Private(require('components/vislib/lib/data'));
|
||||
data = new DataFactory(pieData, {});
|
||||
data._removeZeroSlices(pieData.slices);
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove zero values', function () {
|
||||
var slices = data.data.slices;
|
||||
expect(slices.children.length).to.be(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Data.flatten', function () {
|
||||
var DataFactory;
|
||||
var serIn;
|
||||
|
|
|
@ -5,17 +5,20 @@ define(function (require) {
|
|||
|
||||
var slices = require('vislib_fixtures/mock_data/histogram/_slices');
|
||||
var stackedSeries = require('vislib_fixtures/mock_data/date_histogram/_stacked_series');
|
||||
var histogramSlices = require('vislib_fixtures/mock_data/histogram/_slices');
|
||||
|
||||
var dataArray = [
|
||||
stackedSeries,
|
||||
slices,
|
||||
histogramSlices,
|
||||
stackedSeries,
|
||||
stackedSeries,
|
||||
stackedSeries
|
||||
];
|
||||
|
||||
var chartTypes = [
|
||||
'histogram',
|
||||
'pie',
|
||||
'pie',
|
||||
'area',
|
||||
'line'
|
||||
];
|
||||
|
@ -24,7 +27,7 @@ define(function (require) {
|
|||
histogram: '.chart rect',
|
||||
pie: '.chart path',
|
||||
area: '.chart path',
|
||||
line: '.chart circle',
|
||||
line: '.chart circle'
|
||||
};
|
||||
|
||||
angular.module('LegendFactory', ['kibana']);
|
||||
|
|
|
@ -118,6 +118,41 @@ define(function (require) {
|
|||
expect($(chart1.el).find('.y-axis-chart-title').length).to.be(1);
|
||||
expect($(chart2.el).find('.x-axis-chart-title').length).to.be(1);
|
||||
});
|
||||
|
||||
describe('_validatePieData method', function () {
|
||||
var allZeros = [
|
||||
{ slices: { children: [] } },
|
||||
{ slices: { children: [] } },
|
||||
{ slices: { children: [] } }
|
||||
];
|
||||
|
||||
var someZeros = [
|
||||
{ slices: { children: [{}] } },
|
||||
{ slices: { children: [{}] } },
|
||||
{ slices: { children: [] } }
|
||||
];
|
||||
|
||||
var noZeros = [
|
||||
{ slices: { children: [{}] } },
|
||||
{ slices: { children: [{}] } },
|
||||
{ slices: { children: [{}] } }
|
||||
];
|
||||
|
||||
it('should throw an error when all charts contain zeros', function () {
|
||||
expect(function () {
|
||||
chart1.ChartClass.prototype._validatePieData(allZeros);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should not throw an error when only some or no charts contain zeros', function () {
|
||||
expect(function () {
|
||||
chart1.ChartClass.prototype._validatePieData(someZeros);
|
||||
}).to.not.throwError();
|
||||
expect(function () {
|
||||
chart1.ChartClass.prototype._validatePieData(noZeros);
|
||||
}).to.not.throwError();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
aggArray.forEach(function (dataAgg, i) {
|
||||
|
|
127
test/unit/specs/vislib/visualizations/time_marker.js
Normal file
127
test/unit/specs/vislib/visualizations/time_marker.js
Normal file
|
@ -0,0 +1,127 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
var _ = require('lodash');
|
||||
var $ = require('jquery');
|
||||
var fixtures = require('fixtures/fake_hierarchical_data');
|
||||
var series = require('vislib_fixtures/mock_data/date_histogram/_series');
|
||||
var terms = require('vislib_fixtures/mock_data/terms/_columns');
|
||||
|
||||
angular.module('TimeMarkerFactory', ['kibana']);
|
||||
describe('VisLib Time Marker Test Suite', function () {
|
||||
var height = 50;
|
||||
var color = '#ff0000';
|
||||
var opacity = 0.5;
|
||||
var width = 3;
|
||||
var customClass = 'custom-time-marker';
|
||||
var dateMathTimes = ['now-1m', 'now-5m', 'now-15m'];
|
||||
var myTimes = dateMathTimes.map(function (dateMathString) {
|
||||
return {
|
||||
time: dateMathString,
|
||||
class: customClass,
|
||||
color: color,
|
||||
opacity: opacity,
|
||||
width: width
|
||||
};
|
||||
});
|
||||
var getExtent = function (dataArray, func) {
|
||||
return func(dataArray, function (obj) {
|
||||
return func(obj.values, function (d) {
|
||||
return d.x;
|
||||
});
|
||||
});
|
||||
};
|
||||
var times = [];
|
||||
var TimeMarker;
|
||||
var defaultMarker;
|
||||
var customMarker;
|
||||
var selection;
|
||||
var xScale;
|
||||
var minDomain;
|
||||
var maxDomain;
|
||||
var domain;
|
||||
|
||||
beforeEach(function () {
|
||||
module('TimeMarkerFactory');
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
inject(function (d3, Private) {
|
||||
TimeMarker = Private(require('components/vislib/visualizations/time_marker'));
|
||||
minDomain = getExtent(series.series, d3.min);
|
||||
maxDomain = getExtent(series.series, d3.max);
|
||||
domain = [minDomain, maxDomain];
|
||||
xScale = d3.time.scale().domain(domain).range([0, 500]);
|
||||
defaultMarker = new TimeMarker(times, xScale, height);
|
||||
customMarker = new TimeMarker(myTimes, xScale, height);
|
||||
|
||||
selection = d3.select('body').append('div').attr('class', 'marker');
|
||||
selection.datum(series);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
selection.remove('*');
|
||||
selection = null;
|
||||
defaultMarker = null;
|
||||
});
|
||||
|
||||
describe('_isTimeBaseChart method', function () {
|
||||
var boolean;
|
||||
var newSelection;
|
||||
|
||||
it('should return true when data is time based', function () {
|
||||
boolean = defaultMarker._isTimeBasedChart(selection);
|
||||
expect(boolean).to.be(true);
|
||||
});
|
||||
|
||||
it('should return false when data is not time based', function () {
|
||||
newSelection = selection.datum(terms);
|
||||
boolean = defaultMarker._isTimeBasedChart(newSelection);
|
||||
expect(boolean).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('render method', function () {
|
||||
var lineArray;
|
||||
|
||||
beforeEach(function () {
|
||||
defaultMarker.render(selection);
|
||||
customMarker.render(selection);
|
||||
lineArray = document.getElementsByClassName('custom-time-marker');
|
||||
});
|
||||
|
||||
it('should render the default line', function () {
|
||||
expect(!!$('line.time-marker').length).to.be(true);
|
||||
});
|
||||
|
||||
it('should render the custom (user defined) lines', function () {
|
||||
expect($('line.custom-time-marker').length).to.be(myTimes.length);
|
||||
});
|
||||
|
||||
it('should set the class', function () {
|
||||
Array.prototype.forEach.call(lineArray, function (line) {
|
||||
expect(line.getAttribute('class')).to.be(customClass);
|
||||
});
|
||||
});
|
||||
|
||||
it('should set the stroke', function () {
|
||||
Array.prototype.forEach.call(lineArray, function (line) {
|
||||
expect(line.getAttribute('stroke')).to.be(color);
|
||||
});
|
||||
});
|
||||
|
||||
it('should set the stroke-opacity', function () {
|
||||
Array.prototype.forEach.call(lineArray, function (line) {
|
||||
expect(+line.getAttribute('stroke-opacity')).to.be(opacity);
|
||||
});
|
||||
});
|
||||
|
||||
it('should set the stroke-width', function () {
|
||||
Array.prototype.forEach.call(lineArray, function (line) {
|
||||
expect(+line.getAttribute('stroke-width')).to.be(width);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue