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");
+ });
+ });
+}