mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Minimal field_chooser tests
This commit is contained in:
parent
82e58269ec
commit
d0a8d5d495
17 changed files with 728 additions and 142 deletions
|
@ -1,8 +1,8 @@
|
|||
define(function (require) {
|
||||
var $ = require('jquery');
|
||||
var app = require('modules').get('apps/discover');
|
||||
var html = require('text!apps/discover/partials/discover_field.html');
|
||||
var detailsHtml = require('text!apps/discover/partials/discover_field_details.html');
|
||||
var html = require('text!apps/discover/components/field_chooser/discover_field.html');
|
||||
var detailsHtml = require('text!apps/discover/components/field_chooser/discover_field_details.html');
|
||||
var _ = require('lodash');
|
||||
|
||||
require('directives/css_truncate');
|
|
@ -15,7 +15,7 @@
|
|||
ng-click="showFields = !showFields"
|
||||
class="fa visible-xs visible-sm pull-right field-collapse-toggle" ></i>
|
||||
<button
|
||||
ng-class="{ 'btn-defualt': !filter.active, 'btn-success': filter.active, 'hidden-xs': !showFields, 'hidden-sm': !showFields }"
|
||||
ng-class="{ 'btn-default': !filter.active, 'btn-success': filter.active, 'hidden-xs': !showFields, 'hidden-sm': !showFields }"
|
||||
class="btn btn-xs btn-default pull-right discover-field-filter-toggle"
|
||||
ng-click="showFilter = !showFilter">
|
||||
<i class="fa fa-gear"></i>
|
||||
|
@ -33,7 +33,6 @@
|
|||
Analyzed
|
||||
</label>
|
||||
<select
|
||||
required
|
||||
ng-options="opt.value as opt.label for opt in filter.boolOpts"
|
||||
ng-model="filter.vals.analyzed"
|
||||
class="form-control">
|
||||
|
@ -44,7 +43,6 @@
|
|||
Indexed
|
||||
</label>
|
||||
<select
|
||||
required
|
||||
ng-options="opt.value as opt.label for opt in filter.boolOpts"
|
||||
ng-model="filter.vals.indexed"
|
||||
class="form-control">
|
||||
|
@ -55,7 +53,6 @@
|
|||
Type
|
||||
</label>
|
||||
<select
|
||||
required
|
||||
ng-options="field as field for field in fieldTypes"
|
||||
ng-model="filter.vals.type"
|
||||
class="form-control">
|
||||
|
@ -84,7 +81,7 @@
|
|||
|
||||
<ul bindonce
|
||||
ng-show="(fields | filter:filter.popularity).length > 0"
|
||||
class="list-unstyled sidebar-well" ng-class="{ 'hidden-sm': !showFields, 'hidden-xs': !showFields }">
|
||||
class="list-unstyled sidebar-well discover-popular-fields" ng-class="{ 'hidden-sm': !showFields, 'hidden-xs': !showFields }">
|
||||
<li class="sidebar-item sidebar-list-header"><h6>Popular fields</h6></li>
|
||||
<discover-field
|
||||
ng-repeat="field in popularFields | filter:filter.isFieldFiltered"
|
||||
|
@ -93,7 +90,7 @@
|
|||
</discover-field>
|
||||
</ul>
|
||||
|
||||
<ul bindonce class="list-unstyled" ng-class="{ 'hidden-sm': !showFields, 'hidden-xs': !showFields }">
|
||||
<ul bindonce class="list-unstyled discover-unpopular-fields" ng-class="{ 'hidden-sm': !showFields, 'hidden-xs': !showFields }">
|
||||
<discover-field
|
||||
ng-repeat="field in unpopularFields | filter:filter.isFieldFiltered"
|
||||
field="field"
|
|
@ -1,17 +1,17 @@
|
|||
define(function (require) {
|
||||
var app = require('modules').get('apps/discover');
|
||||
var html = require('text!apps/discover/partials/field_chooser.html');
|
||||
var html = require('text!apps/discover/components/field_chooser/field_chooser.html');
|
||||
var _ = require('lodash');
|
||||
var jsonPath = require('jsonpath');
|
||||
var rison = require('utils/rison');
|
||||
var qs = require('utils/query_string');
|
||||
var fieldCalculator = require('apps/discover/components/field_chooser/lib/field_calculator');
|
||||
|
||||
|
||||
require('directives/css_truncate');
|
||||
require('directives/field_name');
|
||||
require('filters/unique');
|
||||
require('apps/discover/directives/discover_field');
|
||||
|
||||
|
||||
require('apps/discover/components/field_chooser/discover_field');
|
||||
|
||||
app.directive('discFieldChooser', function ($location, globalState, config) {
|
||||
return {
|
||||
|
@ -21,7 +21,7 @@ define(function (require) {
|
|||
toggle: '=',
|
||||
data: '=',
|
||||
state: '=',
|
||||
searchSource: '=',
|
||||
indexPattern: '=',
|
||||
updateFilterInQuery: '=filter'
|
||||
},
|
||||
template: html,
|
||||
|
@ -104,8 +104,7 @@ define(function (require) {
|
|||
});
|
||||
|
||||
$scope.increaseFieldCounter = function (field) {
|
||||
var indexPattern = $scope.searchSource.get('index');
|
||||
indexPattern.popularizeField(field.name, 1);
|
||||
$scope.indexPattern.popularizeField(field.name, 1);
|
||||
field.count++;
|
||||
};
|
||||
|
||||
|
@ -113,7 +112,7 @@ define(function (require) {
|
|||
var agg = {};
|
||||
// If we're visualizing a date field, and our index is time based (and thus has a time filter),
|
||||
// then run a date histogram
|
||||
if (field.type === 'date' && $scope.searchSource.get('index').timeFieldName) {
|
||||
if (field.type === 'date' && $scope.indexPattern.timeFieldName) {
|
||||
agg = {
|
||||
type: 'date_histogram',
|
||||
schema: 'segment',
|
||||
|
@ -134,7 +133,6 @@ define(function (require) {
|
|||
}
|
||||
|
||||
$location.path('/visualize/create').search({
|
||||
//(query:(query_string:(query:'*')),vis:(aggs:!((params:(field:'@tags',order:desc,size:5)
|
||||
indexPattern: $scope.state.index,
|
||||
type: 'histogram',
|
||||
_a: rison.encode({
|
||||
|
@ -158,7 +156,7 @@ define(function (require) {
|
|||
|
||||
$scope.details = function (field, recompute) {
|
||||
if (_.isUndefined(field.details) || recompute) {
|
||||
field.details = getFieldValueCounts({
|
||||
field.details = fieldCalculator.getFieldValueCounts({
|
||||
data: $scope.data,
|
||||
field: field,
|
||||
count: 5,
|
||||
|
@ -170,102 +168,6 @@ define(function (require) {
|
|||
}
|
||||
};
|
||||
|
||||
var getFieldValues = function (data, field) {
|
||||
var name = field.name;
|
||||
var normalize = field.format && field.format.normalize;
|
||||
|
||||
return _.map(data, function (row) {
|
||||
var val;
|
||||
|
||||
val = _.isUndefined(row._source[name]) ? row[name] : row._source[name];
|
||||
|
||||
// for fields that come back in weird formats like geo_point
|
||||
if (val != null && normalize) val = normalize(val);
|
||||
|
||||
return val;
|
||||
});
|
||||
};
|
||||
|
||||
var getFieldValueCounts = function (params) {
|
||||
params = _.defaults(params, {
|
||||
count: 5,
|
||||
grouped: false
|
||||
});
|
||||
|
||||
if (
|
||||
params.field.type === 'geo_point'
|
||||
|| params.field.type === 'geo_shape'
|
||||
|| params.field.type === 'attachment'
|
||||
) {
|
||||
return { error: 'Analysis is not available for geo fields.' };
|
||||
}
|
||||
|
||||
var allValues = getFieldValues(params.data, params.field),
|
||||
groups = {},
|
||||
hasArrays = false,
|
||||
exists = 0,
|
||||
missing = 0,
|
||||
counts;
|
||||
|
||||
var value, k;
|
||||
for (var i = 0; i < allValues.length; ++i) {
|
||||
|
||||
value = allValues[i];
|
||||
if (_.isUndefined(value)) {
|
||||
missing++;
|
||||
}
|
||||
|
||||
if (_.isArray(value)) {
|
||||
hasArrays = true;
|
||||
}
|
||||
else if (_.isObject(value)) {
|
||||
return { error: 'Analysis is not available for object fields' };
|
||||
}
|
||||
|
||||
if (_.isArray(value) && !params.grouped) {
|
||||
k = value;
|
||||
} else {
|
||||
k = _.isUndefined(value) ? '' : [value.toString()];
|
||||
}
|
||||
|
||||
/* jshint -W083 */
|
||||
_.each(k, function (key) {
|
||||
if (_.has(groups, key)) {
|
||||
groups[key].count++;
|
||||
} else {
|
||||
groups[key] = {
|
||||
value: (params.grouped ? value : key),
|
||||
count: 1
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
counts = _.map(
|
||||
_.sortBy(groups, 'count').reverse().slice(0, params.count),
|
||||
function (bucket) {
|
||||
return {
|
||||
value: bucket.value,
|
||||
count: bucket.count,
|
||||
percent: (bucket.count / (params.data.length - missing) * 100).toFixed(1)
|
||||
};
|
||||
});
|
||||
|
||||
if (params.data.length - missing === 0) {
|
||||
return {error: 'This is field is present in your elasticsearch mapping,' +
|
||||
' but not in any documents in the search results. You may still be able to visualize or search on it'};
|
||||
}
|
||||
|
||||
return {
|
||||
total: params.data.length,
|
||||
exists: params.data.length - missing,
|
||||
missing: missing,
|
||||
buckets: counts,
|
||||
hasArrays : hasArrays,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
});
|
|
@ -0,0 +1,114 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
|
||||
var getFieldValues = function (data, field) {
|
||||
var name = field.name;
|
||||
var normalize = field.format && field.format.normalize;
|
||||
|
||||
return _.map(data, function (row) {
|
||||
var val;
|
||||
|
||||
val = _.isUndefined(row._source[name]) ? row[name] : row._source[name];
|
||||
|
||||
// for fields that come back in weird formats like geo_point
|
||||
if (val != null && normalize) val = normalize(val);
|
||||
|
||||
return val;
|
||||
});
|
||||
};
|
||||
|
||||
var getFieldValueCounts = function (params) {
|
||||
params = _.defaults(params, {
|
||||
count: 5,
|
||||
grouped: false
|
||||
});
|
||||
|
||||
if (
|
||||
params.field.type === 'geo_point'
|
||||
|| params.field.type === 'geo_shape'
|
||||
|| params.field.type === 'attachment'
|
||||
) {
|
||||
return { error: 'Analysis is not available for geo fields.' };
|
||||
}
|
||||
|
||||
var allValues = getFieldValues(params.data, params.field),
|
||||
exists = 0,
|
||||
counts;
|
||||
|
||||
var missing = _countMissing(allValues);
|
||||
|
||||
try {
|
||||
var groups = _groupValues(allValues, params);
|
||||
counts = _.map(
|
||||
_.sortBy(groups, 'count').reverse().slice(0, params.count),
|
||||
function (bucket) {
|
||||
return {
|
||||
value: bucket.value,
|
||||
count: bucket.count,
|
||||
percent: (bucket.count / (params.data.length - missing) * 100).toFixed(1)
|
||||
};
|
||||
});
|
||||
|
||||
if (params.data.length - missing === 0) {
|
||||
return {error: 'This is field is present in your elasticsearch mapping,' +
|
||||
' but not in any documents in the search results. You may still be able to visualize or search on it'};
|
||||
}
|
||||
|
||||
return {
|
||||
total: params.data.length,
|
||||
exists: params.data.length - missing,
|
||||
missing: missing,
|
||||
buckets: counts,
|
||||
};
|
||||
} catch (e) {
|
||||
return { error: e };
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// returns a count of fields in the array that are undefined or null
|
||||
var _countMissing = function (array) {
|
||||
return array.length - _.without(array, undefined, null).length;
|
||||
};
|
||||
|
||||
|
||||
var _groupValues = function (allValues, params) {
|
||||
var groups = {},
|
||||
value, k;
|
||||
|
||||
for (var i = 0; i < allValues.length; ++i) {
|
||||
|
||||
value = allValues[i];
|
||||
|
||||
if (_.isObject(value) && !_.isArray(value)) {
|
||||
throw new Error('Analysis is not available for object fields');
|
||||
}
|
||||
|
||||
if (_.isArray(value) && !params.grouped) {
|
||||
k = value;
|
||||
} else {
|
||||
k = _.isUndefined(value) || _.isNull(value) ? undefined : [value.toString()];
|
||||
}
|
||||
|
||||
/* jshint -W083 */
|
||||
_.each(k, function (key) {
|
||||
if (_.has(groups, key)) {
|
||||
groups[key].count++;
|
||||
} else {
|
||||
groups[key] = {
|
||||
value: (params.grouped ? value : key),
|
||||
count: 1
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
return groups;
|
||||
};
|
||||
|
||||
return {
|
||||
_groupValues: _groupValues,
|
||||
_countMissing: _countMissing,
|
||||
getFieldValues: getFieldValues,
|
||||
getFieldValueCounts: getFieldValueCounts
|
||||
};
|
||||
});
|
|
@ -307,7 +307,6 @@ define(function (require) {
|
|||
if (sortFn && hit._formatted) return;
|
||||
|
||||
// Flatten the fields
|
||||
|
||||
var indexPattern = $scope.searchSource.get('index');
|
||||
hit._source = indexPattern.flattenSearchResponse(hit._source);
|
||||
|
||||
|
@ -328,6 +327,7 @@ define(function (require) {
|
|||
});
|
||||
|
||||
// apply the field counts to the field list
|
||||
// We could do this in the field_chooser but it would us to iterate the array again
|
||||
$scope.fields.forEach(function (field) {
|
||||
field.rowCount = counts[field.name] || 0;
|
||||
});
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
refresh="refreshFieldList"
|
||||
data="rows"
|
||||
filter="filterQuery"
|
||||
search-source="searchSource"
|
||||
index-pattern="searchSource.get('index')"
|
||||
state="state">
|
||||
</disc-field-chooser>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@ define(function (require, module, exports) {
|
|||
require('apps/discover/directives/table');
|
||||
require('apps/discover/saved_searches/saved_searches');
|
||||
require('apps/discover/directives/timechart');
|
||||
require('apps/discover/directives/field_chooser');
|
||||
require('apps/discover/components/field_chooser/field_chooser');
|
||||
require('apps/discover/controllers/discover');
|
||||
require('css!apps/discover/styles/main.css');
|
||||
});
|
15
test/unit/fixtures/fake_row.js
Normal file
15
test/unit/fixtures/fake_row.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var longString = Array(200).join('_');
|
||||
|
||||
return function (id, mapping) {
|
||||
var columns = _.keys(mapping);
|
||||
return {
|
||||
_formatted: _.zipObject(_.map(columns, function (c) { return [c, c + '_formatted_' + id + longString]; })),
|
||||
_source: _.zipObject(_.map(columns, function (c) { return [c, c + '_original_' + id + longString]; })),
|
||||
_id: id,
|
||||
_index: 'test',
|
||||
sort: [id]
|
||||
};
|
||||
};
|
||||
});
|
22
test/unit/fixtures/hits.js
Normal file
22
test/unit/fixtures/hits.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
return _.map([
|
||||
{_source: {timestamp: 0, bytes: 10, request: 'foo'}},
|
||||
{_source: {timestamp: 1, bytes: 20, request: 'bar'}},
|
||||
{_source: {timestamp: 2, bytes: 30, request: 'bar'}},
|
||||
{_source: {timestamp: 3, bytes: 30, request: 'baz'}},
|
||||
{_source: {timestamp: 4, bytes: 30, request: 'baz'}},
|
||||
{_source: {timestamp: 5, bytes: 30, request: 'baz'}},
|
||||
{_source: {timestamp: 6, bytes: 40, request: 'bat'}},
|
||||
{_source: {timestamp: 7, bytes: 40, request: 'bat'}},
|
||||
{_source: {timestamp: 8, bytes: 40, request: 'bat'}},
|
||||
{_source: {timestamp: 9, bytes: 40, request: 'bat'}},
|
||||
], function (p, i) {
|
||||
return _.merge({}, p, {
|
||||
_score: 1,
|
||||
_id: 1000 + i,
|
||||
_type: 'test',
|
||||
_index: 'test-index'
|
||||
});
|
||||
});
|
||||
});
|
224
test/unit/fixtures/real_hits.js
Normal file
224
test/unit/fixtures/real_hits.js
Normal file
|
@ -0,0 +1,224 @@
|
|||
define(function (require) {
|
||||
/* Extensions:
|
||||
html: 8
|
||||
php: 5 (thus 5 with phpmemory fields)
|
||||
gif: 5
|
||||
png: 2
|
||||
|
||||
_type:
|
||||
apache: 18
|
||||
nginx: 2
|
||||
|
||||
Bytes (all unique except):
|
||||
374: 2
|
||||
|
||||
All have the same index, ids are unique
|
||||
*/
|
||||
return [
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'apache',
|
||||
'_id': '61',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'html',
|
||||
'bytes': 360.20000000000005
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'apache',
|
||||
'_id': '388',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'gif',
|
||||
'bytes': 5848.700000000001
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'apache',
|
||||
'_id': '403',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'png',
|
||||
'bytes': 841.6
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'apache',
|
||||
'_id': '415',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'html',
|
||||
'bytes': 1626.4
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'apache',
|
||||
'_id': '460',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'php',
|
||||
'bytes': 2070.6,
|
||||
'phpmemory': 276080
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'apache',
|
||||
'_id': '496',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'gif',
|
||||
'bytes': 8421.6
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'apache',
|
||||
'_id': '511',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'html',
|
||||
'bytes': 994.8000000000001
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'apache',
|
||||
'_id': '701',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'html',
|
||||
'bytes': 374
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'apache',
|
||||
'_id': '838',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'php',
|
||||
'bytes': 506.09999999999997,
|
||||
'phpmemory': 67480
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'apache',
|
||||
'_id': '890',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'php',
|
||||
'bytes': 506.09999999999997,
|
||||
'phpmemory': 67480
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'nginx',
|
||||
'_id': '927',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'php',
|
||||
'bytes': 2591.1,
|
||||
'phpmemory': 345480
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'apache',
|
||||
'_id': '1034',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'html',
|
||||
'bytes': 1450
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'apache',
|
||||
'_id': '1142',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'php',
|
||||
'bytes': 1803.8999999999999,
|
||||
'phpmemory': 240520
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'apache',
|
||||
'_id': '1180',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'html',
|
||||
'bytes': 1626.4
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'nginx',
|
||||
'_id': '1224',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'gif',
|
||||
'bytes': 10617.2
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'apache',
|
||||
'_id': '1243',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'gif',
|
||||
'bytes': 10961.5
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'apache',
|
||||
'_id': '1510',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'html',
|
||||
'bytes': 382.8
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'apache',
|
||||
'_id': '1628',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'html',
|
||||
'bytes': 374
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'apache',
|
||||
'_id': '1729',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'png',
|
||||
'bytes': 3059.2000000000003
|
||||
}
|
||||
},
|
||||
{
|
||||
'_index': 'logstash-2014.09.09',
|
||||
'_type': 'apache',
|
||||
'_id': '1945',
|
||||
'_score': 1,
|
||||
'_source': {
|
||||
'extension': 'gif',
|
||||
'bytes': 10617.2
|
||||
}
|
||||
}
|
||||
];
|
||||
});
|
|
@ -2,16 +2,18 @@ define(function (require) {
|
|||
return function stubbedLogstashIndexPatternService(Private) {
|
||||
var StubIndexPattern = Private(require('test_utils/stub_index_pattern'));
|
||||
return new StubIndexPattern('logstash-*', 'time', [
|
||||
{ type: 'number', name: 'bytes' },
|
||||
{ type: 'boolean', name: 'ssl' },
|
||||
{ type: 'date', name: '@timestamp' },
|
||||
{ type: 'ip', name: 'ip' },
|
||||
{ type: 'attachment', name: 'request_body' },
|
||||
{ type: 'string', name: 'extension' },
|
||||
{ type: 'geo_point', name: 'point' },
|
||||
{ type: 'geo_shape', name: 'area' },
|
||||
{ type: 'string', name: 'extension' },
|
||||
{ type: 'conflict', name: 'custom_user_field' }
|
||||
{ type: 'number', indexed: true, analyzed: true, count: 10, name: 'bytes' },
|
||||
{ type: 'boolean', indexed: true, analyzed: true, count: 20, name: 'ssl' },
|
||||
{ type: 'date', indexed: true, analyzed: true, count: 30, name: '@timestamp' },
|
||||
{ type: 'number', indexed: true, analyzed: true, count: 0, name: 'phpmemory' },
|
||||
{ type: 'ip', indexed: true, analyzed: true, count: 0, name: 'ip' },
|
||||
{ type: 'attachment', indexed: true, analyzed: true, count: 0, name: 'request_body' },
|
||||
{ type: 'string', indexed: true, analyzed: true, count: 0, name: 'extension' },
|
||||
{ type: 'geo_point', indexed: true, analyzed: true, count: 0, name: 'point' },
|
||||
{ type: 'geo_shape', indexed: true, analyzed: true, count: 0, name: 'area' },
|
||||
{ type: 'string', indexed: true, analyzed: true, count: 0, name: 'extension' },
|
||||
{ type: 'string', indexed: true, analyzed: true, count: 0, name: '_type' },
|
||||
{ type: 'conflict', indexed: false, analyzed: false, count: 0, name: 'custom_user_field' }
|
||||
]);
|
||||
};
|
||||
});
|
|
@ -69,6 +69,7 @@
|
|||
'sinon/sinon',
|
||||
'specs/apps/discover/hit_sort_fn',
|
||||
'specs/apps/discover/directives/table',
|
||||
'specs/apps/discover/directives/field_chooser',
|
||||
'specs/apps/discover/segmented_fetch',
|
||||
'specs/directives/confirm-click',
|
||||
'specs/directives/timepicker',
|
||||
|
|
318
test/unit/specs/apps/discover/directives/field_chooser.js
Normal file
318
test/unit/specs/apps/discover/directives/field_chooser.js
Normal file
|
@ -0,0 +1,318 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
var $ = require('jquery');
|
||||
var _ = require('lodash');
|
||||
var sinon = require('test_utils/auto_release_sinon');
|
||||
var fieldCalculator = require('apps/discover/components/field_chooser/lib/field_calculator');
|
||||
|
||||
// Load the kibana app dependencies.
|
||||
require('services/private');
|
||||
require('apps/discover/components/field_chooser/field_chooser');
|
||||
|
||||
var $parentScope, $scope, config, indexPattern;
|
||||
|
||||
|
||||
// Sets up the directive, take an element, and a list of properties to attach to the parent scope.
|
||||
var init = function ($elem, props) {
|
||||
inject(function ($rootScope, $compile, _config_) {
|
||||
config = _config_;
|
||||
$parentScope = $rootScope;
|
||||
_.assign($parentScope, props);
|
||||
$compile($elem)($parentScope);
|
||||
$elem.scope().$digest();
|
||||
$scope = $elem.isolateScope();
|
||||
});
|
||||
};
|
||||
|
||||
var destroy = function () {
|
||||
$scope.$destroy();
|
||||
$parentScope.$destroy();
|
||||
};
|
||||
|
||||
|
||||
describe('discover field chooser directives', function () {
|
||||
var $elem = angular.element(
|
||||
'<disc-field-chooser' +
|
||||
' fields="fields"' +
|
||||
' toggle="toggle"' +
|
||||
' data="data"' +
|
||||
' filter="filter"' +
|
||||
' index-pattern="indexPattern"' +
|
||||
' state="state">' +
|
||||
'</disc-field-chooser>'
|
||||
);
|
||||
|
||||
beforeEach(module('kibana'));
|
||||
beforeEach(function () {
|
||||
inject(function (Private) {
|
||||
indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern'));
|
||||
});
|
||||
|
||||
|
||||
init($elem, {
|
||||
fields: _.map(indexPattern.fields.raw, function (v, i) { return _.merge(v, {display: false, rowCount: i}); }),
|
||||
toggle: sinon.spy(),
|
||||
data: require('fixtures/hits'),
|
||||
filter: sinon.spy(),
|
||||
indexPattern: indexPattern
|
||||
});
|
||||
|
||||
$scope.$digest();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
destroy();
|
||||
});
|
||||
|
||||
var getSections = function (ctx) {
|
||||
return {
|
||||
selected: $('.discover-selected-fields', ctx),
|
||||
popular: $('.discover-popular-fields', ctx),
|
||||
unpopular: $('.discover-unpopular-fields', ctx),
|
||||
};
|
||||
};
|
||||
|
||||
describe('Field listing', function () {
|
||||
it('should have Selected Fields, Fields and Popular Fields sections', function (done) {
|
||||
var headers = $elem.find('.sidebar-list-header');
|
||||
expect(headers.length).to.be(3);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should have 2 popular fields, 1 unpopular field and no selected fields', function (done) {
|
||||
var section = getSections($elem);
|
||||
|
||||
expect(section.selected.find('li').length).to.be(0);
|
||||
|
||||
expect(section.popular.text()).to.contain('ssl');
|
||||
expect(section.popular.text()).to.contain('@timestamp');
|
||||
expect(section.popular.text()).to.not.contain('ip');
|
||||
|
||||
expect(section.unpopular.text()).to.contain('extension');
|
||||
expect(section.unpopular.text()).to.contain('area');
|
||||
expect(section.unpopular.text()).to.not.contain('ssl');
|
||||
done();
|
||||
});
|
||||
|
||||
it('setting field.display should move the field into selected', function (done) {
|
||||
var section = getSections($elem);
|
||||
indexPattern.fields.byName.bytes.display = true;
|
||||
$scope.$digest();
|
||||
expect(section.selected.text()).to.contain('bytes');
|
||||
expect(section.popular.text()).to.not.contain('bytes');
|
||||
|
||||
indexPattern.fields.byName.ip.display = true;
|
||||
$scope.$digest();
|
||||
expect(section.selected.text()).to.contain('ip');
|
||||
expect(section.unpopular.text()).to.not.contain('ip');
|
||||
|
||||
expect(section.popular.text()).to.contain('ssl');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('details processing', function () {
|
||||
var field;
|
||||
|
||||
beforeEach(function () {
|
||||
field = indexPattern.fields.byName.bytes;
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
delete field.details;
|
||||
});
|
||||
|
||||
it('should have a details function', function (done) {
|
||||
expect($scope.details).to.be.a(Function);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should increase the field popularity when called', function (done) {
|
||||
var counter = field.count;
|
||||
indexPattern.popularizeField = sinon.spy();
|
||||
$scope.details(field);
|
||||
expect(indexPattern.popularizeField.called).to.be(true);
|
||||
expect(field.count).to.be(counter + 1);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should append a details object to the field', function (done) {
|
||||
$scope.details(field);
|
||||
expect(field.details).to.not.be(undefined);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should delete the field details if they already exist', function (done) {
|
||||
$scope.details(field);
|
||||
expect(field.details).to.not.be(undefined);
|
||||
$scope.details(field);
|
||||
expect(field.details).to.be(undefined);
|
||||
done();
|
||||
});
|
||||
|
||||
it('... unless recompute is true', function (done) {
|
||||
$scope.details(field);
|
||||
expect(field.details).to.not.be(undefined);
|
||||
$scope.details(field, true);
|
||||
expect(field.details).to.not.be(undefined);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should recalculate the details on open fields if the data changes', function () {
|
||||
|
||||
|
||||
$scope.details(field);
|
||||
sinon.stub($scope, 'details');
|
||||
$scope.data = [];
|
||||
$scope.$apply();
|
||||
expect($scope.details.called).to.be(true);
|
||||
$scope.details.restore();
|
||||
|
||||
// close the field, make sure details isnt called again
|
||||
$scope.details(field);
|
||||
sinon.stub($scope, 'details');
|
||||
$scope.data = ['foo'];
|
||||
$scope.$apply();
|
||||
expect($scope.details.called).to.be(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('fieldCalculator', function (done) {
|
||||
|
||||
it('should have a _countMissing that counts nulls & undefineds in an array', function (done) {
|
||||
var values = [['foo', 'bar'], 'foo', 'foo', undefined, ['foo', 'bar'], 'bar', 'baz', null, null, null, 'foo', undefined];
|
||||
expect(fieldCalculator._countMissing(values)).to.be(5);
|
||||
done();
|
||||
});
|
||||
|
||||
describe('_groupValues', function () {
|
||||
var groups, params, values;
|
||||
beforeEach(function () {
|
||||
values = [['foo', 'bar'], 'foo', 'foo', undefined, ['foo', 'bar'], 'bar', 'baz', null, null, null, 'foo', undefined];
|
||||
params = {};
|
||||
groups = fieldCalculator._groupValues(values, params);
|
||||
});
|
||||
|
||||
it('should have a _groupValues that counts values', function (done) {
|
||||
expect(groups).to.be.an(Object);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should throw an error if any value is a plain object', function (done) {
|
||||
expect(function () { fieldCalculator._groupValues([{}, true, false], params); })
|
||||
.to.throwError();
|
||||
done();
|
||||
});
|
||||
|
||||
it('should have a a key for value in the array when not grouping array terms', function (done) {
|
||||
expect(_.keys(groups).length).to.be(3);
|
||||
expect(groups['foo']).to.be.a(Object);
|
||||
expect(groups['bar']).to.be.a(Object);
|
||||
expect(groups['baz']).to.be.a(Object);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should count array terms independently', function (done) {
|
||||
expect(groups['foo,bar']).to.be(undefined);
|
||||
expect(groups['foo'].count).to.be(5);
|
||||
expect(groups['bar'].count).to.be(3);
|
||||
expect(groups['baz'].count).to.be(1);
|
||||
done();
|
||||
});
|
||||
|
||||
describe('grouped array terms', function (done) {
|
||||
beforeEach(function () {
|
||||
params.grouped = true;
|
||||
groups = fieldCalculator._groupValues(values, params);
|
||||
});
|
||||
|
||||
it('should group array terms when passed params.grouped', function (done) {
|
||||
expect(_.keys(groups).length).to.be(4);
|
||||
expect(groups['foo,bar']).to.be.a(Object);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should contain the original array as the value', function (done) {
|
||||
expect(groups['foo,bar'].value).to.eql(['foo', 'bar']);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should count the pairs seperately from the values they contain', function (done) {
|
||||
expect(groups['foo,bar'].count).to.be(2);
|
||||
expect(groups['foo'].count).to.be(3);
|
||||
expect(groups['bar'].count).to.be(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFieldValues', function () {
|
||||
var hits = require('fixtures/real_hits.js');
|
||||
it('Should return an array of values for _source fields', function () {
|
||||
var extensions = fieldCalculator.getFieldValues(hits, indexPattern.fields.byName.extension);
|
||||
expect(extensions).to.be.an(Array);
|
||||
expect(_.filter(extensions, function (v) { return v === 'html'; }).length).to.be(8);
|
||||
expect(_.uniq(_.clone(extensions)).sort()).to.eql(['gif', 'html', 'php', 'png']);
|
||||
});
|
||||
|
||||
it('Should return an array of values for core meta fields', function () {
|
||||
var types = fieldCalculator.getFieldValues(hits, indexPattern.fields.byName._type);
|
||||
expect(types).to.be.an(Array);
|
||||
expect(_.filter(types, function (v) { return v === 'apache'; }).length).to.be(18);
|
||||
expect(_.uniq(_.clone(types)).sort()).to.eql(['apache', 'nginx']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('getFieldValueCounts', function () {
|
||||
var params;
|
||||
beforeEach(function () {
|
||||
params = {
|
||||
data: require('fixtures/real_hits.js'),
|
||||
field: indexPattern.fields.byName.extension,
|
||||
count: 3
|
||||
};
|
||||
});
|
||||
|
||||
it('counts the top 3 values', function () {
|
||||
var extensions = fieldCalculator.getFieldValueCounts(params);
|
||||
expect(extensions).to.be.an(Object);
|
||||
expect(extensions.buckets).to.be.an(Array);
|
||||
expect(extensions.buckets.length).to.be(3);
|
||||
expect(_.pluck(extensions.buckets, 'value')).to.eql(['html', 'php', 'gif']);
|
||||
expect(extensions.error).to.be(undefined);
|
||||
});
|
||||
|
||||
it('fails to analyze geo and attachment types', function () {
|
||||
params.field = indexPattern.fields.byName.point;
|
||||
expect(fieldCalculator.getFieldValueCounts(params).error).to.not.be(undefined);
|
||||
|
||||
params.field = indexPattern.fields.byName.area;
|
||||
expect(fieldCalculator.getFieldValueCounts(params).error).to.not.be(undefined);
|
||||
|
||||
params.field = indexPattern.fields.byName.request_body;
|
||||
expect(fieldCalculator.getFieldValueCounts(params).error).to.not.be(undefined);
|
||||
});
|
||||
|
||||
it('fails to analyze fields that are in the mapping, but not the data', function () {
|
||||
params.field = indexPattern.fields.byName.ip;
|
||||
expect(fieldCalculator.getFieldValueCounts(params).error).to.not.be(undefined);
|
||||
});
|
||||
|
||||
it('counts the total hits', function () {
|
||||
expect(fieldCalculator.getFieldValueCounts(params).total).to.be(params.data.length);
|
||||
});
|
||||
|
||||
it('counts the hits the field exists in', function () {
|
||||
params.field = indexPattern.fields.byName.phpmemory;
|
||||
expect(fieldCalculator.getFieldValueCounts(params).exists).to.be(5);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -3,6 +3,8 @@ define(function (require) {
|
|||
var $ = require('jquery');
|
||||
var _ = require('lodash');
|
||||
var sinon = require('test_utils/auto_release_sinon');
|
||||
var getFakeRow = require('fixtures/fake_row');
|
||||
|
||||
|
||||
// Load the kibana app dependencies.
|
||||
require('angular-route');
|
||||
|
@ -196,19 +198,6 @@ define(function (require) {
|
|||
|
||||
});
|
||||
|
||||
var longString = Array(50).join('_');
|
||||
|
||||
var getFakeRow = function (id) {
|
||||
var columns = _.keys(mapping);
|
||||
return {
|
||||
_formatted: _.zipObject(_.map(columns, function (c) { return [c, c + '_formatted_' + id + longString]; })),
|
||||
_source: _.zipObject(_.map(columns, function (c) { return [c, c + '_original_' + id + longString]; })),
|
||||
_id: id,
|
||||
_index: 'test',
|
||||
sort: [id]
|
||||
};
|
||||
};
|
||||
|
||||
describe('kbnTable', function () {
|
||||
|
||||
var $elem = angular.element(
|
||||
|
@ -232,7 +221,7 @@ define(function (require) {
|
|||
sinon.stub($.prototype, 'scrollTop', function () { return -200; });
|
||||
|
||||
var rows = _.times(200, function (i) {
|
||||
return getFakeRow(i);
|
||||
return getFakeRow(i, mapping);
|
||||
});
|
||||
init($elem, {
|
||||
columns: ['bytes'],
|
||||
|
@ -290,7 +279,7 @@ define(function (require) {
|
|||
beforeEach(function () {
|
||||
|
||||
init($elem, {
|
||||
row: getFakeRow(0),
|
||||
row: getFakeRow(0, mapping),
|
||||
columns: [],
|
||||
sorting: [],
|
||||
filtering: sinon.spy(),
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
define(function (require) {
|
||||
return function (Private) {
|
||||
var sinon = require('sinon/sinon');
|
||||
var Registry = require('utils/registry/registry');
|
||||
var fieldFormats = Private(require('components/index_patterns/_field_formats'));
|
||||
|
||||
function StubIndexPattern(pattern, timeField, fields) {
|
||||
this.popularizeField = sinon.spy();
|
||||
this.fields = new Registry({
|
||||
index: ['name'],
|
||||
group: ['type'],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue