mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Merge branch 'master' of github.com:elastic/kibana into ignorePrivateFields
This commit is contained in:
commit
c4408feb8a
108 changed files with 1977 additions and 828 deletions
|
@ -22,6 +22,13 @@ Please make sure you have signed the [Contributor License Agreement](http://www.
|
|||
```sh
|
||||
npm install -g grunt-cli bower
|
||||
```
|
||||
|
||||
- Clone the kibana repo and move into it
|
||||
|
||||
```sh
|
||||
git clone https://github.com/elastic/kibana.git kibana
|
||||
cd kibana
|
||||
```
|
||||
|
||||
- Install node and bower dependencies
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</span>
|
||||
<div class="hintbox" ng-show="showAnalyzedFieldWarning">
|
||||
<p>
|
||||
<strong>Careful!</strong> The field selected contains analyzed strings. Values such as <i>foo-bar</i> will be broken into <i>foo</i> and <i>bar</i>. See <a href="http://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-core-types.html" target="_blank">Mapping Core Types</a> for more information on setting this field as <i>not_analyzed</i>
|
||||
<strong>Careful!</strong> The field selected contains analyzed strings. Analyzed strings are highly unique and can use a lot of memory to visualize. Values such as <i>foo-bar</i> will be broken into <i>foo</i> and <i>bar</i>. See <a href="http://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-core-types.html" target="_blank">Mapping Core Types</a> for more information on setting this field as <i>not_analyzed</i>
|
||||
</p>
|
||||
|
||||
<p ng-show="indexedFields.byName[agg.params.field.name + '.raw'].analyzed == false">
|
||||
|
@ -22,9 +22,17 @@
|
|||
name="field"
|
||||
required
|
||||
ng-model="agg.params.field"
|
||||
ng-if="indexedFields.length"
|
||||
auto-select-if-only-one="indexedFields"
|
||||
ng-options="field as field.displayName group by field.type for field in indexedFields"
|
||||
ng-change="aggParam.onChange(agg)">
|
||||
</select>
|
||||
|
||||
<div class="hintbox" ng-if="!indexedFields.length">
|
||||
<p>
|
||||
<i class="fa fa-danger text-danger"></i>
|
||||
<strong>No Compatible Fields:</strong> The "{{ vis.indexPattern.id }}" index pattern does not any of the following field types: {{ agg.type.params.byName.field.filterFieldTypes | commaList:false }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -17,6 +17,15 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input ng-model="agg.params.filters.length" name="filterLength" required min="1" type="number" class="ng-hide">
|
||||
<div class="hintbox" ng-show="aggForm.filterLength.$invalid">
|
||||
<p>
|
||||
<i class="fa fa-danger text-danger"></i>
|
||||
<strong>Required:</strong> You must specify at least one filter
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
click-focus="'filter'+(agg.params.filters.length-1)"
|
||||
ng-click="agg.params.filters.push({input:{}})"
|
||||
|
|
|
@ -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,31 +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">
|
||||
<p ng-show="aggForm.validLength.$invalid" class="text-danger text-center">
|
||||
You mush specify at least one percentile
|
||||
</p>
|
||||
|
||||
<button
|
||||
ng-click="add()"
|
||||
type="button"
|
||||
class="sidebar-item-button primary">
|
||||
<i class="fa fa-plus"></i> Add Percent
|
||||
</button>
|
||||
</div>
|
|
@ -3,7 +3,11 @@
|
|||
<label>JSON Input</label>
|
||||
<i class="fa fa-info-circle"></i>
|
||||
</span>
|
||||
<div class="hintbox" ng-show="showJsonHint">Any JSON formatted properties you add here will be merged with the elasticsearch aggregation definition for this section. For example <i>shard_size</i> on a <i>terms</i> aggregation</div>
|
||||
<div class="hintbox" ng-show="showJsonHint">
|
||||
<p>
|
||||
Any JSON formatted properties you add here will be merged with the elasticsearch aggregation definition for this section. For example <i>shard_size</i> on a <i>terms</i> aggregation
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
<textarea
|
||||
type="text"
|
||||
|
@ -12,4 +16,4 @@
|
|||
validate-json
|
||||
></textarea>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,30 +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">
|
||||
<p ng-show="aggForm.validLength.$invalid" class="text-danger text-center">
|
||||
You must specify at least one value
|
||||
</p>
|
||||
|
||||
<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) {
|
|||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
26
src/kibana/components/comma_list_filter.js
Normal file
26
src/kibana/components/comma_list_filter.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
|
||||
require('modules')
|
||||
.get('kibana')
|
||||
.filter('commaList', function () {
|
||||
/**
|
||||
* Angular filter that accepts either an array or a comma-seperated string
|
||||
* and outputs either an array, or a comma-seperated string for presentation.
|
||||
*
|
||||
* @param {String|Array} input - The comma-seperated list or array
|
||||
* @param {Boolean} inclusive - Should the list be joined with an "and"?
|
||||
* @return {String}
|
||||
*/
|
||||
return function (input, inclusive) {
|
||||
var list = _.commaSeperatedList(input);
|
||||
if (list.length < 2) {
|
||||
return list.join('');
|
||||
}
|
||||
|
||||
var conj = inclusive ? ' and ' : ' or ';
|
||||
return list.slice(0, -1).join(', ') + conj + _.last(list);
|
||||
|
||||
};
|
||||
});
|
||||
});
|
|
@ -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) {
|
|||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,16 +5,16 @@
|
|||
</ul>
|
||||
|
||||
<div class="content">
|
||||
<table class="table table-condensed" ng-show="mode == 'table'" bindonce>
|
||||
<table class="table table-condensed" ng-show="mode == 'table'">
|
||||
<tbody>
|
||||
<tr ng-repeat="field in fields" bindonce>
|
||||
<tr ng-repeat="field in fields">
|
||||
<td field-name="field"
|
||||
field-type="mapping[field].type"
|
||||
width="1%"
|
||||
class="doc-viewer-field">
|
||||
</td>
|
||||
<td width="1%" class="doc-viewer-buttons" ng-if="filter">
|
||||
<span bo-if="mapping[field].filterable">
|
||||
<span ng-if="mapping[field].filterable">
|
||||
<i ng-click="filter(mapping[field], flattened[field], '+')"
|
||||
tooltip="Filter for value"
|
||||
tooltip-append-to-body="1"
|
||||
|
@ -24,11 +24,11 @@
|
|||
tooltip-append-to-body="1"
|
||||
class="fa fa-search-minus"></i>
|
||||
</span>
|
||||
<span bo-if="!mapping[field].filterable" tooltip="Unindexed fields can not be searched">
|
||||
<span ng-if="!mapping[field].filterable" tooltip="Unindexed fields can not be searched">
|
||||
<i class="fa fa-search-plus text-muted"></i>
|
||||
<i class="fa fa-search-minus text-muted"></i>
|
||||
</span>
|
||||
<span bo-if="columns">
|
||||
<span ng-if="columns">
|
||||
<i ng-click="toggleColumn(field)"
|
||||
tooltip="Toggle column in table"
|
||||
tooltip-append-to-body="1"
|
||||
|
@ -37,21 +37,19 @@
|
|||
</td>
|
||||
|
||||
<td>
|
||||
<i bo-if="!mapping[field] && field[0] === '_'"
|
||||
<i ng-if="!mapping[field] && field[0] === '_'"
|
||||
tooltip-placement="top"
|
||||
tooltip="Field names beginning with _ are not supported"
|
||||
class="fa fa-warning text-color-warning ng-scope doc-viewer-underscore"></i>
|
||||
<i bo-if="!mapping[field] && field[0] !== '_' && !showArrayInObjectsWarning(doc, field)"
|
||||
<i ng-if="!mapping[field] && field[0] !== '_' && !showArrayInObjectsWarning(doc, field)"
|
||||
tooltip-placement="top"
|
||||
tooltip="No cached mapping for this field. Refresh your mapping from the Settings > Indices page"
|
||||
class="fa fa-warning text-color-warning ng-scope doc-viewer-no-mapping"></i>
|
||||
<i bo-if="showArrayInObjectsWarning(doc, field)"
|
||||
<i ng-if="showArrayInObjectsWarning(doc, field)"
|
||||
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>
|
||||
|
|
|
@ -21,7 +21,11 @@ define(function (require) {
|
|||
key = keyPrefix + key;
|
||||
|
||||
if (flat[key] !== void 0) return;
|
||||
if (fields[key] || !_.isPlainObject(val)) {
|
||||
|
||||
var hasValidMapping = (fields[key] && fields[key].type !== 'conflict');
|
||||
var isValue = !_.isPlainObject(val);
|
||||
|
||||
if (hasValidMapping || isValue) {
|
||||
flat[key] = val;
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
15
src/kibana/components/vislib/lib/_data_label.js
Normal file
15
src/kibana/components/vislib/lib/_data_label.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
define(function (require) {
|
||||
var d3 = require('d3');
|
||||
/**
|
||||
* Creates a string based on the hex color passed in
|
||||
*
|
||||
* @method dataLabel
|
||||
* @param d {Object} object to wrap in d3.select
|
||||
* @returns {string} label value
|
||||
*/
|
||||
function dataLabel(selection, label) {
|
||||
d3.select(selection).attr('data-label', label);
|
||||
}
|
||||
|
||||
return dataLabel;
|
||||
});
|
|
@ -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);
|
||||
|
|
|
@ -227,14 +227,15 @@ define(function (require) {
|
|||
* @method highlightLegend
|
||||
*/
|
||||
Dispatch.prototype.highlightLegend = function (element) {
|
||||
var classList = d3.select(this).node().classList;
|
||||
var liClass = d3.select(this).node().classList[1];
|
||||
var label = this.getAttribute('data-label');
|
||||
|
||||
if (!label) return;
|
||||
|
||||
d3.select(element)
|
||||
.select('.legend-ul')
|
||||
.selectAll('li.color')
|
||||
.filter(function (d, i) {
|
||||
return d3.select(this).node().classList[1] !== liClass;
|
||||
return this.getAttribute('data-label') !== label;
|
||||
})
|
||||
.classed('blur_shape', true);
|
||||
};
|
||||
|
|
|
@ -77,6 +77,9 @@ define(function (require) {
|
|||
Handler.prototype.render = function () {
|
||||
var self = this;
|
||||
var charts = this.charts = [];
|
||||
var selection = d3.select(this.el);
|
||||
|
||||
selection.selectAll('*').remove();
|
||||
|
||||
this._validateData();
|
||||
this.renderArray.forEach(function (property) {
|
||||
|
@ -86,8 +89,7 @@ define(function (require) {
|
|||
});
|
||||
|
||||
// render the chart(s)
|
||||
d3.select(this.el)
|
||||
.selectAll('.chart')
|
||||
selection.selectAll('.chart')
|
||||
.each(function (chartData) {
|
||||
var chart = new self.ChartClass(self, this, chartData);
|
||||
var enabledEvents;
|
||||
|
|
|
@ -2,6 +2,7 @@ define(function (require) {
|
|||
return function LegendFactory(d3) {
|
||||
var _ = require('lodash');
|
||||
var legendHeaderTemplate = _.template(require('text!components/vislib/partials/legend_header.html'));
|
||||
var dataLabel = require('components/vislib/lib/_data_label');
|
||||
|
||||
require('css!components/vislib/styles/main');
|
||||
|
||||
|
@ -76,27 +77,24 @@ define(function (require) {
|
|||
.data(arrOfLabels)
|
||||
.enter()
|
||||
.append('li')
|
||||
.attr('class', function (d) {
|
||||
return 'color ' + self.colorToClass(args.color(d));
|
||||
})
|
||||
.attr('class', 'color')
|
||||
.each(self._addIdentifier)
|
||||
.html(function (d) {
|
||||
return '<i class="fa fa-circle dots" style="color:' + args.color(d) + '"></i>' + d;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a class name based on the hexColor assigned to each label
|
||||
* Append the data label to the element
|
||||
*
|
||||
* @method colorToClass
|
||||
* @param hexColor {String} Label
|
||||
* @returns {string} CSS class name
|
||||
* @method _addIdentifier
|
||||
* @param label {string} label to use
|
||||
*/
|
||||
Legend.prototype.colorToClass = function (hexColor) {
|
||||
if (hexColor) {
|
||||
return 'c' + hexColor.replace(/[#]/g, '');
|
||||
}
|
||||
Legend.prototype._addIdentifier = function (label) {
|
||||
dataLabel(this, label);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Renders legend
|
||||
*
|
||||
|
@ -134,35 +132,20 @@ define(function (require) {
|
|||
});
|
||||
|
||||
legendDiv.select('.legend-ul').selectAll('li')
|
||||
.on('mouseover', function (d) {
|
||||
var liClass = self.colorToClass(self.color(d));
|
||||
.on('mouseover', function (label) {
|
||||
var charts = visEl.selectAll('.chart');
|
||||
|
||||
// legend
|
||||
legendDiv.selectAll('li')
|
||||
.filter(function (d) {
|
||||
return d3.select(this).node().classList[1] !== liClass;
|
||||
return this.getAttribute('data-label') !== label.toString();
|
||||
})
|
||||
.classed('blur_shape', true);
|
||||
|
||||
// lines/area
|
||||
charts.selectAll('.color')
|
||||
// all data-label attribute
|
||||
charts.selectAll('[data-label]')
|
||||
.filter(function (d) {
|
||||
return d3.select(this).node().classList[1] !== liClass;
|
||||
})
|
||||
.classed('blur_shape', true);
|
||||
|
||||
// circles
|
||||
charts.selectAll('.line circle')
|
||||
.filter(function (d) {
|
||||
return d3.select(this).node().classList[1] !== liClass;
|
||||
})
|
||||
.classed('blur_shape', true);
|
||||
|
||||
// pie slices
|
||||
charts.selectAll('.slice')
|
||||
.filter(function (d) {
|
||||
return d3.select(this).node().classList[1] !== liClass;
|
||||
return this.getAttribute('data-label') !== label.toString();
|
||||
})
|
||||
.classed('blur_shape', true);
|
||||
|
||||
|
@ -182,16 +165,8 @@ define(function (require) {
|
|||
legendDiv.selectAll('li')
|
||||
.classed('blur_shape', false);
|
||||
|
||||
// lines/areas
|
||||
charts.selectAll('.color')
|
||||
.classed('blur_shape', false);
|
||||
|
||||
// circles
|
||||
charts.selectAll('.line circle')
|
||||
.classed('blur_shape', false);
|
||||
|
||||
// pie slices
|
||||
charts.selectAll('.slice')
|
||||
// all data-label attribute
|
||||
charts.selectAll('[data-label]')
|
||||
.classed('blur_shape', false);
|
||||
|
||||
var eventEl = d3.select(this);
|
||||
|
|
|
@ -254,7 +254,7 @@ define(function (require) {
|
|||
|
||||
div = d3.select(this);
|
||||
width = parentWidth / n;
|
||||
height = $(this).height();
|
||||
height = $(this.parentElement).height();
|
||||
|
||||
self.validateWidthandHeight(width, height);
|
||||
|
||||
|
|
|
@ -148,7 +148,7 @@
|
|||
.x-axis-div-wrapper {
|
||||
.display(flex);
|
||||
.flex-direction(row);
|
||||
min-height: 15px;
|
||||
min-height: 20px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
@ -100,10 +105,14 @@ define(function (require) {
|
|||
* @method destroy
|
||||
*/
|
||||
Vis.prototype.destroy = function () {
|
||||
var selection = d3.select(this.el).select('.vis-wrapper');
|
||||
|
||||
this.resizeChecker.off('resize', this.resize);
|
||||
this.resizeChecker.destroy();
|
||||
if (this.handler) this._runOnHandler('destroy');
|
||||
d3.select(this.el).selectAll('*').remove();
|
||||
|
||||
selection.remove();
|
||||
selection = null;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
define(function (require) {
|
||||
return function ChartBaseClass(d3, Private) {
|
||||
var _ = require('lodash');
|
||||
var errors = require('errors');
|
||||
|
||||
var Legend = Private(require('components/vislib/lib/legend'));
|
||||
var Dispatch = Private(require('components/vislib/lib/dispatch'));
|
||||
var Tooltip = Private(require('components/vislib/components/tooltip/tooltip'));
|
||||
var dataLabel = require('components/vislib/lib/_data_label');
|
||||
|
||||
/**
|
||||
* The Base Class for all visualizations.
|
||||
|
@ -44,18 +45,25 @@ define(function (require) {
|
|||
* @returns {HTMLElement} Contains the D3 chart
|
||||
*/
|
||||
Chart.prototype.render = function () {
|
||||
return d3.select(this.chartEl).call(this.draw());
|
||||
var selection = d3.select(this.chartEl);
|
||||
|
||||
selection.selectAll('*').remove();
|
||||
selection.call(this.draw());
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a CSS class name
|
||||
* Append the data label to the element
|
||||
*
|
||||
* @method colorToClass
|
||||
* @param label {String} Data point label
|
||||
* @returns {String} CSS class name
|
||||
* @method _addIdentifier
|
||||
* @param selection {Object} d3 select object
|
||||
*/
|
||||
Chart.prototype.colorToClass = function (label) {
|
||||
return Legend.prototype.colorToClass.call(null, label);
|
||||
Chart.prototype._addIdentifier = function (selection, labelProp) {
|
||||
labelProp = labelProp || 'label';
|
||||
selection.each(function (datum) {
|
||||
var label = datum[0] ? datum[0][labelProp] : datum[labelProp];
|
||||
if (label == null) return;
|
||||
dataLabel(this, label);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -64,7 +72,10 @@ define(function (require) {
|
|||
* @method destroy
|
||||
*/
|
||||
Chart.prototype.destroy = function () {
|
||||
d3.select(this.chartEl).selectAll('*').remove();
|
||||
var selection = d3.select(this.chartEl);
|
||||
|
||||
selection.remove();
|
||||
selection = null;
|
||||
};
|
||||
|
||||
return Chart;
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
@ -82,22 +83,18 @@ define(function (require) {
|
|||
})
|
||||
.interpolate(interpolate);
|
||||
|
||||
var layer;
|
||||
var path;
|
||||
|
||||
// Data layers
|
||||
layer = svg.selectAll('.layer')
|
||||
var layer = svg.selectAll('.layer')
|
||||
.data(layers)
|
||||
.enter().append('g')
|
||||
.enter()
|
||||
.append('g')
|
||||
.attr('class', function (d, i) {
|
||||
return i;
|
||||
return 'pathgroup ' + i;
|
||||
});
|
||||
|
||||
// Append path
|
||||
path = layer.append('path')
|
||||
.attr('class', function (d) {
|
||||
return 'color ' + self.colorToClass(color(d[0].label));
|
||||
})
|
||||
var path = layer.append('path')
|
||||
.call(this._addIdentifier)
|
||||
.style('fill', function (d) {
|
||||
return color(d[0].label);
|
||||
})
|
||||
|
@ -151,7 +148,7 @@ define(function (require) {
|
|||
var yScale = this.handler.yAxis.yScale;
|
||||
var ordered = this.handler.data.get('ordered');
|
||||
var circleRadius = 12;
|
||||
var circleStrokeWidth = 1;
|
||||
var circleStrokeWidth = 0;
|
||||
var tooltip = this.tooltip;
|
||||
var isTooltip = this._attr.addTooltip;
|
||||
var isOverlapping = this.isOverlapping;
|
||||
|
@ -180,9 +177,7 @@ define(function (require) {
|
|||
circles
|
||||
.enter()
|
||||
.append('circle')
|
||||
.attr('class', function circleClass(d) {
|
||||
return d.label + ' ' + self.colorToClass(color(d.label));
|
||||
})
|
||||
.call(this._addIdentifier)
|
||||
.attr('stroke', function strokeColor(d) {
|
||||
return color(d.label);
|
||||
})
|
||||
|
@ -272,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;
|
||||
|
@ -289,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();
|
||||
}
|
||||
|
@ -339,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');
|
||||
|
||||
|
@ -68,9 +69,7 @@ define(function (require) {
|
|||
bars
|
||||
.enter()
|
||||
.append('rect')
|
||||
.attr('class', function (d) {
|
||||
return 'color ' + self.colorToClass(color(d.label));
|
||||
})
|
||||
.call(this._addIdentifier)
|
||||
.attr('fill', function (d) {
|
||||
return color(d.label);
|
||||
});
|
||||
|
@ -271,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;
|
||||
|
@ -287,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();
|
||||
}
|
||||
|
@ -327,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');
|
||||
|
||||
/**
|
||||
|
@ -144,9 +145,8 @@ define(function (require) {
|
|||
.attr('cx', cx)
|
||||
.attr('cy', cy)
|
||||
.attr('fill', colorCircle)
|
||||
.attr('class', function circleClass(d) {
|
||||
return 'circle-decoration ' + self.colorToClass(color(d.label));
|
||||
});
|
||||
.attr('class', 'circle-decoration')
|
||||
.call(this._addIdentifier);
|
||||
|
||||
circles
|
||||
.enter()
|
||||
|
@ -155,9 +155,8 @@ define(function (require) {
|
|||
.attr('cx', cx)
|
||||
.attr('cy', cy)
|
||||
.attr('fill', 'transparent')
|
||||
.attr('class', function circleClass(d) {
|
||||
return 'circle ' + self.colorToClass(color(d.label));
|
||||
})
|
||||
.attr('class', 'circle')
|
||||
.call(this._addIdentifier)
|
||||
.attr('stroke', cColor)
|
||||
.attr('stroke-width', 0);
|
||||
|
||||
|
@ -202,12 +201,10 @@ define(function (require) {
|
|||
.data(data)
|
||||
.enter()
|
||||
.append('g')
|
||||
.attr('class', 'lines');
|
||||
.attr('class', 'pathgroup lines');
|
||||
|
||||
lines.append('path')
|
||||
.attr('class', function lineClass(d) {
|
||||
return 'color ' + self.colorToClass(color(d.label));
|
||||
})
|
||||
.call(this._addIdentifier)
|
||||
.attr('d', function lineD(d) {
|
||||
return line(d.values);
|
||||
})
|
||||
|
@ -262,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;
|
||||
|
@ -292,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();
|
||||
}
|
||||
|
@ -335,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
|
||||
*
|
||||
|
@ -83,10 +98,19 @@ define(function (require) {
|
|||
* @returns {D3.Selection} SVG with paths attached
|
||||
*/
|
||||
PieChart.prototype.addPath = function (width, height, svg, slices) {
|
||||
var self = this;
|
||||
var marginFactor = 0.95;
|
||||
var isDonut = this._attr.isDonut;
|
||||
var isDonut = self._attr.isDonut;
|
||||
var radius = (Math.min(width, height) / 2) * marginFactor;
|
||||
var color = this.handler.data.getPieColorFunc();
|
||||
var color = self.handler.data.getPieColorFunc();
|
||||
var tooltip = self.tooltip;
|
||||
var isTooltip = self._attr.addTooltip;
|
||||
|
||||
var format = function (d, label) {
|
||||
var formatter = d.aggConfig ? d.aggConfig.fieldFormatter() : String;
|
||||
return formatter(label);
|
||||
};
|
||||
|
||||
var partition = d3.layout.partition()
|
||||
.sort(null)
|
||||
.value(function (d) {
|
||||
|
@ -115,16 +139,8 @@ define(function (require) {
|
|||
.outerRadius(function (d) {
|
||||
return Math.max(0, y(d.y + d.dy));
|
||||
});
|
||||
var tooltip = this.tooltip;
|
||||
var isTooltip = this._attr.addTooltip;
|
||||
var self = this;
|
||||
var path;
|
||||
var format = function (d, label) {
|
||||
var formatter = d.aggConfig ? d.aggConfig.fieldFormatter() : String;
|
||||
return formatter(label);
|
||||
};
|
||||
|
||||
path = svg
|
||||
var path = svg
|
||||
.datum(slices)
|
||||
.selectAll('path')
|
||||
.data(partition.nodes)
|
||||
|
@ -133,8 +149,9 @@ define(function (require) {
|
|||
.attr('d', arc)
|
||||
.attr('class', function (d) {
|
||||
if (d.depth === 0) { return; }
|
||||
return 'slice ' + self.colorToClass(color(format(d, d.name)));
|
||||
return 'slice';
|
||||
})
|
||||
.call(self._addIdentifier, 'name')
|
||||
.style('stroke', '#fff')
|
||||
.style('fill', function (d) {
|
||||
if (d.depth === 0) { return 'none'; }
|
||||
|
@ -148,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
|
||||
*
|
||||
|
@ -160,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)
|
||||
|
@ -178,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');
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
<div class="pagination-other-pages">
|
||||
<ul class="pagination-other-pages-list pagination-sm" ng-if="page.count > 1">
|
||||
<li ng-class="{disabled: page.first}">
|
||||
<li ng-hide="page.first">
|
||||
<a ng-click="paginate.goToPage(page.prev)">«</a>
|
||||
</li>
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
|||
<a ng-click="paginate.goToPage(page.count)">...{{page.count}}</a>
|
||||
</li>
|
||||
|
||||
<li ng-class="{disabled: page.last}">
|
||||
<li ng-hide="page.last">
|
||||
<a ng-click="paginate.goToPage(page.next)">»</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -30,4 +30,4 @@
|
|||
<select ng-model="paginate.perPage" ng-options="opt.value as opt.title for opt in paginate.sizeOptions">
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -35,7 +35,8 @@ define(function (require) {
|
|||
|
||||
if (field.analyzed && field.type === 'string') {
|
||||
warnings.push('This is an analyzed string field.' +
|
||||
' Analyzed strings are highly unique and can use a lot of memory to visualize.');
|
||||
' Analyzed strings are highly unique and can use a lot of memory to visualize.' +
|
||||
' Values such as foo-bar will be broken into foo and bar.');
|
||||
}
|
||||
|
||||
if (!field.indexed) {
|
||||
|
@ -94,4 +95,4 @@ define(function (require) {
|
|||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -75,6 +75,17 @@ define(function (require) {
|
|||
location: 'Discover'
|
||||
});
|
||||
|
||||
$scope.intervalOptions = Private(require('components/agg_types/buckets/_interval_options'));
|
||||
$scope.showInterval = false;
|
||||
|
||||
$scope.intervalEnabled = function (interval) {
|
||||
return interval.val !== 'custom';
|
||||
};
|
||||
|
||||
$scope.toggleInterval = function () {
|
||||
$scope.showInterval = !$scope.showInterval;
|
||||
};
|
||||
|
||||
// config panel templates
|
||||
$scope.configTemplate = new ConfigTemplate({
|
||||
load: require('text!plugins/discover/partials/load_search.html'),
|
||||
|
@ -166,6 +177,23 @@ define(function (require) {
|
|||
timefilter.enabled = !!timefield;
|
||||
});
|
||||
|
||||
$scope.$watch('state.interval', function (interval, oldInterval) {
|
||||
if (interval !== oldInterval && interval === 'auto') {
|
||||
$scope.showInterval = false;
|
||||
}
|
||||
$scope.fetch();
|
||||
});
|
||||
|
||||
$scope.$watch('vis.aggs', function (aggs) {
|
||||
var buckets = $scope.vis.aggs.bySchemaGroup.buckets;
|
||||
|
||||
if (buckets && buckets.length === 1) {
|
||||
$scope.intervalName = 'by ' + buckets[0].buckets.getInterval().description;
|
||||
} else {
|
||||
$scope.intervalName = 'auto';
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watchMulti([
|
||||
'rows',
|
||||
'fetchStatus'
|
||||
|
@ -394,7 +422,8 @@ define(function (require) {
|
|||
.highlight({
|
||||
pre_tags: [highlightTags.pre],
|
||||
post_tags: [highlightTags.post],
|
||||
fields: {'*': {}}
|
||||
fields: {'*': {}},
|
||||
fragment_size: 2147483647 // Limit of an integer.
|
||||
})
|
||||
.set('filter', $state.filters || []);
|
||||
});
|
||||
|
@ -425,22 +454,37 @@ define(function (require) {
|
|||
if (!$scope.opts.timefield) return Promise.resolve();
|
||||
if (loadingVis) return loadingVis;
|
||||
|
||||
var visStateAggs = [
|
||||
{
|
||||
type: 'count',
|
||||
schema: 'metric'
|
||||
},
|
||||
{
|
||||
type: 'date_histogram',
|
||||
schema: 'segment',
|
||||
params: {
|
||||
field: $scope.opts.timefield,
|
||||
interval: $state.interval,
|
||||
min_doc_count: 0
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// we shouldn't have a vis, delete it
|
||||
if (!$scope.opts.timefield && $scope.vis) {
|
||||
$scope.vis.destroy();
|
||||
$scope.searchSource.set('aggs', undefined);
|
||||
delete $scope.vis;
|
||||
// we have a vis, just modify the aggs
|
||||
if ($scope.vis) {
|
||||
var visState = $scope.vis.getState();
|
||||
visState.aggs = visStateAggs;
|
||||
|
||||
$scope.vis.setState(visState);
|
||||
return Promise.resolve($scope.vis);
|
||||
}
|
||||
|
||||
// we shouldn't have one, or already do, return whatever we already have
|
||||
if (!$scope.opts.timefield || $scope.vis) return Promise.resolve($scope.vis);
|
||||
|
||||
// TODO: a legit way to update the index pattern
|
||||
$scope.vis = new Vis($scope.indexPattern, {
|
||||
type: 'histogram',
|
||||
params: {
|
||||
addLegend: false,
|
||||
addTimeMarker: true
|
||||
},
|
||||
listeners: {
|
||||
click: function (e) {
|
||||
|
@ -451,21 +495,7 @@ define(function (require) {
|
|||
},
|
||||
brush: brushEvent
|
||||
},
|
||||
aggs: [
|
||||
{
|
||||
type: 'count',
|
||||
schema: 'metric'
|
||||
},
|
||||
{
|
||||
type: 'date_histogram',
|
||||
schema: 'segment',
|
||||
params: {
|
||||
field: $scope.opts.timefield,
|
||||
interval: 'auto',
|
||||
min_doc_count: 0
|
||||
}
|
||||
}
|
||||
]
|
||||
aggs: visStateAggs
|
||||
});
|
||||
|
||||
$scope.searchSource.aggs(function () {
|
||||
|
|
|
@ -79,6 +79,7 @@
|
|||
<span bindonce bo-bind="opts.savedSearch.title"></span>
|
||||
<i aria-label="Reload saved query" tooltip="Reload saved query" ng-click="resetQuery();" class="fa fa-undo small"></i>
|
||||
</span>
|
||||
|
||||
<strong class="discover-info-hits">{{(hits || 0) | number:0}}</strong>
|
||||
<ng-pluralize count="hits" when="{'1':'hit', 'other':'hits'}"></ng-pluralize>
|
||||
</div>
|
||||
|
@ -113,7 +114,7 @@
|
|||
|
||||
<h3>Refine your query</h3>
|
||||
<p>
|
||||
The search bar at the top uses Elasticsearch's support for Lucene Query String syntax. Let's say we're searching web server logs that have been parsed into a few fields.
|
||||
The search bar at the top uses Elasticsearch's support for Lucene <a href="http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax" target="_blank">Query String syntax</a>. Let's say we're searching web server logs that have been parsed into a few fields.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
@ -149,17 +150,30 @@
|
|||
<!-- result -->
|
||||
<div class="results" ng-show="resultState === 'ready'">
|
||||
<div class="discover-timechart" ng-if="opts.timefield">
|
||||
<center class="small">
|
||||
<span tooltip="To change the time, click the clock icon in the navigation bar">{{timeRange.from | moment}} - {{timeRange.to | moment}}</span>
|
||||
<!-- TODO: Currently no way to apply this setting to the visualization -->
|
||||
<!-- <select
|
||||
class="form-control"
|
||||
ng-model="state.interval"
|
||||
ng-options="interval as interval for interval in intervalOptions"
|
||||
ng-change="setupVisualization();fetch()"
|
||||
>
|
||||
</select> -->
|
||||
</center>
|
||||
<header>
|
||||
<center class="small">
|
||||
<span tooltip="To change the time, click the clock icon in the navigation bar">{{timeRange.from | moment}} - {{timeRange.to | moment}}</span>
|
||||
|
||||
—
|
||||
|
||||
<span class="results-interval" ng-hide="showInterval">
|
||||
<a
|
||||
ng-click="toggleInterval()">
|
||||
{{ intervalName }}
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<span ng-show="showInterval" class="results-interval form-inline">
|
||||
<select
|
||||
class="form-control"
|
||||
ng-model="state.interval"
|
||||
ng-options="interval.val as interval.display for interval in intervalOptions | filter: intervalEnabled"
|
||||
>
|
||||
</select>
|
||||
</span>
|
||||
</center>
|
||||
|
||||
</header>
|
||||
|
||||
<visualize ng-if="vis && rows.length != 0" vis="vis" es-resp="mergedEsResp" search-source="searchSource"></visualize>
|
||||
</div>
|
||||
|
|
|
@ -21,6 +21,11 @@
|
|||
display: block;
|
||||
position: relative;
|
||||
|
||||
header {
|
||||
min-height: @input-height-base + 8px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
visualize {
|
||||
height: 200px;
|
||||
max-height: 600px;
|
||||
|
@ -248,4 +253,15 @@ disc-field-chooser {
|
|||
margin: -20px 0 10px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&-interval {
|
||||
a {
|
||||
text-decoration: underline;;
|
||||
}
|
||||
|
||||
select {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -45,10 +45,10 @@
|
|||
<i aria-hidden="true" class="fa-link fa"></i>
|
||||
</a>
|
||||
</p>
|
||||
<div bo-if="indexPattern.timeFieldName && indexPattern.intervalName" class="alert alert-info">
|
||||
<div ng-if="indexPattern.timeFieldName && indexPattern.intervalName" class="alert alert-info">
|
||||
This index uses a <strong>Time-based index pattern</strong> which repeats <span bo-text="indexPattern.getInterval().display"></span>
|
||||
</div>
|
||||
<div bo-if="conflictFields.length" class="alert alert-warning">
|
||||
<div ng-if="conflictFields.length" class="alert alert-warning">
|
||||
<strong>Mapping conflict!</strong> {{conflictFields.length > 1 ? conflictFields.length : 'A'}} field{{conflictFields.length > 1 ? 's' : ''}} {{conflictFields.length > 1 ? 'are' : 'is'}} defined as several types (string, integer, etc) across the indices that match this pattern. You may still be able to use these conflict fields in parts of Kibana, but they will be unavailable for functions that require Kibana to know their type. Correcting this issue will require reindexing your data.
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -38,7 +38,9 @@ define(function (require) {
|
|||
if (!tab) $scope.changeTab($scope.fieldTypes[0]);
|
||||
});
|
||||
|
||||
$scope.conflictFields = _.filter($scope.indexPattern.fields, {type: 'conflict'});
|
||||
$scope.$watchCollection('indexPattern.fields', function () {
|
||||
$scope.conflictFields = _.filter($scope.indexPattern.fields, {type: 'conflict'});
|
||||
});
|
||||
|
||||
$scope.refreshFields = function () {
|
||||
$scope.indexPattern.refreshFields();
|
||||
|
@ -73,4 +75,4 @@ define(function (require) {
|
|||
return $scope.indexPattern.save();
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<h5>
|
||||
Index Patterns
|
||||
<a
|
||||
bo-if="edittingId"
|
||||
ng-if="edittingId"
|
||||
href="#/settings/indices"
|
||||
class="btn btn-primary btn-xs"
|
||||
aria-label="Add New">
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -17,4 +17,10 @@
|
|||
Scale Y-Axis to Data Bounds
|
||||
</label>
|
||||
</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>
|
||||
|
|
|
@ -7,13 +7,14 @@ define(function (require) {
|
|||
_(VislibRenderbot).inherits(Renderbot);
|
||||
function VislibRenderbot(vis, $el) {
|
||||
VislibRenderbot.Super.call(this, vis, $el);
|
||||
this.vislibVis = {};
|
||||
this._createVis();
|
||||
}
|
||||
|
||||
VislibRenderbot.prototype._createVis = function () {
|
||||
var self = this;
|
||||
|
||||
if (self.vislibVis) self.destroy();
|
||||
|
||||
self.vislibParams = self._getVislibParams();
|
||||
self.vislibVis = new vislib.Vis(self.$el[0], self.vislibParams);
|
||||
|
||||
|
|
|
@ -20,7 +20,9 @@ define(function (require) {
|
|||
scale: 'linear',
|
||||
mode: 'stacked',
|
||||
interpolate: 'linear',
|
||||
defaultYExtents: false
|
||||
defaultYExtents: false,
|
||||
times: [],
|
||||
addTimeMarker: false
|
||||
},
|
||||
scales: ['linear', 'log', 'square root'],
|
||||
modes: ['stacked', 'overlap', 'percentage', 'wiggle', 'silhouette'],
|
||||
|
|
|
@ -16,7 +16,9 @@ define(function (require) {
|
|||
addLegend: true,
|
||||
scale: 'linear',
|
||||
mode: 'stacked',
|
||||
defaultYExtents: false
|
||||
defaultYExtents: false,
|
||||
times: [],
|
||||
addTimeMarker: false
|
||||
},
|
||||
scales: ['linear', 'log', 'square root'],
|
||||
modes: ['stacked', 'percentage', 'grouped'],
|
||||
|
|
|
@ -20,7 +20,9 @@ define(function (require) {
|
|||
drawLinesBetweenPoints: true,
|
||||
radiusRatio: 9,
|
||||
scale: 'linear',
|
||||
defaultYExtents: false
|
||||
defaultYExtents: false,
|
||||
times: [],
|
||||
addTimeMarker: false
|
||||
},
|
||||
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>
|
||||
|
|
|
@ -8,6 +8,7 @@ define(function (require) {
|
|||
require('directives/saved_object_finder');
|
||||
require('components/visualize/visualize');
|
||||
require('components/clipboard/clipboard');
|
||||
require('components/comma_list_filter');
|
||||
|
||||
require('filters/uriescape');
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -468,3 +468,6 @@ style-compile {
|
|||
background-color: @gray-lighter;
|
||||
}
|
||||
|
||||
mark, .mark {
|
||||
background-color: rgba(252, 229, 113, 1);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -256,5 +256,25 @@ define(function (require) {
|
|||
get: function (obj, path) {
|
||||
return _.deepGet(obj, path);
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse a comma-seperated list into an array
|
||||
* efficiently, or just return if already an array
|
||||
*
|
||||
* @param {string|array} input - the comma-seperated list
|
||||
* @return {array}
|
||||
*/
|
||||
commaSeperatedList: function (input) {
|
||||
if (_.isArray(input)) return input;
|
||||
|
||||
var source = String(input || '').split(',');
|
||||
var list = [];
|
||||
while (source.length) {
|
||||
var item = source.shift().trim();
|
||||
if (item) list.push(item);
|
||||
}
|
||||
|
||||
return list;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
|
@ -34,9 +45,9 @@ try {
|
|||
}
|
||||
|
||||
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,
|
||||
|
@ -46,8 +57,10 @@ var config = module.exports = {
|
|||
package : require(packagePath),
|
||||
htpasswd : htpasswdPath,
|
||||
buildNum : '@@buildNum',
|
||||
maxSockets : kibana.maxSockets || Infinity,
|
||||
log_file : kibana.log_file || null
|
||||
maxSockets : kibana.maxSockets,
|
||||
log_file : kibana.log_file,
|
||||
request_timeout : kibana.request_timeout,
|
||||
ping_timeout : kibana.ping_timeout
|
||||
};
|
||||
|
||||
config.plugins = listPlugins(config);
|
||||
|
|
|
@ -33,6 +33,10 @@ kibana_index: ".kibana"
|
|||
# The default application to load.
|
||||
default_app_id: "discover"
|
||||
|
||||
# Time in milliseconds to wait for elasticsearch to respond to pings, defaults to
|
||||
# request_timeout setting
|
||||
# ping_timeout: 1500
|
||||
|
||||
# Time in milliseconds to wait for responses from the back end or elasticsearch.
|
||||
# This must be > 0
|
||||
request_timeout: 300000
|
||||
|
@ -41,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
|
||||
|
|
|
@ -24,7 +24,7 @@ if (config.kibana.ca) {
|
|||
module.exports = new elasticsearch.Client({
|
||||
host: url.format(uri),
|
||||
ssl: ssl,
|
||||
pingTimeout: config.kibana.ping_timeout,
|
||||
pingTimeout: config.ping_timeout,
|
||||
log: function (config) {
|
||||
this.error = function (err) {
|
||||
logger.error({ err: err });
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ router.use(function (req, res, next) {
|
|||
method: req.method,
|
||||
headers: _.defaults({}, req.headers),
|
||||
strictSSL: config.kibana.verify_ssl,
|
||||
timeout: config.kibana.request_timeout
|
||||
timeout: config.request_timeout
|
||||
};
|
||||
|
||||
options.headers['x-forward-for'] = req.connection.remoteAddress || req.socket.remoteAddress;
|
||||
|
|
|
@ -1,12 +1,5 @@
|
|||
module.exports = function (grunt) {
|
||||
var config = {
|
||||
test: {
|
||||
files: [
|
||||
'<%= unitTestDir %>/**/*.js'
|
||||
],
|
||||
tasks: ['mocha:unit']
|
||||
},
|
||||
|
||||
less: {
|
||||
files: [
|
||||
'<%= app %>/**/styles/**/*.less',
|
||||
|
|
|
@ -36,6 +36,11 @@
|
|||
ScreencastReporter: '../../node_modules/mocha-screencast-reporter/screencast-reporter'
|
||||
},
|
||||
shim: {
|
||||
angular_src: {
|
||||
deps: [
|
||||
'jquery'
|
||||
]
|
||||
},
|
||||
angular: {
|
||||
deps: [
|
||||
'jquery',
|
||||
|
|
|
@ -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]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
32
test/unit/specs/components/comma_list_filter.js
Normal file
32
test/unit/specs/components/comma_list_filter.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
define(function (require) {
|
||||
require('components/comma_list_filter');
|
||||
|
||||
describe('Comma-List filter', function () {
|
||||
|
||||
var commaList;
|
||||
|
||||
beforeEach(module('kibana'));
|
||||
beforeEach(inject(function ($injector) {
|
||||
commaList = $injector.get('commaListFilter');
|
||||
}));
|
||||
|
||||
it('converts a string to a pretty list', function () {
|
||||
expect(commaList('john,jaine,jim', true)).to.be('john, jaine and jim');
|
||||
expect(commaList('john,jaine,jim', false)).to.be('john, jaine or jim');
|
||||
});
|
||||
|
||||
it('can accept an array too', function () {
|
||||
expect(commaList(['john', 'jaine', 'jim'])).to.be('john, jaine or jim');
|
||||
});
|
||||
|
||||
it('handles undefined ok', function () {
|
||||
expect(commaList()).to.be('');
|
||||
});
|
||||
|
||||
it('handls single values ok', function () {
|
||||
expect(commaList(['john'])).to.be('john');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
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);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
|
@ -23,6 +23,9 @@ define(function (require) {
|
|||
'team': { type: 'nested' },
|
||||
'team.name': { type: 'string' },
|
||||
'team.role': { type: 'string' },
|
||||
'user': { type: 'conflict' },
|
||||
'user.name': { type: 'string' },
|
||||
'user.id': { type: 'conflict' },
|
||||
'delta': { type: 'number', scripted: true }
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +48,8 @@ define(function (require) {
|
|||
{ name: 'foo', role: 'leader' },
|
||||
{ name: 'bar', role: 'follower' },
|
||||
{ name: 'baz', role: 'party boy' },
|
||||
]
|
||||
],
|
||||
user: { name: 'smith', id: 123 }
|
||||
},
|
||||
fields: {
|
||||
delta: [42],
|
||||
|
@ -73,6 +77,12 @@ define(function (require) {
|
|||
expect(flat.groups).to.eql(['loners']);
|
||||
});
|
||||
|
||||
it('flattens conflicting types in the mapping', function () {
|
||||
expect(flat).to.not.have.property('user');
|
||||
expect(flat).to.have.property('user.name', hit._source.user.name);
|
||||
expect(flat).to.have.property('user.id', hit._source.user.id);
|
||||
});
|
||||
|
||||
it('preserves objects in arrays', function () {
|
||||
expect(flat).to.have.property('tags', hit._source.tags);
|
||||
});
|
||||
|
|
|
@ -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) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue