diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.addscriptedfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.addscriptedfield.md index 812f014b15a6c9..e0ee1f0ec15a4c 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.addscriptedfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.addscriptedfield.md @@ -6,6 +6,7 @@ > Warning: This API is now obsolete. > +> use runtime field instead > Add scripted field to field list diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getnonscriptedfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getnonscriptedfields.md index 1792a979bf7490..94adbefe535dd3 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getnonscriptedfields.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getnonscriptedfields.md @@ -6,6 +6,7 @@ > Warning: This API is now obsolete. > +> use runtime field instead > Signature: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getruntimefield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getruntimefield.md new file mode 100644 index 00000000000000..c0aca53255b8fd --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getruntimefield.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [getRuntimeField](./kibana-plugin-plugins-data-public.indexpattern.getruntimefield.md) + +## IndexPattern.getRuntimeField() method + +Returns runtime field if exists + +Signature: + +```typescript +getRuntimeField(name: string): RuntimeField | null; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | + +Returns: + +`RuntimeField | null` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getscriptedfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getscriptedfields.md index b6b3dcb19bac17..57e1b5f49371bf 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getscriptedfields.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getscriptedfields.md @@ -6,6 +6,7 @@ > Warning: This API is now obsolete. > +> use runtime field instead > Signature: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.hasruntimefield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.hasruntimefield.md new file mode 100644 index 00000000000000..96dbe13a7f1978 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.hasruntimefield.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [hasRuntimeField](./kibana-plugin-plugins-data-public.indexpattern.hasruntimefield.md) + +## IndexPattern.hasRuntimeField() method + +Checks if runtime field exists + +Signature: + +```typescript +hasRuntimeField(name: string): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | + +Returns: + +`boolean` + 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 53d173d39f50d0..a30b209bb9e2e7 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 @@ -54,13 +54,16 @@ export declare class IndexPattern implements IIndexPattern | [getFormatterForField(field)](./kibana-plugin-plugins-data-public.indexpattern.getformatterforfield.md) | | Provide a field, get its formatter | | [getFormatterForFieldNoDefault(fieldname)](./kibana-plugin-plugins-data-public.indexpattern.getformatterforfieldnodefault.md) | | Get formatter for a given field name. Return undefined if none exists | | [getNonScriptedFields()](./kibana-plugin-plugins-data-public.indexpattern.getnonscriptedfields.md) | | | +| [getRuntimeField(name)](./kibana-plugin-plugins-data-public.indexpattern.getruntimefield.md) | | Returns runtime field if exists | | [getScriptedFields()](./kibana-plugin-plugins-data-public.indexpattern.getscriptedfields.md) | | | | [getSourceFiltering()](./kibana-plugin-plugins-data-public.indexpattern.getsourcefiltering.md) | | Get the source filtering configuration for that index. | | [getTimeField()](./kibana-plugin-plugins-data-public.indexpattern.gettimefield.md) | | | +| [hasRuntimeField(name)](./kibana-plugin-plugins-data-public.indexpattern.hasruntimefield.md) | | Checks if runtime field exists | | [isTimeBased()](./kibana-plugin-plugins-data-public.indexpattern.istimebased.md) | | | | [isTimeNanosBased()](./kibana-plugin-plugins-data-public.indexpattern.istimenanosbased.md) | | | -| [removeRuntimeField(name)](./kibana-plugin-plugins-data-public.indexpattern.removeruntimefield.md) | | Remove a runtime field - removed from mapped field or removed unmapped field as appropriate | +| [removeRuntimeField(name, opts)](./kibana-plugin-plugins-data-public.indexpattern.removeruntimefield.md) | | Remove a runtime field - removed from mapped field or removed unmapped field as appropriate | | [removeScriptedField(fieldName)](./kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md) | | Remove scripted field from field list | +| [replaceAllRuntimeFields(newFields)](./kibana-plugin-plugins-data-public.indexpattern.replaceallruntimefields.md) | | Replaces all existing runtime fields with new fields | | [setFieldAttrs(fieldName, attrName, value)](./kibana-plugin-plugins-data-public.indexpattern.setfieldattrs.md) | | | | [setFieldCount(fieldName, count)](./kibana-plugin-plugins-data-public.indexpattern.setfieldcount.md) | | | | [setFieldCustomLabel(fieldName, customLabel)](./kibana-plugin-plugins-data-public.indexpattern.setfieldcustomlabel.md) | | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removeruntimefield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removeruntimefield.md index 7a5228fece782e..43ed61de5e2121 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removeruntimefield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removeruntimefield.md @@ -9,7 +9,10 @@ Remove a runtime field - removed from mapped field or removed unmapped field as Signature: ```typescript -removeRuntimeField(name: string): void; +removeRuntimeField(name: string, opts?: { + removeFieldFormat: boolean; + removeCustomLabel: boolean; + }): void; ``` ## Parameters @@ -17,6 +20,7 @@ removeRuntimeField(name: string): void; | Parameter | Type | Description | | --- | --- | --- | | name | string | | +| opts | {
removeFieldFormat: boolean;
removeCustomLabel: boolean;
} | | Returns: 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 91f25c09ab197a..d3e1b26810fd74 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 @@ -6,6 +6,7 @@ > Warning: This API is now obsolete. > +> use runtime field instead > Remove scripted field from field list diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.replaceallruntimefields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.replaceallruntimefields.md new file mode 100644 index 00000000000000..076b2b38cf474a --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.replaceallruntimefields.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [replaceAllRuntimeFields](./kibana-plugin-plugins-data-public.indexpattern.replaceallruntimefields.md) + +## IndexPattern.replaceAllRuntimeFields() method + +Replaces all existing runtime fields with new fields + +Signature: + +```typescript +replaceAllRuntimeFields(newFields: Record): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| newFields | Record<string, RuntimeField> | | + +Returns: + +`void` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.addscriptedfield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.addscriptedfield.md index 981f28a51ae09d..33f1ac5e316605 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.addscriptedfield.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.addscriptedfield.md @@ -6,6 +6,7 @@ > Warning: This API is now obsolete. > +> use runtime field instead > Add scripted field to field list diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getnonscriptedfields.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getnonscriptedfields.md index cff2c5de98de61..e791dfc7c3738f 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getnonscriptedfields.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getnonscriptedfields.md @@ -6,6 +6,7 @@ > Warning: This API is now obsolete. > +> use runtime field instead > Signature: diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getruntimefield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getruntimefield.md new file mode 100644 index 00000000000000..d5dc8f966316b0 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getruntimefield.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getRuntimeField](./kibana-plugin-plugins-data-server.indexpattern.getruntimefield.md) + +## IndexPattern.getRuntimeField() method + +Returns runtime field if exists + +Signature: + +```typescript +getRuntimeField(name: string): RuntimeField | null; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | + +Returns: + +`RuntimeField | null` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getscriptedfields.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getscriptedfields.md index 62b8f1b62ac787..95f9f31309674c 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getscriptedfields.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getscriptedfields.md @@ -6,6 +6,7 @@ > Warning: This API is now obsolete. > +> use runtime field instead > Signature: diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.hasruntimefield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.hasruntimefield.md new file mode 100644 index 00000000000000..5000d5e645cbb9 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.hasruntimefield.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [hasRuntimeField](./kibana-plugin-plugins-data-server.indexpattern.hasruntimefield.md) + +## IndexPattern.hasRuntimeField() method + +Checks if runtime field exists + +Signature: + +```typescript +hasRuntimeField(name: string): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | + +Returns: + +`boolean` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md index 97d1cd91152623..1edb9a3ee06fb5 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md @@ -54,13 +54,16 @@ export declare class IndexPattern implements IIndexPattern | [getFormatterForField(field)](./kibana-plugin-plugins-data-server.indexpattern.getformatterforfield.md) | | Provide a field, get its formatter | | [getFormatterForFieldNoDefault(fieldname)](./kibana-plugin-plugins-data-server.indexpattern.getformatterforfieldnodefault.md) | | Get formatter for a given field name. Return undefined if none exists | | [getNonScriptedFields()](./kibana-plugin-plugins-data-server.indexpattern.getnonscriptedfields.md) | | | +| [getRuntimeField(name)](./kibana-plugin-plugins-data-server.indexpattern.getruntimefield.md) | | Returns runtime field if exists | | [getScriptedFields()](./kibana-plugin-plugins-data-server.indexpattern.getscriptedfields.md) | | | | [getSourceFiltering()](./kibana-plugin-plugins-data-server.indexpattern.getsourcefiltering.md) | | Get the source filtering configuration for that index. | | [getTimeField()](./kibana-plugin-plugins-data-server.indexpattern.gettimefield.md) | | | +| [hasRuntimeField(name)](./kibana-plugin-plugins-data-server.indexpattern.hasruntimefield.md) | | Checks if runtime field exists | | [isTimeBased()](./kibana-plugin-plugins-data-server.indexpattern.istimebased.md) | | | | [isTimeNanosBased()](./kibana-plugin-plugins-data-server.indexpattern.istimenanosbased.md) | | | -| [removeRuntimeField(name)](./kibana-plugin-plugins-data-server.indexpattern.removeruntimefield.md) | | Remove a runtime field - removed from mapped field or removed unmapped field as appropriate | +| [removeRuntimeField(name, opts)](./kibana-plugin-plugins-data-server.indexpattern.removeruntimefield.md) | | Remove a runtime field - removed from mapped field or removed unmapped field as appropriate | | [removeScriptedField(fieldName)](./kibana-plugin-plugins-data-server.indexpattern.removescriptedfield.md) | | Remove scripted field from field list | +| [replaceAllRuntimeFields(newFields)](./kibana-plugin-plugins-data-server.indexpattern.replaceallruntimefields.md) | | Replaces all existing runtime fields with new fields | | [setFieldAttrs(fieldName, attrName, value)](./kibana-plugin-plugins-data-server.indexpattern.setfieldattrs.md) | | | | [setFieldCount(fieldName, count)](./kibana-plugin-plugins-data-server.indexpattern.setfieldcount.md) | | | | [setFieldCustomLabel(fieldName, customLabel)](./kibana-plugin-plugins-data-server.indexpattern.setfieldcustomlabel.md) | | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removeruntimefield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removeruntimefield.md index da8e7e40a7fac2..173ebcbd0d01a3 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removeruntimefield.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removeruntimefield.md @@ -9,7 +9,10 @@ Remove a runtime field - removed from mapped field or removed unmapped field as Signature: ```typescript -removeRuntimeField(name: string): void; +removeRuntimeField(name: string, opts?: { + removeFieldFormat: boolean; + removeCustomLabel: boolean; + }): void; ``` ## Parameters @@ -17,6 +20,7 @@ removeRuntimeField(name: string): void; | Parameter | Type | Description | | --- | --- | --- | | name | string | | +| opts | {
removeFieldFormat: boolean;
removeCustomLabel: boolean;
} | | Returns: diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removescriptedfield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removescriptedfield.md index f6beed7389e438..b351eba2ace4f6 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removescriptedfield.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removescriptedfield.md @@ -6,6 +6,7 @@ > Warning: This API is now obsolete. > +> use runtime field instead > Remove scripted field from field list diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.replaceallruntimefields.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.replaceallruntimefields.md new file mode 100644 index 00000000000000..35df871763f8a1 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.replaceallruntimefields.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [replaceAllRuntimeFields](./kibana-plugin-plugins-data-server.indexpattern.replaceallruntimefields.md) + +## IndexPattern.replaceAllRuntimeFields() method + +Replaces all existing runtime fields with new fields + +Signature: + +```typescript +replaceAllRuntimeFields(newFields: Record): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| newFields | Record<string, RuntimeField> | | + +Returns: + +`void` + 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 c897cdbce2309a..bc5b162ab045e9 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 @@ -364,7 +364,6 @@ export class IndexPattern implements IIndexPattern { * @param name Field name * @param runtimeField Runtime field definition */ - addRuntimeField(name: string, runtimeField: RuntimeField) { const existingField = this.getFieldByName(name); if (existingField) { @@ -383,13 +382,50 @@ export class IndexPattern implements IIndexPattern { this.runtimeFieldMap[name] = runtimeField; } + /** + * Checks if runtime field exists + * @param name + */ + hasRuntimeField(name: string): boolean { + return !!this.runtimeFieldMap[name]; + } + + /** + * Returns runtime field if exists + * @param name + */ + getRuntimeField(name: string): RuntimeField | null { + return this.runtimeFieldMap[name] ?? null; + } + + /** + * Replaces all existing runtime fields with new fields + * @param newFields + */ + replaceAllRuntimeFields(newFields: Record) { + const oldRuntimeFieldNames = Object.keys(this.runtimeFieldMap); + oldRuntimeFieldNames.forEach((name) => { + this.removeRuntimeField(name, { removeCustomLabel: false, removeFieldFormat: false }); + }); + + Object.entries(newFields).forEach(([name, field]) => { + this.addRuntimeField(name, field); + }); + } + /** * Remove a runtime field - removed from mapped field or removed unmapped * field as appropriate * @param name Field name + * @param opts specify additional removal options */ - - removeRuntimeField(name: string) { + removeRuntimeField( + name: string, + opts: { removeFieldFormat: boolean; removeCustomLabel: boolean } = { + removeCustomLabel: true, + removeFieldFormat: true, + } + ) { const existingField = this.getFieldByName(name); if (existingField) { if (existingField.isMapped) { @@ -397,8 +433,14 @@ export class IndexPattern implements IIndexPattern { existingField.runtimeField = undefined; } else { // runtimeField only - this.setFieldCustomLabel(name, null); - this.deleteFieldFormat(name); + if (opts.removeCustomLabel) { + this.setFieldCustomLabel(name, null); + } + + if (opts.removeFieldFormat) { + this.deleteFieldFormat(name); + } + this.fields.remove(existingField); } } diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index be6e489b17290d..8c3313cb63eb02 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1368,6 +1368,7 @@ export class IndexPattern implements IIndexPattern { typeMeta?: string | undefined; type?: string | undefined; }; + getRuntimeField(name: string): RuntimeField | null; // @deprecated (undocumented) getScriptedFields(): IndexPatternField[]; getSourceFiltering(): { @@ -1375,6 +1376,7 @@ export class IndexPattern implements IIndexPattern { }; // (undocumented) getTimeField(): IndexPatternField | undefined; + hasRuntimeField(name: string): boolean; // (undocumented) id?: string; // @deprecated (undocumented) @@ -1385,9 +1387,13 @@ export class IndexPattern implements IIndexPattern { isTimeNanosBased(): boolean; // (undocumented) metaFields: string[]; - removeRuntimeField(name: string): void; + removeRuntimeField(name: string, opts?: { + removeFieldFormat: boolean; + removeCustomLabel: boolean; + }): void; // @deprecated removeScriptedField(fieldName: string): void; + replaceAllRuntimeFields(newFields: Record): void; resetOriginalSavedObjectBody: () => void; // (undocumented) protected setFieldAttrs(fieldName: string, attrName: K, value: FieldAttrSet[K]): void; diff --git a/src/plugins/data/server/index_patterns/routes.ts b/src/plugins/data/server/index_patterns/routes.ts index 9bff590b54f1c1..d2d8cb82cf646e 100644 --- a/src/plugins/data/server/index_patterns/routes.ts +++ b/src/plugins/data/server/index_patterns/routes.ts @@ -21,6 +21,11 @@ import { registerDeleteScriptedFieldRoute } from './routes/scripted_fields/delet import { registerUpdateScriptedFieldRoute } from './routes/scripted_fields/update_scripted_field'; import type { DataPluginStart, DataPluginStartDependencies } from '../plugin'; import { registerManageDefaultIndexPatternRoutes } from './routes/default_index_pattern'; +import { registerCreateRuntimeFieldRoute } from './routes/runtime_fields/create_runtime_field'; +import { registerGetRuntimeFieldRoute } from './routes/runtime_fields/get_runtime_field'; +import { registerDeleteRuntimeFieldRoute } from './routes/runtime_fields/delete_runtime_field'; +import { registerPutRuntimeFieldRoute } from './routes/runtime_fields/put_runtime_field'; +import { registerUpdateRuntimeFieldRoute } from './routes/runtime_fields/update_runtime_field'; export function registerRoutes( http: HttpServiceSetup, @@ -55,6 +60,13 @@ export function registerRoutes( registerDeleteScriptedFieldRoute(router, getStartServices); registerUpdateScriptedFieldRoute(router, getStartServices); + // Runtime Fields API + registerCreateRuntimeFieldRoute(router, getStartServices); + registerGetRuntimeFieldRoute(router, getStartServices); + registerDeleteRuntimeFieldRoute(router, getStartServices); + registerPutRuntimeFieldRoute(router, getStartServices); + registerUpdateRuntimeFieldRoute(router, getStartServices); + router.get( { path: '/api/index_patterns/_fields_for_wildcard', diff --git a/src/plugins/data/server/index_patterns/routes/create_index_pattern.ts b/src/plugins/data/server/index_patterns/routes/create_index_pattern.ts index d0767334626229..7049903f84e8c2 100644 --- a/src/plugins/data/server/index_patterns/routes/create_index_pattern.ts +++ b/src/plugins/data/server/index_patterns/routes/create_index_pattern.ts @@ -9,7 +9,11 @@ import { schema } from '@kbn/config-schema'; import { IndexPatternSpec } from 'src/plugins/data/common'; import { handleErrors } from './util/handle_errors'; -import { fieldSpecSchema, serializedFieldFormatSchema } from './util/schemas'; +import { + fieldSpecSchema, + runtimeFieldSpecSchema, + serializedFieldFormatSchema, +} from './util/schemas'; import { IRouter, StartServicesAccessor } from '../../../../../core/server'; import type { DataPluginStart, DataPluginStartDependencies } from '../../plugin'; @@ -39,6 +43,7 @@ const indexPatternSpecSchema = schema.object({ ) ), allowNoIndex: schema.maybe(schema.boolean()), + runtimeFieldMap: schema.maybe(schema.recordOf(schema.string(), runtimeFieldSpecSchema)), }); export const registerCreateIndexPatternRoute = ( @@ -66,6 +71,7 @@ export const registerCreateIndexPatternRoute = ( elasticsearchClient ); const body = req.body; + const indexPattern = await indexPatternsService.createAndSave( body.index_pattern as IndexPatternSpec, body.override, diff --git a/src/plugins/data/server/index_patterns/routes/runtime_fields/create_runtime_field.ts b/src/plugins/data/server/index_patterns/routes/runtime_fields/create_runtime_field.ts new file mode 100644 index 00000000000000..31df70f3b99dbe --- /dev/null +++ b/src/plugins/data/server/index_patterns/routes/runtime_fields/create_runtime_field.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { schema } from '@kbn/config-schema'; +import { handleErrors } from '../util/handle_errors'; +import { runtimeFieldSpecSchema } from '../util/schemas'; +import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; +import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; + +export const registerCreateRuntimeFieldRoute = ( + router: IRouter, + getStartServices: StartServicesAccessor +) => { + router.post( + { + path: '/api/index_patterns/index_pattern/{id}/runtime_field', + validate: { + params: schema.object({ + id: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + }), + body: schema.object({ + name: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + runtimeField: runtimeFieldSpecSchema, + // TODO: extend this API to support `custom label`, `count` and `format`? + }), + }, + }, + + handleErrors(async (ctx, req, res) => { + const savedObjectsClient = ctx.core.savedObjects.client; + const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; + const [, , { indexPatterns }] = await getStartServices(); + const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + elasticsearchClient + ); + const id = req.params.id; + const { name, runtimeField } = req.body; + + const indexPattern = await indexPatternsService.get(id); + + // TODO: can I make a regular field into a runtime field? + if (indexPattern.fields.getByName(name)) { + throw new Error(`Field [name = ${name}] already exists.`); + } + + indexPattern.addRuntimeField(name, runtimeField); + + const addedField = indexPattern.fields.getByName(name); + if (!addedField) throw new Error(`Could not create a field [name = ${name}].`); + + await indexPatternsService.updateSavedObject(indexPattern); + + const savedField = indexPattern.fields.getByName(name); + if (!savedField) throw new Error(`Could not create a field [name = ${name}].`); + + return res.ok({ + body: { + field: savedField.toSpec(), + index_pattern: indexPattern.toSpec(), + }, + }); + }) + ); +}; diff --git a/src/plugins/data/server/index_patterns/routes/runtime_fields/delete_runtime_field.ts b/src/plugins/data/server/index_patterns/routes/runtime_fields/delete_runtime_field.ts new file mode 100644 index 00000000000000..58b8529d7cf5af --- /dev/null +++ b/src/plugins/data/server/index_patterns/routes/runtime_fields/delete_runtime_field.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { schema } from '@kbn/config-schema'; +import { ErrorIndexPatternFieldNotFound } from '../../error'; +import { handleErrors } from '../util/handle_errors'; +import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; +import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; + +export const registerDeleteRuntimeFieldRoute = ( + router: IRouter, + getStartServices: StartServicesAccessor +) => { + router.delete( + { + path: '/api/index_patterns/index_pattern/{id}/runtime_field/{name}', + validate: { + params: schema.object({ + id: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + name: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + }), + }, + }, + handleErrors(async (ctx, req, res) => { + const savedObjectsClient = ctx.core.savedObjects.client; + const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; + const [, , { indexPatterns }] = await getStartServices(); + const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + elasticsearchClient + ); + const id = req.params.id; + const name = req.params.name; + + const indexPattern = await indexPatternsService.get(id); + const field = indexPattern.fields.getByName(name); + + if (!field) { + throw new ErrorIndexPatternFieldNotFound(id, name); + } + + if (!field.runtimeField) { + throw new Error('Only runtime fields can be deleted.'); + } + + indexPattern.removeRuntimeField(name); + + await indexPatternsService.updateSavedObject(indexPattern); + + return res.ok(); + }) + ); +}; diff --git a/src/plugins/data/server/index_patterns/routes/runtime_fields/get_runtime_field.ts b/src/plugins/data/server/index_patterns/routes/runtime_fields/get_runtime_field.ts new file mode 100644 index 00000000000000..664880a953fff0 --- /dev/null +++ b/src/plugins/data/server/index_patterns/routes/runtime_fields/get_runtime_field.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { schema } from '@kbn/config-schema'; +import { ErrorIndexPatternFieldNotFound } from '../../error'; +import { handleErrors } from '../util/handle_errors'; +import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; +import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; + +export const registerGetRuntimeFieldRoute = ( + router: IRouter, + getStartServices: StartServicesAccessor +) => { + router.get( + { + path: '/api/index_patterns/index_pattern/{id}/runtime_field/{name}', + validate: { + params: schema.object({ + id: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + name: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + }), + }, + }, + + handleErrors(async (ctx, req, res) => { + const savedObjectsClient = ctx.core.savedObjects.client; + const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; + const [, , { indexPatterns }] = await getStartServices(); + const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + elasticsearchClient + ); + const id = req.params.id; + const name = req.params.name; + + const indexPattern = await indexPatternsService.get(id); + + const field = indexPattern.fields.getByName(name); + + if (!field) { + throw new ErrorIndexPatternFieldNotFound(id, name); + } + + if (!field.runtimeField) { + throw new Error('Only runtime fields can be retrieved.'); + } + + return res.ok({ + body: { + field: field.toSpec(), + }, + }); + }) + ); +}; diff --git a/src/plugins/data/server/index_patterns/routes/runtime_fields/put_runtime_field.ts b/src/plugins/data/server/index_patterns/routes/runtime_fields/put_runtime_field.ts new file mode 100644 index 00000000000000..0e8965d68b757a --- /dev/null +++ b/src/plugins/data/server/index_patterns/routes/runtime_fields/put_runtime_field.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { schema } from '@kbn/config-schema'; +import { handleErrors } from '../util/handle_errors'; +import { runtimeFieldSpecSchema } from '../util/schemas'; +import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; +import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; + +export const registerPutRuntimeFieldRoute = ( + router: IRouter, + getStartServices: StartServicesAccessor +) => { + router.put( + { + path: '/api/index_patterns/index_pattern/{id}/runtime_field', + validate: { + params: schema.object({ + id: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + }), + body: schema.object({ + name: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + runtimeField: runtimeFieldSpecSchema, + // TODO: extend this API to support `custom label`, `count` and `format`? + }), + }, + }, + handleErrors(async (ctx, req, res) => { + const savedObjectsClient = ctx.core.savedObjects.client; + const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; + const [, , { indexPatterns }] = await getStartServices(); + const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + elasticsearchClient + ); + const id = req.params.id; + const { name, runtimeField } = req.body; + + const indexPattern = await indexPatternsService.get(id); + + const oldFieldObject = indexPattern.fields.getByName(name); + + if (oldFieldObject && !oldFieldObject.runtimeField) { + throw new Error('Only runtime fields can be updated'); + } + + if (oldFieldObject) { + indexPattern.removeRuntimeField(name, { + removeCustomLabel: false, + removeFieldFormat: false, + }); + } + + indexPattern.addRuntimeField(name, runtimeField); + + await indexPatternsService.updateSavedObject(indexPattern); + + const fieldObject = indexPattern.fields.getByName(name); + if (!fieldObject) throw new Error(`Could not create a field [name = ${name}].`); + + return res.ok({ + body: { + field: fieldObject.toSpec(), + index_pattern: indexPattern.toSpec(), + }, + }); + }) + ); +}; diff --git a/src/plugins/data/server/index_patterns/routes/runtime_fields/update_runtime_field.ts b/src/plugins/data/server/index_patterns/routes/runtime_fields/update_runtime_field.ts new file mode 100644 index 00000000000000..8acb9e0b94baa2 --- /dev/null +++ b/src/plugins/data/server/index_patterns/routes/runtime_fields/update_runtime_field.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { schema } from '@kbn/config-schema'; +import { RuntimeField } from 'src/plugins/data/common'; +import { ErrorIndexPatternFieldNotFound } from '../../error'; +import { handleErrors } from '../util/handle_errors'; +import { runtimeFieldSpec, runtimeFieldSpecTypeSchema } from '../util/schemas'; +import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; +import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; + +export const registerUpdateRuntimeFieldRoute = ( + router: IRouter, + getStartServices: StartServicesAccessor +) => { + router.post( + { + path: '/api/index_patterns/index_pattern/{id}/runtime_field/{name}', + validate: { + params: schema.object({ + id: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + name: schema.string({ + minLength: 1, + maxLength: 1_000, + }), + }), + body: schema.object({ + name: schema.never(), + runtimeField: schema.object({ + ...runtimeFieldSpec, + // We need to overwrite the below fields on top of `runtimeFieldSpec`, + // because some fields would be optional + type: schema.maybe(runtimeFieldSpecTypeSchema), + }), + }), + }, + }, + handleErrors(async (ctx, req, res) => { + const savedObjectsClient = ctx.core.savedObjects.client; + const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; + const [, , { indexPatterns }] = await getStartServices(); + const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + elasticsearchClient + ); + const id = req.params.id; + const name = req.params.name; + const runtimeField = req.body.runtimeField as Partial; + + const indexPattern = await indexPatternsService.get(id); + const existingRuntimeField = indexPattern.getRuntimeField(name); + + if (!existingRuntimeField) { + throw new ErrorIndexPatternFieldNotFound(id, name); + } + + indexPattern.removeRuntimeField(name, { removeCustomLabel: false, removeFieldFormat: false }); + indexPattern.addRuntimeField(name, { + ...existingRuntimeField, + ...runtimeField, + }); + + await indexPatternsService.updateSavedObject(indexPattern); + + const fieldObject = indexPattern.fields.getByName(name); + if (!fieldObject) throw new Error(`Could not create a field [name = ${name}].`); + + return res.ok({ + body: { + field: fieldObject.toSpec(), + index_pattern: indexPattern.toSpec(), + }, + }); + }) + ); +}; diff --git a/src/plugins/data/server/index_patterns/routes/update_index_pattern.ts b/src/plugins/data/server/index_patterns/routes/update_index_pattern.ts index c1509b9b848bef..1c88550c154c56 100644 --- a/src/plugins/data/server/index_patterns/routes/update_index_pattern.ts +++ b/src/plugins/data/server/index_patterns/routes/update_index_pattern.ts @@ -8,7 +8,11 @@ import { schema } from '@kbn/config-schema'; import { handleErrors } from './util/handle_errors'; -import { fieldSpecSchema, serializedFieldFormatSchema } from './util/schemas'; +import { + fieldSpecSchema, + runtimeFieldSpecSchema, + serializedFieldFormatSchema, +} from './util/schemas'; import { IRouter, StartServicesAccessor } from '../../../../../core/server'; import type { DataPluginStart, DataPluginStartDependencies } from '../../plugin'; @@ -28,6 +32,7 @@ const indexPatternUpdateSchema = schema.object({ fieldFormats: schema.maybe(schema.recordOf(schema.string(), serializedFieldFormatSchema)), fields: schema.maybe(schema.recordOf(schema.string(), fieldSpecSchema)), allowNoIndex: schema.maybe(schema.boolean()), + runtimeFieldMap: schema.maybe(schema.recordOf(schema.string(), runtimeFieldSpecSchema)), }); export const registerUpdateIndexPatternRoute = ( @@ -78,6 +83,7 @@ export const registerUpdateIndexPatternRoute = ( type, typeMeta, fields, + runtimeFieldMap, }, } = req.body; @@ -131,6 +137,11 @@ export const registerUpdateIndexPatternRoute = ( ); } + if (runtimeFieldMap !== undefined) { + changeCount++; + indexPattern.replaceAllRuntimeFields(runtimeFieldMap); + } + if (changeCount < 1) { throw new Error('Index pattern change set is empty.'); } diff --git a/src/plugins/data/server/index_patterns/routes/util/schemas.ts b/src/plugins/data/server/index_patterns/routes/util/schemas.ts index d916423c4fc69c..4246988f672fc5 100644 --- a/src/plugins/data/server/index_patterns/routes/util/schemas.ts +++ b/src/plugins/data/server/index_patterns/routes/util/schemas.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { schema } from '@kbn/config-schema'; +import { schema, Type } from '@kbn/config-schema'; +import { RUNTIME_FIELD_TYPES, RuntimeType } from '../../../../common'; export const serializedFieldFormatSchema = schema.object({ id: schema.maybe(schema.string()), @@ -53,3 +54,18 @@ export const fieldSpecSchemaFields = { }; export const fieldSpecSchema = schema.object(fieldSpecSchemaFields); + +export const runtimeFieldSpecTypeSchema = schema.oneOf( + RUNTIME_FIELD_TYPES.map((runtimeFieldType) => schema.literal(runtimeFieldType)) as [ + Type + ] +); +export const runtimeFieldSpec = { + type: runtimeFieldSpecTypeSchema, + script: schema.maybe( + schema.object({ + source: schema.string(), + }) + ), +}; +export const runtimeFieldSpecSchema = schema.object(runtimeFieldSpec); diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 8ec412e69d4a10..d850fadb62e5d4 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -814,6 +814,7 @@ export class IndexPattern implements IIndexPattern { typeMeta?: string | undefined; type?: string | undefined; }; + getRuntimeField(name: string): RuntimeField | null; // @deprecated (undocumented) getScriptedFields(): IndexPatternField[]; getSourceFiltering(): { @@ -821,6 +822,7 @@ export class IndexPattern implements IIndexPattern { }; // (undocumented) getTimeField(): IndexPatternField | undefined; + hasRuntimeField(name: string): boolean; // (undocumented) id?: string; // @deprecated (undocumented) @@ -831,9 +833,13 @@ export class IndexPattern implements IIndexPattern { isTimeNanosBased(): boolean; // (undocumented) metaFields: string[]; - removeRuntimeField(name: string): void; + removeRuntimeField(name: string, opts?: { + removeFieldFormat: boolean; + removeCustomLabel: boolean; + }): void; // @deprecated removeScriptedField(fieldName: string): void; + replaceAllRuntimeFields(newFields: Record): void; resetOriginalSavedObjectBody: () => void; // (undocumented) protected setFieldAttrs(fieldName: string, attrName: K, value: FieldAttrSet[K]): void; diff --git a/test/api_integration/apis/index_patterns/index.js b/test/api_integration/apis/index_patterns/index.js index 9c1e1bba0ab9a5..0f6c633efc7e67 100644 --- a/test/api_integration/apis/index_patterns/index.js +++ b/test/api_integration/apis/index_patterns/index.js @@ -15,5 +15,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./scripted_fields_crud')); loadTestFile(require.resolve('./fields_api')); loadTestFile(require.resolve('./default_index_pattern')); + loadTestFile(require.resolve('./runtime_fields_crud')); }); } diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/main.ts b/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/main.ts index 91f165dbdda7cc..a0ca9a034adefe 100644 --- a/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/main.ts +++ b/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/main.ts @@ -113,7 +113,7 @@ export default function ({ getService }: FtrProviderContext) { expect(response.body.index_pattern.fields.bar.type).to.be('boolean'); }); - it('Can add scripted fields, other fields created from es index', async () => { + it('can add scripted fields, other fields created from es index', async () => { const title = `basic_index*`; const response = await supertest.post('/api/index_patterns/index_pattern').send({ override: true, @@ -155,6 +155,32 @@ export default function ({ getService }: FtrProviderContext) { expect(response.body.index_pattern.fields.bar.esTypes[0]).to.be('test-type'); expect(response.body.index_pattern.fields.bar.scripted).to.be(true); }); + + it('can add runtime fields', async () => { + const title = `basic_index*`; + const response = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + runtimeFieldMap: { + runtimeFoo: { + type: 'keyword', + script: { + source: 'emit(doc["foo"].value)', + }, + }, + }, + }, + }); + + expect(response.status).to.be(200); + expect(response.body.index_pattern.title).to.be(title); + + expect(response.body.index_pattern.runtimeFieldMap.runtimeFoo.type).to.be('keyword'); + expect(response.body.index_pattern.runtimeFieldMap.runtimeFoo.script.source).to.be( + 'emit(doc["foo"].value)' + ); + }); }); it('can specify optional typeMeta attribute when creating an index pattern', async () => { diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/validation.ts b/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/validation.ts index 2f62ea231b7229..598001644eedbe 100644 --- a/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/validation.ts +++ b/test/api_integration/apis/index_patterns/index_pattern_crud/create_index_pattern/validation.ts @@ -64,5 +64,25 @@ export default function ({ getService }: FtrProviderContext) { '[request body.refresh_fields]: expected value of type [boolean] but got [number]' ); }); + + it('returns an error when unknown runtime field type', async () => { + const title = `basic_index*`; + const response = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + runtimeFieldMap: { + runtimeFoo: { + type: 'wrong-type', + script: { + source: 'emit(doc["foo"].value)', + }, + }, + }, + }, + }); + + expect(response.status).to.be(400); + }); }); } diff --git a/test/api_integration/apis/index_patterns/index_pattern_crud/update_index_pattern/main.ts b/test/api_integration/apis/index_patterns/index_pattern_crud/update_index_pattern/main.ts index cd34724e6cda35..7532278d7eb128 100644 --- a/test/api_integration/apis/index_patterns/index_pattern_crud/update_index_pattern/main.ts +++ b/test/api_integration/apis/index_patterns/index_pattern_crud/update_index_pattern/main.ts @@ -284,5 +284,53 @@ export default function ({ getService }: FtrProviderContext) { expect(response3.body.index_pattern.intervalName).to.be('intervalName2'); expect(response3.body.index_pattern.typeMeta.baz).to.be('qux'); }); + + it('can update runtime fields', async () => { + const title = `basic_index*`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + runtimeFieldMap: { + runtimeFoo: { + type: 'keyword', + script: { + source: 'emit(doc["foo"].value)', + }, + }, + }, + }, + }); + + expect(response1.status).to.be(200); + expect(response1.body.index_pattern.title).to.be(title); + + expect(response1.body.index_pattern.runtimeFieldMap.runtimeFoo.type).to.be('keyword'); + expect(response1.body.index_pattern.runtimeFieldMap.runtimeFoo.script.source).to.be( + 'emit(doc["foo"].value)' + ); + + const id = response1.body.index_pattern.id; + const response2 = await supertest.post('/api/index_patterns/index_pattern/' + id).send({ + index_pattern: { + runtimeFieldMap: { + runtimeBar: { + type: 'keyword', + script: { + source: 'emit(doc["foo"].value)', + }, + }, + }, + }, + }); + + expect(response2.body.index_pattern.runtimeFieldMap.runtimeBar.type).to.be('keyword'); + expect(response2.body.index_pattern.runtimeFieldMap.runtimeFoo).to.be(undefined); + + const response3 = await supertest.get('/api/index_patterns/index_pattern/' + id); + + expect(response3.body.index_pattern.runtimeFieldMap.runtimeBar.type).to.be('keyword'); + expect(response3.body.index_pattern.runtimeFieldMap.runtimeFoo).to.be(undefined); + }); }); } diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/errors.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/errors.ts new file mode 100644 index 00000000000000..da9f45a3312718 --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/errors.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + describe('errors', () => { + it('returns an error field object is not provided', async () => { + const title = `foo-${Date.now()}-${Math.random()}*`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + index_pattern: { + title, + }, + }); + const id = response1.body.index_pattern.id; + const response2 = await supertest + .post(`/api/index_patterns/index_pattern/${id}/runtime_field`) + .send({}); + + expect(response2.status).to.be(400); + expect(response2.body.statusCode).to.be(400); + expect(response2.body.message).to.be( + '[request body.field.name]: expected value of type [string] but got [undefined]' + ); + }); + + it('returns an error when creating a non-runtime field', async () => { + const title = `foo-${Date.now()}-${Math.random()}*`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + index_pattern: { + title, + }, + }); + const id = response1.body.index_pattern.id; + const response2 = await supertest + .post(`/api/index_patterns/index_pattern/${id}/runtime_field`) + .send({ + field: { + name: 'bar', + type: 'number', + }, + }); + + expect(response2.status).to.be(400); + expect(response2.body.statusCode).to.be(400); + expect(response2.body.message).to.be('Only runtime fields can be created'); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/index.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/index.ts new file mode 100644 index 00000000000000..2cb90ca087f497 --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('create_runtime_field', () => { + loadTestFile(require.resolve('./errors')); + loadTestFile(require.resolve('./main')); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/main.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/main.ts new file mode 100644 index 00000000000000..825016b27f812c --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/create_runtime_field/main.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('main', () => { + before(async () => { + await esArchiver.load('index_patterns/basic_index'); + }); + + after(async () => { + await esArchiver.unload('index_patterns/basic_index'); + }); + + it('can create a new runtime field', async () => { + const title = `basic_index*`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + }, + }); + const id = response1.body.index_pattern.id; + const response2 = await supertest + .post(`/api/index_patterns/index_pattern/${id}/runtime_field`) + .send({ + name: 'runtimeBar', + runtimeField: { + type: 'long', + script: { + source: "emit(doc['field_name'].value)", + }, + }, + }); + + expect(response2.status).to.be(200); + expect(response2.body.field.name).to.be('runtimeBar'); + expect(response2.body.field.runtimeField.type).to.be('long'); + expect(response2.body.field.runtimeField.script.source).to.be( + "emit(doc['field_name'].value)" + ); + expect(response2.body.field.scripted).to.be(false); + }); + + it('newly created runtime field is available in the index_pattern object', async () => { + const title = `basic_index`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + index_pattern: { + title, + }, + }); + + await supertest + .post(`/api/index_patterns/index_pattern/${response1.body.index_pattern.id}/runtime_field`) + .send({ + name: 'runtimeBar', + runtimeField: { + type: 'long', + script: { + source: "emit(doc['field_name'].value)", + }, + }, + }); + + const response2 = await supertest.get( + '/api/index_patterns/index_pattern/' + response1.body.index_pattern.id + ); + + expect(response2.status).to.be(200); + + const field = response2.body.index_pattern.fields.runtimeBar; + + expect(field.name).to.be('runtimeBar'); + expect(field.runtimeField.type).to.be('long'); + expect(field.runtimeField.script.source).to.be("emit(doc['field_name'].value)"); + expect(field.scripted).to.be(false); + await supertest.delete( + '/api/index_patterns/index_pattern/' + response1.body.index_pattern.id + ); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/errors.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/errors.ts new file mode 100644 index 00000000000000..a8a0f7b19d8cbf --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/errors.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + + describe('errors', () => { + const basicIndex = 'b*sic_index'; + let indexPattern: any; + + before(async () => { + await esArchiver.load('index_patterns/basic_index'); + + indexPattern = ( + await supertest.post('/api/index_patterns/index_pattern').send({ + index_pattern: { + title: basicIndex, + }, + }) + ).body.index_pattern; + }); + + after(async () => { + await esArchiver.unload('index_patterns/basic_index'); + + if (indexPattern) { + await supertest.delete('/api/index_patterns/index_pattern/' + indexPattern.id); + } + }); + + it('returns 404 error on non-existing index_pattern', async () => { + const id = `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-${Date.now()}`; + const response = await supertest.delete( + `/api/index_patterns/index_pattern/${id}/runtime_field/foo` + ); + + expect(response.status).to.be(404); + }); + + it('returns 404 error on non-existing runtime field', async () => { + const response1 = await supertest.delete( + `/api/index_patterns/index_pattern/${indexPattern.id}/runtime_field/test` + ); + + expect(response1.status).to.be(404); + }); + + it('returns error when attempting to delete a field which is not a runtime field', async () => { + const response2 = await supertest.delete( + `/api/index_patterns/index_pattern/${indexPattern.id}/runtime_field/foo` + ); + + expect(response2.status).to.be(400); + expect(response2.body.statusCode).to.be(400); + expect(response2.body.message).to.be('Only runtime fields can be deleted.'); + }); + + it('returns error when ID is too long', async () => { + const id = `xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx`; + const response = await supertest.delete( + `/api/index_patterns/index_pattern/${id}/runtime_field/foo` + ); + + expect(response.status).to.be(400); + expect(response.body.message).to.be( + '[request params.id]: value has length [1759] but it must have a maximum length of [1000].' + ); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/index.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/index.ts new file mode 100644 index 00000000000000..a14201e750ddaf --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('delete_runtime_field', () => { + loadTestFile(require.resolve('./errors')); + loadTestFile(require.resolve('./main')); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/main.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/main.ts new file mode 100644 index 00000000000000..2134943bbcdae7 --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/delete_runtime_field/main.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('main', () => { + before(async () => { + await esArchiver.load('index_patterns/basic_index'); + }); + + after(async () => { + await esArchiver.unload('index_patterns/basic_index'); + }); + + it('can delete a runtime field', async () => { + const title = `basic_index*`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + runtimeFieldMap: { + runtimeBar: { + type: 'long', + script: { + source: "emit(doc['field_name'].value)", + }, + }, + }, + }, + }); + + const response2 = await supertest.get( + '/api/index_patterns/index_pattern/' + response1.body.index_pattern.id + ); + + expect(typeof response2.body.index_pattern.fields.runtimeBar).to.be('object'); + + const response3 = await supertest.delete( + `/api/index_patterns/index_pattern/${response1.body.index_pattern.id}/runtime_field/runtimeBar` + ); + + expect(response3.status).to.be(200); + + const response4 = await supertest.get( + '/api/index_patterns/index_pattern/' + response1.body.index_pattern.id + ); + + expect(typeof response4.body.index_pattern.fields.runtimeBar).to.be('undefined'); + await supertest.delete( + '/api/index_patterns/index_pattern/' + response1.body.index_pattern.id + ); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/errors.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/errors.ts new file mode 100644 index 00000000000000..6f9f27444fee0e --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/errors.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + + describe('errors', () => { + const basicIndex = '*asic_index'; + let indexPattern: any; + + before(async () => { + await esArchiver.load('index_patterns/basic_index'); + + indexPattern = ( + await supertest.post('/api/index_patterns/index_pattern').send({ + index_pattern: { + title: basicIndex, + }, + }) + ).body.index_pattern; + }); + + after(async () => { + await esArchiver.unload('index_patterns/basic_index'); + + if (indexPattern) { + await supertest.delete('/api/index_patterns/index_pattern/' + indexPattern.id); + } + }); + + it('returns 404 error on non-existing index_pattern', async () => { + const id = `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-${Date.now()}`; + const response = await supertest.get( + `/api/index_patterns/index_pattern/${id}/runtime_field/foo` + ); + + expect(response.status).to.be(404); + }); + + it('returns 404 error on non-existing runtime field', async () => { + const response1 = await supertest.get( + `/api/index_patterns/index_pattern/${indexPattern.id}/runtime_field/sf` + ); + + expect(response1.status).to.be(404); + }); + + it('returns error when ID is too long', async () => { + const id = `xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx`; + const response = await supertest.get( + `/api/index_patterns/index_pattern/${id}/runtime_field/foo` + ); + + expect(response.status).to.be(400); + expect(response.body.message).to.be( + '[request params.id]: value has length [1759] but it must have a maximum length of [1000].' + ); + }); + + it('returns error when attempting to fetch a field which is not a runtime field', async () => { + const response2 = await supertest.get( + `/api/index_patterns/index_pattern/${indexPattern.id}/runtime_field/foo` + ); + + expect(response2.status).to.be(400); + expect(response2.body.statusCode).to.be(400); + expect(response2.body.message).to.be('Only runtime fields can be retrieved.'); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/index.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/index.ts new file mode 100644 index 00000000000000..2e48ba64841eeb --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('get_runtime_field', () => { + loadTestFile(require.resolve('./errors')); + loadTestFile(require.resolve('./main')); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/main.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/main.ts new file mode 100644 index 00000000000000..72711a70ae661d --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/get_runtime_field/main.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('main', () => { + before(async () => { + await esArchiver.load('index_patterns/basic_index'); + }); + + after(async () => { + await esArchiver.unload('index_patterns/basic_index'); + }); + + it('can fetch a runtime field', async () => { + const title = `basic_index*`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + runtimeFieldMap: { + runtimeFoo: { + type: 'keyword', + script: { + source: "emit(doc['field_name'].value)", + }, + }, + runtimeBar: { + type: 'keyword', + script: { + source: "emit(doc['field_name'].value)", + }, + }, + }, + }, + }); + + expect(response1.status).to.be(200); + + const response2 = await supertest.get( + '/api/index_patterns/index_pattern/' + + response1.body.index_pattern.id + + '/runtime_field/runtimeFoo' + ); + + expect(response2.status).to.be(200); + expect(typeof response2.body.field).to.be('object'); + expect(response2.body.field.name).to.be('runtimeFoo'); + expect(response2.body.field.type).to.be('string'); + expect(response2.body.field.scripted).to.be(false); + expect(response2.body.field.runtimeField.script.source).to.be( + "emit(doc['field_name'].value)" + ); + await supertest.delete( + '/api/index_patterns/index_pattern/' + response1.body.index_pattern.id + ); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/index.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/index.ts new file mode 100644 index 00000000000000..7a727a3e867550 --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('runtime_fields_crud', () => { + loadTestFile(require.resolve('./create_runtime_field')); + loadTestFile(require.resolve('./get_runtime_field')); + loadTestFile(require.resolve('./delete_runtime_field')); + loadTestFile(require.resolve('./put_runtime_field')); + loadTestFile(require.resolve('./update_runtime_field')); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/errors.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/errors.ts new file mode 100644 index 00000000000000..a029b4f4d3686b --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/errors.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('errors', () => { + before(async () => { + await esArchiver.load('index_patterns/basic_index'); + }); + + after(async () => { + await esArchiver.unload('index_patterns/basic_index'); + }); + + it('returns 404 error on non-existing index_pattern', async () => { + const id = `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-${Date.now()}`; + const response = await supertest + .put(`/api/index_patterns/index_pattern/${id}/runtime_field`) + .send({ + name: 'runtimeBar', + runtimeField: { + type: 'long', + script: { + source: "emit(doc['field_name'].value)", + }, + }, + }); + + expect(response.status).to.be(404); + }); + + it('returns error on non-runtime field update attempt', async () => { + const title = `basic_index`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + }, + }); + + const response2 = await supertest + .put(`/api/index_patterns/index_pattern/${response1.body.index_pattern.id}/runtime_field`) + .send({ + name: 'bar', + runtimeField: { + type: 'long', + script: { + source: "emit(doc['field_name'].value)", + }, + }, + }); + + expect(response2.status).to.be(400); + expect(response2.body.message).to.be('Only runtime fields can be updated'); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/index.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/index.ts new file mode 100644 index 00000000000000..724f18e57f3efd --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('put_runtime_field', () => { + loadTestFile(require.resolve('./errors')); + loadTestFile(require.resolve('./main')); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/main.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/main.ts new file mode 100644 index 00000000000000..6b12a2ae7d6cc7 --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/put_runtime_field/main.ts @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('main', () => { + before(async () => { + await esArchiver.load('index_patterns/basic_index'); + }); + + after(async () => { + await esArchiver.unload('index_patterns/basic_index'); + }); + + it('can overwrite an existing field', async () => { + const title = `basic_index`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + runtimeFieldMap: { + runtimeFoo: { + type: 'keyword', + script: { + source: "doc['field_name'].value", + }, + }, + runtimeBar: { + type: 'keyword', + script: { + source: "doc['field_name'].value", + }, + }, + }, + }, + }); + + const response2 = await supertest + .put(`/api/index_patterns/index_pattern/${response1.body.index_pattern.id}/runtime_field`) + .send({ + name: 'runtimeFoo', + runtimeField: { + type: 'long', + script: { + source: "doc['field_name'].value", + }, + }, + }); + + expect(response2.status).to.be(200); + + const response3 = await supertest.get( + '/api/index_patterns/index_pattern/' + + response1.body.index_pattern.id + + '/runtime_field/runtimeFoo' + ); + + expect(response3.status).to.be(200); + expect(response3.body.field.type).to.be('number'); + + const response4 = await supertest.get( + '/api/index_patterns/index_pattern/' + + response1.body.index_pattern.id + + '/runtime_field/runtimeBar' + ); + + expect(response4.status).to.be(200); + expect(response4.body.field.type).to.be('string'); + }); + + it('can add a new runtime field', async () => { + const title = `basic_index`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + runtimeFieldMap: { + runtimeFoo: { + type: 'keyword', + script: { + source: "doc['field_name'].value", + }, + }, + }, + }, + }); + + await supertest + .put(`/api/index_patterns/index_pattern/${response1.body.index_pattern.id}/runtime_field`) + .send({ + name: 'runtimeBar', + runtimeField: { + type: 'long', + script: { + source: "doc['field_name'].value", + }, + }, + }); + + const response2 = await supertest.get( + '/api/index_patterns/index_pattern/' + + response1.body.index_pattern.id + + '/runtime_field/runtimeBar' + ); + + expect(response2.status).to.be(200); + expect(typeof response2.body.field.runtimeField).to.be('object'); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/errors.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/errors.ts new file mode 100644 index 00000000000000..3980821c0fd096 --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/errors.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + describe('errors', () => { + it('returns 404 error on non-existing index_pattern', async () => { + const id = `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-${Date.now()}`; + const response = await supertest + .post(`/api/index_patterns/index_pattern/${id}/runtime_field/foo`) + .send({ + runtimeField: { + script: { + source: "doc['something_new'].value", + }, + }, + }); + + expect(response.status).to.be(404); + }); + + it('returns error when field name is specified', async () => { + const id = `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-${Date.now()}`; + const response = await supertest + .post(`/api/index_patterns/index_pattern/${id}/runtime_field/foo`) + .send({ + name: 'foo', + runtimeField: { + script: { + source: "doc['something_new'].value", + }, + }, + }); + + expect(response.status).to.be(400); + expect(response.body.statusCode).to.be(400); + expect(response.body.message).to.be( + "[request body.name]: a value wasn't expected to be present" + ); + }); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/index.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/index.ts new file mode 100644 index 00000000000000..f5d556ca9994ab --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('update_runtime_field', () => { + loadTestFile(require.resolve('./errors')); + loadTestFile(require.resolve('./main')); + }); +} diff --git a/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/main.ts b/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/main.ts new file mode 100644 index 00000000000000..c94ba3cbd57017 --- /dev/null +++ b/test/api_integration/apis/index_patterns/runtime_fields_crud/update_runtime_field/main.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('main', () => { + before(async () => { + await esArchiver.load('index_patterns/basic_index'); + }); + + after(async () => { + await esArchiver.unload('index_patterns/basic_index'); + }); + + it('can update an existing field', async () => { + const title = `basic_index`; + const response1 = await supertest.post('/api/index_patterns/index_pattern').send({ + override: true, + index_pattern: { + title, + runtimeFieldMap: { + runtimeFoo: { + type: 'keyword', + script: { + source: "doc['field_name'].value", + }, + }, + runtimeBar: { + type: 'keyword', + script: { + source: "doc['field_name'].value", + }, + }, + }, + }, + }); + + const response2 = await supertest + .post( + `/api/index_patterns/index_pattern/${response1.body.index_pattern.id}/runtime_field/runtimeFoo` + ) + .send({ + runtimeField: { + script: { + source: "doc['something_new'].value", + }, + }, + }); + + expect(response2.status).to.be(200); + + const response3 = await supertest.get( + '/api/index_patterns/index_pattern/' + + response1.body.index_pattern.id + + '/runtime_field/runtimeFoo' + ); + + expect(response3.status).to.be(200); + expect(response3.body.field.type).to.be('string'); + expect(response3.body.field.runtimeField.type).to.be('keyword'); + expect(response3.body.field.runtimeField.script.source).to.be("doc['something_new'].value"); + }); + }); +}