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 1e86fe8e080a7..3e5cccb780914 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, onUnsupportedTimePattern, shortDotsEnable, metaFields, }: IndexPatternDeps);
+constructor(id: string | undefined, { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, onUnsupportedTimePattern, 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, onUnsupportedTimePattern, shortDotsEnable, metaFields, } | IndexPatternDeps
| |
+| { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, onUnsupportedTimePattern, 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 c14c6af68e3be..6534fc4ae09d9 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, onUnsupportedTimePattern, 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, onUnsupportedTimePattern, 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 | null
| |
| [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
@@ -63,6 +65,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 729b3c2941bb7..3887d131501c1 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: () => {},
onUnsupportedTimePattern: () => {},
@@ -152,7 +153,6 @@ describe('IndexPattern', () => {
expect(indexPattern).toHaveProperty('getNonScriptedFields');
expect(indexPattern).toHaveProperty('addScriptedField');
expect(indexPattern).toHaveProperty('removeScriptedField');
- expect(indexPattern).toHaveProperty('save');
// properties
expect(indexPattern).toHaveProperty('fields');
@@ -390,62 +390,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: () => {},
- onUnsupportedTimePattern: () => {},
- 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: () => {},
- onUnsupportedTimePattern: () => {},
- 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 bdc208cb5866a..2cce8ed69a15d 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
@@ -48,8 +48,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 {
@@ -57,6 +57,7 @@ interface IndexPatternDeps {
apiClient: IIndexPatternsApiClient;
patternCache: PatternCache;
fieldFormats: FieldFormatsStartCommon;
+ indexPatternsService: IndexPatternsService;
onNotification: OnNotification;
onError: OnError;
onUnsupportedTimePattern: OnUnsupportedTimePattern;
@@ -78,18 +79,19 @@ 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 onUnsupportedTimePattern: OnUnsupportedTimePattern;
- private apiClient: IIndexPatternsApiClient;
private mapping: MappingObject = expandShorthand({
title: ES_FIELD_TYPES.TEXT,
@@ -120,6 +122,7 @@ export class IndexPattern implements IIndexPattern {
apiClient,
patternCache,
fieldFormats,
+ indexPatternsService,
onNotification,
onError,
onUnsupportedTimePattern,
@@ -131,6 +134,7 @@ export class IndexPattern implements IIndexPattern {
this.savedObjectsClient = savedObjectsClient;
this.patternCache = patternCache;
this.fieldFormats = fieldFormats;
+ this.indexPatternsService = indexPatternsService;
this.onNotification = onNotification;
this.onError = onError;
this.onUnsupportedTimePattern = onUnsupportedTimePattern;
@@ -138,7 +142,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(
@@ -430,8 +433,6 @@ export class IndexPattern implements IIndexPattern {
} else {
throw err;
}
-
- await this.save();
}
}
@@ -440,7 +441,6 @@ export class IndexPattern implements IIndexPattern {
if (field) {
this.fields.remove(field);
}
- return this.save();
}
async popularizeField(fieldName: string, unit = 1) {
@@ -584,93 +584,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,
- onUnsupportedTimePattern: this.onUnsupportedTimePattern,
- 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);
@@ -686,30 +599,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 2c2ab5b07f9e5..9bc286d5b9a91 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: () => {},
@@ -71,6 +88,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();
@@ -108,4 +133,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 fb4af4bf35555..9f813ec9a8fb3 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 '.';
@@ -38,6 +39,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';
@@ -186,6 +189,7 @@ export class IndexPatternsService {
apiClient: this.apiClient,
patternCache: indexPatternCache,
fieldFormats: this.fieldFormats,
+ indexPatternsService: this,
onNotification: this.onNotification,
onError: this.onError,
onUnsupportedTimePattern: this.onUnsupportedTimePattern,
@@ -197,6 +201,94 @@ 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,
+ onUnsupportedTimePattern: this.onUnsupportedTimePattern,
+ 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);
@@ -206,6 +298,7 @@ export class IndexPatternsService {
apiClient: this.apiClient,
patternCache: indexPatternCache,
fieldFormats: this.fieldFormats,
+ indexPatternsService: this,
onNotification: this.onNotification,
onError: this.onError,
onUnsupportedTimePattern: this.onUnsupportedTimePattern,
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index 94353647da61c..0af6c95679de3 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -914,7 +914,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, onUnsupportedTimePattern, shortDotsEnable, metaFields, }: IndexPatternDeps);
+ constructor(id: string | undefined, { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, onUnsupportedTimePattern, shortDotsEnable, metaFields, }: IndexPatternDeps);
// (undocumented)
addScriptedField(name: string, script: string, fieldType: string | undefined, lang: string): Promise;
// (undocumented)
@@ -994,6 +994,10 @@ export class IndexPattern implements IIndexPattern {
// (undocumented)
migrate(newTitle: string): Promise;
// (undocumented)
+ originalBody: {
+ [key: string]: any;
+ };
+ // (undocumented)
popularizeField(fieldName: string, unit?: number): Promise;
// (undocumented)
prepBody(): {
@@ -1009,9 +1013,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)
@@ -1026,7 +1028,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();
}
);