diff --git a/src/plugins/data_view_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap b/src/plugins/data_view_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap index 32304ef596a7..5d67cd1e1760 100644 --- a/src/plugins/data_view_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap +++ b/src/plugins/data_view_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap @@ -31,6 +31,8 @@ exports[`FieldEditor should render create new scripted field correctly 1`] = ` }, "getFormatterForField": [Function], "getFormatterForFieldNoDefault": [Function], + "setFieldCustomLabel": [Function], + "upsertScriptedField": [Function], } } isVisible={false} @@ -285,6 +287,8 @@ exports[`FieldEditor should render edit scripted field correctly 1`] = ` }, "getFormatterForField": [Function], "getFormatterForFieldNoDefault": [Function], + "setFieldCustomLabel": [Function], + "upsertScriptedField": [Function], } } isVisible={false} @@ -538,6 +542,8 @@ exports[`FieldEditor should show conflict field warning 1`] = ` }, "getFormatterForField": [Function], "getFormatterForFieldNoDefault": [Function], + "setFieldCustomLabel": [Function], + "upsertScriptedField": [Function], } } isVisible={false} @@ -821,6 +827,8 @@ exports[`FieldEditor should show deprecated lang warning 1`] = ` }, "getFormatterForField": [Function], "getFormatterForFieldNoDefault": [Function], + "setFieldCustomLabel": [Function], + "upsertScriptedField": [Function], } } isVisible={false} @@ -1167,6 +1175,8 @@ exports[`FieldEditor should show multiple type field warning with a table contai }, "getFormatterForField": [Function], "getFormatterForFieldNoDefault": [Function], + "setFieldCustomLabel": [Function], + "upsertScriptedField": [Function], } } isVisible={false} diff --git a/src/plugins/data_view_management/public/components/field_editor/field_editor.test.tsx b/src/plugins/data_view_management/public/components/field_editor/field_editor.test.tsx index f3b533b7d829..3ba5945e3e53 100644 --- a/src/plugins/data_view_management/public/components/field_editor/field_editor.test.tsx +++ b/src/plugins/data_view_management/public/components/field_editor/field_editor.test.tsx @@ -119,6 +119,10 @@ describe('FieldEditor', () => { fields, getFormatterForField: () => ({ params: () => ({}) }), getFormatterForFieldNoDefault: () => ({ params: () => ({}) }), + upsertScriptedField: () => undefined, + setFieldCustomLabel: (name: string, label: string) => { + indexPattern.fields.getByName(name)!.customLabel = label; + }, } as unknown as DataView; }); diff --git a/src/plugins/data_view_management/public/components/field_editor/field_editor.tsx b/src/plugins/data_view_management/public/components/field_editor/field_editor.tsx index 7b12aeda3060..b8d77c41a634 100644 --- a/src/plugins/data_view_management/public/components/field_editor/field_editor.tsx +++ b/src/plugins/data_view_management/public/components/field_editor/field_editor.tsx @@ -822,16 +822,9 @@ export class FieldEditor extends PureComponent; + /** + * Prevents errors when index pattern exists before indices + */ + public readonly allowNoIndex: boolean = false; + /** + * Name of the data view. Human readable name used to differentiate data view. + */ + public name: string = ''; + + /* + * list of indices that the index pattern matched + */ + public matchedIndices: string[] = []; + + protected scriptedFields: DataViewFieldBase[]; + + constructor(config: AbstractDataViewDeps) { + const { spec = {}, fieldFormats, shortDotsEnable = false, metaFields = [] } = config; + + const extractedFieldAttrs = spec?.fields + ? Object.entries(spec.fields).reduce((acc, [key, value]) => { + const attrs: FieldAttrSet = {}; + let hasAttrs = false; + + if (value.count) { + attrs.count = value.count; + hasAttrs = true; + } + + if (value.customLabel) { + attrs.customLabel = value.customLabel; + hasAttrs = true; + } + + if (hasAttrs) { + acc[key] = attrs; + } + return acc; + }, {} as Record) + : []; + + this.allowNoIndex = spec?.allowNoIndex || false; + // CRUD operations on scripted fields need to be examined + this.scriptedFields = spec?.fields + ? Object.values(spec.fields).filter((field) => field.scripted) + : []; + + // set dependencies + this.fieldFormats = { ...fieldFormats }; + // set config + this.shortDotsEnable = shortDotsEnable; + this.metaFields = metaFields; + + // set values + this.id = spec.id; + this.fieldFormatMap = { ...spec.fieldFormats }; + + this.version = spec.version; + + this.title = spec.title || ''; + this.timeFieldName = spec.timeFieldName; + this.sourceFilters = [...(spec.sourceFilters || [])]; + this.type = spec.type; + this.typeMeta = spec.typeMeta; + this.fieldAttrs = cloneDeep(merge({}, extractedFieldAttrs, spec.fieldAttrs)) || {}; + this.runtimeFieldMap = cloneDeep(spec.runtimeFieldMap) || {}; + this.namespaces = spec.namespaces || []; + this.name = spec.name || ''; + } + + /** + * Get name of Data View + */ + getName = () => (this.name ? this.name : this.title); + + /** + * Get index pattern + * @returns index pattern string + */ + + getIndexPattern = () => this.title; + + /** + * Set index pattern + * @param string index pattern string + */ + + setIndexPattern = (indexPattern: string) => { + this.title = indexPattern; + }; + + /** + * Get last saved saved object fields + */ + getOriginalSavedObjectBody = () => ({ ...this.originalSavedObjectBody }); + + /** + * Reset last saved saved object fields. Used after saving. + */ + resetOriginalSavedObjectBody = () => { + this.originalSavedObjectBody = this.getAsSavedObjectBody(); + }; + + isPersisted() { + return typeof this.version === 'string'; + } + + /** + * Get the source filtering configuration for that index. + */ + getSourceFiltering() { + return { + excludes: (this.sourceFilters && this.sourceFilters.map((filter) => filter.value)) || [], + }; + } + + /** + * Get aggregation restrictions. Rollup fields can only perform a subset of aggregations. + */ + + getAggregationRestrictions() { + return this.typeMeta?.aggs; + } + + /** + * Provide a field, get its formatter + * @param field field to get formatter for + */ + getFormatterForField(field: DataViewField | DataViewField['spec']): FieldFormat { + const fieldFormat = this.getFormatterForFieldNoDefault(field.name); + if (fieldFormat) { + return fieldFormat; + } + + 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 fieldname name of field to get formatter for + */ + getFormatterForFieldNoDefault(fieldname: string) { + const formatSpec = this.fieldFormatMap[fieldname]; + if (formatSpec?.id) { + return this.fieldFormats.getInstance(formatSpec.id, formatSpec.params); + } + } + + /** + * Set field attribute + * @param fieldName name of field to set attribute on + * @param attrName name of attribute to set + * @param value value of attribute + */ + + protected setFieldAttrs( + fieldName: string, + attrName: K, + value: FieldAttrSet[K] + ) { + if (!this.fieldAttrs[fieldName]) { + this.fieldAttrs[fieldName] = {} as FieldAttrSet; + } + this.fieldAttrs[fieldName][attrName] = value; + } + + /** + * Set field custom label + * @param fieldName name of field to set custom label on + * @param customLabel custom label value. If undefined, custom label is removed + */ + + protected setFieldCustomLabelInternal(fieldName: string, customLabel: string | undefined | null) { + this.setFieldAttrs(fieldName, 'customLabel', customLabel === null ? undefined : customLabel); + } + + /** + * Set field formatter + * @param fieldName name of field to set format on + * @param format field format in serialized form + */ + public readonly setFieldFormat = (fieldName: string, format: SerializedFieldFormat) => { + this.fieldFormatMap[fieldName] = format; + }; + + /** + * Remove field format from the field format map. + * @param fieldName field name associated with the format for removal + */ + + public readonly deleteFieldFormat = (fieldName: string) => { + delete this.fieldFormatMap[fieldName]; + }; + + /** + * Returns index pattern as saved object body for saving + */ + getAsSavedObjectBody(): DataViewAttributes { + const stringifyOrUndefined = (obj: any) => (obj ? JSON.stringify(obj) : undefined); + + return { + fieldAttrs: stringifyOrUndefined(this.fieldAttrs), + title: this.getIndexPattern(), + timeFieldName: this.timeFieldName, + sourceFilters: stringifyOrUndefined(this.sourceFilters), + fields: stringifyOrUndefined(this.scriptedFields), + fieldFormatMap: stringifyOrUndefined(this.fieldFormatMap), + type: this.type!, + typeMeta: stringifyOrUndefined(this.typeMeta), + allowNoIndex: this.allowNoIndex ? this.allowNoIndex : undefined, + runtimeFieldMap: stringifyOrUndefined(this.runtimeFieldMap), + name: this.name, + }; + } + + protected upsertScriptedFieldInternal = (field: FieldSpec) => { + // search for scriped field with same name + const findByName = (f: DataViewFieldBase) => f.name === field.name; + + const fieldIndex = findIndex(this.scriptedFields, findByName); + + const scriptedField: DataViewFieldBase = { + name: field.name, + script: field.script, + lang: field.lang, + type: field.type, + scripted: field.scripted, + }; + + if (fieldIndex === -1) { + this.scriptedFields.push(scriptedField); + } else { + this.scriptedFields[fieldIndex] = scriptedField; + } + }; + + protected deleteScriptedFieldInternal = (fieldName: string) => { + this.scriptedFields = this.scriptedFields.filter((field) => field.name !== fieldName); + }; + + /** + * Checks if runtime field exists + * @param name field name + */ + hasRuntimeField(name: string): boolean { + return !!this.runtimeFieldMap[name]; + } + + /** + * Returns runtime field if exists + * @param name Runtime field name + */ + getRuntimeField(name: string): RuntimeField | null { + if (!this.runtimeFieldMap[name]) { + return null; + } + + const { type, script, fields } = { ...this.runtimeFieldMap[name] }; + const runtimeField: RuntimeField = { + type, + script, + }; + + if (type === 'composite') { + runtimeField.fields = fields; + } + + return runtimeField; + } + + /** + * Get all runtime field definitions. + * NOTE: this does not strip out runtime fields that match mapped field names + * @returns map of runtime field definitions by field name + */ + + getAllRuntimeFields(): Record { + return Object.keys(this.runtimeFieldMap).reduce>( + (acc, fieldName) => ({ + ...acc, + [fieldName]: this.getRuntimeField(fieldName)!, + }), + {} + ); + } + + protected removeRuntimeFieldInteral(name: string) { + delete this.runtimeFieldMap[name]; + } + + protected addRuntimeFieldInteral(name: string, runtimeField: RuntimeField) { + this.runtimeFieldMap[name] = removeFieldAttrs(runtimeField); + } + + getFieldAttrs = () => cloneDeep(this.fieldAttrs); +} diff --git a/src/plugins/data_views/common/data_views/data_view.test.ts b/src/plugins/data_views/common/data_views/data_view.test.ts index 2f663ac480ba..18ab045d81dd 100644 --- a/src/plugins/data_views/common/data_views/data_view.test.ts +++ b/src/plugins/data_views/common/data_views/data_view.test.ts @@ -8,7 +8,7 @@ import { FieldFormat } from '@kbn/field-formats-plugin/common'; -import { RuntimeField, RuntimePrimitiveTypes, FieldSpec } from '../types'; +import { RuntimeField, RuntimePrimitiveTypes, FieldSpec, DataViewSpec } from '../types'; import { stubLogstashFields } from '../field.stub'; import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; import { CharacterNotAllowedInField } from '@kbn/kibana-utils-plugin/common'; @@ -41,7 +41,7 @@ const runtimeField = { fieldFormatsMock.getInstance = jest.fn().mockImplementation(() => new MockFieldFormatter()); // helper function to create index patterns -function create(id: string, spec?: object) { +function create(id: string, spec?: DataViewSpec) { const { type, version, @@ -315,11 +315,9 @@ describe('IndexPattern', () => { id: 'bytes', }); expect(field.customLabel).toEqual('custom name'); - expect(indexPattern.toSpec().fieldAttrs).toEqual({ - '@tags': { - customLabel: 'custom name', - count: 5, - }, + expect(indexPattern.toSpec().fieldAttrs!['@tags']).toEqual({ + customLabel: 'custom name', + count: 5, }); indexPattern.removeRuntimeField('@tags'); @@ -393,15 +391,13 @@ describe('IndexPattern', () => { expect(indexPattern.getRuntimeField('new_field')).toMatchSnapshot(); expect(indexPattern.toSpec()!.fields!['new_field.a']).toBeDefined(); expect(indexPattern.toSpec()!.fields!['new_field.b']).toBeDefined(); - expect(indexPattern.toSpec()!.fieldAttrs).toEqual({ - 'new_field.a': { - count: 3, - customLabel: 'custom name a', - }, - 'new_field.b': { - count: 4, - customLabel: 'custom name b', - }, + expect(indexPattern.toSpec().fieldAttrs!['new_field.a']).toEqual({ + count: 3, + customLabel: 'custom name a', + }); + expect(indexPattern.toSpec().fieldAttrs!['new_field.b']).toEqual({ + count: 4, + customLabel: 'custom name b', }); indexPattern.removeRuntimeField('new_field'); @@ -485,7 +481,7 @@ describe('IndexPattern', () => { }); test('creating from spec does not contain references to spec', () => { - const sourceFilters = ['test']; + const sourceFilters = [{ value: 'test' }]; const spec = { sourceFilters }; const dataView1 = create('test1', spec); const dataView2 = create('test2', spec); diff --git a/src/plugins/data_views/common/data_views/data_view.ts b/src/plugins/data_views/common/data_views/data_view.ts index ffda65af2a89..27681021757f 100644 --- a/src/plugins/data_views/common/data_views/data_view.ts +++ b/src/plugins/data_views/common/data_views/data_view.ts @@ -7,31 +7,25 @@ */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { DataViewBase } from '@kbn/es-query'; -import type { - FieldFormat, - FieldFormatsStartCommon, - SerializedFieldFormat, -} from '@kbn/field-formats-plugin/common'; -import { castEsToKbnFieldTypeName, ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; +import type { FieldFormatsStartCommon } from '@kbn/field-formats-plugin/common'; +import { castEsToKbnFieldTypeName } from '@kbn/field-types'; import { CharacterNotAllowedInField } from '@kbn/kibana-utils-plugin/common'; +import type { DataViewBase } from '@kbn/es-query'; import { cloneDeep, each, mapValues, omit, pickBy, reject } from 'lodash'; -import type { DataViewAttributes, FieldAttrs, FieldAttrSet } from '..'; import type { DataViewField, IIndexPatternFieldList } from '../fields'; import { fieldList } from '../fields'; import type { DataViewFieldMap, DataViewSpec, FieldConfiguration, - FieldFormatMap, RuntimeField, RuntimeFieldSpec, RuntimeType, - SourceFilter, - TypeMeta, + FieldSpec, } from '../types'; -import { flattenHitWrapper } from './flatten_hit'; import { removeFieldAttrs } from './utils'; +import { AbstractDataView } from './abstract_data_views'; +import { flattenHitWrapper } from './flatten_hit'; interface DataViewDeps { spec?: DataViewSpec; @@ -40,17 +34,6 @@ interface DataViewDeps { metaFields?: string[]; } -interface SavedObjectBody { - fieldAttrs?: string; - title?: string; - timeFieldName?: string; - fields?: string; - sourceFilters?: string; - fieldFormatMap?: string; - typeMeta?: string; - type?: string; -} - /** * An interface representing a data view that is time based. */ @@ -68,89 +51,15 @@ export interface TimeBasedDataView extends DataView { /** * Data view class. Central kibana abstraction around multiple indices. */ -export class DataView implements DataViewBase { - /** - * Saved object id - */ - public id?: string; - /** - * Title of data view - * @deprecated use getIndexPattern instead - */ - public title: string = ''; - /** - * Map of field formats by field name - */ - public fieldFormatMap: FieldFormatMap; - /** - * Only used by rollup indices, used by rollup specific endpoint to load field list. - */ - public typeMeta?: TypeMeta; +export class DataView extends AbstractDataView implements DataViewBase { /** * Field list, in extended array format */ public fields: IIndexPatternFieldList & { toSpec: () => DataViewFieldMap }; - /** - * Timestamp field name - */ - public timeFieldName: string | undefined; - /** - * Type is used to identify rollup index patterns. - */ - public type: string | undefined; /** * @deprecated Use `flattenHit` utility method exported from data plugin instead. */ public flattenHit: (hit: Record, deep?: boolean) => Record; - /** - * List of meta fields by name - */ - public metaFields: string[]; - /** - * SavedObject version - */ - public version: string | undefined; - /** - * Array of filters - hides fields in discover - */ - public sourceFilters?: SourceFilter[]; - /** - * Array of namespace ids - */ - public namespaces: string[]; - /** - * Original saved object body. Used to check for saved object changes. - */ - private originalSavedObjectBody: SavedObjectBody = {}; - /** - * Returns true if short dot notation is enabled - */ - private shortDotsEnable: boolean = false; - /** - * FieldFormats service interface - */ - private fieldFormats: FieldFormatsStartCommon; - /** - * Map of field attributes by field name. Currently count and customLabel. - */ - private fieldAttrs: FieldAttrs; - /** - * Map of runtime field definitions by field name - */ - private runtimeFieldMap: Record; - /** - * Prevents errors when index pattern exists before indices - */ - public readonly allowNoIndex: boolean = false; - /** - * Name of the data view. Human readable name used to differentiate data view. - */ - public name: string = ''; - - /* - * list of indices that the index pattern matched - */ - public matchedIndices: string[] = []; /** * constructor @@ -158,98 +67,16 @@ export class DataView implements DataViewBase { */ constructor(config: DataViewDeps) { - const { spec = {}, fieldFormats, shortDotsEnable = false, metaFields = [] } = config; - - // set dependencies - this.fieldFormats = { ...fieldFormats }; - // set config - this.shortDotsEnable = shortDotsEnable; - this.metaFields = metaFields; - // initialize functionality - this.fields = fieldList([], this.shortDotsEnable); + super(config); + const { spec = {}, metaFields } = config; + this.fields = fieldList([], this.shortDotsEnable); this.flattenHit = flattenHitWrapper(this, metaFields); // set values - this.id = spec.id; - this.fieldFormatMap = { ...spec.fieldFormats }; - - this.version = spec.version; - - this.title = spec.title || ''; - this.timeFieldName = spec.timeFieldName; - this.sourceFilters = [...(spec.sourceFilters || [])]; this.fields.replaceAll(Object.values(spec.fields || {})); - this.type = spec.type; - this.typeMeta = spec.typeMeta; - this.fieldAttrs = cloneDeep(spec.fieldAttrs) || {}; - this.allowNoIndex = spec.allowNoIndex || false; - this.runtimeFieldMap = cloneDeep(spec.runtimeFieldMap) || {}; - this.namespaces = spec.namespaces || []; - this.name = spec.name || ''; } - /** - * Get name of Data View - */ - getName = () => (this.name ? this.name : this.title); - - /** - * Get index pattern - * @returns index pattern string - */ - - getIndexPattern = () => this.title; - - /** - * Set index pattern - * @param string index pattern string - */ - - setIndexPattern = (indexPattern: string) => { - this.title = indexPattern; - }; - - /** - * Get last saved saved object fields - */ - getOriginalSavedObjectBody = () => ({ ...this.originalSavedObjectBody }); - - /** - * Reset last saved saved object fields. Used after saving. - */ - resetOriginalSavedObjectBody = () => { - this.originalSavedObjectBody = this.getAsSavedObjectBody(); - }; - - /** - * Returns field attributes map - */ - getFieldAttrs = () => { - const newFieldAttrs = { ...this.fieldAttrs }; - - this.fields.forEach((field) => { - const attrs: FieldAttrSet = {}; - let hasAttr = false; - if (field.customLabel) { - attrs.customLabel = field.customLabel; - hasAttr = true; - } - if (field.count) { - attrs.count = field.count; - hasAttr = true; - } - - if (hasAttr) { - newFieldAttrs[field.name] = attrs; - } else { - delete newFieldAttrs[field.name]; - } - }); - - return newFieldAttrs; - }; - /** * Returns scripted fields */ @@ -297,10 +124,6 @@ export class DataView implements DataViewBase { }; } - isPersisted() { - return typeof this.version === 'string'; - } - /** * Creates static representation of the data view. * @param includeFields Whether or not to include the `fields` list as part of this spec. If not included, the list @@ -312,6 +135,17 @@ export class DataView implements DataViewBase { ? this.fields.toSpec({ getFormatterForField: this.getFormatterForField.bind(this) }) : undefined; + // if fields aren't included, don't include count + const fieldAttrs = cloneDeep(this.fieldAttrs); + if (!includeFields) { + Object.keys(fieldAttrs).forEach((key) => { + delete fieldAttrs[key].count; + if (Object.keys(fieldAttrs[key]).length === 0) { + delete fieldAttrs[key]; + } + }); + } + const spec: DataViewSpec = { id: this.id, version: this.version, @@ -323,7 +157,7 @@ export class DataView implements DataViewBase { type: this.type, fieldFormats: { ...this.fieldFormatMap }, runtimeFieldMap: cloneDeep(this.runtimeFieldMap), - fieldAttrs: cloneDeep(this.fieldAttrs), + fieldAttrs, allowNoIndex: this.allowNoIndex, name: this.name, }; @@ -354,15 +188,6 @@ export class DataView implements DataViewBase { return dataViewSpec; } - /** - * Get the source filtering configuration for that index. - */ - getSourceFiltering() { - return { - excludes: (this.sourceFilters && this.sourceFilters.map((filter) => filter.value)) || [], - }; - } - /** * Removes scripted field from field list. * @param fieldName name of scripted field to remove @@ -370,9 +195,12 @@ export class DataView implements DataViewBase { */ removeScriptedField(fieldName: string) { + this.deleteScriptedFieldInternal(fieldName); const field = this.fields.getByName(fieldName); - if (field) { + if (field && field.scripted) { this.fields.remove(field); + } else { + throw new Error(`Scripted field ${fieldName} does not exist in data view ${this.getName()}`); } } @@ -434,52 +262,6 @@ export class DataView implements DataViewBase { return this.fields.getByName(name); } - /** - * Get aggregation restrictions. Rollup fields can only perform a subset of aggregations. - */ - - getAggregationRestrictions() { - return this.typeMeta?.aggs; - } - - /** - * Returns index pattern as saved object body for saving - */ - getAsSavedObjectBody(): DataViewAttributes { - const fieldAttrs = this.getFieldAttrs(); - const runtimeFieldMap = this.runtimeFieldMap; - - return { - fieldAttrs: fieldAttrs ? JSON.stringify(fieldAttrs) : undefined, - title: this.getIndexPattern(), - timeFieldName: this.timeFieldName, - sourceFilters: this.sourceFilters ? JSON.stringify(this.sourceFilters) : undefined, - fields: JSON.stringify(this.fields?.filter((field) => field.scripted) ?? []), - fieldFormatMap: this.fieldFormatMap ? JSON.stringify(this.fieldFormatMap) : undefined, - type: this.type!, - typeMeta: JSON.stringify(this.typeMeta ?? {}), - allowNoIndex: this.allowNoIndex ? this.allowNoIndex : undefined, - runtimeFieldMap: runtimeFieldMap ? JSON.stringify(runtimeFieldMap) : undefined, - name: this.name, - }; - } - - /** - * Provide a field, get its formatter - * @param field field to get formatter for - */ - getFormatterForField(field: DataViewField | DataViewField['spec']): FieldFormat { - const fieldFormat = this.getFormatterForFieldNoDefault(field.name); - if (fieldFormat) { - return fieldFormat; - } - - return this.fieldFormats.getDefaultInstance( - field.type as KBN_FIELD_TYPES, - field.esTypes as ES_FIELD_TYPES[] - ); - } - /** * Add a runtime field - Appended to existing mapped field or a new field is * created as appropriate. @@ -497,7 +279,7 @@ export class DataView implements DataViewBase { return this.addCompositeRuntimeField(name, runtimeField); } - this.runtimeFieldMap[name] = removeFieldAttrs(runtimeField); + this.addRuntimeFieldInteral(name, runtimeField); const field = this.updateOrAddRuntimeField( name, type, @@ -512,52 +294,6 @@ export class DataView implements DataViewBase { return [field]; } - /** - * Checks if runtime field exists - * @param name field name - */ - hasRuntimeField(name: string): boolean { - return !!this.runtimeFieldMap[name]; - } - - /** - * Returns runtime field if exists - * @param name Runtime field name - */ - getRuntimeField(name: string): RuntimeField | null { - if (!this.runtimeFieldMap[name]) { - return null; - } - - const { type, script, fields } = { ...this.runtimeFieldMap[name] }; - const runtimeField: RuntimeField = { - type, - script, - }; - - if (type === 'composite') { - runtimeField.fields = fields; - } - - return runtimeField; - } - - /** - * Get all runtime field definitions. - * NOTE: this does not strip out runtime fields that match mapped field names - * @returns map of runtime field definitions by field name - */ - - getAllRuntimeFields(): Record { - return Object.keys(this.runtimeFieldMap).reduce>( - (acc, fieldName) => ({ - ...acc, - [fieldName]: this.getRuntimeField(fieldName)!, - }), - {} - ); - } - /** * Returns data view fields backed by runtime fields. * @param name runtime field name @@ -624,7 +360,7 @@ export class DataView implements DataViewBase { this.fields.remove(field); }); } - delete this.runtimeFieldMap[name]; + this.removeRuntimeFieldInteral(name); } /** @@ -646,35 +382,6 @@ export class DataView implements DataViewBase { return records as estypes.MappingRuntimeFields; } - /** - * Get formatter for a given field name. Return undefined if none exists. - * @param fieldname name of field to get formatter for - */ - getFormatterForFieldNoDefault(fieldname: string) { - const formatSpec = this.fieldFormatMap[fieldname]; - if (formatSpec?.id) { - return this.fieldFormats.getInstance(formatSpec.id, formatSpec.params); - } - } - - /** - * Set field attribute - * @param fieldName name of field to set attribute on - * @param attrName name of attribute to set - * @param value value of attribute - */ - - protected setFieldAttrs( - fieldName: string, - attrName: K, - value: FieldAttrSet[K] - ) { - if (!this.fieldAttrs[fieldName]) { - this.fieldAttrs[fieldName] = {} as FieldAttrSet; - } - this.fieldAttrs[fieldName][attrName] = value; - } - /** * Set field custom label * @param fieldName name of field to set custom label on @@ -689,7 +396,7 @@ export class DataView implements DataViewBase { fieldObject.customLabel = newCustomLabel; } - this.setFieldAttrs(fieldName, 'customLabel', newCustomLabel); + this.setFieldCustomLabelInternal(fieldName, customLabel); } /** @@ -709,24 +416,6 @@ export class DataView implements DataViewBase { this.setFieldAttrs(fieldName, 'count', newCount); } - /** - * Set field formatter - * @param fieldName name of field to set format on - * @param format field format in serialized form - */ - public readonly setFieldFormat = (fieldName: string, format: SerializedFieldFormat) => { - this.fieldFormatMap[fieldName] = format; - }; - - /** - * Remove field format from the field format map. - * @param fieldName field name associated with the format for removal - */ - - public readonly deleteFieldFormat = (fieldName: string) => { - delete this.fieldFormatMap[fieldName]; - }; - private getMappedFieldNames() { return this.fields.getAll().reduce((acc, dataViewField) => { if (dataViewField.isMapped) { @@ -776,7 +465,7 @@ export class DataView implements DataViewBase { }) ); - this.runtimeFieldMap[name] = removeFieldAttrs(runtimeField); + this.addRuntimeFieldInteral(name, runtimeField); return dataViewFields; } @@ -826,4 +515,15 @@ export class DataView implements DataViewBase { return createdField ?? existingField!; } + + upsertScriptedField = (field: FieldSpec) => { + this.upsertScriptedFieldInternal(field); + const fieldExists = !!this.fields.getByName(field.name); + + if (fieldExists) { + this.fields.update(field); + } else { + this.fields.add(field); + } + }; } diff --git a/src/plugins/data_views/server/rest_api_routes/public/scripted_fields/create_scripted_field.ts b/src/plugins/data_views/server/rest_api_routes/public/scripted_fields/create_scripted_field.ts index d977333aab07..ac33e1be0e4d 100644 --- a/src/plugins/data_views/server/rest_api_routes/public/scripted_fields/create_scripted_field.ts +++ b/src/plugins/data_views/server/rest_api_routes/public/scripted_fields/create_scripted_field.ts @@ -76,7 +76,7 @@ export const registerCreateScriptedFieldRoute = ( throw new Error(`Field [name = ${field.name}] already exists.`); } - indexPattern.fields.add({ + indexPattern.upsertScriptedField({ ...field, runtimeField: undefined, aggregatable: true, diff --git a/src/plugins/data_views/server/rest_api_routes/public/scripted_fields/delete_scripted_field.ts b/src/plugins/data_views/server/rest_api_routes/public/scripted_fields/delete_scripted_field.ts index fc7dc17c6956..f631440544d4 100644 --- a/src/plugins/data_views/server/rest_api_routes/public/scripted_fields/delete_scripted_field.ts +++ b/src/plugins/data_views/server/rest_api_routes/public/scripted_fields/delete_scripted_field.ts @@ -79,7 +79,7 @@ export const registerDeleteScriptedFieldRoute = ( throw new Error('Only scripted fields can be deleted.'); } - indexPattern.fields.remove(field); + indexPattern.removeScriptedField(name); await indexPatternsService.updateSavedObject(indexPattern); diff --git a/src/plugins/data_views/server/rest_api_routes/public/scripted_fields/put_scripted_field.ts b/src/plugins/data_views/server/rest_api_routes/public/scripted_fields/put_scripted_field.ts index 868ec935e0f9..4d3e772bf8c8 100644 --- a/src/plugins/data_views/server/rest_api_routes/public/scripted_fields/put_scripted_field.ts +++ b/src/plugins/data_views/server/rest_api_routes/public/scripted_fields/put_scripted_field.ts @@ -71,13 +71,7 @@ export const registerPutScriptedFieldRoute = ( } const indexPattern = await indexPatternsService.get(id); - - const oldFieldObject = indexPattern.fields.getByName(field.name); - if (!!oldFieldObject) { - indexPattern.fields.remove(oldFieldObject); - } - - indexPattern.fields.add({ + indexPattern.upsertScriptedField({ ...field, runtimeField: undefined, // make sure not creating runttime field with scripted field endpoint aggregatable: true, diff --git a/src/plugins/data_views/server/rest_api_routes/public/scripted_fields/update_scripted_field.ts b/src/plugins/data_views/server/rest_api_routes/public/scripted_fields/update_scripted_field.ts index aa52c0eea8c8..08b587e53003 100644 --- a/src/plugins/data_views/server/rest_api_routes/public/scripted_fields/update_scripted_field.ts +++ b/src/plugins/data_views/server/rest_api_routes/public/scripted_fields/update_scripted_field.ts @@ -104,10 +104,10 @@ export const registerUpdateScriptedFieldRoute = ( const oldSpec = fieldObject.toSpec(); - indexPattern.fields.remove(fieldObject); - indexPattern.fields.add({ + indexPattern.upsertScriptedField({ ...oldSpec, ...field, + name: field.name, }); await indexPatternsService.updateSavedObject(indexPattern); diff --git a/x-pack/plugins/logs_shared/server/services/log_views/log_views_client.test.ts b/x-pack/plugins/logs_shared/server/services/log_views/log_views_client.test.ts index 5efdf9e125de..125167d6deea 100644 --- a/x-pack/plugins/logs_shared/server/services/log_views/log_views_client.test.ts +++ b/x-pack/plugins/logs_shared/server/services/log_views/log_views_client.test.ts @@ -251,6 +251,7 @@ describe('LogViewsClient class', () => { "dataViewReference": DataView { "allowNoIndex": false, "deleteFieldFormat": [Function], + "deleteScriptedFieldInternal": [Function], "fieldAttrs": Object {}, "fieldFormatMap": Object {}, "fieldFormats": Object { @@ -296,6 +297,7 @@ describe('LogViewsClient class', () => { "type": "keyword", }, }, + "scriptedFields": Array [], "setFieldFormat": [Function], "setIndexPattern": [Function], "shortDotsEnable": false, @@ -304,6 +306,8 @@ describe('LogViewsClient class', () => { "title": "log-indices-*", "type": undefined, "typeMeta": undefined, + "upsertScriptedField": [Function], + "upsertScriptedFieldInternal": [Function], "version": "1", }, "description": "LOG VIEW DESCRIPTION",