diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.deletefieldformat.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.deletefieldformat.md new file mode 100644 index 0000000000000..26276a809a613 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.deletefieldformat.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [deleteFieldFormat](./kibana-plugin-plugins-data-public.indexpattern.deletefieldformat.md) + +## IndexPattern.deleteFieldFormat property + +Signature: + +```typescript +deleteFieldFormat: (fieldName: string) => void; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getformatterforfieldnodefault.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getformatterforfieldnodefault.md new file mode 100644 index 0000000000000..0dd171108b20b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getformatterforfieldnodefault.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [getFormatterForFieldNoDefault](./kibana-plugin-plugins-data-public.indexpattern.getformatterforfieldnodefault.md) + +## IndexPattern.getFormatterForFieldNoDefault() method + +Get formatter for a given field name. Return undefined if none exists + +Signature: + +```typescript +getFormatterForFieldNoDefault(fieldname: string): FieldFormat | undefined; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldname | string | | + +Returns: + +`FieldFormat | undefined` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md index 537460b85d72c..9ac310a78d725 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md @@ -20,6 +20,7 @@ export declare class IndexPattern implements IIndexPattern | Property | Modifiers | Type | Description | | --- | --- | --- | --- | +| [deleteFieldFormat](./kibana-plugin-plugins-data-public.indexpattern.deletefieldformat.md) | | (fieldName: string) => void | | | [fieldFormatMap](./kibana-plugin-plugins-data-public.indexpattern.fieldformatmap.md) | | Record<string, any> | | | [fields](./kibana-plugin-plugins-data-public.indexpattern.fields.md) | | IIndexPatternFieldList & {
toSpec: () => IndexPatternFieldMap;
} | | | [flattenHit](./kibana-plugin-plugins-data-public.indexpattern.flattenhit.md) | | (hit: Record<string, any>, deep?: boolean) => Record<string, any> | | @@ -30,6 +31,7 @@ export declare class IndexPattern implements IIndexPattern | [intervalName](./kibana-plugin-plugins-data-public.indexpattern.intervalname.md) | | string | undefined | | | [metaFields](./kibana-plugin-plugins-data-public.indexpattern.metafields.md) | | string[] | | | [resetOriginalSavedObjectBody](./kibana-plugin-plugins-data-public.indexpattern.resetoriginalsavedobjectbody.md) | | () => void | Reset last saved saved object fields. used after saving | +| [setFieldFormat](./kibana-plugin-plugins-data-public.indexpattern.setfieldformat.md) | | (fieldName: string, format: SerializedFieldFormat) => void | | | [sourceFilters](./kibana-plugin-plugins-data-public.indexpattern.sourcefilters.md) | | SourceFilter[] | | | [timeFieldName](./kibana-plugin-plugins-data-public.indexpattern.timefieldname.md) | | string | undefined | | | [title](./kibana-plugin-plugins-data-public.indexpattern.title.md) | | string | | @@ -48,17 +50,15 @@ export declare class IndexPattern implements IIndexPattern | [getFieldByName(name)](./kibana-plugin-plugins-data-public.indexpattern.getfieldbyname.md) | | | | [getFormatterForField(field)](./kibana-plugin-plugins-data-public.indexpattern.getformatterforfield.md) | | Provide a field, get its formatter | | [getIndex()](./kibana-plugin-plugins-data-public.indexpattern.getindex.md) | | | +| [getFormatterForFieldNoDefault(fieldname)](./kibana-plugin-plugins-data-public.indexpattern.getformatterforfieldnodefault.md) | | Get formatter for a given field name. Return undefined if none exists | | [getNonScriptedFields()](./kibana-plugin-plugins-data-public.indexpattern.getnonscriptedfields.md) | | | | [getScriptedFields()](./kibana-plugin-plugins-data-public.indexpattern.getscriptedfields.md) | | | | [getSourceFiltering()](./kibana-plugin-plugins-data-public.indexpattern.getsourcefiltering.md) | | Get the source filtering configuration for that index. | | [getTimeField()](./kibana-plugin-plugins-data-public.indexpattern.gettimefield.md) | | | | [isTimeBased()](./kibana-plugin-plugins-data-public.indexpattern.istimebased.md) | | | | [isTimeNanosBased()](./kibana-plugin-plugins-data-public.indexpattern.istimenanosbased.md) | | | -<<<<<<< HEAD | [isUnsupportedTimePattern()](./kibana-plugin-plugins-data-public.indexpattern.isunsupportedtimepattern.md) | | | | [popularizeField(fieldName, unit)](./kibana-plugin-plugins-data-public.indexpattern.popularizefield.md) | | | -======= ->>>>>>> cdb922f2a2... Move indexPattern.popularizeField into discover (#77668) | [removeScriptedField(fieldName)](./kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md) | | Remove scripted field from field list | | [toSpec()](./kibana-plugin-plugins-data-public.indexpattern.tospec.md) | | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.setfieldformat.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.setfieldformat.md new file mode 100644 index 0000000000000..9774fc8c7308c --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.setfieldformat.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [setFieldFormat](./kibana-plugin-plugins-data-public.indexpattern.setfieldformat.md) + +## IndexPattern.setFieldFormat property + +Signature: + +```typescript +setFieldFormat: (fieldName: string, format: SerializedFieldFormat) => void; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.fieldformats.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.fieldformats.md new file mode 100644 index 0000000000000..af4115e4c4e09 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.fieldformats.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [fieldFormats](./kibana-plugin-plugins-data-public.indexpatternspec.fieldformats.md) + +## IndexPatternSpec.fieldFormats property + +Signature: + +```typescript +fieldFormats?: Record; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.md index 74c4df126e1bf..f3b692209ca67 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.md @@ -14,6 +14,7 @@ export interface IndexPatternSpec | Property | Type | Description | | --- | --- | --- | +| [fieldFormats](./kibana-plugin-plugins-data-public.indexpatternspec.fieldformats.md) | Record<string, SerializedFieldFormat> | | | [fields](./kibana-plugin-plugins-data-public.indexpatternspec.fields.md) | IndexPatternFieldMap | | | [id](./kibana-plugin-plugins-data-public.indexpatternspec.id.md) | string | | | [intervalName](./kibana-plugin-plugins-data-public.indexpatternspec.intervalname.md) | string | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.deletefieldformat.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.deletefieldformat.md new file mode 100644 index 0000000000000..4bfda56527474 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.deletefieldformat.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [deleteFieldFormat](./kibana-plugin-plugins-data-server.indexpattern.deletefieldformat.md) + +## IndexPattern.deleteFieldFormat property + +Signature: + +```typescript +deleteFieldFormat: (fieldName: string) => void; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getformatterforfieldnodefault.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getformatterforfieldnodefault.md new file mode 100644 index 0000000000000..77cc879e2f2f2 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getformatterforfieldnodefault.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getFormatterForFieldNoDefault](./kibana-plugin-plugins-data-server.indexpattern.getformatterforfieldnodefault.md) + +## IndexPattern.getFormatterForFieldNoDefault() method + +Get formatter for a given field name. Return undefined if none exists + +Signature: + +```typescript +getFormatterForFieldNoDefault(fieldname: string): FieldFormat | undefined; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldname | string | | + +Returns: + +`FieldFormat | undefined` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md index 47fb76aa3b593..252f59e17960e 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md @@ -20,6 +20,7 @@ export declare class IndexPattern implements IIndexPattern | Property | Modifiers | Type | Description | | --- | --- | --- | --- | +| [deleteFieldFormat](./kibana-plugin-plugins-data-server.indexpattern.deletefieldformat.md) | | (fieldName: string) => void | | | [fieldFormatMap](./kibana-plugin-plugins-data-server.indexpattern.fieldformatmap.md) | | Record<string, any> | | | [fields](./kibana-plugin-plugins-data-server.indexpattern.fields.md) | | IIndexPatternFieldList & {
toSpec: () => IndexPatternFieldMap;
} | | | [flattenHit](./kibana-plugin-plugins-data-server.indexpattern.flattenhit.md) | | (hit: Record<string, any>, deep?: boolean) => Record<string, any> | | @@ -30,6 +31,7 @@ export declare class IndexPattern implements IIndexPattern | [intervalName](./kibana-plugin-plugins-data-server.indexpattern.intervalname.md) | | string | undefined | | | [metaFields](./kibana-plugin-plugins-data-server.indexpattern.metafields.md) | | string[] | | | [resetOriginalSavedObjectBody](./kibana-plugin-plugins-data-server.indexpattern.resetoriginalsavedobjectbody.md) | | () => void | Reset last saved saved object fields. used after saving | +| [setFieldFormat](./kibana-plugin-plugins-data-server.indexpattern.setfieldformat.md) | | (fieldName: string, format: SerializedFieldFormat) => void | | | [sourceFilters](./kibana-plugin-plugins-data-server.indexpattern.sourcefilters.md) | | SourceFilter[] | | | [timeFieldName](./kibana-plugin-plugins-data-server.indexpattern.timefieldname.md) | | string | undefined | | | [title](./kibana-plugin-plugins-data-server.indexpattern.title.md) | | string | | @@ -47,6 +49,7 @@ export declare class IndexPattern implements IIndexPattern | [getComputedFields()](./kibana-plugin-plugins-data-server.indexpattern.getcomputedfields.md) | | | | [getFieldByName(name)](./kibana-plugin-plugins-data-server.indexpattern.getfieldbyname.md) | | | | [getFormatterForField(field)](./kibana-plugin-plugins-data-server.indexpattern.getformatterforfield.md) | | Provide a field, get its formatter | +| [getFormatterForFieldNoDefault(fieldname)](./kibana-plugin-plugins-data-server.indexpattern.getformatterforfieldnodefault.md) | | Get formatter for a given field name. Return undefined if none exists | | [getIndex()](./kibana-plugin-plugins-data-server.indexpattern.getindex.md) | | | | [getNonScriptedFields()](./kibana-plugin-plugins-data-server.indexpattern.getnonscriptedfields.md) | | | | [getScriptedFields()](./kibana-plugin-plugins-data-server.indexpattern.getscriptedfields.md) | | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.setfieldformat.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.setfieldformat.md new file mode 100644 index 0000000000000..a8f2e726dd9b3 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.setfieldformat.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [setFieldFormat](./kibana-plugin-plugins-data-server.indexpattern.setfieldformat.md) + +## IndexPattern.setFieldFormat property + +Signature: + +```typescript +setFieldFormat: (fieldName: string, format: SerializedFieldFormat) => void; +``` diff --git a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap index ed84aceb60e5a..dc4da2456b47b 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap +++ b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap @@ -2,6 +2,7 @@ exports[`IndexPattern toSpec should match snapshot 1`] = ` Object { + "fieldFormats": Object {}, "fields": Object { "@tags": Object { "aggregatable": true, diff --git a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_patterns.test.ts.snap b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_patterns.test.ts.snap index 752fdcf11991c..a3d19f311b765 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_patterns.test.ts.snap +++ b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_patterns.test.ts.snap @@ -2,6 +2,9 @@ exports[`IndexPatterns savedObjectToSpec 1`] = ` Object { + "fieldFormats": Object { + "field": Object {}, + }, "fields": Object {}, "id": "id", "intervalName": undefined, diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts index 6e11bc8f1d508..9fd43be8dc5b3 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts @@ -183,6 +183,20 @@ describe('IndexPattern', () => { }); }); + describe('setFieldFormat and deleteFieldFormaat', () => { + test('should persist changes', () => { + const formatter = { + toJSON: () => ({ id: 'bytes' }), + } as FieldFormat; + indexPattern.getFormatterForField = () => formatter; + indexPattern.setFieldFormat('bytes', { id: 'bytes' }); + expect(indexPattern.toSpec().fieldFormats).toEqual({ bytes: { id: 'bytes' } }); + + indexPattern.deleteFieldFormat('bytes'); + expect(indexPattern.toSpec().fieldFormats).toEqual({}); + }); + }); + describe('toSpec', () => { test('should match snapshot', () => { const formatter = { @@ -209,7 +223,6 @@ describe('IndexPattern', () => { expect(restoredPattern.title).toEqual(indexPattern.title); expect(restoredPattern.timeFieldName).toEqual(indexPattern.timeFieldName); expect(restoredPattern.fields.length).toEqual(indexPattern.fields.length); - expect(restoredPattern.fieldFormatMap.bytes instanceof MockFieldFormatter).toEqual(true); }); }); }); diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts index 0cf3f2f5d07f6..3d05b19a1e04f 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts @@ -21,13 +21,7 @@ import _, { each, reject } from 'lodash'; import { SavedObjectsClientCommon } from '../..'; import { DuplicateField } from '../../../../kibana_utils/common'; -import { - ES_FIELD_TYPES, - KBN_FIELD_TYPES, - IIndexPattern, - FieldFormatNotFoundError, - IFieldType, -} from '../../../common'; +import { ES_FIELD_TYPES, KBN_FIELD_TYPES, IIndexPattern, IFieldType } from '../../../common'; import { IndexPatternField, IIndexPatternFieldList, fieldList } from '../fields'; import { formatHitProvider } from './format_hit'; import { flattenHitWrapper } from './flatten_hit'; @@ -102,7 +96,7 @@ export class IndexPattern implements IIndexPattern { // set values this.id = spec.id; - const fieldFormatMap = this.fieldSpecsToFieldFormatMap(spec.fields); + this.fieldFormatMap = spec.fieldFormats || {}; this.version = spec.version; @@ -113,12 +107,16 @@ export class IndexPattern implements IIndexPattern { this.fields.replaceAll(Object.values(spec.fields || {})); this.type = spec.type; this.typeMeta = spec.typeMeta; - - this.fieldFormatMap = _.mapValues(fieldFormatMap, (mapping) => { - return this.deserializeFieldFormatMap(mapping); - }); } + setFieldFormat = (fieldName: string, format: SerializedFieldFormat) => { + this.fieldFormatMap[fieldName] = format; + }; + + deleteFieldFormat = (fieldName: string) => { + delete this.fieldFormatMap[fieldName]; + }; + /** * Get last saved saved object fields */ @@ -131,34 +129,6 @@ export class IndexPattern implements IIndexPattern { this.originalSavedObjectBody = this.getAsSavedObjectBody(); }; - /** - * Converts field format spec to field format instance - * @param mapping - */ - private deserializeFieldFormatMap(mapping: SerializedFieldFormat>) { - try { - return this.fieldFormats.getInstance(mapping.id as string, mapping.params); - } catch (err) { - if (err instanceof FieldFormatNotFoundError) { - return undefined; - } else { - throw err; - } - } - } - - /** - * Extracts FieldFormatMap from FieldSpec map - * @param fldList FieldSpec map - */ - private fieldSpecsToFieldFormatMap = (fldList: IndexPatternSpec['fields'] = {}) => - Object.values(fldList).reduce>((col, fieldSpec) => { - if (fieldSpec.format) { - col[fieldSpec.name] = { ...fieldSpec.format }; - } - return col; - }, {}); - getComputedFields() { const scriptFields: any = {}; if (!this.fields) { @@ -211,6 +181,7 @@ export class IndexPattern implements IIndexPattern { fields: this.fields.toSpec({ getFormatterForField: this.getFormatterForField.bind(this) }), typeMeta: this.typeMeta, type: this.type, + fieldFormats: this.fieldFormatMap, }; } @@ -322,17 +293,9 @@ export class IndexPattern implements IIndexPattern { * Returns index pattern as saved object body for saving */ getAsSavedObjectBody() { - const serializeFieldFormatMap = ( - flat: any, - format: FieldFormat | undefined, - field: string | undefined - ) => { - if (format && field) { - flat[field] = format; - } - }; - const serialized = _.transform(this.fieldFormatMap, serializeFieldFormatMap); - const fieldFormatMap = _.isEmpty(serialized) ? undefined : JSON.stringify(serialized); + const fieldFormatMap = _.isEmpty(this.fieldFormatMap) + ? undefined + : JSON.stringify(this.fieldFormatMap); return { title: this.title, @@ -353,12 +316,25 @@ export class IndexPattern implements IIndexPattern { getFormatterForField( field: IndexPatternField | IndexPatternField['spec'] | IFieldType ): FieldFormat { - return ( - this.fieldFormatMap[field.name] || - this.fieldFormats.getDefaultInstance( + const formatSpec = this.fieldFormatMap[field.name]; + if (formatSpec) { + return this.fieldFormats.getInstance(formatSpec.id, formatSpec.params); + } else { + return this.fieldFormats.getDefaultInstance( field.type as KBN_FIELD_TYPES, field.esTypes as ES_FIELD_TYPES[] - ) - ); + ); + } + } + + /** + * Get formatter for a given field name. Return undefined if none exists + * @param field + */ + getFormatterForFieldNoDefault(fieldname: string) { + const formatSpec = this.fieldFormatMap[fieldname]; + if (formatSpec) { + return this.fieldFormats.getInstance(formatSpec.id, formatSpec.params); + } } } diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts index 587feb4b5d4cc..b66e3a4fa8910 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts @@ -36,7 +36,6 @@ import { IndexPatternSpec, IndexPatternAttributes, FieldSpec, - FieldFormatMap, IndexPatternFieldMap, } from '../types'; import { FieldFormatsStartCommon } from '../../field_formats'; @@ -301,20 +300,6 @@ export class IndexPatternsService { return fields; }; - /** - * Applies a set of formats to a set of fields - * @param fieldSpecs - * @param fieldFormatMap - */ - private addFormatsToFields = (fieldSpecs: FieldSpec[], fieldFormatMap: FieldFormatMap) => { - Object.entries(fieldFormatMap).forEach(([fieldName, value]) => { - const field = fieldSpecs.find((fld: FieldSpec) => fld.name === fieldName); - if (field) { - field.format = value; - } - }); - }; - /** * Converts field array to map * @param fields @@ -351,7 +336,6 @@ export class IndexPatternsService { const parsedFieldFormatMap = fieldFormatMap ? JSON.parse(fieldFormatMap) : {}; const parsedFields: FieldSpec[] = fields ? JSON.parse(fields) : []; - this.addFormatsToFields(parsedFields, parsedFieldFormatMap); return { id, version, @@ -362,6 +346,7 @@ export class IndexPatternsService { fields: this.fieldArrayToMap(parsedFields), typeMeta: parsedTypeMeta, type, + fieldFormats: parsedFieldFormatMap, }; }; @@ -387,9 +372,6 @@ export class IndexPatternsService { const spec = this.savedObjectToSpec(savedObject); const { title, type, typeMeta } = spec; - const parsedFieldFormats: FieldFormatMap = savedObject.attributes.fieldFormatMap - ? JSON.parse(savedObject.attributes.fieldFormatMap) - : {}; const isFieldRefreshRequired = this.isFieldRefreshRequired(spec.fields); let isSaveRequired = isFieldRefreshRequired; @@ -420,12 +402,9 @@ export class IndexPatternsService { } } - Object.entries(parsedFieldFormats).forEach(([fieldName, value]) => { - const field = spec.fields?.[fieldName]; - if (field) { - field.format = value; - } - }); + spec.fieldFormats = savedObject.attributes.fieldFormatMap + ? JSON.parse(savedObject.attributes.fieldFormatMap) + : {}; const indexPattern = await this.create(spec, true); indexPatternCache.set(id, indexPattern); diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts index 8a38b7ba857f9..9174e23586ff6 100644 --- a/src/plugins/data/common/index_patterns/types.ts +++ b/src/plugins/data/common/index_patterns/types.ts @@ -183,6 +183,7 @@ export interface IndexPatternSpec { fields?: IndexPatternFieldMap; typeMeta?: TypeMeta; type?: string; + fieldFormats?: Record; } export interface SourceFilter { diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index a09bbe2ccf192..f879346da4981 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1065,6 +1065,8 @@ export class IndexPattern implements IIndexPattern { constructor({ spec, fieldFormats, shortDotsEnable, metaFields, }: IndexPatternDeps); addScriptedField(name: string, script: string, fieldType?: string): Promise; // (undocumented) + deleteFieldFormat: (fieldName: string) => void; + // (undocumented) fieldFormatMap: Record; // (undocumented) fields: IIndexPatternFieldList & { @@ -1110,6 +1112,7 @@ export class IndexPattern implements IIndexPattern { // (undocumented) getFieldByName(name: string): IndexPatternField | undefined; getFormatterForField(field: IndexPatternField | IndexPatternField['spec'] | IFieldType): FieldFormat; + getFormatterForFieldNoDefault(fieldname: string): FieldFormat | undefined; // (undocumented) getIndex(): string; // (undocumented) @@ -1145,6 +1148,8 @@ export class IndexPattern implements IIndexPattern { metaFields: string[]; removeScriptedField(fieldName: string): void; resetOriginalSavedObjectBody: () => void; + // (undocumented) + setFieldFormat: (fieldName: string, format: SerializedFieldFormat) => void; // Warning: (ae-forgotten-export) The symbol "SourceFilter" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -1293,6 +1298,8 @@ export type IndexPatternSelectProps = Required, 'isLo // // @public (undocumented) export interface IndexPatternSpec { + // (undocumented) + fieldFormats?: Record; // (undocumented) fields?: IndexPatternFieldMap; // (undocumented) @@ -2282,7 +2289,7 @@ export const UI_SETTINGS: { // src/plugins/data/common/es_query/filters/meta_filter.ts:54:3 - (ae-forgotten-export) The symbol "FilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrase_filter.ts:33:3 - (ae-forgotten-export) The symbol "PhraseFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrases_filter.ts:31:3 - (ae-forgotten-export) The symbol "PhrasesFilterMeta" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:70:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:64:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts // src/plugins/data/common/search/aggs/types.ts:98:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts // src/plugins/data/public/field_formats/field_formats_service.ts:67:3 - (ae-forgotten-export) The symbol "FormatFactory" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index d645955afd9ed..efc51c50c063e 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -513,6 +513,8 @@ export class IndexPattern implements IIndexPattern { constructor({ spec, fieldFormats, shortDotsEnable, metaFields, }: IndexPatternDeps); addScriptedField(name: string, script: string, fieldType?: string): Promise; // (undocumented) + deleteFieldFormat: (fieldName: string) => void; + // (undocumented) fieldFormatMap: Record; // Warning: (ae-forgotten-export) The symbol "IIndexPatternFieldList" needs to be exported by the entry point index.d.ts // @@ -560,6 +562,7 @@ export class IndexPattern implements IIndexPattern { // (undocumented) getFieldByName(name: string): IndexPatternField | undefined; getFormatterForField(field: IndexPatternField | IndexPatternField['spec'] | IFieldType): FieldFormat; + getFormatterForFieldNoDefault(fieldname: string): FieldFormat | undefined; // (undocumented) getIndex(): string; // Warning: (ae-forgotten-export) The symbol "IndexPatternField" needs to be exported by the entry point index.d.ts @@ -597,6 +600,10 @@ export class IndexPattern implements IIndexPattern { metaFields: string[]; removeScriptedField(fieldName: string): void; resetOriginalSavedObjectBody: () => void; + // Warning: (ae-forgotten-export) The symbol "SerializedFieldFormat" needs to be exported by the entry point index.d.ts + // + // (undocumented) + setFieldFormat: (fieldName: string, format: SerializedFieldFormat) => void; // Warning: (ae-forgotten-export) The symbol "SourceFilter" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -1108,8 +1115,8 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // // src/plugins/data/common/es_query/filters/meta_filter.ts:53:3 - (ae-forgotten-export) The symbol "FilterState" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/meta_filter.ts:54:3 - (ae-forgotten-export) The symbol "FilterMeta" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:64:45 - (ae-forgotten-export) The symbol "IndexPatternFieldMap" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:70:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:58:45 - (ae-forgotten-export) The symbol "IndexPatternFieldMap" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:64:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildCustomFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:71:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx index 08edf42df60d8..545eb86311dad 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx @@ -96,7 +96,7 @@ export const CreateEditField = withRouter( indexPattern={indexPattern} spec={spec} services={{ - saveIndexPattern: data.indexPatterns.updateSavedObject.bind(data.indexPatterns), + indexPatternService: data.indexPatterns, redirectAway, }} /> diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap index 45253f6ad27c0..8e7fac9c6c148 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap @@ -6,6 +6,7 @@ exports[`IndexedFieldsTable should filter based on the query bar 1`] = ` editField={[Function]} indexPattern={ Object { + "getFormatterForFieldNoDefault": [Function], "getNonScriptedFields": [Function], } } @@ -14,7 +15,7 @@ exports[`IndexedFieldsTable should filter based on the query bar 1`] = ` Object { "displayName": "Elastic", "excluded": false, - "format": undefined, + "format": "", "info": Array [], "name": "Elastic", "searchable": true, @@ -32,6 +33,7 @@ exports[`IndexedFieldsTable should filter based on the type filter 1`] = ` editField={[Function]} indexPattern={ Object { + "getFormatterForFieldNoDefault": [Function], "getNonScriptedFields": [Function], } } @@ -40,7 +42,7 @@ exports[`IndexedFieldsTable should filter based on the type filter 1`] = ` Object { "displayName": "timestamp", "excluded": false, - "format": undefined, + "format": "", "info": Array [], "name": "timestamp", "type": "date", @@ -57,6 +59,7 @@ exports[`IndexedFieldsTable should render normally 1`] = ` editField={[Function]} indexPattern={ Object { + "getFormatterForFieldNoDefault": [Function], "getNonScriptedFields": [Function], } } @@ -65,7 +68,7 @@ exports[`IndexedFieldsTable should render normally 1`] = ` Object { "displayName": "Elastic", "excluded": false, - "format": undefined, + "format": "", "info": Array [], "name": "Elastic", "searchable": true, @@ -74,7 +77,7 @@ exports[`IndexedFieldsTable should render normally 1`] = ` Object { "displayName": "timestamp", "excluded": false, - "format": undefined, + "format": "", "info": Array [], "name": "timestamp", "type": "date", @@ -82,7 +85,7 @@ exports[`IndexedFieldsTable should render normally 1`] = ` Object { "displayName": "conflictingField", "excluded": false, - "format": undefined, + "format": "", "info": Array [], "name": "conflictingField", "type": "conflict", diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx index d23afc9aab5a9..224d956cd9cf7 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx @@ -44,6 +44,7 @@ const helpers = { const indexPattern = ({ getNonScriptedFields: () => fields, + getFormatterForFieldNoDefault: () => ({ params: () => ({}) }), } as unknown) as IndexPattern; const mockFieldToIndexPatternField = (spec: Record) => { diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx index 7be420e2af50d..92f0c4576e931 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx @@ -21,7 +21,6 @@ import React, { Component } from 'react'; import { createSelector } from 'reselect'; import { IndexPatternField, IndexPattern, IFieldType } from '../../../../../../plugins/data/public'; import { Table } from './components/table'; -import { getFieldFormat } from './lib'; import { IndexedFieldItem } from './types'; interface IndexedFieldsTableProps { @@ -73,7 +72,7 @@ export class IndexedFieldsTable extends Component< return { ...field.spec, displayName: field.displayName, - format: getFieldFormat(indexPattern, field.name), + format: indexPattern.getFormatterForFieldNoDefault(field.name)?.type?.title || '', excluded: fieldWildcardMatch ? fieldWildcardMatch(field.name) : false, info: helpers.getFieldInfo && helpers.getFieldInfo(indexPattern, field), }; diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/lib/get_field_format.test.ts b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/lib/get_field_format.test.ts deleted file mode 100644 index 2786df641fdb2..0000000000000 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/lib/get_field_format.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 { IIndexPattern } from '../../../../../../data/public'; -import { getFieldFormat } from './get_field_format'; - -const indexPattern = ({ - fieldFormatMap: { - Elastic: { - type: { - title: 'string', - }, - }, - }, -} as unknown) as IIndexPattern; - -describe('getFieldFormat', () => { - test('should handle no arguments', () => { - expect(getFieldFormat()).toEqual(''); - }); - - test('should handle no field name', () => { - expect(getFieldFormat(indexPattern)).toEqual(''); - }); - - test('should handle empty name', () => { - expect(getFieldFormat(indexPattern, '')).toEqual(''); - }); - - test('should handle undefined field name', () => { - expect(getFieldFormat(indexPattern, 'none')).toEqual(undefined); - }); - - test('should retrieve field format', () => { - expect(getFieldFormat(indexPattern, 'Elastic')).toEqual('string'); - }); -}); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/lib/get_field_format.ts b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/lib/get_field_format.ts deleted file mode 100644 index 861017d99962e..0000000000000 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/lib/get_field_format.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 { get } from 'lodash'; -import { IIndexPattern } from '../../../../../../data/public'; - -export function getFieldFormat(indexPattern?: IIndexPattern, fieldName?: string): string { - return indexPattern && fieldName - ? get(indexPattern, ['fieldFormatMap', fieldName, 'type', 'title']) - : ''; -} diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/lib/index.ts b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/lib/index.ts deleted file mode 100644 index 9ab950fbfb2f2..0000000000000 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/lib/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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. - */ - -export { getFieldFormat } from './get_field_format'; diff --git a/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap b/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap index 3f4190eed9170..1e8fb6f9492fe 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap @@ -30,6 +30,7 @@ exports[`FieldEditor should render create new scripted field correctly 1`] = ` "getByName": [Function], }, "getFormatterForField": [Function], + "getFormatterForFieldNoDefault": [Function], } } isVisible={false} @@ -265,6 +266,7 @@ exports[`FieldEditor should render edit scripted field correctly 1`] = ` "getByName": [Function], }, "getFormatterForField": [Function], + "getFormatterForFieldNoDefault": [Function], } } isVisible={false} @@ -499,6 +501,7 @@ exports[`FieldEditor should show conflict field warning 1`] = ` "getByName": [Function], }, "getFormatterForField": [Function], + "getFormatterForFieldNoDefault": [Function], } } isVisible={false} @@ -762,6 +765,7 @@ exports[`FieldEditor should show deprecated lang warning 1`] = ` "getByName": [Function], }, "getFormatterForField": [Function], + "getFormatterForFieldNoDefault": [Function], } } isVisible={false} @@ -1077,6 +1081,7 @@ exports[`FieldEditor should show multiple type field warning with a table contai "getByName": [Function], }, "getFormatterForField": [Function], + "getFormatterForFieldNoDefault": [Function], } } isVisible={false} diff --git a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.test.tsx b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.test.tsx index 23f52475d413d..8817e0f5c2c5f 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.test.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.test.tsx @@ -17,7 +17,12 @@ * under the License. */ -import { IndexPattern, IndexPatternField, FieldFormatInstanceType } from 'src/plugins/data/public'; +import { + IndexPattern, + IndexPatternField, + FieldFormatInstanceType, + IndexPatternsService, +} from 'src/plugins/data/public'; jest.mock('brace/mode/groovy', () => ({})); @@ -94,7 +99,11 @@ const field = { format: new Format(), }; -const services = { redirectAway: () => {}, saveIndexPattern: async () => {} }; +const services = { + redirectAway: () => {}, + saveIndexPattern: async () => {}, + indexPatternService: {} as IndexPatternsService, +}; describe('FieldEditor', () => { let indexPattern: IndexPattern; @@ -115,6 +124,7 @@ describe('FieldEditor', () => { indexPattern = ({ fields, getFormatterForField: () => ({ params: () => ({}) }), + getFormatterForFieldNoDefault: () => ({ params: () => ({}) }), } as unknown) as IndexPattern; }); diff --git a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx index 4fae91e78f8f9..d02338a6aee24 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx @@ -133,7 +133,7 @@ export interface FieldEdiorProps { spec: IndexPatternField['spec']; services: { redirectAway: () => void; - saveIndexPattern: DataPublicPluginStart['indexPatterns']['updateSavedObject']; + indexPatternService: DataPublicPluginStart['indexPatterns']; }; } @@ -208,7 +208,7 @@ export class FieldEditor extends PureComponent { - const { uiSettings, data } = this.context.services; + const { data } = this.context.services; const { spec, format } = this.state; const DefaultFieldFormat = data.fieldFormats.getDefaultType(type) as FieldFormatInstanceType; spec.type = type; - spec.format = new DefaultFieldFormat(null, (key) => uiSettings.get(key)); - this.setState({ fieldTypeFormats: getFieldTypeFormatsList(spec, DefaultFieldFormat, data.fieldFormats), fieldFormatId: DefaultFieldFormat.id, @@ -247,7 +245,7 @@ export class FieldEditor extends PureComponent { - const { spec, fieldTypeFormats } = this.state; + const { fieldTypeFormats } = this.state; const { uiSettings, data } = this.context.services; const FieldFormat = data.fieldFormats.getType( @@ -255,11 +253,10 @@ export class FieldEditor extends PureComponent uiSettings.get(key)); - spec.format = newFormat; this.setState({ - fieldFormatId: FieldFormat.id, - fieldFormatParams: newFormat.params(), + fieldFormatId: formatId, + fieldFormatParams: params, format: newFormat, }); }; @@ -515,7 +512,7 @@ export class FieldEditor extends PureComponent { - const { redirectAway, saveIndexPattern } = this.props.services; + const { redirectAway, indexPatternService } = this.props.services; const { indexPattern } = this.props; const { spec } = this.state; indexPattern.removeScriptedField(spec.name); - saveIndexPattern(indexPattern).then(() => { + indexPatternService.updateSavedObject(indexPattern).then(() => { const message = i18n.translate('indexPatternManagement.deleteField.deletedHeader', { defaultMessage: "Deleted '{fieldName}'", values: { fieldName: spec.name }, @@ -775,7 +772,7 @@ export class FieldEditor extends PureComponent { const field = this.state.spec; const { indexPattern } = this.props; - const { fieldFormatId } = this.state; + const { fieldFormatId, fieldFormatParams } = this.state; if (field.scripted) { this.setState({ @@ -798,7 +795,7 @@ export class FieldEditor extends PureComponent { const message = i18n.translate('indexPatternManagement.deleteField.savedHeader', { defaultMessage: "Saved '{fieldName}'",