[data.search.aggs] Move agg-specific field formats to search service (#69586)
This commit is contained in:
parent
1eede3f128
commit
16eaf82d5c
|
@ -18,107 +18,42 @@
|
|||
*/
|
||||
|
||||
import { identity } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { convertDateRangeToString, DateRangeKey } from '../../search/aggs/buckets/lib/date_range';
|
||||
import { convertIPRangeToString, IpRangeKey } from '../../search/aggs/buckets/lib/ip_range';
|
||||
|
||||
import { SerializedFieldFormat } from '../../../../expressions/common/types';
|
||||
import { FieldFormatId, FieldFormatsContentType, IFieldFormat } from '../..';
|
||||
|
||||
import { FieldFormat } from '../../../common';
|
||||
import { DataPublicPluginStart } from '../../../public';
|
||||
import { getUiSettings } from '../../../public/services';
|
||||
import { FormatFactory } from '../../../common/field_formats/utils';
|
||||
|
||||
interface TermsFieldFormatParams {
|
||||
otherBucketLabel: string;
|
||||
missingBucketLabel: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
function isTermsFieldFormat(
|
||||
serializedFieldFormat: SerializedFieldFormat
|
||||
): serializedFieldFormat is SerializedFieldFormat<TermsFieldFormatParams> {
|
||||
return serializedFieldFormat.id === 'terms';
|
||||
}
|
||||
import { DataPublicPluginStart, IFieldFormat } from '../../../public';
|
||||
import { getUiSettings } from '../../../public/services';
|
||||
import { getFormatWithAggs } from '../../search/aggs/utils';
|
||||
|
||||
const getConfig = (key: string, defaultOverride?: any): any =>
|
||||
getUiSettings().get(key, defaultOverride);
|
||||
const DefaultFieldFormat = FieldFormat.from(identity);
|
||||
|
||||
const getFieldFormat = (
|
||||
fieldFormatsService: DataPublicPluginStart['fieldFormats'],
|
||||
id?: FieldFormatId,
|
||||
params: object = {}
|
||||
): IFieldFormat => {
|
||||
if (id) {
|
||||
const Format = fieldFormatsService.getType(id);
|
||||
|
||||
if (Format) {
|
||||
return new Format(params, getConfig);
|
||||
}
|
||||
}
|
||||
|
||||
return new DefaultFieldFormat();
|
||||
};
|
||||
|
||||
export const deserializeFieldFormat: FormatFactory = function (
|
||||
this: DataPublicPluginStart['fieldFormats'],
|
||||
mapping?: SerializedFieldFormat
|
||||
serializedFieldFormat?: SerializedFieldFormat
|
||||
) {
|
||||
if (!mapping) {
|
||||
if (!serializedFieldFormat) {
|
||||
return new DefaultFieldFormat();
|
||||
}
|
||||
const { id } = mapping;
|
||||
if (id === 'range') {
|
||||
const RangeFormat = FieldFormat.from((range: any) => {
|
||||
const nestedFormatter = mapping.params as SerializedFieldFormat;
|
||||
const format = getFieldFormat(this, nestedFormatter.id, nestedFormatter.params);
|
||||
const gte = '\u2265';
|
||||
const lt = '\u003c';
|
||||
return i18n.translate('data.aggTypes.buckets.ranges.rangesFormatMessage', {
|
||||
defaultMessage: '{gte} {from} and {lt} {to}',
|
||||
values: {
|
||||
gte,
|
||||
from: format.convert(range.gte),
|
||||
lt,
|
||||
to: format.convert(range.lt),
|
||||
},
|
||||
});
|
||||
});
|
||||
return new RangeFormat();
|
||||
} else if (id === 'date_range') {
|
||||
const nestedFormatter = mapping.params as SerializedFieldFormat;
|
||||
const DateRangeFormat = FieldFormat.from((range: DateRangeKey) => {
|
||||
const format = getFieldFormat(this, nestedFormatter.id, nestedFormatter.params);
|
||||
return convertDateRangeToString(range, format.convert.bind(format));
|
||||
});
|
||||
return new DateRangeFormat();
|
||||
} else if (id === 'ip_range') {
|
||||
const nestedFormatter = mapping.params as SerializedFieldFormat;
|
||||
const IpRangeFormat = FieldFormat.from((range: IpRangeKey) => {
|
||||
const format = getFieldFormat(this, nestedFormatter.id, nestedFormatter.params);
|
||||
return convertIPRangeToString(range, format.convert.bind(format));
|
||||
});
|
||||
return new IpRangeFormat();
|
||||
} else if (isTermsFieldFormat(mapping) && mapping.params) {
|
||||
const { params } = mapping;
|
||||
const convert = (val: string, type: FieldFormatsContentType) => {
|
||||
const format = getFieldFormat(this, params.id, mapping.params);
|
||||
|
||||
if (val === '__other__') {
|
||||
return params.otherBucketLabel;
|
||||
}
|
||||
if (val === '__missing__') {
|
||||
return params.missingBucketLabel;
|
||||
const getFormat = (mapping: SerializedFieldFormat): IFieldFormat => {
|
||||
const { id, params = {} } = mapping;
|
||||
if (id) {
|
||||
const Format = this.getType(id);
|
||||
|
||||
if (Format) {
|
||||
return new Format(params, getConfig);
|
||||
}
|
||||
}
|
||||
|
||||
return format.convert(val, type);
|
||||
};
|
||||
return new DefaultFieldFormat();
|
||||
};
|
||||
|
||||
return {
|
||||
convert,
|
||||
getConverterFor: (type: FieldFormatsContentType) => (val: string) => convert(val, type),
|
||||
} as IFieldFormat;
|
||||
} else {
|
||||
return getFieldFormat(this, id, mapping.params);
|
||||
}
|
||||
// decorate getFormat to handle custom types created by aggs
|
||||
const getFieldFormat = getFormatWithAggs(getFormat);
|
||||
|
||||
return getFieldFormat(serializedFieldFormat);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { identity } from 'lodash';
|
||||
|
||||
import { SerializedFieldFormat } from '../../../../../expressions/common/types';
|
||||
import { FieldFormat } from '../../../../common';
|
||||
import { IFieldFormat } from '../../../../public';
|
||||
|
||||
import { getFormatWithAggs } from './get_format_with_aggs';
|
||||
|
||||
describe('getFormatWithAggs', () => {
|
||||
let getFormat: jest.MockedFunction<(mapping: SerializedFieldFormat) => IFieldFormat>;
|
||||
|
||||
beforeEach(() => {
|
||||
getFormat = jest.fn().mockImplementation(() => {
|
||||
const DefaultFieldFormat = FieldFormat.from(identity);
|
||||
return new DefaultFieldFormat();
|
||||
});
|
||||
});
|
||||
|
||||
test('calls provided getFormat if no matching aggs exist', () => {
|
||||
const mapping = { id: 'foo', params: {} };
|
||||
const getFieldFormat = getFormatWithAggs(getFormat);
|
||||
getFieldFormat(mapping);
|
||||
|
||||
expect(getFormat).toHaveBeenCalledTimes(1);
|
||||
expect(getFormat).toHaveBeenCalledWith(mapping);
|
||||
});
|
||||
|
||||
test('creates custom format for date_range', () => {
|
||||
const mapping = { id: 'date_range', params: {} };
|
||||
const getFieldFormat = getFormatWithAggs(getFormat);
|
||||
const format = getFieldFormat(mapping);
|
||||
|
||||
expect(format.convert({ from: '2020-05-01', to: '2020-06-01' })).toBe(
|
||||
'2020-05-01 to 2020-06-01'
|
||||
);
|
||||
expect(format.convert({ to: '2020-06-01' })).toBe('Before 2020-06-01');
|
||||
expect(format.convert({ from: '2020-06-01' })).toBe('After 2020-06-01');
|
||||
expect(getFormat).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
test('creates custom format for ip_range', () => {
|
||||
const mapping = { id: 'ip_range', params: {} };
|
||||
const getFieldFormat = getFormatWithAggs(getFormat);
|
||||
const format = getFieldFormat(mapping);
|
||||
|
||||
expect(format.convert({ type: 'range', from: '10.0.0.1', to: '10.0.0.10' })).toBe(
|
||||
'10.0.0.1 to 10.0.0.10'
|
||||
);
|
||||
expect(format.convert({ type: 'range', to: '10.0.0.10' })).toBe('-Infinity to 10.0.0.10');
|
||||
expect(format.convert({ type: 'range', from: '10.0.0.10' })).toBe('10.0.0.10 to Infinity');
|
||||
format.convert({ type: 'mask', mask: '10.0.0.1/24' });
|
||||
expect(getFormat).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
test('creates custom format for range', () => {
|
||||
const mapping = { id: 'range', params: {} };
|
||||
const getFieldFormat = getFormatWithAggs(getFormat);
|
||||
const format = getFieldFormat(mapping);
|
||||
|
||||
expect(format.convert({ gte: 1, lt: 20 })).toBe('≥ 1 and < 20');
|
||||
expect(getFormat).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('creates custom format for terms', () => {
|
||||
const mapping = {
|
||||
id: 'terms',
|
||||
params: {
|
||||
otherBucketLabel: 'other bucket',
|
||||
missingBucketLabel: 'missing bucket',
|
||||
},
|
||||
};
|
||||
const getFieldFormat = getFormatWithAggs(getFormat);
|
||||
const format = getFieldFormat(mapping);
|
||||
|
||||
expect(format.convert('machine.os.keyword')).toBe('machine.os.keyword');
|
||||
expect(format.convert('__other__')).toBe(mapping.params.otherBucketLabel);
|
||||
expect(format.convert('__missing__')).toBe(mapping.params.missingBucketLabel);
|
||||
expect(getFormat).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { SerializedFieldFormat } from '../../../../../expressions/common/types';
|
||||
import { FieldFormat } from '../../../../common';
|
||||
import { FieldFormatsContentType, IFieldFormat } from '../../../../public';
|
||||
import { convertDateRangeToString, DateRangeKey } from '../buckets/lib/date_range';
|
||||
import { convertIPRangeToString, IpRangeKey } from '../buckets/lib/ip_range';
|
||||
|
||||
type GetFieldFormat = (mapping: SerializedFieldFormat) => IFieldFormat;
|
||||
|
||||
/**
|
||||
* Certain aggs have custom field formats that are not part of the field formats
|
||||
* registry. This function will take the `getFormat` function which is used inside
|
||||
* `deserializeFieldFormat` and decorate it with the additional custom formats
|
||||
* that the field formats service doesn't know anything about.
|
||||
*
|
||||
* This function is internal to the data plugin, and only exists for use inside
|
||||
* the field formats service.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function getFormatWithAggs(getFieldFormat: GetFieldFormat): GetFieldFormat {
|
||||
return (mapping) => {
|
||||
const { id, params = {} } = mapping;
|
||||
|
||||
const customFormats: Record<string, () => IFieldFormat> = {
|
||||
range: () => {
|
||||
const RangeFormat = FieldFormat.from((range: any) => {
|
||||
const nestedFormatter = params as SerializedFieldFormat;
|
||||
const format = getFieldFormat({
|
||||
id: nestedFormatter.id,
|
||||
params: nestedFormatter.params,
|
||||
});
|
||||
const gte = '\u2265';
|
||||
const lt = '\u003c';
|
||||
return i18n.translate('data.aggTypes.buckets.ranges.rangesFormatMessage', {
|
||||
defaultMessage: '{gte} {from} and {lt} {to}',
|
||||
values: {
|
||||
gte,
|
||||
from: format.convert(range.gte),
|
||||
lt,
|
||||
to: format.convert(range.lt),
|
||||
},
|
||||
});
|
||||
});
|
||||
return new RangeFormat();
|
||||
},
|
||||
date_range: () => {
|
||||
const nestedFormatter = params as SerializedFieldFormat;
|
||||
const DateRangeFormat = FieldFormat.from((range: DateRangeKey) => {
|
||||
const format = getFieldFormat({
|
||||
id: nestedFormatter.id,
|
||||
params: nestedFormatter.params,
|
||||
});
|
||||
return convertDateRangeToString(range, format.convert.bind(format));
|
||||
});
|
||||
return new DateRangeFormat();
|
||||
},
|
||||
ip_range: () => {
|
||||
const nestedFormatter = params as SerializedFieldFormat;
|
||||
const IpRangeFormat = FieldFormat.from((range: IpRangeKey) => {
|
||||
const format = getFieldFormat({
|
||||
id: nestedFormatter.id,
|
||||
params: nestedFormatter.params,
|
||||
});
|
||||
return convertIPRangeToString(range, format.convert.bind(format));
|
||||
});
|
||||
return new IpRangeFormat();
|
||||
},
|
||||
terms: () => {
|
||||
const convert = (val: string, type: FieldFormatsContentType) => {
|
||||
const format = getFieldFormat({ id: params.id, params });
|
||||
|
||||
if (val === '__other__') {
|
||||
return params.otherBucketLabel;
|
||||
}
|
||||
if (val === '__missing__') {
|
||||
return params.missingBucketLabel;
|
||||
}
|
||||
|
||||
return format.convert(val, type);
|
||||
};
|
||||
|
||||
return {
|
||||
convert,
|
||||
getConverterFor: (type: FieldFormatsContentType) => (val: string) => convert(val, type),
|
||||
} as IFieldFormat;
|
||||
},
|
||||
};
|
||||
|
||||
if (!id || !(id in customFormats)) {
|
||||
return getFieldFormat(mapping);
|
||||
}
|
||||
|
||||
return customFormats[id]();
|
||||
};
|
||||
}
|
|
@ -18,5 +18,6 @@
|
|||
*/
|
||||
|
||||
export * from './calculate_auto_time_expression';
|
||||
export * from './get_format_with_aggs';
|
||||
export * from './prop_filter';
|
||||
export * from './to_angular_json';
|
||||
|
|
|
@ -61,7 +61,7 @@ export type UnmappedTypeStrings = 'date' | 'filter';
|
|||
* Is used to carry information about how to format data in
|
||||
* a data table as part of the column definition.
|
||||
*/
|
||||
export interface SerializedFieldFormat<TParams = object> {
|
||||
export interface SerializedFieldFormat<TParams = Record<string, any>> {
|
||||
id?: string;
|
||||
params?: TParams;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue