Field list - from indexed array to arrays and maps (#47921)

* make fieldList extend array
This commit is contained in:
Matthew Kime 2019-10-14 12:27:50 -05:00 committed by GitHub
parent 4a7f36d6cf
commit b92faf22b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 247 additions and 170 deletions

View file

@ -17,13 +17,13 @@
* under the License.
*/
import _ from 'lodash';
const longString = Array(200).join('_');
export function getFakeRowVals(type, id, mapping) {
return _.mapValues(mapping, function (f, c) {
return c + '_' + type + '_' + id + longString;
});
return mapping.reduce((collector, field) => {
collector[field.name] = `${field.name}_${type}_${id}_${longString}`;
return collector;
}, {});
}
export function getFakeRow(id, mapping) {

View file

@ -20,7 +20,7 @@
import { Filter, RangeFilter, FILTERS, isRangeFilter, isScriptedRangeFilter } from '@kbn/es-query';
import { get, has } from 'lodash';
import { SavedObjectNotFound } from '../../../../../../../plugins/kibana_utils/public';
import { IndexPatterns, IndexPattern } from '../../../index_patterns';
import { IndexPatterns, IndexPattern, Field } from '../../../index_patterns';
const getFirstRangeKey = (filter: RangeFilter) => filter.range && Object.keys(filter.range)[0];
const getRangeByKey = (filter: RangeFilter, key: string) => get(filter, ['range', key]);
@ -44,9 +44,8 @@ function getParams(filter: RangeFilter, indexPattern?: IndexPattern) {
// for example a user might manually edit the url or the index pattern's ID might change due to
// external factors e.g. a reindex. We only need the index in order to grab the field formatter, so we fallback
// on displaying the raw value if the index is invalid.
if (key && indexPattern && indexPattern.fields.byName[key]) {
const convert = indexPattern.fields.byName[key].format.getConverterFor('text');
if (key && indexPattern && indexPattern.fields.getByName(key)) {
const convert = (indexPattern.fields.getByName(key) as Field).format.getConverterFor('text');
value = `${convert(left)} to ${convert(right)}`;
}

View file

@ -21,7 +21,7 @@ export class StubIndexPatterns {
async get(index: string) {
return {
fields: {
byName: {},
getByName: () => undefined,
},
};
}

View file

@ -33,6 +33,7 @@ export { FilterBar, ApplyFiltersPopover } from './filter';
export {
Field,
FieldType,
FieldListInterface,
IndexPattern,
IndexPatterns,
StaticIndexPattern,

View file

@ -17,24 +17,69 @@
* under the License.
*/
import { IndexedArray } from 'ui/indexed_array';
import { NotificationsSetup } from 'kibana/public';
import { findIndex } from 'lodash';
import { IndexPattern } from '../index_patterns';
import { Field, FieldSpec } from './field';
import { Field, FieldType, FieldSpec } from './field';
export class FieldList extends IndexedArray<Field> {
type FieldMap = Map<Field['name'], Field>;
export interface FieldListInterface extends Array<Field> {
getByName(name: Field['name']): Field | undefined;
getByType(type: Field['type']): Field[];
add(field: FieldSpec): void;
remove(field: FieldType): void;
}
export class FieldList extends Array<Field> implements FieldListInterface {
private byName: FieldMap = new Map();
private groups: Map<Field['type'], FieldMap> = new Map();
private indexPattern: IndexPattern;
private shortDotsEnable: boolean;
private notifications: NotificationsSetup;
private setByName = (field: Field) => this.byName.set(field.name, field);
private setByGroup = (field: Field) => {
if (typeof this.groups.get(field.type) === 'undefined') {
this.groups.set(field.type, new Map());
}
this.groups.get(field.type)!.set(field.name, field);
};
private removeByGroup = (field: FieldType) => this.groups.get(field.type)!.delete(field.name);
constructor(
indexPattern: IndexPattern,
specs: FieldSpec[],
specs: FieldSpec[] = [],
shortDotsEnable = false,
notifications: NotificationsSetup
) {
super({
index: ['name'],
group: ['type'],
initialSet: specs.map(function(field) {
return new Field(indexPattern, field, shortDotsEnable, notifications);
}),
});
super();
this.indexPattern = indexPattern;
this.shortDotsEnable = shortDotsEnable;
this.notifications = notifications;
specs.map(field => this.add(field));
}
getByName = (name: Field['name']) => this.byName.get(name);
getByType = (type: Field['type']) => [...(this.groups.get(type) || new Map()).values()];
add = (field: FieldSpec) => {
const newField = new Field(this.indexPattern, field, this.shortDotsEnable, this.notifications);
this.push(newField);
this.setByName(newField);
this.setByGroup(newField);
};
remove = (field: FieldType) => {
this.removeByGroup(field);
this.byName.delete(field.name);
const fieldIndex = findIndex(this, { name: field.name });
this.splice(fieldIndex, 1);
};
update = (field: Field) => {
const index = this.findIndex(f => f.name === field.name);
this.splice(index, 1, field);
this.setByName(field);
this.removeByGroup(field);
this.setByGroup(field);
};
}

View file

@ -27,14 +27,15 @@ function flattenHit(indexPattern: IndexPattern, hit: Record<string, any>, deep:
const flat = {} as Record<string, any>;
// recursively merge _source
const fields = indexPattern.fields.byName;
const fields = indexPattern.fields.getByName;
(function flatten(obj, keyPrefix = '') {
keyPrefix = keyPrefix ? keyPrefix + '.' : '';
_.forOwn(obj, function(val, key) {
key = keyPrefix + key;
if (deep) {
const isNestedField = fields[key] && fields[key].type === 'nested';
const field = fields(key);
const isNestedField = field && field.type === 'nested';
const isArrayOfObjects = Array.isArray(val) && _.isPlainObject(_.first(val));
if (isArrayOfObjects && !isNestedField) {
_.each(val, v => flatten(v, key));
@ -44,7 +45,8 @@ function flattenHit(indexPattern: IndexPattern, hit: Record<string, any>, deep:
return;
}
const hasValidMapping = fields[key] && fields[key].type !== 'conflict';
const field = fields(key);
const hasValidMapping = field && field.type !== 'conflict';
const isValue = !_.isPlainObject(val);
if (hasValidMapping || isValue) {

View file

@ -27,7 +27,7 @@ const partialFormattedCache = new WeakMap();
// returns a formatted version
export function formatHitProvider(indexPattern: IndexPattern, defaultFormat: any) {
function convert(hit: Record<string, any>, val: any, fieldName: string, type: string = 'html') {
const field = indexPattern.fields.byName[fieldName];
const field = indexPattern.fields.getByName(fieldName);
if (!field) return defaultFormat.convert(val, type);
const parsedUrl = {
origin: window.location.origin,

View file

@ -18,7 +18,6 @@
*/
import { defaults, pluck, last, get } from 'lodash';
import { IndexedArray } from 'ui/indexed_array';
import { IndexPattern } from './index_pattern';
import { DuplicateField } from '../../../../../../plugins/kibana_utils/public';
@ -170,7 +169,6 @@ describe('IndexPattern', () => {
test('should append the found fields', () => {
expect(savedObjectsClient.get).toHaveBeenCalled();
expect(indexPattern.fields).toHaveLength(mockLogStashFields().length);
expect(indexPattern.fields).toBeInstanceOf(IndexedArray);
});
});
@ -295,7 +293,9 @@ describe('IndexPattern', () => {
const scriptedFields = indexPattern.getScriptedFields();
// expect(saveSpy.callCount).to.equal(1);
expect(scriptedFields).toHaveLength(oldCount + 1);
expect(indexPattern.fields.byName[scriptedField.name].name).toEqual(scriptedField.name);
expect((indexPattern.fields.getByName(scriptedField.name) as Field).name).toEqual(
scriptedField.name
);
});
test('should remove scripted field, by name', async () => {
@ -304,11 +304,11 @@ describe('IndexPattern', () => {
const oldCount = scriptedFields.length;
const scriptedField = last(scriptedFields);
await indexPattern.removeScriptedField(scriptedField.name);
await indexPattern.removeScriptedField(scriptedField);
// expect(saveSpy.callCount).to.equal(1);
expect(indexPattern.getScriptedFields().length).toEqual(oldCount - 1);
expect(indexPattern.fields.byName[scriptedField.name]).toEqual(undefined);
expect(indexPattern.fields.getByName(scriptedField.name)).toEqual(undefined);
});
test('should not allow duplicate names', async () => {

View file

@ -28,7 +28,7 @@ import { NotificationsSetup, SavedObjectsClientContract } from 'src/core/public'
import { SavedObjectNotFound, DuplicateField } from '../../../../../../plugins/kibana_utils/public';
import { IndexPatternMissingIndices } from '../errors';
import { Field, FieldList, FieldType } from '../fields';
import { Field, FieldList, FieldType, FieldListInterface } from '../fields';
import { createFieldsFetcher } from './_fields_fetcher';
import { getRoutes } from '../utils';
import { formatHitProvider } from './format_hit';
@ -63,7 +63,7 @@ export class IndexPattern implements StaticIndexPattern {
public type?: string;
public fieldFormatMap: any;
public typeMeta: any;
public fields: FieldList;
public fields: FieldListInterface;
public timeFieldName: string | undefined;
public formatHit: any;
public formatField: any;
@ -211,15 +211,17 @@ export class IndexPattern implements StaticIndexPattern {
// Date value returned in "_source" could be in any number of formats
// Use a docvalue for each date field to ensure standardized formats when working with date fields
// indexPattern.flattenHit will override "_source" values when the same field is also defined in "fields"
const docvalueFields = reject(this.fields.byType.date, 'scripted').map((dateField: any) => {
return {
field: dateField.name,
format:
dateField.esTypes && dateField.esTypes.indexOf('date_nanos') !== -1
? 'strict_date_time'
: 'date_time',
};
});
const docvalueFields = reject(this.fields.getByType('date'), 'scripted').map(
(dateField: any) => {
return {
field: dateField.name,
format:
dateField.esTypes && dateField.esTypes.indexOf('date_nanos') !== -1
? 'strict_date_time'
: 'date_time',
};
}
);
each(this.getScriptedFields(), function(field) {
scriptFields[field.name] = {
@ -275,7 +277,7 @@ export class IndexPattern implements StaticIndexPattern {
throw new DuplicateField(name);
}
this.fields.push(
this.fields.add(
new Field(
this,
{
@ -296,21 +298,13 @@ export class IndexPattern implements StaticIndexPattern {
await this.save();
}
removeScriptedField(name: string) {
const fieldIndex = _.findIndex(this.fields, {
name,
scripted: true,
});
if (fieldIndex > -1) {
this.fields.splice(fieldIndex, 1);
delete this.fieldFormatMap[name];
return this.save();
}
removeScriptedField(field: FieldType) {
this.fields.remove(field);
return this.save();
}
async popularizeField(fieldName: string, unit = 1) {
const field = this.fields.byName[fieldName];
const field = this.fields.getByName(fieldName);
if (!field) {
return;
}
@ -344,13 +338,13 @@ export class IndexPattern implements StaticIndexPattern {
}
getTimeField() {
if (!this.timeFieldName || !this.fields || !this.fields.byName) return;
return this.fields.byName[this.timeFieldName];
if (!this.timeFieldName || !this.fields || !this.fields.getByName) return;
return this.fields.getByName(this.timeFieldName);
}
getFieldByName(name: string): Field | void {
if (!this.fields || !this.fields.byName) return;
return this.fields.byName[name];
if (!this.fields || !this.fields.getByName) return;
return this.fields.getByName(name);
}
isWildcard() {

View file

@ -23,7 +23,7 @@ import {
HttpServiceBase,
NotificationsSetup,
} from 'src/core/public';
import { Field, FieldList, FieldType } from './fields';
import { Field, FieldList, FieldListInterface, FieldType } from './fields';
import { createFlattenHitWrapper } from './index_patterns';
import { createIndexPatternSelect } from './components';
import {
@ -107,4 +107,4 @@ export type IndexPatternsSetup = ReturnType<IndexPatternsService['setup']>;
export type IndexPatternsStart = ReturnType<IndexPatternsService['start']>;
/** @public */
export { IndexPattern, IndexPatterns, StaticIndexPattern, Field, FieldType };
export { IndexPattern, IndexPatterns, StaticIndexPattern, Field, FieldType, FieldListInterface };

View file

@ -33,7 +33,7 @@ export class FilterManager {
}
getField() {
return this.indexPattern.fields.byName[this.fieldName];
return this.indexPattern.fields.getByName(this.fieldName);
}
createFilter() {

View file

@ -38,12 +38,12 @@ export class PhraseFilterManager extends FilterManager {
let newFilter;
if (phrases.length === 1) {
newFilter = buildPhraseFilter(
this.indexPattern.fields.byName[this.fieldName],
this.indexPattern.fields.getByName(this.fieldName),
phrases[0],
this.indexPattern);
} else {
newFilter = buildPhrasesFilter(
this.indexPattern.fields.byName[this.fieldName],
this.indexPattern.fields.getByName(this.fieldName),
phrases,
this.indexPattern);
}

View file

@ -35,8 +35,9 @@ describe('PhraseFilterManager', function () {
const indexPatternMock = {
id: indexPatternId,
fields: {
byName: {
field1: fieldMock
getByName: name => {
const fields = { field1: fieldMock };
return fields[name];
}
}
};

View file

@ -56,7 +56,7 @@ export class RangeFilterManager extends FilterManager {
*/
createFilter(value) {
const newFilter = buildRangeFilter(
this.indexPattern.fields.byName[this.fieldName],
this.indexPattern.fields.getByName(this.fieldName),
toRange(value),
this.indexPattern);
newFilter.meta.key = this.fieldName;

View file

@ -32,8 +32,11 @@ describe('RangeFilterManager', function () {
const indexPatternMock = {
id: indexPatternId,
fields: {
byName: {
field1: fieldMock
getByName: name => {
const fields = {
field1: fieldMock
};
return fields[name];
}
}
};

View file

@ -110,7 +110,7 @@ class ListControl extends Control {
terminate_after: chrome.getInjected('autocompleteTerminateAfter')
};
const aggs = termsAgg({
field: indexPattern.fields.byName[fieldName],
field: indexPattern.fields.getByName(fieldName),
size: this.options.dynamicOptions ? null : _.get(this.options, 'size', 5),
direction: 'desc',
query

View file

@ -29,7 +29,10 @@ jest.mock('../../../../core_plugins/data/public/legacy', () => ({
indexPatterns: {
indexPatterns: {
get: () => ({
fields: { byName: { myField: { name: 'myField' } } }
fields: { getByName: name => {
const fields = { myField: { name: 'myField' } };
return fields[name];
} }
}),
}
},
@ -37,7 +40,10 @@ jest.mock('../../../../core_plugins/data/public/legacy', () => ({
filterManager: {
fieldName: 'myNumberField',
getIndexPattern: () => ({
fields: { byName: { myField: { name: 'myField' } } }
fields: { getByName: name => {
const fields = { myField: { name: 'myField' } };
return fields[name];
} }
}),
getAppFilters: jest.fn().mockImplementation(() => ([])),
getGlobalFilters: jest.fn().mockImplementation(() => ([])),

View file

@ -64,7 +64,7 @@ class RangeControl extends Control {
const fieldName = this.filterManager.fieldName;
const aggs = minMaxAgg(indexPattern.fields.byName[fieldName]);
const aggs = minMaxAgg(indexPattern.fields.getByName(fieldName));
const searchSource = createSearchSource(this.kbnApi, null, indexPattern, aggs, this.useTimeFilter);
this.abortController.signal.addEventListener('abort', () => searchSource.cancelQueued());

View file

@ -37,16 +37,22 @@ jest.mock('../../../../core_plugins/data/public/legacy', () => ({
indexPatterns: {
indexPatterns: {
get: () => ({
fields: { byName: { myNumberField: { name: 'myNumberField' } } }
}),
fields: { getByName: name => {
const fields = { myNumberField: { name: 'myNumberField' } };
return fields[name];
}
} }),
}
},
filter: {
filterManager: {
fieldName: 'myNumberField',
getIndexPattern: () => ({
fields: { byName: { myNumberField: { name: 'myNumberField' } } }
}),
fields: { getByName: name => {
const fields = { myNumberField: { name: 'myNumberField' } };
return fields[name];
}
} }),
getAppFilters: jest.fn().mockImplementation(() => ([])),
getGlobalFilters: jest.fn().mockImplementation(() => ([])),
}

View file

@ -27,37 +27,40 @@ import { DocViewTable } from './table';
// @ts-ignore
const indexPattern = {
fields: {
byName: {
_index: {
name: '_index',
type: 'string',
scripted: false,
filterable: true,
},
message: {
name: 'message',
type: 'string',
scripted: false,
filterable: false,
},
extension: {
name: 'extension',
type: 'string',
scripted: false,
filterable: true,
},
bytes: {
name: 'bytes',
type: 'number',
scripted: false,
filterable: true,
},
scripted: {
name: 'scripted',
type: 'number',
scripted: true,
filterable: false,
},
getByName: (name: string) => {
const fields: { [name: string]: {} } = {
_index: {
name: '_index',
type: 'string',
scripted: false,
filterable: true,
},
message: {
name: 'message',
type: 'string',
scripted: false,
filterable: false,
},
extension: {
name: 'extension',
type: 'string',
scripted: false,
filterable: true,
},
bytes: {
name: 'bytes',
type: 'number',
scripted: false,
filterable: true,
},
scripted: {
name: 'scripted',
type: 'number',
scripted: true,
filterable: false,
},
};
return fields[name];
},
},
metaFields: ['_index', '_score'],

View file

@ -31,7 +31,7 @@ export function DocViewTable({
onAddColumn,
onRemoveColumn,
}: DocViewRenderProps) {
const mapping = indexPattern.fields.byName;
const mapping = indexPattern.fields.getByName;
const flattened = indexPattern.flattenHit(hit);
const formatted = indexPattern.formatHit(hit, 'html');
const [fieldRowOpen, setFieldRowOpen] = useState({} as Record<string, boolean>);
@ -63,15 +63,15 @@ export function DocViewTable({
: undefined;
const isArrayOfObjects =
Array.isArray(flattened[field]) && arrayContainsObjects(flattened[field]);
const displayUnderscoreWarning = !mapping[field] && field.indexOf('_') === 0;
const displayUnderscoreWarning = !mapping(field) && field.indexOf('_') === 0;
const displayNoMappingWarning =
!mapping[field] && !displayUnderscoreWarning && !isArrayOfObjects;
!mapping(field) && !displayUnderscoreWarning && !isArrayOfObjects;
return (
<DocViewTableRow
key={field}
field={field}
fieldMapping={mapping[field]}
fieldMapping={mapping(field)}
displayUnderscoreWarning={displayUnderscoreWarning}
displayNoMappingWarning={displayNoMappingWarning}
isCollapsed={isCollapsed}

View file

@ -39,7 +39,7 @@ export function getFirstSortableField(indexPattern: IndexPattern, fieldNames: st
fieldName =>
META_FIELD_NAMES.includes(fieldName) ||
// @ts-ignore
(indexPattern.fields.byName[fieldName] || { sortable: false }).sortable
(indexPattern.fields.getByName(fieldName) || { sortable: false }).sortable
);
return sortableFields[0];
}

View file

@ -47,7 +47,7 @@ describe('discoverField', function () {
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
_.assign($rootScope, {
field: indexPattern.fields.byName.extension,
field: indexPattern.fields.getByName('extension'),
addField: sinon.spy(() => $rootScope.field.display = true),
removeField: sinon.spy(() => $rootScope.field.display = false),
showDetails: sinon.spy(() => $rootScope.field.details = { exists: true }),

View file

@ -114,14 +114,14 @@ describe('fieldCalculator', function () {
});
it('Should return an array of values for _source fields', function () {
const extensions = fieldCalculator.getFieldValues(hits, indexPattern.fields.byName.extension);
const extensions = fieldCalculator.getFieldValues(hits, indexPattern.fields.getByName('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 () {
const types = fieldCalculator.getFieldValues(hits, indexPattern.fields.byName._type);
const types = fieldCalculator.getFieldValues(hits, indexPattern.fields.getByName('_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']);
@ -134,7 +134,7 @@ describe('fieldCalculator', function () {
beforeEach(function () {
params = {
hits: require('fixtures/real_hits.js'),
field: indexPattern.fields.byName.extension,
field: indexPattern.fields.getByName('extension'),
count: 3
};
});
@ -149,18 +149,18 @@ describe('fieldCalculator', function () {
});
it('fails to analyze geo and attachment types', function () {
params.field = indexPattern.fields.byName.point;
params.field = indexPattern.fields.getByName('point');
expect(fieldCalculator.getFieldValueCounts(params).error).to.not.be(undefined);
params.field = indexPattern.fields.byName.area;
params.field = indexPattern.fields.getByName('area');
expect(fieldCalculator.getFieldValueCounts(params).error).to.not.be(undefined);
params.field = indexPattern.fields.byName.request_body;
params.field = indexPattern.fields.getByName('request_body');
expect(fieldCalculator.getFieldValueCounts(params).error).to.not.be(undefined);
});
it('fails to analyze fields that are in the mapping, but not the hits', function () {
params.field = indexPattern.fields.byName.ip;
params.field = indexPattern.fields.getByName('ip');
expect(fieldCalculator.getFieldValueCounts(params).error).to.not.be(undefined);
});
@ -169,7 +169,7 @@ describe('fieldCalculator', function () {
});
it('counts the hits the field exists in', function () {
params.field = indexPattern.fields.byName.phpmemory;
params.field = indexPattern.fields.getByName('phpmemory');
expect(fieldCalculator.getFieldValueCounts(params).exists).to.be(5);
});
});

View file

@ -87,7 +87,7 @@
</div>
<ul class="list-unstyled dscFieldList--selected" aria-labelledby="selected_fields">
<discover-field
ng-repeat="field in fields.raw|filter:filter.isFieldFilteredAndDisplayed"
ng-repeat="field in fields|filter:filter.isFieldFilteredAndDisplayed"
field="field"
on-add-field="onAddField"
on-add-filter="onAddFilter"

View file

@ -281,7 +281,7 @@ app.directive('discFieldChooser', function ($location, config, $route) {
const fieldSpecs = indexPattern.fields.slice(0);
const fieldNamesInDocs = _.keys(fieldCounts);
const fieldNamesInIndexPattern = _.keys(indexPattern.fields.byName);
const fieldNamesInIndexPattern = _.map(indexPattern.fields, 'name');
_.difference(fieldNamesInDocs, fieldNamesInIndexPattern)
.forEach(function (unknownFieldName) {
@ -295,7 +295,7 @@ app.directive('discFieldChooser', function ($location, config, $route) {
if (prevFields) {
fields.forEach(function (field) {
field.details = _.get(prevFields, ['byName', field.name, 'details']);
field.details = (prevFields.getByName(field.name) || {}).details;
});
}

View file

@ -44,13 +44,13 @@ describe('Doc Table', function () {
config = _config_;
$parentScope = $rootScope;
$parentScope.indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
mapping = $parentScope.indexPattern.fields.byName;
mapping = $parentScope.indexPattern.fields;
// Stub `getConverterFor` for a field in the indexPattern to return mock data.
// Returns `val` if provided, otherwise generates fake data for the field.
fakeRowVals = getFakeRowVals('formatted', 0, mapping);
stubFieldFormatConverter = function ($root, field, val = null) {
$root.indexPattern.fields.byName[field].format.getConverterFor = () => (...args) => {
$root.indexPattern.fields.getByName(field).format.getConverterFor = () => (...args) => {
if (val) {
return val;
}
@ -252,8 +252,8 @@ describe('Doc Table', function () {
$root.indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
// Stub field format converters for every field in the indexPattern
Object.keys($root.indexPattern.fields.byName).forEach(f =>
stubFieldFormatConverter($root, f)
$root.indexPattern.fields.forEach(f =>
stubFieldFormatConverter($root, f.name)
);
$row = $('<tr>').attr({

View file

@ -104,7 +104,7 @@ module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl
$scope.inlineFilter = function inlineFilter($event, type) {
const column = $($event.target).data().column;
const field = $scope.indexPattern.fields.byName[column];
const field = $scope.indexPattern.fields.getByName(column);
$scope.filter(field, $scope.flattenedRow[column], type);
};
@ -130,7 +130,7 @@ module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl
// We just create a string here because its faster.
const newHtmls = [openRowHtml];
const mapping = indexPattern.fields.byName;
const mapping = indexPattern.fields.getByName;
const hideTimeColumn = config.get('doc_table:hideTimeColumn');
if (indexPattern.timeFieldName && !hideTimeColumn) {
newHtmls.push(
@ -138,7 +138,7 @@ module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl
timefield: true,
formatted: _displayField(row, indexPattern.timeFieldName),
filterable:
mapping[indexPattern.timeFieldName].filterable && _.isFunction($scope.filter),
mapping(indexPattern.timeFieldName).filterable && _.isFunction($scope.filter),
column: indexPattern.timeFieldName,
})
);
@ -147,8 +147,8 @@ module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl
$scope.columns.forEach(function (column) {
const isFilterable =
$scope.flattenedRow[column] !== undefined &&
mapping[column] &&
mapping[column].filterable &&
mapping(column) &&
mapping(column).filterable &&
_.isFunction($scope.filter);
newHtmls.push(

View file

@ -20,7 +20,7 @@
import _ from 'lodash';
function isSortable(field, indexPattern) {
return (indexPattern.fields.byName[field] && indexPattern.fields.byName[field].sortable);
return (indexPattern.fields.getByName(field) && indexPattern.fields.getByName(field).sortable);
}
function createSortObject(sortPair, indexPattern) {

View file

@ -50,7 +50,7 @@ describe('_source formatting', function () {
it('uses the _source, field, and hit to create a <dl>', function () {
const hit = _.first(hits);
const $nonBindable = $(convertHtml(hit._source, indexPattern.fields.byName._source, hit));
const $nonBindable = $(convertHtml(hit._source, indexPattern.fields, hit));
expect($nonBindable.is('span[ng-non-bindable]')).to.be.ok();
const $dl = $nonBindable.children();
expect($dl.is('dl')).to.be.ok();

View file

@ -115,7 +115,7 @@ uiRoutes
if (this.mode === 'edit') {
const fieldName = $route.current.params.fieldName;
this.field = this.indexPattern.fields.byName[fieldName];
this.field = this.indexPattern.fields.getByName(fieldName);
if (!this.field) {
const message = i18n.translate('kbn.management.editIndexPattern.scripted.noFieldLabel',

View file

@ -129,7 +129,7 @@ export class ScriptedFieldsTable extends Component {
const { indexPattern, onRemoveField } = this.props;
const { fieldToDelete } = this.state;
indexPattern.removeScriptedField(fieldToDelete.name);
indexPattern.removeScriptedField(fieldToDelete);
onRemoveField && onRemoveField();
this.fetchFields();
this.hideDeleteConfirmationModal();

View file

@ -77,12 +77,12 @@ export default function AggParamWriterHelper(Private) {
if (this.aggType.paramByName('field') && !paramValues.field) {
// pick a field rather than force a field to be specified everywhere
if (this.aggType.type === AggGroupNames.Metrics) {
paramValues.field = _.sample(this.indexPattern.fields.byType.number);
paramValues.field = _.sample(this.indexPattern.fields.getByType('number'));
} else {
const type = this.aggType.paramByName('field').filterFieldTypes || 'string';
let field;
do {
field = _.sample(this.indexPattern.fields.byType[type]);
field = _.sample(this.indexPattern.fields.getByType(type));
} while (!field.aggregatable);
paramValues.field = field.name;
}

View file

@ -47,7 +47,7 @@ describe('AggConfig Filters', function () {
interval = interval || 'auto';
if (interval === 'custom') interval = agg.params.customInterval;
duration = duration || moment.duration(15, 'minutes');
field = _.sample(_.reject(indexPattern.fields.byType.date, 'scripted'));
field = _.sample(_.reject(indexPattern.fields.getByType('date'), 'scripted'));
vis = new Vis(indexPattern, {
type: 'histogram',
aggs: [

View file

@ -25,7 +25,7 @@ import { FieldParamEditor } from '../../vis/editors/default/controls/field';
import { BaseParamType } from './base';
import { toastNotifications } from '../../notify';
import { propFilter } from '../filter';
import { Field } from '../../index_patterns';
import { Field, FieldListInterface } from '../../index_patterns';
const filterByType = propFilter('type');
@ -79,7 +79,7 @@ export class FieldParamType extends BaseParamType {
if (!aggConfig) {
throw new Error('aggConfig was not provided to FieldParamType deserialize function');
}
const field = aggConfig.getIndexPattern().fields.byName[fieldName];
const field = aggConfig.getIndexPattern().fields.getByName(fieldName);
if (!field) {
throw new SavedObjectNotFound('index-pattern-field', fieldName);
@ -111,7 +111,7 @@ export class FieldParamType extends BaseParamType {
/**
* filter the fields to the available ones
*/
getAvailableFields = (fields: Field[]) => {
getAvailableFields = (fields: FieldListInterface) => {
const filteredFields = fields.filter((field: Field) => {
const { onlyAggregatable, scriptable, filterFieldTypes } = this;

View file

@ -71,7 +71,7 @@ describe('SearchSource#normalizeSortRequest', function () {
it('should enable script based sorting', function () {
const fieldName = 'script string';
const direction = 'desc';
const indexField = indexPattern.fields.byName[fieldName];
const indexField = indexPattern.fields.getByName(fieldName);
const sortState = {};
sortState[fieldName] = direction;

View file

@ -42,7 +42,7 @@ export function NormalizeSortRequestProvider(config) {
const normalized = {};
let sortField = _.keys(sortable)[0];
let sortValue = sortable[sortField];
const indexField = indexPattern.fields.byName[sortField];
const indexField = indexPattern.fields.getByName(sortField);
if (indexField && indexField.scripted && indexField.sortable) {
let direction;

View file

@ -148,7 +148,7 @@ export class FieldEditorComponent extends PureComponent {
this.setState({
isReady: true,
isCreating: !indexPattern.fields.byName[field.name],
isCreating: !indexPattern.fields.getByName(field.name),
isDeprecatedLang: this.deprecatedLangs.includes(field.lang),
errors: [],
scriptingLangs,
@ -673,7 +673,7 @@ export class FieldEditorComponent extends PureComponent {
const { redirectAway } = this.props.helpers;
const { indexPattern, intl } = this.props;
const { field } = this.state;
const remove = indexPattern.removeScriptedField(field.name);
const remove = indexPattern.removeScriptedField(field);
if(remove) {
remove.then(() => {
@ -718,9 +718,9 @@ export class FieldEditorComponent extends PureComponent {
const index = indexPattern.fields.findIndex(f => f.name === field.name);
if (index > -1) {
indexPattern.fields.splice(index, 1, field);
indexPattern.fields.update(field);
} else {
indexPattern.fields.push(field);
indexPattern.fields.add(field);
}
if (!fieldFormatId) {

View file

@ -97,10 +97,13 @@ jest.mock('./components/field_format_editor', () => ({
const fields = [{
name: 'foobar',
}];
fields.byName = {
foobar: {
name: 'foobar',
},
fields.getByName = name => {
const fields = {
foobar: {
name: 'foobar',
},
};
return fields[name];
};
class Format {
@ -153,7 +156,12 @@ describe('FieldEditor', () => {
script: 'doc.test.value',
};
indexPattern.fields.push(testField);
indexPattern.fields.byName[testField.name] = testField;
indexPattern.fields.getByName = (name) => {
const fields = {
[testField.name]: testField
};
return fields[name];
};
const component = shallowWithIntl(
<FieldEditorComponent
@ -176,7 +184,12 @@ describe('FieldEditor', () => {
lang: 'testlang'
};
indexPattern.fields.push(testField);
indexPattern.fields.byName[testField.name] = testField;
indexPattern.fields.getByName = (name) => {
const fields = {
[testField.name]: testField
};
return fields[name];
};
const component = shallowWithIntl(
<FieldEditorComponent

View file

@ -55,6 +55,7 @@ export {
export {
Field,
FieldType,
FieldListInterface,
IndexPattern,
IndexPatterns,
StaticIndexPattern,

View file

@ -479,7 +479,7 @@ describe('AggConfig', function () {
});
it('returns the html converter if "html" is passed in', function () {
const field = indexPattern.fields.byName.bytes;
const field = indexPattern.fields.getByName('bytes');
expect(vis.aggs.aggs[0].fieldFormatter('html')).to.be(field.format.getConverterFor('html'));
});
});

View file

@ -74,7 +74,7 @@ describe('<Settings />', () => {
.simulate('click');
// Wait for the animation and DOM update
await tick(20);
await tick(40);
portal(wrapper).update();
expect(portal(wrapper).html()).toMatchSnapshot();

View file

@ -138,10 +138,13 @@ describe('hitsToGeoJson', () => {
describe('dot in geoFieldName', () => {
const indexPatternMock = {
fields: {
byName: {
['my.location']: {
type: 'geo_point'
}
getByName: name => {
const fields = {
['my.location']: {
type: 'geo_point'
}
};
return fields[name];
}
}
};

View file

@ -261,7 +261,7 @@ export class ESPewPewSource extends AbstractESSource {
async _getGeoField() {
const indexPattern = await this._getIndexPattern();
const geoField = indexPattern.fields.byName[this._descriptor.destGeoField];
const geoField = indexPattern.fields.getByName(this._descriptor.destGeoField);
if (!geoField) {
throw new Error(i18n.translate('xpack.maps.source.esSource.noGeoFieldErrorMessage', {
defaultMessage: `Index pattern {indexPatternTitle} no longer contains the geo field {geoField}`,

View file

@ -83,7 +83,7 @@ export class ESSearchSource extends AbstractESSource {
async getNumberFields() {
try {
const indexPattern = await this._getIndexPattern();
return indexPattern.fields.byType.number.map(field => {
return indexPattern.fields.getByType('number').map(field => {
return { name: field.name, label: field.name };
});
} catch (error) {
@ -163,7 +163,7 @@ export class ESSearchSource extends AbstractESSource {
const scriptFields = {};
searchFilters.fieldNames.forEach(fieldName => {
const field = indexPattern.fields.byName[fieldName];
const field = indexPattern.fields.getByName(fieldName);
if (field && field.scripted) {
scriptFields[field.name] = {
script: {

View file

@ -273,7 +273,7 @@ export class AbstractESSource extends AbstractVectorSource {
async _getGeoField() {
const indexPattern = await this._getIndexPattern();
const geoField = indexPattern.fields.byName[this._descriptor.geoField];
const geoField = indexPattern.fields.getByName(this._descriptor.geoField);
if (!geoField) {
throw new Error(i18n.translate('xpack.maps.source.esSource.noGeoFieldErrorMessage', {
defaultMessage: `Index pattern {indexPatternTitle} no longer contains the geo field {geoField}`,

View file

@ -24,7 +24,7 @@ export class ESAggMetricTooltipProperty extends ESTooltipProperty {
if (this._metricField.type === 'count') {
return this._rawValue;
}
const indexPatternField = this._indexPattern.fields.byName[this._metricField.field];
const indexPatternField = this._indexPattern.fields.getByName(this._metricField.field);
if (!indexPatternField) {
return this._rawValue;
}

View file

@ -21,7 +21,7 @@ export class ESTooltipProperty extends TooltipProperty {
return '-';
}
const field = this._indexPattern.fields.byName[this._propertyName];
const field = this._indexPattern.fields.getByName(this._propertyName);
if (!field) {
return _.escape(this._rawValue);
}
@ -30,14 +30,14 @@ export class ESTooltipProperty extends TooltipProperty {
}
isFilterable() {
const field = this._indexPattern.fields.byName[this._propertyName];
const field = this._indexPattern.fields.getByName(this._propertyName);
return field && (field.type === 'string' || field.type === 'date' || field.type === 'ip' || field.type === 'number');
}
async getESFilters() {
return [
buildPhraseFilter(
this._indexPattern.fields.byName[this._propertyName],
this._indexPattern.fields.getByName(this._propertyName),
this._rawValue,
this._indexPattern)
];

View file

@ -79,7 +79,7 @@ class FieldFormatService {
// e.g. distinct_count(clientip) should be formatted as a count, not as an IP address.
let fieldFormat = undefined;
if (esAggName !== 'cardinality') {
const indexPatternFields = _.get(fullIndexPattern, 'fields.byName', []);
const indexPatternFields = _.get(fullIndexPattern, 'fields', []);
fieldFormat = _.get(indexPatternFields, [fieldName, 'format']);
}
@ -99,7 +99,7 @@ class FieldFormatService {
getIndexPatternById(indexPatternId)
.then((indexPatternData) => {
// Store the FieldFormat for each job by detector_index.
const fieldsByName = _.get(indexPatternData, 'fields.byName', []);
const fieldsByName = _.get(indexPatternData, 'fields', []);
_.each(detectors, (dtr) => {
const esAgg = mlFunctionToESAggregation(dtr.function);
// distinct_count detectors should fall back to the default