diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md index 2e078e3404fe6..a5bb15c963978 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md @@ -9,7 +9,7 @@ Constructs a new instance of the `IndexPattern` class Signature: ```typescript -constructor(id: string | undefined, { savedObjectsClient, apiClient, patternCache, fieldFormats, onNotification, onError, shortDotsEnable, metaFields, }: IndexPatternDeps); +constructor(id: string | undefined, { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, shortDotsEnable, metaFields, }: IndexPatternDeps); ``` ## Parameters @@ -17,5 +17,5 @@ constructor(id: string | undefined, { savedObjectsClient, apiClient, patternCach | Parameter | Type | Description | | --- | --- | --- | | id | string | undefined | | -| { savedObjectsClient, apiClient, patternCache, fieldFormats, onNotification, onError, shortDotsEnable, metaFields, } | IndexPatternDeps | | +| { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, shortDotsEnable, metaFields, } | IndexPatternDeps | | 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 4c53af3f8970e..87ce1e258712a 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 @@ -14,7 +14,7 @@ export declare class IndexPattern implements IIndexPattern | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(id, { savedObjectsClient, apiClient, patternCache, fieldFormats, onNotification, onError, shortDotsEnable, metaFields, })](./kibana-plugin-plugins-data-public.indexpattern._constructor_.md) | | Constructs a new instance of the IndexPattern class | +| [(constructor)(id, { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, shortDotsEnable, metaFields, })](./kibana-plugin-plugins-data-public.indexpattern._constructor_.md) | | Constructs a new instance of the IndexPattern class | ## Properties @@ -29,11 +29,13 @@ export declare class IndexPattern implements IIndexPattern | [id](./kibana-plugin-plugins-data-public.indexpattern.id.md) | | string | | | [intervalName](./kibana-plugin-plugins-data-public.indexpattern.intervalname.md) | | string | undefined | | | [metaFields](./kibana-plugin-plugins-data-public.indexpattern.metafields.md) | | string[] | | +| [originalBody](./kibana-plugin-plugins-data-public.indexpattern.originalbody.md) | | {
[key: string]: any;
} | | | [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 | | | [type](./kibana-plugin-plugins-data-public.indexpattern.type.md) | | string | undefined | | | [typeMeta](./kibana-plugin-plugins-data-public.indexpattern.typemeta.md) | | TypeMeta | | +| [version](./kibana-plugin-plugins-data-public.indexpattern.version.md) | | string | undefined | | ## Methods @@ -60,6 +62,5 @@ export declare class IndexPattern implements IIndexPattern | [prepBody()](./kibana-plugin-plugins-data-public.indexpattern.prepbody.md) | | | | [refreshFields()](./kibana-plugin-plugins-data-public.indexpattern.refreshfields.md) | | | | [removeScriptedField(fieldName)](./kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md) | | | -| [save(saveAttempts)](./kibana-plugin-plugins-data-public.indexpattern.save.md) | | | | [toSpec()](./kibana-plugin-plugins-data-public.indexpattern.tospec.md) | | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.originalbody.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.originalbody.md new file mode 100644 index 0000000000000..4bc3c76afbae9 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.originalbody.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [originalBody](./kibana-plugin-plugins-data-public.indexpattern.originalbody.md) + +## IndexPattern.originalBody property + +Signature: + +```typescript +originalBody: { + [key: string]: any; + }; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md index 42c6dd72b8c4e..e902d9c42b082 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md @@ -7,7 +7,7 @@ Signature: ```typescript -removeScriptedField(fieldName: string): Promise; +removeScriptedField(fieldName: string): void; ``` ## Parameters @@ -18,5 +18,5 @@ removeScriptedField(fieldName: string): Promise; Returns: -`Promise` +`void` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.save.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.save.md deleted file mode 100644 index d0b471cc2bc21..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.save.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [save](./kibana-plugin-plugins-data-public.indexpattern.save.md) - -## IndexPattern.save() method - -Signature: - -```typescript -save(saveAttempts?: number): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| saveAttempts | number | | - -Returns: - -`Promise` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.version.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.version.md new file mode 100644 index 0000000000000..99d3bc4e7a04d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.version.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) > [version](./kibana-plugin-plugins-data-public.indexpattern.version.md) + +## IndexPattern.version property + +Signature: + +```typescript +version: string | undefined; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md index 9f3ed8c1263ba..3dbfd9430e913 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md @@ -7,5 +7,5 @@ Signature: ```typescript -QueryStringInput: React.FC> +QueryStringInput: React.FC> ``` diff --git a/src/fixtures/stubbed_saved_object_index_pattern.ts b/src/fixtures/stubbed_saved_object_index_pattern.ts index 02e6cb85e341f..44b391f14cf9c 100644 --- a/src/fixtures/stubbed_saved_object_index_pattern.ts +++ b/src/fixtures/stubbed_saved_object_index_pattern.ts @@ -30,6 +30,7 @@ export function stubbedSavedObjectIndexPattern(id: string | null = null) { timeFieldName: 'timestamp', customFormats: '{}', fields: mockLogstashFields, + title: 'title', }, version: 2, }; 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 a0c380ec55bf6..1871627da76de 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 @@ -631,7 +631,7 @@ Object { "id": "test-pattern", "sourceFilters": undefined, "timeFieldName": "timestamp", - "title": "test-pattern", + "title": "title", "typeMeta": undefined, "version": 2, } 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 f037a71b508a2..f49897c47d562 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 @@ -17,7 +17,7 @@ * under the License. */ -import { defaults, map, last, get } from 'lodash'; +import { defaults, map, last } from 'lodash'; import { IndexPattern } from './index_pattern'; @@ -29,7 +29,7 @@ import { stubbedSavedObjectIndexPattern } from '../../../../../fixtures/stubbed_ import { IndexPatternField } from '../fields'; import { fieldFormatsMock } from '../../field_formats/mocks'; -import { FieldFormat } from '../..'; +import { FieldFormat, IndexPatternsService } from '../..'; class MockFieldFormatter {} @@ -116,6 +116,7 @@ function create(id: string, payload?: any): Promise { apiClient, patternCache, fieldFormats: fieldFormatsMock, + indexPatternsService: {} as IndexPatternsService, onNotification: () => {}, onError: () => {}, shortDotsEnable: false, @@ -151,7 +152,6 @@ describe('IndexPattern', () => { expect(indexPattern).toHaveProperty('getNonScriptedFields'); expect(indexPattern).toHaveProperty('addScriptedField'); expect(indexPattern).toHaveProperty('removeScriptedField'); - expect(indexPattern).toHaveProperty('save'); // properties expect(indexPattern).toHaveProperty('fields'); @@ -389,60 +389,4 @@ describe('IndexPattern', () => { }); }); }); - - test('should handle version conflicts', async () => { - setDocsourcePayload(null, { - id: 'foo', - version: 'foo', - attributes: { - title: 'something', - }, - }); - // Create a normal index pattern - const pattern = new IndexPattern('foo', { - savedObjectsClient: savedObjectsClient as any, - apiClient, - patternCache, - fieldFormats: fieldFormatsMock, - onNotification: () => {}, - onError: () => {}, - shortDotsEnable: false, - metaFields: [], - }); - await pattern.init(); - - expect(get(pattern, 'version')).toBe('fooa'); - - // Create the same one - we're going to handle concurrency - const samePattern = new IndexPattern('foo', { - savedObjectsClient: savedObjectsClient as any, - apiClient, - patternCache, - fieldFormats: fieldFormatsMock, - onNotification: () => {}, - onError: () => {}, - shortDotsEnable: false, - metaFields: [], - }); - await samePattern.init(); - - expect(get(samePattern, 'version')).toBe('fooaa'); - - // This will conflict because samePattern did a save (from refreshFields) - // but the resave should work fine - pattern.title = 'foo2'; - await pattern.save(); - - // This should not be able to recover - samePattern.title = 'foo3'; - - let result; - try { - await samePattern.save(); - } catch (err) { - result = err; - } - - expect(result.res.status).toBe(409); - }); }); 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 0558808573580..76f1a5e59d0ee 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 @@ -41,8 +41,8 @@ import { PatternCache } from './_pattern_cache'; import { expandShorthand, FieldMappingSpec, MappingObject } from '../../field_mapping'; import { IndexPatternSpec, TypeMeta, FieldSpec, SourceFilter } from '../types'; import { SerializedFieldFormat } from '../../../../expressions/common'; +import { IndexPatternsService } from '..'; -const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; const savedObjectType = 'index-pattern'; interface IndexPatternDeps { @@ -50,6 +50,7 @@ interface IndexPatternDeps { apiClient: IIndexPatternsApiClient; patternCache: PatternCache; fieldFormats: FieldFormatsStartCommon; + indexPatternsService: IndexPatternsService; onNotification: OnNotification; onError: OnError; shortDotsEnable: boolean; @@ -70,17 +71,18 @@ export class IndexPattern implements IIndexPattern { public flattenHit: any; public metaFields: string[]; - private version: string | undefined; + public version: string | undefined; private savedObjectsClient: SavedObjectsClientCommon; private patternCache: PatternCache; public sourceFilters?: SourceFilter[]; - private originalBody: { [key: string]: any } = {}; + // todo make read only, update via method or factor out + public originalBody: { [key: string]: any } = {}; public fieldsFetcher: any; // probably want to factor out any direct usage and change to private + private indexPatternsService: IndexPatternsService; private shortDotsEnable: boolean = false; private fieldFormats: FieldFormatsStartCommon; private onNotification: OnNotification; private onError: OnError; - private apiClient: IIndexPatternsApiClient; private mapping: MappingObject = expandShorthand({ title: ES_FIELD_TYPES.TEXT, @@ -111,6 +113,7 @@ export class IndexPattern implements IIndexPattern { apiClient, patternCache, fieldFormats, + indexPatternsService, onNotification, onError, shortDotsEnable = false, @@ -121,6 +124,7 @@ export class IndexPattern implements IIndexPattern { this.savedObjectsClient = savedObjectsClient; this.patternCache = patternCache; this.fieldFormats = fieldFormats; + this.indexPatternsService = indexPatternsService; this.onNotification = onNotification; this.onError = onError; @@ -128,7 +132,6 @@ export class IndexPattern implements IIndexPattern { this.metaFields = metaFields; this.fields = fieldList([], this.shortDotsEnable); - this.apiClient = apiClient; this.fieldsFetcher = createFieldsFetcher(this, apiClient, metaFields); this.flattenHit = flattenHitWrapper(this, metaFields); this.formatHit = formatHitProvider( @@ -392,8 +395,6 @@ export class IndexPattern implements IIndexPattern { } else { throw err; } - - await this.save(); } } @@ -402,7 +403,6 @@ export class IndexPattern implements IIndexPattern { if (field) { this.fields.remove(field); } - return this.save(); } async popularizeField(fieldName: string, unit = 1) { @@ -523,92 +523,6 @@ export class IndexPattern implements IIndexPattern { return await _create(potentialDuplicateByTitle.id); } - async save(saveAttempts: number = 0): Promise { - if (!this.id) return; - const body = this.prepBody(); - - const originalChangedKeys: string[] = []; - Object.entries(body).forEach(([key, value]) => { - if (value !== this.originalBody[key]) { - originalChangedKeys.push(key); - } - }); - - return this.savedObjectsClient - .update(savedObjectType, this.id, body, { version: this.version }) - .then((resp) => { - this.id = resp.id; - this.version = resp.version; - }) - .catch((err) => { - if ( - _.get(err, 'res.status') === 409 && - saveAttempts++ < MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS - ) { - const samePattern = new IndexPattern(this.id, { - savedObjectsClient: this.savedObjectsClient, - apiClient: this.apiClient, - patternCache: this.patternCache, - fieldFormats: this.fieldFormats, - onNotification: this.onNotification, - onError: this.onError, - shortDotsEnable: this.shortDotsEnable, - metaFields: this.metaFields, - }); - - return samePattern.init().then(() => { - // What keys changed from now and what the server returned - const updatedBody = samePattern.prepBody(); - - // Build a list of changed keys from the server response - // and ensure we ignore the key if the server response - // is the same as the original response (since that is expected - // if we made a change in that key) - - const serverChangedKeys: string[] = []; - Object.entries(updatedBody).forEach(([key, value]) => { - if (value !== (body as any)[key] && value !== this.originalBody[key]) { - serverChangedKeys.push(key); - } - }); - - let unresolvedCollision = false; - for (const originalKey of originalChangedKeys) { - for (const serverKey of serverChangedKeys) { - if (originalKey === serverKey) { - unresolvedCollision = true; - break; - } - } - } - - if (unresolvedCollision) { - const title = i18n.translate('data.indexPatterns.unableWriteLabel', { - defaultMessage: - 'Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.', - }); - - this.onNotification({ title, color: 'danger' }); - throw err; - } - - // Set the updated response on this object - serverChangedKeys.forEach((key) => { - (this as any)[key] = (samePattern as any)[key]; - }); - this.version = samePattern.version; - - // Clear cache - this.patternCache.clear(this.id!); - - // Try the save again - return this.save(saveAttempts); - }); - } - throw err; - }); - } - async _fetchFields() { const fields = await this.fieldsFetcher.fetch(this); const scripted = this.getScriptedFields().map((field) => field.spec); @@ -624,30 +538,37 @@ export class IndexPattern implements IIndexPattern { } refreshFields() { - return this._fetchFields() - .then(() => this.save()) - .catch((err) => { - // https://github.com/elastic/kibana/issues/9224 - // This call will attempt to remap fields from the matching - // ES index which may not actually exist. In that scenario, - // we still want to notify the user that there is a problem - // but we do not want to potentially make any pages unusable - // so do not rethrow the error here - - if (err instanceof IndexPatternMissingIndices) { - this.onNotification({ title: (err as any).message, color: 'danger', iconType: 'alert' }); - return []; - } + return ( + this._fetchFields() + // todo + .then(() => this.indexPatternsService.save(this)) + .catch((err) => { + // https://github.com/elastic/kibana/issues/9224 + // This call will attempt to remap fields from the matching + // ES index which may not actually exist. In that scenario, + // we still want to notify the user that there is a problem + // but we do not want to potentially make any pages unusable + // so do not rethrow the error here + + if (err instanceof IndexPatternMissingIndices) { + this.onNotification({ + title: (err as any).message, + color: 'danger', + iconType: 'alert', + }); + return []; + } - this.onError(err, { - title: i18n.translate('data.indexPatterns.fetchFieldErrorTitle', { - defaultMessage: 'Error fetching fields for index pattern {title} (ID: {id})', - values: { - id: this.id, - title: this.title, - }, - }), - }); - }); + this.onError(err, { + title: i18n.translate('data.indexPatterns.fetchFieldErrorTitle', { + defaultMessage: 'Error fetching fields for index pattern {title} (ID: {id})', + values: { + id: this.id, + title: this.title, + }, + }), + }); + }) + ); } } diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts index 8223b31042124..c79c7900148ea 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts @@ -17,28 +17,26 @@ * under the License. */ -import { IndexPatternsService } from './index_patterns'; +import { defaults } from 'lodash'; +import { IndexPatternsService } from '.'; import { fieldFormatsMock } from '../../field_formats/mocks'; -import { - UiSettingsCommon, - IIndexPatternsApiClient, - SavedObjectsClientCommon, - SavedObject, -} from '../types'; +import { stubbedSavedObjectIndexPattern } from '../../../../../fixtures/stubbed_saved_object_index_pattern'; +import { UiSettingsCommon, SavedObjectsClientCommon, SavedObject } from '../types'; + +const createFieldsFetcher = jest.fn().mockImplementation(() => ({ + getFieldsForWildcard: jest.fn().mockImplementation(() => { + return new Promise((resolve) => resolve([])); + }), + every: jest.fn(), +})); const fieldFormats = fieldFormatsMock; -jest.mock('./index_pattern', () => { - class IndexPattern { - init = async () => { - return this; - }; - } +let object: any = {}; - return { - IndexPattern, - }; -}); +function setDocsourcePayload(id: string | null, providedPayload: any) { + object = defaults(providedPayload || {}, stubbedSavedObjectIndexPattern(id)); +} describe('IndexPatterns', () => { let indexPatterns: IndexPatternsService; @@ -53,6 +51,25 @@ describe('IndexPatterns', () => { > ); savedObjectsClient.delete = jest.fn(() => Promise.resolve({}) as Promise); + savedObjectsClient.get = jest.fn().mockImplementation(() => object); + savedObjectsClient.create = jest.fn(); + savedObjectsClient.update = jest + .fn() + .mockImplementation(async (type, id, body, { version }) => { + if (object.version !== version) { + throw new Object({ + res: { + status: 409, + }, + }); + } + object.attributes.title = body.title; + object.version += 'a'; + return { + id: object.id, + version: object.version, + }; + }); indexPatterns = new IndexPatternsService({ uiSettings: ({ @@ -60,7 +77,7 @@ describe('IndexPatterns', () => { getAll: () => {}, } as any) as UiSettingsCommon, savedObjectsClient: (savedObjectsClient as unknown) as SavedObjectsClientCommon, - apiClient: {} as IIndexPatternsApiClient, + apiClient: createFieldsFetcher(), fieldFormats, onNotification: () => {}, onError: () => {}, @@ -70,6 +87,14 @@ describe('IndexPatterns', () => { test('does cache gets for the same id', async () => { const id = '1'; + setDocsourcePayload(id, { + id: 'foo', + version: 'foo', + attributes: { + title: 'something', + }, + }); + const indexPattern = await indexPatterns.get(id); expect(indexPattern).toBeDefined(); @@ -107,4 +132,41 @@ describe('IndexPatterns', () => { await indexPatterns.delete(id); expect(indexPattern).not.toBe(await indexPatterns.get(id)); }); + + test('should handle version conflicts', async () => { + setDocsourcePayload(null, { + id: 'foo', + version: 'foo', + attributes: { + title: 'something', + }, + }); + + // Create a normal index patterns + const pattern = await indexPatterns.make('foo'); + + expect(pattern.version).toBe('fooa'); + + // Create the same one - we're going to handle concurrency + const samePattern = await indexPatterns.make('foo'); + + expect(samePattern.version).toBe('fooaa'); + + // This will conflict because samePattern did a save (from refreshFields) + // but the resave should work fine + pattern.title = 'foo2'; + await indexPatterns.save(pattern); + + // This should not be able to recover + samePattern.title = 'foo3'; + + let result; + try { + await indexPatterns.save(samePattern); + } catch (err) { + result = err; + } + + expect(result.res.status).toBe(409); + }); }); 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 fe0d14b2d9c19..88a7e9f6cef4c 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 @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { SavedObjectsClientCommon } from '../..'; import { createIndexPatternCache } from '.'; @@ -37,6 +38,8 @@ import { FieldFormatsStartCommon } from '../../field_formats'; import { UI_SETTINGS, SavedObject } from '../../../common'; const indexPatternCache = createIndexPatternCache(); +const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; +const savedObjectType = 'index-pattern'; type IndexPatternCachedFieldType = 'id' | 'title'; @@ -181,6 +184,7 @@ export class IndexPatternsService { apiClient: this.apiClient, patternCache: indexPatternCache, fieldFormats: this.fieldFormats, + indexPatternsService: this, onNotification: this.onNotification, onError: this.onError, shortDotsEnable, @@ -191,6 +195,93 @@ export class IndexPatternsService { return indexPattern; } + async save(indexPattern: IndexPattern, saveAttempts: number = 0): Promise { + if (!indexPattern.id) return; + const shortDotsEnable = await this.config.get(UI_SETTINGS.SHORT_DOTS_ENABLE); + const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); + + const body = indexPattern.prepBody(); + + const originalChangedKeys: string[] = []; + Object.entries(body).forEach(([key, value]) => { + if (value !== indexPattern.originalBody[key]) { + originalChangedKeys.push(key); + } + }); + + return this.savedObjectsClient + .update(savedObjectType, indexPattern.id, body, { version: indexPattern.version }) + .then((resp) => { + indexPattern.id = resp.id; + indexPattern.version = resp.version; + }) + .catch((err) => { + if (err?.res?.status === 409 && saveAttempts++ < MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS) { + const samePattern = new IndexPattern(indexPattern.id, { + savedObjectsClient: this.savedObjectsClient, + apiClient: this.apiClient, + patternCache: indexPatternCache, + fieldFormats: this.fieldFormats, + indexPatternsService: this, + onNotification: this.onNotification, + onError: this.onError, + shortDotsEnable, + metaFields, + }); + + return samePattern.init().then(() => { + // What keys changed from now and what the server returned + const updatedBody = samePattern.prepBody(); + + // Build a list of changed keys from the server response + // and ensure we ignore the key if the server response + // is the same as the original response (since that is expected + // if we made a change in that key) + + const serverChangedKeys: string[] = []; + Object.entries(updatedBody).forEach(([key, value]) => { + if (value !== (body as any)[key] && value !== indexPattern.originalBody[key]) { + serverChangedKeys.push(key); + } + }); + + let unresolvedCollision = false; + for (const originalKey of originalChangedKeys) { + for (const serverKey of serverChangedKeys) { + if (originalKey === serverKey) { + unresolvedCollision = true; + break; + } + } + } + + if (unresolvedCollision) { + const title = i18n.translate('data.indexPatterns.unableWriteLabel', { + defaultMessage: + 'Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.', + }); + + this.onNotification({ title, color: 'danger' }); + throw err; + } + + // Set the updated response on this object + serverChangedKeys.forEach((key) => { + (indexPattern as any)[key] = (samePattern as any)[key]; + }); + indexPattern.version = samePattern.version; + + // Clear cache + indexPatternCache.clear(indexPattern.id!); + + // Try the save again + return this.save(indexPattern, saveAttempts); + }); + } + throw err; + }); + } + async make(id?: string): Promise { const shortDotsEnable = await this.config.get(UI_SETTINGS.SHORT_DOTS_ENABLE); const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); @@ -200,6 +291,7 @@ export class IndexPatternsService { apiClient: this.apiClient, patternCache: indexPatternCache, fieldFormats: this.fieldFormats, + indexPatternsService: this, onNotification: this.onNotification, onError: this.onError, shortDotsEnable, diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index c2cc2fdc3c134..a3de45189a85d 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -912,7 +912,7 @@ export type IMetricAggType = MetricAggType; // @public (undocumented) export class IndexPattern implements IIndexPattern { // Warning: (ae-forgotten-export) The symbol "IndexPatternDeps" needs to be exported by the entry point index.d.ts - constructor(id: string | undefined, { savedObjectsClient, apiClient, patternCache, fieldFormats, onNotification, onError, shortDotsEnable, metaFields, }: IndexPatternDeps); + constructor(id: string | undefined, { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, shortDotsEnable, metaFields, }: IndexPatternDeps); // (undocumented) addScriptedField(name: string, script: string, fieldType: string | undefined, lang: string): Promise; // (undocumented) @@ -986,6 +986,10 @@ export class IndexPattern implements IIndexPattern { // (undocumented) metaFields: string[]; // (undocumented) + originalBody: { + [key: string]: any; + }; + // (undocumented) popularizeField(fieldName: string, unit?: number): Promise; // (undocumented) prepBody(): { @@ -1001,9 +1005,7 @@ export class IndexPattern implements IIndexPattern { // (undocumented) refreshFields(): Promise; // (undocumented) - removeScriptedField(fieldName: string): Promise; - // (undocumented) - save(saveAttempts?: number): Promise; + removeScriptedField(fieldName: string): void; // Warning: (ae-forgotten-export) The symbol "SourceFilter" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -1018,7 +1020,9 @@ export class IndexPattern implements IIndexPattern { type: string | undefined; // (undocumented) typeMeta?: IndexPatternTypeMeta; - } + // (undocumented) + version: string | undefined; +} // Warning: (ae-missing-release-tag) "AggregationRestrictions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // 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 22bc78ee0538e..13be9ca6c9c25 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 @@ -44,7 +44,7 @@ const newFieldPlaceholder = i18n.translate( export const CreateEditField = withRouter( ({ indexPattern, mode, fieldName, history }: CreateEditFieldProps) => { - const { uiSettings, chrome, notifications } = useKibana< + const { uiSettings, chrome, notifications, data } = useKibana< IndexPatternManagmentContext >().services; const spec = @@ -96,6 +96,7 @@ export const CreateEditField = withRouter( indexPattern={indexPattern} spec={spec} services={{ + saveIndexPattern: data.indexPatterns.save.bind(data.indexPatterns), redirectAway, }} /> diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx index a0eecef66ff93..d09836019b0bc 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx @@ -234,7 +234,13 @@ export const EditIndexPattern = withRouter( )} - + ); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx index ed50317aed6a0..84469a7e1fbd9 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { ScriptedFieldsTable } from '../scripted_fields_table'; -import { IIndexPattern } from '../../../../../../plugins/data/common/index_patterns'; +import { IIndexPattern, IndexPattern } from '../../../../../../plugins/data/common/index_patterns'; jest.mock('@elastic/eui', () => ({ EuiTitle: 'eui-title', @@ -54,7 +54,7 @@ const helpers = { const getIndexPatternMock = (mockedFields: any = {}) => ({ ...mockedFields } as IIndexPattern); describe('ScriptedFieldsTable', () => { - let indexPattern: IIndexPattern; + let indexPattern: IndexPattern; beforeEach(() => { indexPattern = getIndexPatternMock({ @@ -62,7 +62,7 @@ describe('ScriptedFieldsTable', () => { { name: 'ScriptedField', lang: 'painless', script: 'x++' }, { name: 'JustATest', lang: 'painless', script: 'z++' }, ], - }); + }) as IndexPattern; }); test('should render normally', async () => { @@ -71,6 +71,7 @@ describe('ScriptedFieldsTable', () => { indexPattern={indexPattern} helpers={helpers} painlessDocLink={'painlessDoc'} + saveIndexPattern={async () => {}} /> ); @@ -88,6 +89,7 @@ describe('ScriptedFieldsTable', () => { indexPattern={indexPattern} helpers={helpers} painlessDocLink={'painlessDoc'} + saveIndexPattern={async () => {}} /> ); @@ -105,15 +107,18 @@ describe('ScriptedFieldsTable', () => { test('should filter based on the lang filter', async () => { const component = shallow( [ - { name: 'ScriptedField', lang: 'painless', script: 'x++' }, - { name: 'JustATest', lang: 'painless', script: 'z++' }, - { name: 'Bad', lang: 'somethingElse', script: 'z++' }, - ], - })} + indexPattern={ + getIndexPatternMock({ + getScriptedFields: () => [ + { name: 'ScriptedField', lang: 'painless', script: 'x++' }, + { name: 'JustATest', lang: 'painless', script: 'z++' }, + { name: 'Bad', lang: 'somethingElse', script: 'z++' }, + ], + }) as IndexPattern + } painlessDocLink={'painlessDoc'} helpers={helpers} + saveIndexPattern={async () => {}} /> ); @@ -131,11 +136,14 @@ describe('ScriptedFieldsTable', () => { test('should hide the table if there are no scripted fields', async () => { const component = shallow( [], - })} + indexPattern={ + getIndexPatternMock({ + getScriptedFields: () => [], + }) as IndexPattern + } painlessDocLink={'painlessDoc'} helpers={helpers} + saveIndexPattern={async () => {}} /> ); @@ -153,6 +161,7 @@ describe('ScriptedFieldsTable', () => { indexPattern={indexPattern} helpers={helpers} painlessDocLink={'painlessDoc'} + saveIndexPattern={async () => {}} /> ); @@ -168,12 +177,15 @@ describe('ScriptedFieldsTable', () => { const removeScriptedField = jest.fn(); const component = shallow( {}} /> ); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx index 532af2757915b..08cc90faf75fa 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx @@ -27,10 +27,10 @@ import { import { Table, Header, CallOuts, DeleteScritpedFieldConfirmationModal } from './components'; import { ScriptedFieldItem } from './types'; -import { IIndexPattern } from '../../../../../../plugins/data/public'; +import { IndexPattern, DataPublicPluginStart } from '../../../../../../plugins/data/public'; interface ScriptedFieldsTableProps { - indexPattern: IIndexPattern; + indexPattern: IndexPattern; fieldFilter?: string; scriptedFieldLanguageFilter?: string; helpers: { @@ -39,6 +39,7 @@ interface ScriptedFieldsTableProps { }; onRemoveField?: () => void; painlessDocLink: string; + saveIndexPattern: DataPublicPluginStart['indexPatterns']['save']; } interface ScriptedFieldsTableState { @@ -68,7 +69,7 @@ export class ScriptedFieldsTable extends Component< } fetchFields = async () => { - const fields = await this.props.indexPattern.getScriptedFields(); + const fields = await (this.props.indexPattern.getScriptedFields() as ScriptedFieldItem[]); const deprecatedLangsInUse = []; const deprecatedLangs = getDeprecatedScriptingLanguages(); @@ -121,10 +122,11 @@ export class ScriptedFieldsTable extends Component< }; deleteField = () => { - const { indexPattern, onRemoveField } = this.props; + const { indexPattern, onRemoveField, saveIndexPattern } = this.props; const { fieldToDelete } = this.state; - indexPattern.removeScriptedField(fieldToDelete); + indexPattern.removeScriptedField(fieldToDelete!.name); + saveIndexPattern(indexPattern); if (onRemoveField) { onRemoveField(); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/__snapshots__/source_filters_table.test.tsx.snap b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/__snapshots__/source_filters_table.test.tsx.snap index a7b73624c4665..6a2b208c47987 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/__snapshots__/source_filters_table.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/__snapshots__/source_filters_table.test.tsx.snap @@ -14,17 +14,6 @@ exports[`SourceFiltersTable should add a filter 1`] = ` fieldWildcardMatcher={[Function]} indexPattern={ Object { - "save": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, "sourceFilters": Array [ Object { "value": "tim*", @@ -108,17 +97,6 @@ exports[`SourceFiltersTable should remove a filter 1`] = ` fieldWildcardMatcher={[Function]} indexPattern={ Object { - "save": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, "sourceFilters": Array [ Object { "clientId": 2, @@ -279,17 +257,6 @@ exports[`SourceFiltersTable should update a filter 1`] = ` fieldWildcardMatcher={[Function]} indexPattern={ Object { - "save": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, "sourceFilters": Array [ Object { "clientId": 1, diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.test.tsx index fa048af7c7a70..395e1f3744e94 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { SourceFiltersTable } from './source_filters_table'; -import { IIndexPattern } from 'src/plugins/data/public'; +import { IndexPattern } from 'src/plugins/data/public'; jest.mock('@elastic/eui', () => ({ EuiButton: 'eui-button', @@ -52,7 +52,7 @@ const getIndexPatternMock = (mockedFields: any = {}) => ({ sourceFilters: [{ value: 'time*' }, { value: 'nam*' }, { value: 'age*' }], ...mockedFields, - } as IIndexPattern); + } as IndexPattern); describe('SourceFiltersTable', () => { test('should render normally', () => { @@ -61,6 +61,7 @@ describe('SourceFiltersTable', () => { indexPattern={getIndexPatternMock()} fieldWildcardMatcher={() => {}} filterFilter={''} + saveIndexPattern={async () => {}} /> ); @@ -73,6 +74,7 @@ describe('SourceFiltersTable', () => { indexPattern={getIndexPatternMock()} fieldWildcardMatcher={() => {}} filterFilter={''} + saveIndexPattern={async () => {}} /> ); @@ -88,6 +90,7 @@ describe('SourceFiltersTable', () => { })} filterFilter={''} fieldWildcardMatcher={() => {}} + saveIndexPattern={async () => {}} /> ); @@ -98,11 +101,14 @@ describe('SourceFiltersTable', () => { test('should show a delete modal', () => { const component = shallow( {}} + saveIndexPattern={async () => {}} /> ); @@ -112,15 +118,17 @@ describe('SourceFiltersTable', () => { }); test('should remove a filter', async () => { - const save = jest.fn(); + const saveIndexPattern = jest.fn(async () => {}); const component = shallow( {}} + saveIndexPattern={saveIndexPattern} /> ); @@ -129,47 +137,49 @@ describe('SourceFiltersTable', () => { await component.instance().deleteFilter(); component.update(); // We are not calling `.setState` directly so we need to re-render - expect(save).toBeCalled(); + expect(saveIndexPattern).toBeCalled(); expect(component).toMatchSnapshot(); }); test('should add a filter', async () => { - const save = jest.fn(); + const saveIndexPattern = jest.fn(async () => {}); const component = shallow( {}} + saveIndexPattern={saveIndexPattern} /> ); await component.instance().onAddFilter('na*'); component.update(); // We are not calling `.setState` directly so we need to re-render - expect(save).toBeCalled(); + expect(saveIndexPattern).toBeCalled(); expect(component).toMatchSnapshot(); }); test('should update a filter', async () => { - const save = jest.fn(); + const saveIndexPattern = jest.fn(async () => {}); const component = shallow( {}} + saveIndexPattern={saveIndexPattern} /> ); await component.instance().saveFilter({ clientId: 'tim*', value: 'ti*' }); component.update(); // We are not calling `.setState` directly so we need to re-render - expect(save).toBeCalled(); + expect(saveIndexPattern).toBeCalled(); expect(component).toMatchSnapshot(); }); }); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx index e5c753886ea9f..b00648f124716 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx @@ -22,14 +22,15 @@ import { createSelector } from 'reselect'; import { EuiSpacer } from '@elastic/eui'; import { AddFilter, Table, Header, DeleteFilterConfirmationModal } from './components'; -import { IIndexPattern } from '../../../../../../plugins/data/public'; +import { IndexPattern, DataPublicPluginStart } from '../../../../../../plugins/data/public'; import { SourceFiltersTableFilter } from './types'; export interface SourceFiltersTableProps { - indexPattern: IIndexPattern; + indexPattern: IndexPattern; filterFilter: string; fieldWildcardMatcher: Function; onAddOrRemoveFilter?: Function; + saveIndexPattern: DataPublicPluginStart['indexPatterns']['save']; } export interface SourceFiltersTableState { @@ -104,7 +105,7 @@ export class SourceFiltersTable extends Component< }; deleteFilter = async () => { - const { indexPattern, onAddOrRemoveFilter } = this.props; + const { indexPattern, onAddOrRemoveFilter, saveIndexPattern } = this.props; const { filterToDelete, filters } = this.state; indexPattern.sourceFilters = filters.filter((filter) => { @@ -112,7 +113,7 @@ export class SourceFiltersTable extends Component< }); this.setState({ isSaving: true }); - await indexPattern.save(); + await saveIndexPattern(indexPattern); if (onAddOrRemoveFilter) { onAddOrRemoveFilter(); @@ -124,12 +125,12 @@ export class SourceFiltersTable extends Component< }; onAddFilter = async (value: string) => { - const { indexPattern, onAddOrRemoveFilter } = this.props; + const { indexPattern, onAddOrRemoveFilter, saveIndexPattern } = this.props; indexPattern.sourceFilters = [...(indexPattern.sourceFilters || []), { value }]; this.setState({ isSaving: true }); - await indexPattern.save(); + await saveIndexPattern(indexPattern); if (onAddOrRemoveFilter) { onAddOrRemoveFilter(); @@ -140,7 +141,7 @@ export class SourceFiltersTable extends Component< }; saveFilter = async ({ clientId, value }: SourceFiltersTableFilter) => { - const { indexPattern } = this.props; + const { indexPattern, saveIndexPattern } = this.props; const { filters } = this.state; indexPattern.sourceFilters = filters.map((filter) => { @@ -155,7 +156,7 @@ export class SourceFiltersTable extends Component< }); this.setState({ isSaving: true }); - await indexPattern.save(); + await saveIndexPattern(indexPattern); this.updateFilters(); this.setState({ isSaving: false }); }; diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx index 3bc9cd34f2984..101399ef02b73 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx @@ -35,6 +35,7 @@ import { IndexPattern, IndexPatternField, UI_SETTINGS, + DataPublicPluginStart, } from '../../../../../../plugins/data/public'; import { useKibana } from '../../../../../../plugins/kibana_react/public'; import { IndexPatternManagmentContext } from '../../../types'; @@ -48,6 +49,7 @@ import { getTabs, getPath, convertToEuiSelectOption } from './utils'; interface TabsProps extends Pick { indexPattern: IndexPattern; fields: IndexPatternField[]; + saveIndexPattern: DataPublicPluginStart['indexPatterns']['save']; } const searchAriaLabel = i18n.translate( @@ -71,7 +73,7 @@ const filterPlaceholder = i18n.translate( } ); -export function Tabs({ indexPattern, fields, history, location }: TabsProps) { +export function Tabs({ indexPattern, saveIndexPattern, fields, history, location }: TabsProps) { const { uiSettings, indexPatternManagementStart, docLinks } = useKibana< IndexPatternManagmentContext >().services; @@ -191,6 +193,7 @@ export function Tabs({ indexPattern, fields, history, location }: TabsProps) { {}, saveIndexPattern: async () => {} }; + describe('FieldEditor', () => { let indexPattern: IndexPattern; @@ -122,7 +124,7 @@ describe('FieldEditor', () => { { indexPattern, spec: (field as unknown) as IndexPatternField, - services: { redirectAway: () => {} }, + services, }, mockContext ); @@ -151,7 +153,7 @@ describe('FieldEditor', () => { { indexPattern, spec: (testField as unknown) as IndexPatternField, - services: { redirectAway: () => {} }, + services, }, mockContext ); @@ -181,7 +183,7 @@ describe('FieldEditor', () => { { indexPattern, spec: (testField as unknown) as IndexPatternField, - services: { redirectAway: () => {} }, + services, }, mockContext ); @@ -198,7 +200,7 @@ describe('FieldEditor', () => { { indexPattern, spec: (testField as unknown) as IndexPatternField, - services: { redirectAway: () => {} }, + services, }, mockContext ); @@ -223,7 +225,7 @@ describe('FieldEditor', () => { { indexPattern, spec: (testField as unknown) as IndexPatternField, - services: { redirectAway: () => {} }, + services, }, mockContext ); 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 6a3f632a9582e..4857a402cc4b2 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,6 +133,7 @@ export interface FieldEdiorProps { spec: IndexPatternField['spec']; services: { redirectAway: () => void; + saveIndexPattern: DataPublicPluginStart['indexPatterns']['save']; }; } @@ -757,23 +758,18 @@ export class FieldEditor extends PureComponent { - const { redirectAway } = this.props.services; + const { redirectAway, saveIndexPattern } = this.props.services; const { indexPattern } = this.props; const { spec } = this.state; - const remove = indexPattern.removeScriptedField(spec.name); - - if (remove) { - remove.then(() => { - const message = i18n.translate('indexPatternManagement.deleteField.deletedHeader', { - defaultMessage: "Deleted '{fieldName}'", - values: { fieldName: spec.name }, - }); - this.context.services.notifications.toasts.addSuccess(message); - redirectAway(); + indexPattern.removeScriptedField(spec.name); + saveIndexPattern(indexPattern).then(() => { + const message = i18n.translate('indexPatternManagement.deleteField.deletedHeader', { + defaultMessage: "Deleted '{fieldName}'", + values: { fieldName: spec.name }, }); - } else { + this.context.services.notifications.toasts.addSuccess(message); redirectAway(); - } + }); }; saveField = async () => { @@ -803,7 +799,7 @@ export class FieldEditor extends PureComponent { const message = i18n.translate('indexPatternManagement.deleteField.savedHeader', { defaultMessage: "Saved '{fieldName}'", diff --git a/test/plugin_functional/plugins/index_patterns/server/plugin.ts b/test/plugin_functional/plugins/index_patterns/server/plugin.ts index d6a4fdd67b0a1..1c85f226623cb 100644 --- a/test/plugin_functional/plugins/index_patterns/server/plugin.ts +++ b/test/plugin_functional/plugins/index_patterns/server/plugin.ts @@ -78,7 +78,7 @@ export class IndexPatternsTestPlugin const id = (req.params as Record).id; const service = await data.indexPatterns.indexPatternsServiceFactory(req); const ip = await service.get(id); - await ip.save(); + await service.save(ip); return res.ok(); } );