diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/format_column.ts b/x-pack/plugins/lens/public/indexpattern_datasource/format_column.ts index 3666528f43166..1f337298a03ad 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/format_column.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/format_column.ts @@ -116,9 +116,18 @@ export const formatColumn: ExpressionFunctionDefinition< }); } if (parentFormatParams) { - const innerParams = (col.meta.params?.params as Record) ?? {}; + // if original format is already a nested one, we are just replacing the wrapper params + // otherwise wrapping it inside parentFormatId/parentFormatParams + const isNested = isNestedFormat(col.meta.params); + const innerParams = isNested + ? col.meta.params?.params + : { id: col.meta.params?.id, params: col.meta.params?.params }; + + const formatId = isNested ? col.meta.params?.id : parentFormatId; + return withParams(col, { ...col.meta.params, + id: formatId, params: { ...innerParams, ...parentFormatParams, @@ -132,6 +141,11 @@ export const formatColumn: ExpressionFunctionDefinition< }, }; +function isNestedFormat(params: DatatableColumn['meta']['params']) { + // if there is a nested params object with an id, it's a nested format + return !!params?.params?.id; +} + function withParams(col: DatatableColumn, params: Record) { return { ...col, meta: { ...col.meta, params } }; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts index 92280b0fb6ce6..793f3387e707d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts @@ -38,8 +38,10 @@ export class IndexPatternDatasource { renameColumns, formatColumn, getTimeScaleFunction, + getSuffixFormatter, } = await import('../async_services'); return core.getStartServices().then(([coreStart, { data }]) => { + data.fieldFormats.register([getSuffixFormatter(data.fieldFormats.deserialize)]); expressions.registerFunction(getTimeScaleFunction(data)); expressions.registerFunction(renameColumns); expressions.registerFunction(formatColumn); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index e37c31559cd0c..94f240058d618 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -78,6 +78,7 @@ export function columnToOperation(column: IndexPatternColumn, uniqueLabel?: stri export * from './rename_columns'; export * from './format_column'; export * from './time_scale'; +export * from './suffix_formatter'; export function getIndexPatternDatasource({ core, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.test.ts new file mode 100644 index 0000000000000..ef1739e4424fa --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.test.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FormatFactory } from '../types'; +import { getSuffixFormatter } from './suffix_formatter'; + +describe('suffix formatter', () => { + it('should call nested formatter and apply suffix', () => { + const convertMock = jest.fn((x) => x); + const formatFactory = jest.fn(() => ({ convert: convertMock })); + const SuffixFormatter = getSuffixFormatter((formatFactory as unknown) as FormatFactory); + const nestedParams = { abc: 123 }; + const formatterInstance = new SuffixFormatter({ + unit: 'h', + id: 'nestedFormatter', + params: nestedParams, + }); + + const result = formatterInstance.convert(12345); + + expect(result).toEqual('12345/h'); + expect(convertMock).toHaveBeenCalledWith(12345); + expect(formatFactory).toHaveBeenCalledWith({ id: 'nestedFormatter', params: nestedParams }); + }); +}); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.ts b/x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.ts new file mode 100644 index 0000000000000..5594976738efe --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { FieldFormat, KBN_FIELD_TYPES } from '../../../../../src/plugins/data/public'; +import { FormatFactory } from '../types'; +import { TimeScaleUnit } from './time_scale'; + +const unitSuffixes: Record = { + s: i18n.translate('xpack.lens.fieldFormats.suffix.s', { defaultMessage: '/h' }), + m: i18n.translate('xpack.lens.fieldFormats.suffix.m', { defaultMessage: '/m' }), + h: i18n.translate('xpack.lens.fieldFormats.suffix.h', { defaultMessage: '/h' }), + d: i18n.translate('xpack.lens.fieldFormats.suffix.d', { defaultMessage: '/d' }), +}; + +export function getSuffixFormatter(formatFactory: FormatFactory) { + return class SuffixFormatter extends FieldFormat { + static id = 'suffix'; + static title = i18n.translate('xpack.lens.fieldFormats.suffix.title', { + defaultMessage: 'Suffix', + }); + static fieldType = KBN_FIELD_TYPES.NUMBER; + allowsNumericalAggregations = true; + + getParamDefaults() { + return { + unit: undefined, + nestedParams: {}, + }; + } + + textConvert = (val: unknown) => { + const unit = this.param('unit') as TimeScaleUnit | undefined; + const suffix = unit ? unitSuffixes[unit] : undefined; + const nestedFormatter = this.param('id'); + const nestedParams = this.param('params'); + + const formattedValue = formatFactory({ id: nestedFormatter, params: nestedParams }).convert( + val + ); + + if (suffix) { + return `${formattedValue}${suffix}`; + } + return formattedValue; + }; + }; +} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts index 7a4e8f6bc0638..06ff8058b1d09 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts @@ -11,7 +11,7 @@ import { DataPublicPluginStart } from 'src/plugins/data/public'; import { search } from '../../../../../src/plugins/data/public'; import { buildResultColumns } from '../../../../../src/plugins/expressions/common'; -type TimeScaleUnit = 's' | 'm' | 'h' | 'd'; +export type TimeScaleUnit = 's' | 'm' | 'h' | 'd'; export interface TimeScaleArgs { dateColumnId: string;