diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 5347f0b55e19b..98d7b0610abea 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -153,6 +153,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectMigrationMap](./kibana-plugin-core-server.savedobjectmigrationmap.md) | A map of [migration functions](./kibana-plugin-core-server.savedobjectmigrationfn.md) to be used for a given type. The map's keys must be valid semver versions.For a given document, only migrations with a higher version number than that of the document will be applied. Migrations are executed in order, starting from the lowest version and ending with the highest one. | | [SavedObjectReference](./kibana-plugin-core-server.savedobjectreference.md) | A reference to another saved object. | | [SavedObjectsAddToNamespacesOptions](./kibana-plugin-core-server.savedobjectsaddtonamespacesoptions.md) | | +| [SavedObjectsAddToNamespacesResponse](./kibana-plugin-core-server.savedobjectsaddtonamespacesresponse.md) | | | [SavedObjectsBaseOptions](./kibana-plugin-core-server.savedobjectsbaseoptions.md) | | | [SavedObjectsBulkCreateObject](./kibana-plugin-core-server.savedobjectsbulkcreateobject.md) | | | [SavedObjectsBulkGetObject](./kibana-plugin-core-server.savedobjectsbulkgetobject.md) | | @@ -167,6 +168,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md) | | | [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-core-server.savedobjectsdeletebynamespaceoptions.md) | | | [SavedObjectsDeleteFromNamespacesOptions](./kibana-plugin-core-server.savedobjectsdeletefromnamespacesoptions.md) | | +| [SavedObjectsDeleteFromNamespacesResponse](./kibana-plugin-core-server.savedobjectsdeletefromnamespacesresponse.md) | | | [SavedObjectsDeleteOptions](./kibana-plugin-core-server.savedobjectsdeleteoptions.md) | | | [SavedObjectsExportOptions](./kibana-plugin-core-server.savedobjectsexportoptions.md) | Options controlling the export operation. | | [SavedObjectsExportResultDetails](./kibana-plugin-core-server.savedobjectsexportresultdetails.md) | Structure of the export result details entry | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsaddtonamespacesresponse.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsaddtonamespacesresponse.md new file mode 100644 index 0000000000000..306f502f0b0b3 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsaddtonamespacesresponse.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsAddToNamespacesResponse](./kibana-plugin-core-server.savedobjectsaddtonamespacesresponse.md) + +## SavedObjectsAddToNamespacesResponse interface + + +Signature: + +```typescript +export interface SavedObjectsAddToNamespacesResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [namespaces](./kibana-plugin-core-server.savedobjectsaddtonamespacesresponse.namespaces.md) | string[] | The namespaces the object exists in after this operation is complete. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsaddtonamespacesresponse.namespaces.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsaddtonamespacesresponse.namespaces.md new file mode 100644 index 0000000000000..4fc2e376304d4 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsaddtonamespacesresponse.namespaces.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsAddToNamespacesResponse](./kibana-plugin-core-server.savedobjectsaddtonamespacesresponse.md) > [namespaces](./kibana-plugin-core-server.savedobjectsaddtonamespacesresponse.namespaces.md) + +## SavedObjectsAddToNamespacesResponse.namespaces property + +The namespaces the object exists in after this operation is complete. + +Signature: + +```typescript +namespaces: string[]; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.addtonamespaces.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.addtonamespaces.md index 45c9c39f9626a..567390faba9b2 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.addtonamespaces.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.addtonamespaces.md @@ -9,7 +9,7 @@ Adds namespaces to a SavedObject Signature: ```typescript -addToNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsAddToNamespacesOptions): Promise<{}>; +addToNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsAddToNamespacesOptions): Promise; ``` ## Parameters @@ -23,5 +23,5 @@ addToNamespaces(type: string, id: string, namespaces: string[], options?: SavedO Returns: -`Promise<{}>` +`Promise` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.deletefromnamespaces.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.deletefromnamespaces.md index 80b58d29d393b..18ef5c3e6350c 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.deletefromnamespaces.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.deletefromnamespaces.md @@ -9,7 +9,7 @@ Removes namespaces from a SavedObject Signature: ```typescript -deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise<{}>; +deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise; ``` ## Parameters @@ -23,5 +23,5 @@ deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: S Returns: -`Promise<{}>` +`Promise` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsdeletefromnamespacesresponse.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsdeletefromnamespacesresponse.md new file mode 100644 index 0000000000000..6021c8866f018 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsdeletefromnamespacesresponse.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsDeleteFromNamespacesResponse](./kibana-plugin-core-server.savedobjectsdeletefromnamespacesresponse.md) + +## SavedObjectsDeleteFromNamespacesResponse interface + + +Signature: + +```typescript +export interface SavedObjectsDeleteFromNamespacesResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [namespaces](./kibana-plugin-core-server.savedobjectsdeletefromnamespacesresponse.namespaces.md) | string[] | The namespaces the object exists in after this operation is complete. An empty array indicates the object was deleted. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsdeletefromnamespacesresponse.namespaces.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsdeletefromnamespacesresponse.namespaces.md new file mode 100644 index 0000000000000..9600a9e891380 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsdeletefromnamespacesresponse.namespaces.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsDeleteFromNamespacesResponse](./kibana-plugin-core-server.savedobjectsdeletefromnamespacesresponse.md) > [namespaces](./kibana-plugin-core-server.savedobjectsdeletefromnamespacesresponse.namespaces.md) + +## SavedObjectsDeleteFromNamespacesResponse.namespaces property + +The namespaces the object exists in after this operation is complete. An empty array indicates the object was deleted. + +Signature: + +```typescript +namespaces: string[]; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.addtonamespaces.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.addtonamespaces.md index bbb20d2bc3b96..4b69b10318ed3 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.addtonamespaces.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.addtonamespaces.md @@ -9,7 +9,7 @@ Adds one or more namespaces to a given multi-namespace saved object. This method Signature: ```typescript -addToNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsAddToNamespacesOptions): Promise<{}>; +addToNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsAddToNamespacesOptions): Promise; ``` ## Parameters @@ -23,5 +23,5 @@ addToNamespaces(type: string, id: string, namespaces: string[], options?: SavedO Returns: -`Promise<{}>` +`Promise` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.deletefromnamespaces.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.deletefromnamespaces.md index 471c3e3c5092d..d5ffb6d9ff9d8 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.deletefromnamespaces.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.deletefromnamespaces.md @@ -9,7 +9,7 @@ Removes one or more namespaces from a given multi-namespace saved object. If no Signature: ```typescript -deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise<{}>; +deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise; ``` ## Parameters @@ -23,5 +23,5 @@ deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: S Returns: -`Promise<{}>` +`Promise` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldformatsgetconfigfn.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldformatsgetconfigfn.md index 4233eea42cded..c54e549c42a44 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldformatsgetconfigfn.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldformatsgetconfigfn.md @@ -7,5 +7,5 @@ Signature: ```typescript -export declare type FieldFormatsGetConfigFn = (key: string, defaultOverride?: T) => T; +export declare type FieldFormatsGetConfigFn = GetConfigFn; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getsearchparamsfromrequest.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getsearchparamsfromrequest.md index 5bd1694cbeea3..337b4b3302cc3 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getsearchparamsfromrequest.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getsearchparamsfromrequest.md @@ -4,12 +4,13 @@ ## getSearchParamsFromRequest() function + Signature: ```typescript export declare function getSearchParamsFromRequest(searchRequest: SearchRequest, dependencies: { - injectedMetadata: CoreStart['injectedMetadata']; - uiSettings: IUiSettingsClient; + esShardTimeout: number; + getConfig: GetConfigFn; }): ISearchRequestParams; ``` @@ -18,7 +19,7 @@ export declare function getSearchParamsFromRequest(searchRequest: SearchRequest, | Parameter | Type | Description | | --- | --- | --- | | searchRequest | SearchRequest | | -| dependencies | {
injectedMetadata: CoreStart['injectedMetadata'];
uiSettings: IUiSettingsClient;
} | | +| dependencies | {
esShardTimeout: number;
getConfig: GetConfigFn;
} | | Returns: diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fieldformatsgetconfigfn.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fieldformatsgetconfigfn.md index c8815ed42d3b3..a3f6c709246ea 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fieldformatsgetconfigfn.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fieldformatsgetconfigfn.md @@ -7,5 +7,5 @@ Signature: ```typescript -export declare type FieldFormatsGetConfigFn = (key: string, defaultOverride?: T) => T; +export declare type FieldFormatsGetConfigFn = GetConfigFn; ``` diff --git a/docs/visualize/vega.asciidoc b/docs/visualize/vega.asciidoc index 9b8c32d7e41f0..b231159e86bde 100644 --- a/docs/visualize/vega.asciidoc +++ b/docs/visualize/vega.asciidoc @@ -1,8 +1,6 @@ [[vega-graph]] == Vega -experimental[] - Build custom visualizations using Vega and Vega-Lite, backed by one or more data sources including {es}, Elastic Map Service, URL, or static data. Use the {kib} extensions to Vega to embed Vega into @@ -1259,7 +1257,7 @@ Override it by providing a different `stroke`, `fill`, or `color` (Vega-Lite) va [[vega-queries]] ==== Writing {es} queries in Vega -experimental[] {kib} extends the Vega https://vega.github.io/vega/docs/data/[data] elements +{kib} extends the Vega https://vega.github.io/vega/docs/data/[data] elements with support for direct {es} queries specified as a `url`. Because of this, {kib} is **unable to support dynamically loaded data**, @@ -1414,7 +1412,7 @@ try to get about 10-15 data points (buckets). [[vega-esmfiles]] === Access Elastic Map Service files -experimental[] Access the Elastic Map Service files via the same mechanism: +Access the Elastic Map Service files via the same mechanism: [source,yaml] ---- @@ -1619,7 +1617,7 @@ kibanaSetTimeFilter(start, end) [[vega-useful-links]] === Resources and examples -experimental[] To learn more about Vega and Vega-Lite, refer to the resources and examples. +To learn more about Vega and Vega-Lite, refer to the resources and examples. ==== Vega editor The https://vega.github.io/editor/[Vega Editor] includes examples for Vega & Vega-Lite, but does not support any diff --git a/examples/embeddable_examples/public/book/book_embeddable.tsx b/examples/embeddable_examples/public/book/book_embeddable.tsx index 73b1629d985b7..33876ab24414e 100644 --- a/examples/embeddable_examples/public/book/book_embeddable.tsx +++ b/examples/embeddable_examples/public/book/book_embeddable.tsx @@ -26,7 +26,6 @@ import { EmbeddableOutput, SavedObjectEmbeddableInput, ReferenceOrValueEmbeddable, - Container, } from '../../../../src/plugins/embeddable/public'; import { BookSavedObjectAttributes } from '../../common'; import { BookEmbeddableComponent } from './book_component'; @@ -104,16 +103,13 @@ export class BookEmbeddable extends Embeddable => { - const input = - this.getRoot() && (this.getRoot() as Container).getInput().panels[this.id].explicitInput - ? ((this.getRoot() as Container).getInput().panels[this.id] - .explicitInput as BookEmbeddableInput) - : this.input; + const input = this.attributeService.getExplicitInputFromEmbeddable(this); return this.attributeService.getInputAsValueType(input); }; getInputAsRefType = async (): Promise => { - return this.attributeService.getInputAsRefType(this.input, { showSaveModal: true }); + const input = this.attributeService.getExplicitInputFromEmbeddable(this); + return this.attributeService.getInputAsRefType(input, { showSaveModal: true }); }; public render(node: HTMLElement) { diff --git a/package.json b/package.json index 7d12af2eb5066..23fc31f369b8d 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "dependencies": { "@babel/core": "^7.11.1", "@babel/register": "^7.10.5", - "@elastic/apm-rum": "^5.4.0", + "@elastic/apm-rum": "^5.5.0", "@elastic/charts": "19.8.1", "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "7.9.0-rc.2", diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 382318ea86a34..76bcf5f7df665 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -274,7 +274,9 @@ export { SavedObjectsUpdateOptions, SavedObjectsUpdateResponse, SavedObjectsAddToNamespacesOptions, + SavedObjectsAddToNamespacesResponse, SavedObjectsDeleteFromNamespacesOptions, + SavedObjectsDeleteFromNamespacesResponse, SavedObjectsServiceStart, SavedObjectsServiceSetup, SavedObjectStatusMeta, diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index 941c4091a66a7..6d85223d1fc88 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -393,14 +393,14 @@ describe('SavedObjectsRepository', () => { }); describe('returns', () => { - it(`returns an empty object on success`, async () => { + it(`returns all existing and new namespaces on success`, async () => { const result = await addToNamespacesSuccess(type, id, [newNs1, newNs2]); - expect(result).toEqual({}); + expect(result).toEqual({ namespaces: [currentNs1, currentNs2, newNs1, newNs2] }); }); it(`succeeds when adding existing namespaces`, async () => { const result = await addToNamespacesSuccess(type, id, [currentNs1]); - expect(result).toEqual({}); + expect(result).toEqual({ namespaces: [currentNs1, currentNs2] }); }); }); }); @@ -3102,17 +3102,17 @@ describe('SavedObjectsRepository', () => { }); describe('returns', () => { - it(`returns an empty object on success (delete)`, async () => { + it(`returns an empty namespaces array on success (delete)`, async () => { const test = async (namespaces) => { const result = await deleteFromNamespacesSuccess(type, id, namespaces, namespaces); - expect(result).toEqual({}); + expect(result).toEqual({ namespaces: [] }); client.delete.mockClear(); }; await test([namespace1]); await test([namespace1, namespace2]); }); - it(`returns an empty object on success (update)`, async () => { + it(`returns remaining namespaces on success (update)`, async () => { const test = async (remaining) => { const currentNamespaces = [namespace1].concat(remaining); const result = await deleteFromNamespacesSuccess( @@ -3121,7 +3121,7 @@ describe('SavedObjectsRepository', () => { [namespace1], currentNamespaces ); - expect(result).toEqual({}); + expect(result).toEqual({ namespaces: remaining }); client.delete.mockClear(); }; await test([namespace2]); @@ -3132,7 +3132,7 @@ describe('SavedObjectsRepository', () => { const namespaces = [namespace2]; const currentNamespaces = [namespace1]; const result = await deleteFromNamespacesSuccess(type, id, namespaces, currentNamespaces); - expect(result).toEqual({}); + expect(result).toEqual({ namespaces: currentNamespaces }); }); }); }); diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 9f6db446ea195..28d409f7b65bb 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -52,7 +52,9 @@ import { SavedObjectsBulkUpdateOptions, SavedObjectsDeleteOptions, SavedObjectsAddToNamespacesOptions, + SavedObjectsAddToNamespacesResponse, SavedObjectsDeleteFromNamespacesOptions, + SavedObjectsDeleteFromNamespacesResponse, } from '../saved_objects_client'; import { SavedObject, @@ -947,7 +949,7 @@ export class SavedObjectsRepository { id: string, namespaces: string[], options: SavedObjectsAddToNamespacesOptions = {} - ): Promise<{}> { + ): Promise { if (!this._allowedTypes.includes(type)) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } @@ -996,7 +998,7 @@ export class SavedObjectsRepository { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } - return {}; + return { namespaces: doc.namespaces }; } /** @@ -1009,7 +1011,7 @@ export class SavedObjectsRepository { id: string, namespaces: string[], options: SavedObjectsDeleteFromNamespacesOptions = {} - ): Promise<{}> { + ): Promise { if (!this._allowedTypes.includes(type)) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } @@ -1063,7 +1065,7 @@ export class SavedObjectsRepository { // see "404s from missing index" above throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } - return {}; + return { namespaces: doc.namespaces }; } else { // if there are no namespaces remaining, delete the saved object const { body, statusCode } = await this.client.delete( @@ -1080,7 +1082,7 @@ export class SavedObjectsRepository { const deleted = body.result === 'deleted'; if (deleted) { - return {}; + return { namespaces: [] }; } const deleteDocNotFound = body.result === 'not_found'; diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts index 6a9f4f5143e84..812669ee108a2 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.ts @@ -135,6 +135,15 @@ export interface SavedObjectsAddToNamespacesOptions extends SavedObjectsBaseOpti refresh?: MutatingOperationRefreshSetting; } +/** + * + * @public + */ +export interface SavedObjectsAddToNamespacesResponse { + /** The namespaces the object exists in after this operation is complete. */ + namespaces: string[]; +} + /** * * @public @@ -144,6 +153,15 @@ export interface SavedObjectsDeleteFromNamespacesOptions extends SavedObjectsBas refresh?: MutatingOperationRefreshSetting; } +/** + * + * @public + */ +export interface SavedObjectsDeleteFromNamespacesResponse { + /** The namespaces the object exists in after this operation is complete. An empty array indicates the object was deleted. */ + namespaces: string[]; +} + /** * * @public @@ -320,7 +338,7 @@ export class SavedObjectsClient { id: string, namespaces: string[], options: SavedObjectsAddToNamespacesOptions = {} - ): Promise<{}> { + ): Promise { return await this._repository.addToNamespaces(type, id, namespaces, options); } @@ -337,7 +355,7 @@ export class SavedObjectsClient { id: string, namespaces: string[], options: SavedObjectsDeleteFromNamespacesOptions = {} - ): Promise<{}> { + ): Promise { return await this._repository.deleteFromNamespaces(type, id, namespaces, options); } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index ff178e236a12f..772be68f507d5 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -2023,6 +2023,11 @@ export interface SavedObjectsAddToNamespacesOptions extends SavedObjectsBaseOpti version?: string; } +// @public (undocumented) +export interface SavedObjectsAddToNamespacesResponse { + namespaces: string[]; +} + // Warning: (ae-forgotten-export) The symbol "SavedObjectDoc" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "Referencable" needs to be exported by the entry point index.d.ts // @@ -2092,13 +2097,13 @@ export interface SavedObjectsBulkUpdateResponse { export class SavedObjectsClient { // @internal constructor(repository: ISavedObjectsRepository); - addToNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsAddToNamespacesOptions): Promise<{}>; + addToNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsAddToNamespacesOptions): Promise; bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; - deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise<{}>; + deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise; // (undocumented) static errors: typeof SavedObjectsErrorHelpers; // (undocumented) @@ -2194,6 +2199,11 @@ export interface SavedObjectsDeleteFromNamespacesOptions extends SavedObjectsBas refresh?: MutatingOperationRefreshSetting; } +// @public (undocumented) +export interface SavedObjectsDeleteFromNamespacesResponse { + namespaces: string[]; +} + // @public (undocumented) export interface SavedObjectsDeleteOptions extends SavedObjectsBaseOptions { refresh?: MutatingOperationRefreshSetting; @@ -2492,7 +2502,7 @@ export interface SavedObjectsRawDoc { // @public (undocumented) export class SavedObjectsRepository { - addToNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsAddToNamespacesOptions): Promise<{}>; + addToNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsAddToNamespacesOptions): Promise; bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; @@ -2503,7 +2513,7 @@ export class SavedObjectsRepository { static createRepository(migrator: KibanaMigrator, typeRegistry: SavedObjectTypeRegistry, indexName: string, client: ElasticsearchClient, includedHiddenTypes?: string[], injectedConstructor?: any): ISavedObjectsRepository; delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; - deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise<{}>; + deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise; // (undocumented) find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespaces, type, filter, preference, }: SavedObjectsFindOptions): Promise>; get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; diff --git a/src/legacy/ui/public/new_platform/set_services.ts b/src/legacy/ui/public/new_platform/set_services.ts index ee92eda064aa8..036157a9f3fbc 100644 --- a/src/legacy/ui/public/new_platform/set_services.ts +++ b/src/legacy/ui/public/new_platform/set_services.ts @@ -41,7 +41,6 @@ interface NpStart { export function setSetupServices(npSetup: NpSetup) { // Services that need to be set in the legacy platform since the legacy data plugin // which previously provided them has been removed. - dataServices.setInjectedMetadata(npSetup.core.injectedMetadata); visualizationsServices.setUISettings(npSetup.core.uiSettings); visualizationsServices.setUsageCollector(npSetup.plugins.usageCollection); } @@ -49,7 +48,6 @@ export function setSetupServices(npSetup: NpSetup) { export function setStartServices(npStart: NpStart) { // Services that need to be set in the legacy platform since the legacy data plugin // which previously provided them has been removed. - dataServices.setHttp(npStart.core.http); dataServices.setNotifications(npStart.core.notifications); dataServices.setOverlays(npStart.core.overlays); dataServices.setUiSettings(npStart.core.uiSettings); diff --git a/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx b/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx new file mode 100644 index 0000000000000..9fa7fff9ad087 --- /dev/null +++ b/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx @@ -0,0 +1,162 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { isErrorEmbeddable, IContainer, ReferenceOrValueEmbeddable } from '../../embeddable_plugin'; +import { DashboardContainer } from '../embeddable'; +import { getSampleDashboardInput } from '../test_helpers'; +import { + CONTACT_CARD_EMBEDDABLE, + ContactCardEmbeddableFactory, + ContactCardEmbeddable, + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, +} from '../../embeddable_plugin_test_samples'; +import { coreMock } from '../../../../../core/public/mocks'; +import { CoreStart } from 'kibana/public'; +import { AddToLibraryAction } from '.'; +import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks'; +import { ViewMode } from '../../../../embeddable/public'; + +const { setup, doStart } = embeddablePluginMock.createInstance(); +setup.registerEmbeddableFactory( + CONTACT_CARD_EMBEDDABLE, + new ContactCardEmbeddableFactory((() => null) as any, {} as any) +); +const start = doStart(); + +let container: DashboardContainer; +let embeddable: ContactCardEmbeddable & ReferenceOrValueEmbeddable; +let coreStart: CoreStart; +beforeEach(async () => { + coreStart = coreMock.createStart(); + + const containerOptions = { + ExitFullScreenButton: () => null, + SavedObjectFinder: () => null, + application: {} as any, + embeddable: start, + inspector: {} as any, + notifications: {} as any, + overlays: coreStart.overlays, + savedObjectMetaData: {} as any, + uiActions: {} as any, + }; + + container = new DashboardContainer(getSampleDashboardInput(), containerOptions); + + const contactCardEmbeddable = await container.addNewEmbeddable< + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, + ContactCardEmbeddable + >(CONTACT_CARD_EMBEDDABLE, { + firstName: 'Kibanana', + }); + + if (isErrorEmbeddable(contactCardEmbeddable)) { + throw new Error('Failed to create embeddable'); + } else { + embeddable = embeddablePluginMock.mockRefOrValEmbeddable< + ContactCardEmbeddable, + ContactCardEmbeddableInput + >(contactCardEmbeddable, { + mockedByReferenceInput: { savedObjectId: 'testSavedObjectId', id: contactCardEmbeddable.id }, + mockedByValueInput: { firstName: 'Kibanana', id: contactCardEmbeddable.id }, + }); + embeddable.updateInput({ viewMode: ViewMode.EDIT }); + } +}); + +test('Add to library is compatible when embeddable on dashboard has value type input', async () => { + const action = new AddToLibraryAction(); + embeddable.updateInput(await embeddable.getInputAsValueType()); + expect(await action.isCompatible({ embeddable })).toBe(true); +}); + +test('Add to library is not compatible when embeddable input is by reference', async () => { + const action = new AddToLibraryAction(); + embeddable.updateInput(await embeddable.getInputAsRefType()); + expect(await action.isCompatible({ embeddable })).toBe(false); +}); + +test('Add to library is not compatible when view mode is set to view', async () => { + const action = new AddToLibraryAction(); + embeddable.updateInput(await embeddable.getInputAsRefType()); + embeddable.updateInput({ viewMode: ViewMode.VIEW }); + expect(await action.isCompatible({ embeddable })).toBe(false); +}); + +test('Add to library is not compatible when embeddable is not in a dashboard container', async () => { + let orphanContactCard = await container.addNewEmbeddable< + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, + ContactCardEmbeddable + >(CONTACT_CARD_EMBEDDABLE, { + firstName: 'Orphan', + }); + orphanContactCard = embeddablePluginMock.mockRefOrValEmbeddable< + ContactCardEmbeddable, + ContactCardEmbeddableInput + >(orphanContactCard, { + mockedByReferenceInput: { savedObjectId: 'test', id: orphanContactCard.id }, + mockedByValueInput: { firstName: 'Kibanana', id: orphanContactCard.id }, + }); + const action = new AddToLibraryAction(); + expect(await action.isCompatible({ embeddable: orphanContactCard })).toBe(false); +}); + +test('Add to library replaces embeddableId but retains panel count', async () => { + const dashboard = embeddable.getRoot() as IContainer; + const originalPanelCount = Object.keys(dashboard.getInput().panels).length; + const originalPanelKeySet = new Set(Object.keys(dashboard.getInput().panels)); + const action = new AddToLibraryAction(); + await action.execute({ embeddable }); + expect(Object.keys(container.getInput().panels).length).toEqual(originalPanelCount); + + const newPanelId = Object.keys(container.getInput().panels).find( + (key) => !originalPanelKeySet.has(key) + ); + expect(newPanelId).toBeDefined(); + const newPanel = container.getInput().panels[newPanelId!]; + expect(newPanel.type).toEqual(embeddable.type); +}); + +test('Add to library returns reference type input', async () => { + const complicatedAttributes = { + attribute1: 'The best attribute', + attribute2: 22, + attribute3: ['array', 'of', 'strings'], + attribute4: { nestedattribute: 'hello from the nest' }, + }; + + embeddable = embeddablePluginMock.mockRefOrValEmbeddable(embeddable, { + mockedByReferenceInput: { savedObjectId: 'testSavedObjectId', id: embeddable.id }, + mockedByValueInput: { attributes: complicatedAttributes, id: embeddable.id }, + }); + const dashboard = embeddable.getRoot() as IContainer; + const originalPanelKeySet = new Set(Object.keys(dashboard.getInput().panels)); + const action = new AddToLibraryAction(); + await action.execute({ embeddable }); + const newPanelId = Object.keys(container.getInput().panels).find( + (key) => !originalPanelKeySet.has(key) + ); + expect(newPanelId).toBeDefined(); + const newPanel = container.getInput().panels[newPanelId!]; + expect(newPanel.type).toEqual(embeddable.type); + expect(newPanel.explicitInput.attributes).toBeUndefined(); + expect(newPanel.explicitInput.savedObjectId).toBe('testSavedObjectId'); +}); diff --git a/src/plugins/dashboard/public/application/actions/add_to_library_action.tsx b/src/plugins/dashboard/public/application/actions/add_to_library_action.tsx new file mode 100644 index 0000000000000..3cc1a8a1dffe1 --- /dev/null +++ b/src/plugins/dashboard/public/application/actions/add_to_library_action.tsx @@ -0,0 +1,93 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import _ from 'lodash'; +import uuid from 'uuid'; +import { ActionByType, IncompatibleActionError } from '../../ui_actions_plugin'; +import { ViewMode, PanelState, IEmbeddable } from '../../embeddable_plugin'; +import { + PanelNotFoundError, + EmbeddableInput, + isReferenceOrValueEmbeddable, +} from '../../../../embeddable/public'; +import { DashboardPanelState, DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '..'; + +export const ACTION_ADD_TO_LIBRARY = 'addToFromLibrary'; + +export interface AddToLibraryActionContext { + embeddable: IEmbeddable; +} + +export class AddToLibraryAction implements ActionByType { + public readonly type = ACTION_ADD_TO_LIBRARY; + public readonly id = ACTION_ADD_TO_LIBRARY; + public order = 15; + + constructor() {} + + public getDisplayName({ embeddable }: AddToLibraryActionContext) { + if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) { + throw new IncompatibleActionError(); + } + return i18n.translate('dashboard.panel.AddToLibrary', { + defaultMessage: 'Add to library', + }); + } + + public getIconType({ embeddable }: AddToLibraryActionContext) { + if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) { + throw new IncompatibleActionError(); + } + return 'folderCheck'; + } + + public async isCompatible({ embeddable }: AddToLibraryActionContext) { + return Boolean( + embeddable.getInput()?.viewMode !== ViewMode.VIEW && + embeddable.getRoot() && + embeddable.getRoot().isContainer && + embeddable.getRoot().type === DASHBOARD_CONTAINER_TYPE && + isReferenceOrValueEmbeddable(embeddable) && + !embeddable.inputIsRefType(embeddable.getInput()) + ); + } + + public async execute({ embeddable }: AddToLibraryActionContext) { + if (!isReferenceOrValueEmbeddable(embeddable)) { + throw new IncompatibleActionError(); + } + + const newInput = await embeddable.getInputAsRefType(); + + embeddable.updateInput(newInput); + + const dashboard = embeddable.getRoot() as DashboardContainer; + const panelToReplace = dashboard.getInput().panels[embeddable.id] as DashboardPanelState; + if (!panelToReplace) { + throw new PanelNotFoundError(); + } + + const newPanel: PanelState = { + type: embeddable.type, + explicitInput: { ...newInput, id: uuid.v4() }, + }; + dashboard.replacePanel(panelToReplace, newPanel); + } +} diff --git a/src/plugins/dashboard/public/application/actions/index.ts b/src/plugins/dashboard/public/application/actions/index.ts index be183976c676f..4343a3409b696 100644 --- a/src/plugins/dashboard/public/application/actions/index.ts +++ b/src/plugins/dashboard/public/application/actions/index.ts @@ -33,7 +33,12 @@ export { ACTION_CLONE_PANEL, } from './clone_panel_action'; export { + AddToLibraryAction, + AddToLibraryActionContext, + ACTION_ADD_TO_LIBRARY, +} from './add_to_library_action'; +export { + UnlinkFromLibraryAction, UnlinkFromLibraryActionContext, ACTION_UNLINK_FROM_LIBRARY, - UnlinkFromLibraryAction, } from './unlink_from_library_action'; diff --git a/src/plugins/dashboard/public/attribute_service/attribute_service.tsx b/src/plugins/dashboard/public/attribute_service/attribute_service.tsx index c2f529fe399f3..fe5f6a0c8e2bd 100644 --- a/src/plugins/dashboard/public/attribute_service/attribute_service.tsx +++ b/src/plugins/dashboard/public/attribute_service/attribute_service.tsx @@ -30,13 +30,21 @@ import { SimpleSavedObject, I18nStart, NotificationsStart, + OverlayStart, } from '../../../../core/public'; import { SavedObjectSaveModal, showSaveModal, OnSaveProps, SaveResult, + checkForDuplicateTitle, } from '../../../saved_objects/public'; +import { + EmbeddableStart, + EmbeddableFactory, + EmbeddableFactoryNotFoundError, + Container, +} from '../../../embeddable/public'; /** * The attribute service is a shared, generic service that embeddables can use to provide the functionality @@ -49,12 +57,22 @@ export class AttributeService< ValType extends EmbeddableInput & { attributes: SavedObjectAttributes }, RefType extends SavedObjectEmbeddableInput > { + private embeddableFactory: EmbeddableFactory; + constructor( private type: string, private savedObjectsClient: SavedObjectsClientContract, + private overlays: OverlayStart, private i18nContext: I18nStart['Context'], - private toasts: NotificationsStart['toasts'] - ) {} + private toasts: NotificationsStart['toasts'], + getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'] + ) { + const factory = getEmbeddableFactory(this.type); + if (!factory) { + throw new EmbeddableFactoryNotFoundError(this.type); + } + this.embeddableFactory = factory; + } public async unwrapAttributes(input: RefType | ValType): Promise { if (this.inputIsRefType(input)) { @@ -105,6 +123,15 @@ export class AttributeService< return isSavedObjectEmbeddableInput(input); }; + public getExplicitInputFromEmbeddable(embeddable: IEmbeddable): ValType | RefType { + return embeddable.getRoot() && + (embeddable.getRoot() as Container).getInput().panels[embeddable.id].explicitInput + ? ((embeddable.getRoot() as Container).getInput().panels[embeddable.id].explicitInput as + | ValType + | RefType) + : (embeddable.getInput() as ValType | RefType); + } + getInputAsValueType = async (input: ValType | RefType): Promise => { if (!this.inputIsRefType(input)) { return input; @@ -124,16 +151,31 @@ export class AttributeService< if (this.inputIsRefType(input)) { return input; } - return new Promise((resolve, reject) => { const onSave = async (props: OnSaveProps): Promise => { + await checkForDuplicateTitle( + { + title: props.newTitle, + copyOnSave: false, + lastSavedTitle: '', + getEsType: () => this.type, + getDisplayName: this.embeddableFactory.getDisplayName, + }, + props.isTitleDuplicateConfirmed, + props.onTitleDuplicate, + { + savedObjectsClient: this.savedObjectsClient, + overlays: this.overlays, + } + ); try { - input.attributes.title = props.newTitle; - const wrappedInput = (await this.wrapAttributes(input.attributes, true)) as RefType; + const newAttributes = { ...input.attributes }; + newAttributes.title = props.newTitle; + const wrappedInput = (await this.wrapAttributes(newAttributes, true)) as RefType; resolve(wrappedInput); return { id: wrappedInput.savedObjectId }; } catch (error) { - reject(); + reject(error); return { error }; } }; diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 2a36f2d801850..a788b06f91905 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -95,6 +95,11 @@ import { addEmbeddableToDashboardUrl } from './url_utils/url_helper'; import { PlaceholderEmbeddableFactory } from './application/embeddable/placeholder'; import { UrlGeneratorState } from '../../share/public'; import { AttributeService } from '.'; +import { + AddToLibraryAction, + ACTION_ADD_TO_LIBRARY, + AddToLibraryActionContext, +} from './application/actions/add_to_library_action'; declare module '../../share/public' { export interface UrlGeneratorStateMapping { @@ -155,6 +160,7 @@ declare module '../../../plugins/ui_actions/public' { [ACTION_EXPAND_PANEL]: ExpandPanelActionContext; [ACTION_REPLACE_PANEL]: ReplacePanelActionContext; [ACTION_CLONE_PANEL]: ClonePanelActionContext; + [ACTION_ADD_TO_LIBRARY]: AddToLibraryActionContext; [ACTION_UNLINK_FROM_LIBRARY]: UnlinkFromLibraryActionContext; } } @@ -406,6 +412,7 @@ export class DashboardPlugin const { uiActions, data: { indexPatterns, search }, + embeddable, } = plugins; const SavedObjectFinder = getSavedObjectFinder(core.savedObjects, core.uiSettings); @@ -424,6 +431,9 @@ export class DashboardPlugin uiActions.attachAction(CONTEXT_MENU_TRIGGER, clonePanelAction.id); if (this.dashboardFeatureFlagConfig?.allowByValueEmbeddables) { + const addToLibraryAction = new AddToLibraryAction(); + uiActions.registerAction(addToLibraryAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, addToLibraryAction.id); const unlinkFromLibraryAction = new UnlinkFromLibraryAction(); uiActions.registerAction(unlinkFromLibraryAction); uiActions.attachAction(CONTEXT_MENU_TRIGGER, unlinkFromLibraryAction.id); @@ -452,8 +462,10 @@ export class DashboardPlugin new AttributeService( type, core.savedObjects.client, + core.overlays, core.i18n.Context, - core.notifications.toasts + core.notifications.toasts, + embeddable.getEmbeddableFactory ), }; } diff --git a/src/plugins/data/common/es_query/es_query/get_es_query_config.ts b/src/plugins/data/common/es_query/es_query/get_es_query_config.ts index ff8fc5b11b26e..87dbbcee03b81 100644 --- a/src/plugins/data/common/es_query/es_query/get_es_query_config.ts +++ b/src/plugins/data/common/es_query/es_query/get_es_query_config.ts @@ -18,10 +18,10 @@ */ import { EsQueryConfig } from './build_es_query'; -import { UI_SETTINGS } from '../../'; +import { GetConfigFn, UI_SETTINGS } from '../../'; interface KibanaConfig { - get(key: string): T; + get: GetConfigFn; } export function getEsQueryConfig(config: KibanaConfig) { diff --git a/src/plugins/data/common/field_formats/converters/date_nanos_shared.ts b/src/plugins/data/common/field_formats/converters/date_nanos_shared.ts index 89a63243c76f0..7a225cd7d1ae7 100644 --- a/src/plugins/data/common/field_formats/converters/date_nanos_shared.ts +++ b/src/plugins/data/common/field_formats/converters/date_nanos_shared.ts @@ -20,7 +20,8 @@ import { i18n } from '@kbn/i18n'; import { memoize, noop } from 'lodash'; import moment, { Moment } from 'moment'; -import { FieldFormat, FIELD_FORMAT_IDS, KBN_FIELD_TYPES, TextContextTypeConvert } from '../../'; +import { FieldFormat, FIELD_FORMAT_IDS, KBN_FIELD_TYPES } from '../../'; +import { TextContextTypeConvert } from '../types'; /** * Analyse the given moment.js format pattern for the fractional sec part (S,SS,SSS...) diff --git a/src/plugins/data/common/field_formats/field_formats_registry.ts b/src/plugins/data/common/field_formats/field_formats_registry.ts index 4b847ebc358d7..32f9f37b9ba53 100644 --- a/src/plugins/data/common/field_formats/field_formats_registry.ts +++ b/src/plugins/data/common/field_formats/field_formats_registry.ts @@ -32,7 +32,7 @@ import { import { baseFormatters } from './constants/base_formatters'; import { FieldFormat } from './field_format'; import { SerializedFieldFormat } from '../../../expressions/common/types'; -import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../types'; +import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../kbn_field_types/types'; import { UI_SETTINGS } from '../constants'; export class FieldFormatsRegistry { diff --git a/src/plugins/data/common/field_formats/types.ts b/src/plugins/data/common/field_formats/types.ts index 6f773378de08d..daa44b2b0f85b 100644 --- a/src/plugins/data/common/field_formats/types.ts +++ b/src/plugins/data/common/field_formats/types.ts @@ -16,6 +16,8 @@ * specific language governing permissions and limitations * under the License. */ + +import { GetConfigFn } from '../types'; import { FieldFormat } from './field_format'; import { FieldFormatsRegistry } from './field_formats_registry'; @@ -72,7 +74,7 @@ export interface FieldFormatConfig { es?: boolean; } -export type FieldFormatsGetConfigFn = (key: string, defaultOverride?: T) => T; +export type FieldFormatsGetConfigFn = GetConfigFn; export type IFieldFormat = PublicMethodsOf; diff --git a/src/plugins/data/common/search/aggs/aggs_service.ts b/src/plugins/data/common/search/aggs/aggs_service.ts index 59c54fcce6838..6f3e3904dbbd5 100644 --- a/src/plugins/data/common/search/aggs/aggs_service.ts +++ b/src/plugins/data/common/search/aggs/aggs_service.ts @@ -19,6 +19,7 @@ import { ExpressionsServiceSetup } from 'src/plugins/expressions/common'; import { UI_SETTINGS } from '../../../common'; +import { GetConfigFn } from '../../types'; import { AggConfigs, AggTypesRegistry, @@ -48,7 +49,7 @@ export interface AggsCommonSetupDependencies { /** @internal */ export interface AggsCommonStartDependencies { - getConfig: (key: string) => T; + getConfig: GetConfigFn; } /** diff --git a/src/plugins/data/common/types.ts b/src/plugins/data/common/types.ts index e2ec1a031b0ca..a1e1252b3112f 100644 --- a/src/plugins/data/common/types.ts +++ b/src/plugins/data/common/types.ts @@ -20,4 +20,15 @@ export * from './query/types'; export * from './kbn_field_types/types'; export * from './index_patterns/types'; -export { TextContextTypeConvert, IFieldFormatMetaParams } from './field_formats/types'; + +/** + * If a service is being shared on both the client and the server, and + * the client code requires synchronous access to uiSettings, both client + * and server should wrap the core uiSettings services in a function + * matching this signature. + * + * This matches the signature of the public `core.uiSettings.get`, and + * should only be used in scenarios where async access to uiSettings is + * not possible. + */ +export type GetConfigFn = (key: string, defaultOverride?: T) => T; diff --git a/src/plugins/data/public/field_formats/converters/date.ts b/src/plugins/data/public/field_formats/converters/date.ts index 78ef8b293e8b9..8070633ef2dfe 100644 --- a/src/plugins/data/public/field_formats/converters/date.ts +++ b/src/plugins/data/public/field_formats/converters/date.ts @@ -20,12 +20,8 @@ import { i18n } from '@kbn/i18n'; import { memoize, noop } from 'lodash'; import moment from 'moment'; -import { - FieldFormat, - KBN_FIELD_TYPES, - TextContextTypeConvert, - FIELD_FORMAT_IDS, -} from '../../../common'; +import { FieldFormat, KBN_FIELD_TYPES, FIELD_FORMAT_IDS } from '../../../common'; +import { TextContextTypeConvert } from '../../../common/field_formats/types'; export class DateFormat extends FieldFormat { static id = FIELD_FORMAT_IDS.DATE; diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 564c571b6ccd6..ee0b0714febc0 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -48,9 +48,7 @@ import { } from './index_patterns'; import { setFieldFormats, - setHttp, setIndexPatterns, - setInjectedMetadata, setNotifications, setOverlays, setQueryService, @@ -164,11 +162,9 @@ export class DataPublicPlugin public start(core: CoreStart, { uiActions }: DataStartDependencies): DataPublicPluginStart { const { uiSettings, http, notifications, savedObjects, overlays, application } = core; - setHttp(http); setNotifications(notifications); setOverlays(overlays); setUiSettings(uiSettings); - setInjectedMetadata(core.injectedMetadata); const fieldFormats = this.fieldFormatsService.start(); setFieldFormats(fieldFormats); diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 7041bcdfa4221..7defddb8f570a 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -34,7 +34,6 @@ import { InjectedIntl } from '@kbn/i18n/react'; import { ISearchSource as ISearchSource_2 } from 'src/plugins/data/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { IUiSettingsClient } from 'src/core/public'; -import { IUiSettingsClient as IUiSettingsClient_3 } from 'kibana/public'; import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { KibanaConfigType } from 'src/core/server/kibana_config'; import { Location } from 'history'; @@ -594,10 +593,11 @@ export const fieldFormats: { // @public (undocumented) export type FieldFormatsContentType = 'html' | 'text'; +// Warning: (ae-forgotten-export) The symbol "GetConfigFn" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "FieldFormatsGetConfigFn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type FieldFormatsGetConfigFn = (key: string, defaultOverride?: T) => T; +export type FieldFormatsGetConfigFn = GetConfigFn; // Warning: (ae-missing-release-tag) "FieldList" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -727,12 +727,11 @@ export function getEsPreference(uiSettings: IUiSettingsClient_2, sessionId?: str export const getKbnTypeNames: () => string[]; // Warning: (ae-forgotten-export) The symbol "ISearchRequestParams" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "getSearchParamsFromRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export function getSearchParamsFromRequest(searchRequest: SearchRequest, dependencies: { - injectedMetadata: CoreStart['injectedMetadata']; - uiSettings: IUiSettingsClient_3; + esShardTimeout: number; + getConfig: GetConfigFn; }): ISearchRequestParams; // Warning: (ae-missing-release-tag) "getTime" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/src/plugins/data/public/search/collectors/create_usage_collector.test.ts b/src/plugins/data/public/search/collectors/create_usage_collector.test.ts index aaaac5ae6ff7c..315d4678cabf1 100644 --- a/src/plugins/data/public/search/collectors/create_usage_collector.test.ts +++ b/src/plugins/data/public/search/collectors/create_usage_collector.test.ts @@ -42,7 +42,7 @@ describe('Search Usage Collector', () => { {} as any, ]); mockUsageCollectionSetup = usageCollectionPluginMock.createSetupContract(); - usageCollector = createUsageCollector(mockCoreSetup, mockUsageCollectionSetup); + usageCollector = createUsageCollector(mockCoreSetup.getStartServices, mockUsageCollectionSetup); }); test('tracks query timeouts', async () => { diff --git a/src/plugins/data/public/search/collectors/create_usage_collector.ts b/src/plugins/data/public/search/collectors/create_usage_collector.ts index 7adb0c3caa675..321b2c5b99049 100644 --- a/src/plugins/data/public/search/collectors/create_usage_collector.ts +++ b/src/plugins/data/public/search/collectors/create_usage_collector.ts @@ -18,16 +18,16 @@ */ import { first } from 'rxjs/operators'; -import { CoreSetup } from '../../../../../core/public'; +import { StartServicesAccessor } from '../../../../../core/public'; import { METRIC_TYPE, UsageCollectionSetup } from '../../../../usage_collection/public'; import { SEARCH_EVENT_TYPE, SearchUsageCollector } from './types'; export const createUsageCollector = ( - core: CoreSetup, + getStartServices: StartServicesAccessor, usageCollection?: UsageCollectionSetup ): SearchUsageCollector => { const getCurrentApp = async () => { - const [{ application }] = await core.getStartServices(); + const [{ application }] = await getStartServices(); return application.currentAppId$.pipe(first()).toPromise(); }; diff --git a/src/plugins/data/public/search/fetch/get_search_params.test.ts b/src/plugins/data/public/search/fetch/get_search_params.test.ts index f9b62fdd4fc61..1ecb879b1602d 100644 --- a/src/plugins/data/public/search/fetch/get_search_params.test.ts +++ b/src/plugins/data/public/search/fetch/get_search_params.test.ts @@ -18,13 +18,10 @@ */ import { getSearchParams } from './get_search_params'; -import { IUiSettingsClient } from 'kibana/public'; -import { UI_SETTINGS } from '../../../common'; +import { GetConfigFn, UI_SETTINGS } from '../../../common'; -function getConfigStub(config: any = {}) { - return { - get: (key) => config[key], - } as IUiSettingsClient; +function getConfigStub(config: any = {}): GetConfigFn { + return (key) => config[key]; } describe('getSearchParams', () => { diff --git a/src/plugins/data/public/search/fetch/get_search_params.ts b/src/plugins/data/public/search/fetch/get_search_params.ts index 3246156b6b37e..5e0395189f647 100644 --- a/src/plugins/data/public/search/fetch/get_search_params.ts +++ b/src/plugins/data/public/search/fetch/get_search_params.ts @@ -17,37 +17,36 @@ * under the License. */ -import { IUiSettingsClient, CoreStart } from 'kibana/public'; -import { UI_SETTINGS, ISearchRequestParams } from '../../../common'; +import { UI_SETTINGS, ISearchRequestParams, GetConfigFn } from '../../../common'; import { SearchRequest } from './types'; const sessionId = Date.now(); -export function getSearchParams(config: IUiSettingsClient, esShardTimeout: number = 0) { +export function getSearchParams(getConfig: GetConfigFn, esShardTimeout: number = 0) { return { rest_total_hits_as_int: true, ignore_unavailable: true, - ignore_throttled: getIgnoreThrottled(config), - max_concurrent_shard_requests: getMaxConcurrentShardRequests(config), - preference: getPreference(config), + ignore_throttled: getIgnoreThrottled(getConfig), + max_concurrent_shard_requests: getMaxConcurrentShardRequests(getConfig), + preference: getPreference(getConfig), timeout: getTimeout(esShardTimeout), }; } -export function getIgnoreThrottled(config: IUiSettingsClient) { - return !config.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN); +export function getIgnoreThrottled(getConfig: GetConfigFn) { + return !getConfig(UI_SETTINGS.SEARCH_INCLUDE_FROZEN); } -export function getMaxConcurrentShardRequests(config: IUiSettingsClient) { - const maxConcurrentShardRequests = config.get(UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS); +export function getMaxConcurrentShardRequests(getConfig: GetConfigFn) { + const maxConcurrentShardRequests = getConfig(UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS); return maxConcurrentShardRequests > 0 ? maxConcurrentShardRequests : undefined; } -export function getPreference(config: IUiSettingsClient) { - const setRequestPreference = config.get(UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE); +export function getPreference(getConfig: GetConfigFn) { + const setRequestPreference = getConfig(UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE); if (setRequestPreference === 'sessionId') return sessionId; return setRequestPreference === 'custom' - ? config.get(UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE) + ? getConfig(UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE) : undefined; } @@ -55,13 +54,15 @@ export function getTimeout(esShardTimeout: number) { return esShardTimeout > 0 ? `${esShardTimeout}ms` : undefined; } +/** @public */ +// TODO: Could provide this on runtime contract with dependencies +// already wired up. export function getSearchParamsFromRequest( searchRequest: SearchRequest, - dependencies: { injectedMetadata: CoreStart['injectedMetadata']; uiSettings: IUiSettingsClient } + dependencies: { esShardTimeout: number; getConfig: GetConfigFn } ): ISearchRequestParams { - const { injectedMetadata, uiSettings } = dependencies; - const esShardTimeout = injectedMetadata.getInjectedVar('esShardTimeout') as number; - const searchParams = getSearchParams(uiSettings, esShardTimeout); + const { esShardTimeout, getConfig } = dependencies; + const searchParams = getSearchParams(getConfig, esShardTimeout); return { index: searchRequest.index.title || searchRequest.index, diff --git a/src/plugins/data/public/search/fetch/types.ts b/src/plugins/data/public/search/fetch/types.ts index dda66d6b5238d..18d277204815b 100644 --- a/src/plugins/data/public/search/fetch/types.ts +++ b/src/plugins/data/public/search/fetch/types.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IUiSettingsClient } from '../../../../../core/public'; +import { GetConfigFn } from '../../../common'; import { ISearchStartLegacy } from '../types'; export type SearchRequest = any; @@ -30,7 +30,7 @@ export interface FetchOptions { export interface FetchHandlers { legacySearchService: ISearchStartLegacy; - config: IUiSettingsClient; + config: { get: GetConfigFn }; esShardTimeout: number; } diff --git a/src/plugins/data/public/search/legacy/default_search_strategy.ts b/src/plugins/data/public/search/legacy/default_search_strategy.ts index 284768bc5a1cc..6ccb0a01cf898 100644 --- a/src/plugins/data/public/search/legacy/default_search_strategy.ts +++ b/src/plugins/data/public/search/legacy/default_search_strategy.ts @@ -42,7 +42,7 @@ function msearch({ index: index.title || index, search_type: searchType, ignore_unavailable: true, - preference: getPreference(config), + preference: getPreference(config.get), }; const inlineBody = { ...body, @@ -52,7 +52,7 @@ function msearch({ }); const searching = es.msearch({ - ...getMSearchParams(config), + ...getMSearchParams(config.get), body: `${inlineRequests.join('\n')}\n`, }); diff --git a/src/plugins/data/public/search/legacy/es_client/get_es_client.ts b/src/plugins/data/public/search/legacy/es_client/get_es_client.ts index 93d9d24920271..4367544ad9ff4 100644 --- a/src/plugins/data/public/search/legacy/es_client/get_es_client.ts +++ b/src/plugins/data/public/search/legacy/es_client/get_es_client.ts @@ -22,17 +22,20 @@ import { default as es } from 'elasticsearch-browser/elasticsearch'; import { CoreStart, PackageInfo } from 'kibana/public'; import { BehaviorSubject } from 'rxjs'; -export function getEsClient( - injectedMetadata: CoreStart['injectedMetadata'], - http: CoreStart['http'], - packageInfo: PackageInfo -) { - const esRequestTimeout = injectedMetadata.getInjectedVar('esRequestTimeout') as number; - const esApiVersion = injectedMetadata.getInjectedVar('esApiVersion') as string; - +export function getEsClient({ + esRequestTimeout, + esApiVersion, + http, + packageVersion, +}: { + esRequestTimeout: number; + esApiVersion: string; + http: CoreStart['http']; + packageVersion: PackageInfo['version']; +}) { // Use legacy es client for msearch. const client = es.Client({ - host: getEsUrl(http, packageInfo), + host: getEsUrl(http, packageVersion), log: 'info', requestTimeout: esRequestTimeout, apiVersion: esApiVersion, @@ -78,7 +81,7 @@ function wrapEsClientMethod(esClient: any, method: string, loadingCount$: Behavi }; } -function getEsUrl(http: CoreStart['http'], packageInfo: PackageInfo) { +function getEsUrl(http: CoreStart['http'], packageVersion: PackageInfo['version']) { const a = document.createElement('a'); a.href = http.basePath.prepend('/elasticsearch'); const protocolPort = /https/.test(a.protocol) ? 443 : 80; @@ -89,7 +92,7 @@ function getEsUrl(http: CoreStart['http'], packageInfo: PackageInfo) { protocol: a.protocol, pathname: a.pathname, headers: { - 'kbn-version': packageInfo.version, + 'kbn-version': packageVersion, }, }; } diff --git a/src/plugins/data/public/search/legacy/fetch_soon.test.ts b/src/plugins/data/public/search/legacy/fetch_soon.test.ts index 61d3568350b6b..d375398af1add 100644 --- a/src/plugins/data/public/search/legacy/fetch_soon.test.ts +++ b/src/plugins/data/public/search/legacy/fetch_soon.test.ts @@ -19,15 +19,12 @@ import { fetchSoon } from './fetch_soon'; import { callClient } from './call_client'; -import { IUiSettingsClient } from 'kibana/public'; import { FetchHandlers, FetchOptions } from '../fetch/types'; import { SearchRequest, SearchResponse } from '../index'; -import { UI_SETTINGS } from '../../../common'; +import { GetConfigFn, UI_SETTINGS } from '../../../common'; -function getConfigStub(config: any = {}) { - return { - get: (key) => config[key], - } as IUiSettingsClient; +function getConfigStub(config: any = {}): GetConfigFn { + return (key) => config[key]; } const mockResponses: Record = { @@ -60,9 +57,9 @@ describe('fetchSoon', () => { }); test('should execute asap if config is set to not batch searches', () => { - const config = getConfigStub({ - [UI_SETTINGS.COURIER_BATCH_SEARCHES]: false, - }); + const config = { + get: getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: false }), + }; const request = {}; const options = {}; @@ -72,9 +69,9 @@ describe('fetchSoon', () => { }); test('should delay by 50ms if config is set to batch searches', () => { - const config = getConfigStub({ - [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true, - }); + const config = { + get: getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }), + }; const request = {}; const options = {}; @@ -88,9 +85,9 @@ describe('fetchSoon', () => { }); test('should send a batch of requests to callClient', () => { - const config = getConfigStub({ - [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true, - }); + const config = { + get: getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }), + }; const requests = [{ foo: 1 }, { foo: 2 }]; const options = [{ bar: 1 }, { bar: 2 }]; @@ -105,9 +102,9 @@ describe('fetchSoon', () => { }); test('should return the response to the corresponding call for multiple batched requests', async () => { - const config = getConfigStub({ - [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true, - }); + const config = { + get: getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }), + }; const requests = [{ _mockResponseId: 'foo' }, { _mockResponseId: 'bar' }]; const promises = requests.map((request) => { @@ -120,9 +117,9 @@ describe('fetchSoon', () => { }); test('should wait for the previous batch to start before starting a new batch', () => { - const config = getConfigStub({ - [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true, - }); + const config = { + get: getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }), + }; const firstBatch = [{ foo: 1 }, { foo: 2 }]; const secondBatch = [{ bar: 1 }, { bar: 2 }]; diff --git a/src/plugins/data/public/search/legacy/get_msearch_params.test.ts b/src/plugins/data/public/search/legacy/get_msearch_params.test.ts index dc61e19406631..d3206950174c8 100644 --- a/src/plugins/data/public/search/legacy/get_msearch_params.test.ts +++ b/src/plugins/data/public/search/legacy/get_msearch_params.test.ts @@ -18,13 +18,10 @@ */ import { getMSearchParams } from './get_msearch_params'; -import { IUiSettingsClient } from '../../../../../core/public'; -import { UI_SETTINGS } from '../../../common'; +import { GetConfigFn, UI_SETTINGS } from '../../../common'; -function getConfigStub(config: any = {}) { - return { - get: (key) => config[key], - } as IUiSettingsClient; +function getConfigStub(config: any = {}): GetConfigFn { + return (key) => config[key]; } describe('getMSearchParams', () => { diff --git a/src/plugins/data/public/search/legacy/get_msearch_params.ts b/src/plugins/data/public/search/legacy/get_msearch_params.ts index 48d13903c972f..c4f77b25078cd 100644 --- a/src/plugins/data/public/search/legacy/get_msearch_params.ts +++ b/src/plugins/data/public/search/legacy/get_msearch_params.ts @@ -17,13 +17,13 @@ * under the License. */ -import { IUiSettingsClient } from 'kibana/public'; +import { GetConfigFn } from '../../../common'; import { getIgnoreThrottled, getMaxConcurrentShardRequests } from '../fetch'; -export function getMSearchParams(config: IUiSettingsClient) { +export function getMSearchParams(getConfig: GetConfigFn) { return { rest_total_hits_as_int: true, - ignore_throttled: getIgnoreThrottled(config), - max_concurrent_shard_requests: getMaxConcurrentShardRequests(config), + ignore_throttled: getIgnoreThrottled(getConfig), + max_concurrent_shard_requests: getMaxConcurrentShardRequests(getConfig), }; } diff --git a/src/plugins/data/public/search/search_service.test.ts b/src/plugins/data/public/search/search_service.test.ts index 4360a0caa7cde..738f1e8ffbca0 100644 --- a/src/plugins/data/public/search/search_service.test.ts +++ b/src/plugins/data/public/search/search_service.test.ts @@ -52,6 +52,7 @@ describe('Search service', () => { } as any); expect(start).toHaveProperty('aggs'); expect(start).toHaveProperty('search'); + expect(start).toHaveProperty('searchSource'); }); }); }); diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index 04e1a46c84652..a65b2b4b71f20 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -51,11 +51,22 @@ export class SearchService implements Plugin { private usageCollector?: SearchUsageCollector; public setup( - core: CoreSetup, - { packageInfo, usageCollection, expressions }: SearchServiceSetupDependencies + { http, getStartServices, injectedMetadata, notifications, uiSettings }: CoreSetup, + { expressions, packageInfo, usageCollection }: SearchServiceSetupDependencies ): ISearchSetup { - this.usageCollector = createUsageCollector(core, usageCollection); - this.esClient = getEsClient(core.injectedMetadata, core.http, packageInfo); + const esApiVersion = injectedMetadata.getInjectedVar('esApiVersion') as string; + const esRequestTimeout = injectedMetadata.getInjectedVar('esRequestTimeout') as number; + const packageVersion = packageInfo.version; + + this.usageCollector = createUsageCollector(getStartServices, usageCollection); + + this.esClient = getEsClient({ + esRequestTimeout, + esApiVersion, + http, + packageVersion, + }); + /** * A global object that intercepts all searches and provides convenience methods for cancelling * all pending search requests, as well as getting the number of pending search requests. @@ -64,13 +75,13 @@ export class SearchService implements Plugin { */ this.searchInterceptor = new SearchInterceptor( { - toasts: core.notifications.toasts, - http: core.http, - uiSettings: core.uiSettings, - startServices: core.getStartServices(), + toasts: notifications.toasts, + http, + uiSettings, + startServices: getStartServices(), usageCollector: this.usageCollector!, }, - core.injectedMetadata.getInjectedVar('esRequestTimeout') as number + esRequestTimeout ); expressions.registerFunction(esdsl); @@ -79,7 +90,7 @@ export class SearchService implements Plugin { return { aggs: this.aggsService.setup({ registerFunction: expressions.registerFunction, - uiSettings: core.uiSettings, + uiSettings, }), usageCollector: this.usageCollector!, __enhance: (enhancements: SearchEnhancements) => { @@ -101,8 +112,8 @@ export class SearchService implements Plugin { }; const searchSourceDependencies: SearchSourceDependencies = { - uiSettings, - injectedMetadata, + getConfig: uiSettings.get.bind(uiSettings), + esShardTimeout: injectedMetadata.getInjectedVar('esShardTimeout') as number, search, legacySearch, }; diff --git a/src/plugins/data/public/search/search_source/create_search_source.test.ts b/src/plugins/data/public/search/search_source/create_search_source.test.ts index 23ab5979595af..56f6ca6c56270 100644 --- a/src/plugins/data/public/search/search_source/create_search_source.test.ts +++ b/src/plugins/data/public/search/search_source/create_search_source.test.ts @@ -16,27 +16,28 @@ * specific language governing permissions and limitations * under the License. */ + import { createSearchSource as createSearchSourceFactory } from './create_search_source'; +import { SearchSourceDependencies } from './search_source'; import { IIndexPattern } from '../../../common/index_patterns'; import { IndexPatternsContract } from '../../index_patterns/index_patterns'; import { Filter } from '../../../common/es_query/filters'; -import { coreMock } from '../../../../../core/public/mocks'; import { dataPluginMock } from '../../mocks'; describe('createSearchSource', () => { const indexPatternMock: IIndexPattern = {} as IIndexPattern; let indexPatternContractMock: jest.Mocked; - let dependencies: any; + let dependencies: SearchSourceDependencies; let createSearchSource: ReturnType; beforeEach(() => { - const core = coreMock.createStart(); const data = dataPluginMock.createStartContract(); dependencies = { - searchService: data.search, - uiSettings: core.uiSettings, - injectedMetadata: core.injectedMetadata, + getConfig: jest.fn(), + search: jest.fn(), + legacySearch: data.search.__LEGACY, + esShardTimeout: 30000, }; indexPatternContractMock = ({ diff --git a/src/plugins/data/public/search/search_source/mocks.ts b/src/plugins/data/public/search/search_source/mocks.ts index cf2d009e41b54..4e1c35557ffa6 100644 --- a/src/plugins/data/public/search/search_source/mocks.ts +++ b/src/plugins/data/public/search/search_source/mocks.ts @@ -17,10 +17,7 @@ * under the License. */ -import { - injectedMetadataServiceMock, - uiSettingsServiceMock, -} from '../../../../../core/public/mocks'; +import { uiSettingsServiceMock } from '../../../../../core/public/mocks'; import { ISearchSource, SearchSource } from './search_source'; import { SearchSourceFields } from './types'; @@ -54,6 +51,8 @@ export const searchSourceMock = { export const createSearchSourceMock = (fields?: SearchSourceFields) => new SearchSource(fields, { + getConfig: uiSettingsServiceMock.createStartContract().get, + esShardTimeout: 30000, search: jest.fn(), legacySearch: { esClient: { @@ -61,6 +60,4 @@ export const createSearchSourceMock = (fields?: SearchSourceFields) => msearch: jest.fn(), }, }, - uiSettings: uiSettingsServiceMock.createStartContract(), - injectedMetadata: injectedMetadataServiceMock.createStartContract(), }); diff --git a/src/plugins/data/public/search/search_source/search_source.test.ts b/src/plugins/data/public/search/search_source/search_source.test.ts index 6d53b8dfc4b4e..2f0fa0765e25a 100644 --- a/src/plugins/data/public/search/search_source/search_source.test.ts +++ b/src/plugins/data/public/search/search_source/search_source.test.ts @@ -16,13 +16,13 @@ * specific language governing permissions and limitations * under the License. */ + import { Observable } from 'rxjs'; -import { SearchSource } from './search_source'; +import { GetConfigFn } from 'src/plugins/data/common'; +import { SearchSource, SearchSourceDependencies } from './search_source'; import { IndexPattern, SortDirection } from '../..'; import { fetchSoon } from '../legacy'; -import { IUiSettingsClient } from '../../../../../core/public'; import { dataPluginMock } from '../../../../data/public/mocks'; -import { coreMock } from '../../../../../core/public/mocks'; jest.mock('../legacy', () => ({ fetchSoon: jest.fn().mockResolvedValue({}), @@ -51,10 +51,9 @@ const indexPattern2 = ({ describe('SearchSource', () => { let mockSearchMethod: any; - let searchSourceDependencies: any; + let searchSourceDependencies: SearchSourceDependencies; beforeEach(() => { - const core = coreMock.createStart(); const data = dataPluginMock.createStartContract(); mockSearchMethod = jest.fn(() => { @@ -69,10 +68,10 @@ describe('SearchSource', () => { }); searchSourceDependencies = { + getConfig: jest.fn(), search: mockSearchMethod, legacySearch: data.search.__LEGACY, - injectedMetadata: core.injectedMetadata, - uiSettings: core.uiSettings, + esShardTimeout: 30000, }; }); @@ -184,16 +183,11 @@ describe('SearchSource', () => { describe('#legacy fetch()', () => { beforeEach(() => { - const core = coreMock.createStart(); - searchSourceDependencies = { ...searchSourceDependencies, - uiSettings: { - ...core.uiSettings, - get: jest.fn(() => { - return true; // batchSearches = true - }), - } as IUiSettingsClient, + getConfig: jest.fn(() => { + return true; // batchSearches = true + }) as GetConfigFn, }; }); diff --git a/src/plugins/data/public/search/search_source/search_source.ts b/src/plugins/data/public/search/search_source/search_source.ts index 847dc8853d6ba..06ad13bfcfdf5 100644 --- a/src/plugins/data/public/search/search_source/search_source.ts +++ b/src/plugins/data/public/search/search_source/search_source.ts @@ -72,7 +72,6 @@ import { setWith } from '@elastic/safer-lodash-set'; import { uniqueId, uniq, extend, pick, difference, omit, isObject, keys, isFunction } from 'lodash'; import { map } from 'rxjs/operators'; -import { CoreStart } from 'kibana/public'; import { normalizeSortRequest } from './normalize_sort_request'; import { filterDocvalueFields } from './filter_docvalue_fields'; import { fieldWildcardFilter } from '../../../../kibana_utils/common'; @@ -82,15 +81,32 @@ import { FetchOptions, RequestFailure, handleResponse, getSearchParamsFromReques import { getEsQueryConfig, buildEsQuery, Filter, UI_SETTINGS } from '../../../common'; import { getHighlightRequest } from '../../../common/field_formats'; +import { GetConfigFn } from '../../../common/types'; import { fetchSoon } from '../legacy'; import { extractReferences } from './extract_references'; import { ISearchStartLegacy } from '../types'; +/** @internal */ +export const searchSourceRequiredUiSettings = [ + 'dateFormat:tz', + UI_SETTINGS.COURIER_BATCH_SEARCHES, + UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE, + UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX, + UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS, + UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE, + UI_SETTINGS.DOC_HIGHLIGHT, + UI_SETTINGS.META_FIELDS, + UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS, + UI_SETTINGS.QUERY_STRING_OPTIONS, + UI_SETTINGS.SEARCH_INCLUDE_FROZEN, + UI_SETTINGS.SORT_OPTIONS, +]; + export interface SearchSourceDependencies { - uiSettings: CoreStart['uiSettings']; + getConfig: GetConfigFn; search: ISearchGeneric; legacySearch: ISearchStartLegacy; - injectedMetadata: CoreStart['injectedMetadata']; + esShardTimeout: number; } /** @public **/ @@ -204,11 +220,11 @@ export class SearchSource { * @return {Observable>} */ private fetch$(searchRequest: SearchRequest, signal?: AbortSignal) { - const { search, injectedMetadata, uiSettings } = this.dependencies; + const { search, esShardTimeout, getConfig } = this.dependencies; const params = getSearchParamsFromRequest(searchRequest, { - injectedMetadata, - uiSettings, + esShardTimeout, + getConfig, }); return search({ params, indexType: searchRequest.indexType }, { signal }).pipe( @@ -221,8 +237,7 @@ export class SearchSource { * @return {Promise>} */ private async legacyFetch(searchRequest: SearchRequest, options: FetchOptions) { - const { injectedMetadata, legacySearch, uiSettings } = this.dependencies; - const esShardTimeout = injectedMetadata.getInjectedVar('esShardTimeout') as number; + const { esShardTimeout, legacySearch, getConfig } = this.dependencies; return await fetchSoon( searchRequest, @@ -232,7 +247,7 @@ export class SearchSource { }, { legacySearchService: legacySearch, - config: uiSettings, + config: { get: getConfig }, esShardTimeout, } ); @@ -243,14 +258,14 @@ export class SearchSource { * @async */ async fetch(options: FetchOptions = {}) { - const { uiSettings } = this.dependencies; + const { getConfig } = this.dependencies; await this.requestIsStarting(options); const searchRequest = await this.flatten(); this.history = [searchRequest]; let response; - if (uiSettings.get(UI_SETTINGS.COURIER_BATCH_SEARCHES)) { + if (getConfig(UI_SETTINGS.COURIER_BATCH_SEARCHES)) { response = await this.legacyFetch(searchRequest, options); } else { response = this.fetch$(searchRequest, options.abortSignal).toPromise(); @@ -342,7 +357,7 @@ export class SearchSource { } }; - const { uiSettings } = this.dependencies; + const { getConfig } = this.dependencies; switch (key) { case 'filter': @@ -364,7 +379,7 @@ export class SearchSource { const sort = normalizeSortRequest( val, this.getField('index'), - uiSettings.get(UI_SETTINGS.SORT_OPTIONS) + getConfig(UI_SETTINGS.SORT_OPTIONS) ); return addToBody(key, sort); default: @@ -418,14 +433,11 @@ export class SearchSource { body._source = index.getSourceFiltering(); } - const { uiSettings } = this.dependencies; + const { getConfig } = this.dependencies; if (body._source) { // exclude source fields for this index pattern specified by the user - const filter = fieldWildcardFilter( - body._source.excludes, - uiSettings.get(UI_SETTINGS.META_FIELDS) - ); + const filter = fieldWildcardFilter(body._source.excludes, getConfig(UI_SETTINGS.META_FIELDS)); body.docvalue_fields = body.docvalue_fields.filter((docvalueField: any) => filter(docvalueField.field) ); @@ -445,11 +457,11 @@ export class SearchSource { ); } - const esQueryConfigs = getEsQueryConfig(uiSettings); + const esQueryConfigs = getEsQueryConfig({ get: getConfig }); body.query = buildEsQuery(index, query, filters, esQueryConfigs); if (highlightAll && body.query) { - body.highlight = getHighlightRequest(body.query, uiSettings.get(UI_SETTINGS.DOC_HIGHLIGHT)); + body.highlight = getHighlightRequest(body.query, getConfig(UI_SETTINGS.DOC_HIGHLIGHT)); delete searchRequest.highlightAll; } diff --git a/src/plugins/data/public/services.ts b/src/plugins/data/public/services.ts index ba0b2de393bde..032bce6d8d2aa 100644 --- a/src/plugins/data/public/services.ts +++ b/src/plugins/data/public/services.ts @@ -31,8 +31,6 @@ export const [getUiSettings, setUiSettings] = createGetterSetter('Http'); - export const [getFieldFormats, setFieldFormats] = createGetterSetter( 'FieldFormats' ); @@ -47,10 +45,6 @@ export const [getQueryService, setQueryService] = createGetterSetter< DataPublicPluginStart['query'] >('Query'); -export const [getInjectedMetadata, setInjectedMetadata] = createGetterSetter< - CoreStart['injectedMetadata'] ->('InjectedMetadata'); - export const [getSearchService, setSearchService] = createGetterSetter< DataPublicPluginStart['search'] >('Search'); diff --git a/src/plugins/data/server/field_formats/converters/date_nanos_server.ts b/src/plugins/data/server/field_formats/converters/date_nanos_server.ts index 299b2aac93d49..b99febf0c7e73 100644 --- a/src/plugins/data/server/field_formats/converters/date_nanos_server.ts +++ b/src/plugins/data/server/field_formats/converters/date_nanos_server.ts @@ -24,7 +24,7 @@ import { DateNanosFormat, formatWithNanos, } from '../../../common/field_formats/converters/date_nanos_shared'; -import { TextContextTypeConvert } from '../../../common'; +import { TextContextTypeConvert } from '../../../common/field_formats/types'; class DateNanosFormatServer extends DateNanosFormat { textConvert: TextContextTypeConvert = (val) => { diff --git a/src/plugins/data/server/field_formats/converters/date_server.ts b/src/plugins/data/server/field_formats/converters/date_server.ts index 85eb65dfc6a8d..19c4adc90bade 100644 --- a/src/plugins/data/server/field_formats/converters/date_server.ts +++ b/src/plugins/data/server/field_formats/converters/date_server.ts @@ -23,11 +23,13 @@ import moment from 'moment-timezone'; import { FieldFormat, KBN_FIELD_TYPES, - TextContextTypeConvert, FIELD_FORMAT_IDS, FieldFormatsGetConfigFn, - IFieldFormatMetaParams, } from '../../../common'; +import { + IFieldFormatMetaParams, + TextContextTypeConvert, +} from '../../../common/field_formats/types'; export class DateFormat extends FieldFormat { static id = FIELD_FORMAT_IDS.DATE; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index e259a7878398d..f0a0d2763ff23 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -419,10 +419,11 @@ export const fieldFormats: { TruncateFormat: typeof TruncateFormat; }; +// Warning: (ae-forgotten-export) The symbol "GetConfigFn" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "FieldFormatsGetConfigFn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type FieldFormatsGetConfigFn = (key: string, defaultOverride?: T) => T; +export type FieldFormatsGetConfigFn = GetConfigFn; // Warning: (ae-missing-release-tag) "Filter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/src/plugins/discover/public/application/angular/directives/render_complete.ts b/src/plugins/discover/public/application/angular/directives/render_complete.ts index 635cf68f12fcb..72f97138790b2 100644 --- a/src/plugins/discover/public/application/angular/directives/render_complete.ts +++ b/src/plugins/discover/public/application/angular/directives/render_complete.ts @@ -17,14 +17,14 @@ * under the License. */ import { IScope } from 'angular'; -import { RenderCompleteHelper } from '../../../../../kibana_utils/public'; +import { RenderCompleteListener } from '../../../../../kibana_utils/public'; export function createRenderCompleteDirective() { return { controller($scope: IScope, $element: JQLite) { const el = $element[0]; - const renderCompleteHelper = new RenderCompleteHelper(el); - $scope.$on('$destroy', renderCompleteHelper.destroy); + const renderCompleteListener = new RenderCompleteListener(el); + $scope.$on('$destroy', renderCompleteListener.destroy); }, }; } diff --git a/src/plugins/embeddable/kibana.json b/src/plugins/embeddable/kibana.json index c9694ad7b9423..6a8e6079232aa 100644 --- a/src/plugins/embeddable/kibana.json +++ b/src/plugins/embeddable/kibana.json @@ -12,6 +12,7 @@ ], "requiredBundles": [ "savedObjects", - "kibanaReact" + "kibanaReact", + "kibanaUtils" ] } diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx index fcecf117d7d52..ffe8a5bf6e7dc 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx @@ -19,6 +19,8 @@ import { cloneDeep, isEqual } from 'lodash'; import * as Rx from 'rxjs'; +import { distinctUntilChanged, map } from 'rxjs/operators'; +import { RenderCompleteDispatcher } from '../../../../kibana_utils/public'; import { Adapters, ViewMode } from '../types'; import { IContainer } from '../containers'; import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable'; @@ -47,6 +49,8 @@ export abstract class Embeddable< private readonly input$: Rx.BehaviorSubject; private readonly output$: Rx.BehaviorSubject; + protected renderComplete = new RenderCompleteDispatcher(); + // Listener to parent changes, if this embeddable exists in a parent, in order // to update input when the parent changes. private parentSubscription?: Rx.Subscription; @@ -77,6 +81,15 @@ export abstract class Embeddable< this.onResetInput(newInput); }); } + + this.getOutput$() + .pipe( + map(({ title }) => title || ''), + distinctUntilChanged() + ) + .subscribe((title) => { + this.renderComplete.setTitle(title); + }); } public getIsContainer(): this is IContainer { @@ -105,8 +118,8 @@ export abstract class Embeddable< return this.input; } - public getTitle() { - return this.output.title; + public getTitle(): string { + return this.output.title || ''; } /** @@ -133,7 +146,10 @@ export abstract class Embeddable< } } - public render(domNode: HTMLElement | Element): void { + public render(el: HTMLElement): void { + this.renderComplete.setEl(el); + this.renderComplete.setTitle(this.output.title || ''); + if (this.destroyed) { throw new Error('Embeddable has been destroyed'); } diff --git a/src/plugins/embeddable/public/mocks.tsx b/src/plugins/embeddable/public/mocks.tsx index 7ec03ba659cda..2064236e9ae7f 100644 --- a/src/plugins/embeddable/public/mocks.tsx +++ b/src/plugins/embeddable/public/mocks.tsx @@ -25,6 +25,9 @@ import { EmbeddableStateTransfer, IEmbeddable, EmbeddablePanel, + EmbeddableInput, + SavedObjectEmbeddableInput, + ReferenceOrValueEmbeddable, } from '.'; import { EmbeddablePublicPlugin } from './plugin'; import { coreMock } from '../../../core/public/mocks'; @@ -35,7 +38,6 @@ import { dataPluginMock } from '../../data/public/mocks'; import { inspectorPluginMock } from '../../inspector/public/mocks'; import { uiActionsPluginMock } from '../../ui_actions/public/mocks'; -import { SavedObjectEmbeddableInput, ReferenceOrValueEmbeddable, EmbeddableInput } from './lib'; export type Setup = jest.Mocked; export type Start = jest.Mocked; diff --git a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts index 419e80ad1608f..8c66a87adbaa1 100644 --- a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts +++ b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/json_xjson_translation_tools.test.ts @@ -23,6 +23,7 @@ import collapsingTests from './utils_string_collapsing.txt'; import expandingTests from './utils_string_expanding.txt'; import * as utils from '../index'; +import { extractJSONStringValues } from '../parser'; describe('JSON to XJSON conversion tools', () => { it('will collapse multiline strings', () => { @@ -34,6 +35,32 @@ describe('JSON to XJSON conversion tools', () => { const multiline = '{ "foo": """bar\r\nbaz""" }'; expect(utils.collapseLiteralStrings(multiline)).toEqual('{ "foo": "bar\\r\\nbaz" }'); }); + + describe('JSON string values parser', () => { + test('correctly extracts JSON string values', () => { + const json = { + myString: 'string', + notAString: 1, + myStringArray: ['a', 1, 'test', { nestedString: 'string' }], + }; + const jsonString = JSON.stringify(json); + const { stringValues } = extractJSONStringValues(jsonString); + expect(stringValues.length).toBe(4); + + expect(jsonString.substring(stringValues[0].startIndex, stringValues[0].endIndex + 1)).toBe( + '"string"' + ); + expect(jsonString.substring(stringValues[1].startIndex, stringValues[1].endIndex + 1)).toBe( + '"a"' + ); + expect(jsonString.substring(stringValues[2].startIndex, stringValues[2].endIndex + 1)).toBe( + '"test"' + ); + expect(jsonString.substring(stringValues[3].startIndex, stringValues[3].endIndex + 1)).toBe( + '"string"' + ); + }); + }); }); _.each(collapsingTests.split(/^=+$/m), function (fixture) { diff --git a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_expanding.txt b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_expanding.txt index 7de874c244e74..d157ed73817c2 100644 --- a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_expanding.txt +++ b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/__tests__/utils_string_expanding.txt @@ -1,8 +1,9 @@ + ========== Scripts in requests ------------------------------------- { - "f": { "script": { "source": "\ntest\ntest\\\\\\\\\\\\\\\\2\n" } }, + "f": { "script": { "source": "\ntest\ntest\\2\n" } }, "g": { "script": "second + \"\\\";" }, "a": "short with \\", "\\\\h": 1, @@ -12,7 +13,7 @@ Scripts in requests { "f": { "script": { "source": """ test -test\\\\\\\\2 +test\2 """ } }, "g": { "script": """second + "\";""" }, "a": """short with \""", @@ -23,11 +24,11 @@ test\\\\\\\\2 Preserve triple quotes ------------------------------------- { - "content\\\": "tri\"ple", + "content\\": "tri\"ple", } ------------------------------------- { - "content\\\": """tri"ple""", + "content\\": """tri"ple""", } ========== Correctly parse with JSON embedded inside values @@ -82,3 +83,13 @@ Single quotes escaped special case, end { "query": "test\"" } +========== +Strings in Arrays +------------------------------------- +{ + "array": ["expand \\ me", "do not expand", "do expand \\"] +} +------------------------------------- +{ + "array": ["""expand \ me""", "do not expand", """do expand \"""] +} diff --git a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/index.ts b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/index.ts index 28f1aca95efab..86fe9535a9619 100644 --- a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/index.ts +++ b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/index.ts @@ -17,6 +17,8 @@ * under the License. */ +import { extractJSONStringValues } from './parser'; + export function collapseLiteralStrings(data: string) { const splitData = data.split(`"""`); for (let idx = 1; idx < splitData.length - 1; idx += 2) { @@ -25,47 +27,60 @@ export function collapseLiteralStrings(data: string) { return splitData.join(''); } -/* - The following regex describes global match on: - 1. one colon followed by any number of space characters - 2. one double quote (not escaped, special case for JSON in JSON). - 3. greedily match any non double quote and non newline char OR any escaped double quote char (non-capturing). - 4. handle a special case where an escaped slash may be the last character - 5. one double quote - - For instance: `: "some characters \" here"` - Will match and be expanded to: `"""some characters " here"""` +// 5 megabytes +const MAX_EXPANDABLE_JSON_SIZE = 5 * 1024 * 1024; +/** + * Takes in a string representing some JSON data and expands strings, + * where needed, to a string literal representation. + * + * For example; given a value like: "{ "my_string": "\nhey!\n" }" + * + * Will return: "{ "my_string": """ + * hey! + * """ + * }" */ +export function expandLiteralStrings(data: string) { + // Assuming 1 byte per char + if (data.length > MAX_EXPANDABLE_JSON_SIZE) { + return data; + } -const LITERAL_STRING_CANDIDATES = /((:[\s\r\n]*)([^\\])"(\\"|[^"\n])*\\?")/g; + const { stringValues } = extractJSONStringValues(data); -export function expandLiteralStrings(data: string) { - return data.replace(LITERAL_STRING_CANDIDATES, (match, string) => { - // Expand to triple quotes if there are _any_ slashes - if (string.match(/\\./)) { - const firstDoubleQuoteIdx = string.indexOf('"'); - const lastDoubleQuoteIdx = string.lastIndexOf('"'); + if (stringValues.length === 0) { + return data; + } - // Handle a special case where we may have a value like "\"test\"". We don't - // want to expand this to """"test"""" - so we terminate before processing the string - // further if we detect this either at the start or end of the double quote section. + // Include JSON before our first string value + let result = data.substring(0, stringValues[0].startIndex); - if (string[firstDoubleQuoteIdx + 1] === '\\' && string[firstDoubleQuoteIdx + 2] === '"') { - return string; - } + for (let x = 0; x < stringValues.length; x++) { + const { startIndex, endIndex } = stringValues[x]; + const candidate = data.substring(startIndex, endIndex + 1); - if (string[lastDoubleQuoteIdx - 1] === '"' && string[lastDoubleQuoteIdx - 2] === '\\') { - return string; - } + // Handle a special case where we may have a value like "\"test\"". We don't + // want to expand this to """"test"""" - so we terminate before processing the string + // further if we detect this either at the start or end of the double quote section. + const skip = + (candidate[1] === '\\' && candidate[2] === '"') || + (candidate[candidate.length - 2] === '"' && candidate[candidate.length - 3] === '\\'); - const colonAndAnySpacing = string.slice(0, firstDoubleQuoteIdx); - const rawStringifiedValue = string.slice(firstDoubleQuoteIdx, string.length); - // Remove one level of JSON stringification - const jsonValue = JSON.parse(rawStringifiedValue); - return `${colonAndAnySpacing}"""${jsonValue}"""`; + if (!skip && candidate.match(/\\./)) { + result += `"""${JSON.parse(candidate)}"""`; } else { - return string; + result += candidate; + } + + if (stringValues[x + 1]) { + // Add any JSON between string values + result += data.substring(endIndex + 1, stringValues[x + 1].startIndex); } - }); + } + + // Add any remaining JSON after all string values + result += data.substring(stringValues[stringValues.length - 1].endIndex + 1); + + return result; } diff --git a/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/parser.ts b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/parser.ts new file mode 100644 index 0000000000000..2cbd886860a07 --- /dev/null +++ b/src/plugins/es_ui_shared/public/console_lang/lib/json_xjson_translation_tools/parser.ts @@ -0,0 +1,97 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +type StringValues = Array<{ startIndex: number; endIndex: number }>; + +interface ParseResult { + stringValues: StringValues; +} + +const JSON_COLON = ':'; +const JSON_STRING_DELIMITER = '"'; +const JSON_STRING_ESCAPE = '\\'; + +/** + * Accepts JSON (as a string) and extracts the positions of all JSON string + * values. + * + * For example: + * + * '{ "my_string_value": "is this", "my_number_value": 42 }' + * + * Would extract one result: + * + * [ { startIndex: 21, endIndex: 29 } ] + * + * This result maps to `"is this"` from the example JSON. + * + */ +export const extractJSONStringValues = (input: string): ParseResult => { + let position = 0; + let currentStringStartPos: number; + let isInsideString = false; + const stringValues: StringValues = []; + + function read() { + return input[position]; + } + + function peekNextNonWhitespace(): string | undefined { + let peekPosition = position + 1; + + while (peekPosition < input.length) { + const peekChar = input[peekPosition]; + if (peekChar.match(/[^\s\r\n]/)) { + return peekChar; + } + ++peekPosition; + } + } + + function advance() { + ++position; + } + + while (position < input.length) { + const char = read(); + if (!isInsideString) { + if (char === JSON_STRING_DELIMITER) { + currentStringStartPos = position; + isInsideString = true; + } + // else continue scanning for JSON_STRING_DELIMITER + } else { + if (char === JSON_STRING_ESCAPE) { + // skip ahead - we are still inside of a string + advance(); + } else if (char === JSON_STRING_DELIMITER) { + if (peekNextNonWhitespace() !== JSON_COLON) { + stringValues.push({ + startIndex: currentStringStartPos!, + endIndex: position, + }); + } + isInsideString = false; + } + } + advance(); + } + + return { stringValues }; +}; diff --git a/src/plugins/kibana_utils/public/render_complete/index.ts b/src/plugins/kibana_utils/public/render_complete/index.ts index ec86144f65a35..8f14b6ca8f0ab 100644 --- a/src/plugins/kibana_utils/public/render_complete/index.ts +++ b/src/plugins/kibana_utils/public/render_complete/index.ts @@ -17,19 +17,5 @@ * under the License. */ -const dispatchCustomEvent = (el: HTMLElement, eventName: string) => { - // we're using the native events so that we aren't tied to the jQuery custom events, - // otherwise we have to use jQuery(element).on(...) because jQuery's events sit on top - // of the native events per https://github.com/jquery/jquery/issues/2476 - el.dispatchEvent(new CustomEvent(eventName, { bubbles: true })); -}; - -export function dispatchRenderComplete(el: HTMLElement) { - dispatchCustomEvent(el, 'renderComplete'); -} - -export function dispatchRenderStart(el: HTMLElement) { - dispatchCustomEvent(el, 'renderStart'); -} - -export * from './render_complete_helper'; +export * from './render_complete_listener'; +export * from './render_complete_dispatcher'; diff --git a/src/plugins/kibana_utils/public/render_complete/render_complete_dispatcher.ts b/src/plugins/kibana_utils/public/render_complete/render_complete_dispatcher.ts new file mode 100644 index 0000000000000..386bf1eb62568 --- /dev/null +++ b/src/plugins/kibana_utils/public/render_complete/render_complete_dispatcher.ts @@ -0,0 +1,83 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const dispatchEvent = (el: HTMLElement, eventName: string) => { + el.dispatchEvent(new CustomEvent(eventName, { bubbles: true })); +}; + +export function dispatchRenderComplete(el: HTMLElement) { + dispatchEvent(el, 'renderComplete'); +} + +export function dispatchRenderStart(el: HTMLElement) { + dispatchEvent(el, 'renderStart'); +} + +/** + * Should call `dispatchComplete()` when UI block has finished loading its data and has + * completely rendered. Should `dispatchInProgress()` every time UI block + * starts loading data again. At the start it is assumed that UI block is loading + * so it dispatches "in progress" automatically, so you need to call `setRenderComplete` + * at least once. + * + * This is used for reporting to know that UI block is ready, so + * it can take a screenshot. It is also used in functional tests to know that + * page has stabilized. + */ +export class RenderCompleteDispatcher { + private count: number = 0; + private el?: HTMLElement; + + constructor(el?: HTMLElement) { + this.setEl(el); + } + + public setEl(el?: HTMLElement) { + this.el = el; + this.count = 0; + if (el) this.dispatchInProgress(); + } + + public dispatchInProgress() { + if (!this.el) return; + this.el.setAttribute('data-render-complete', 'false'); + this.el.setAttribute('data-rendering-count', String(this.count)); + dispatchRenderStart(this.el); + } + + public dispatchComplete() { + if (!this.el) return; + this.count++; + this.el.setAttribute('data-render-complete', 'true'); + this.el.setAttribute('data-rendering-count', String(this.count)); + dispatchRenderComplete(this.el); + } + + public dispatchError() { + if (!this.el) return; + this.count++; + this.el.setAttribute('data-render-complete', 'false'); + this.el.setAttribute('data-rendering-count', String(this.count)); + } + + public setTitle(title: string) { + if (!this.el) return; + this.el.setAttribute('data-title', title); + } +} diff --git a/src/plugins/kibana_utils/public/render_complete/render_complete_helper.ts b/src/plugins/kibana_utils/public/render_complete/render_complete_listener.ts similarity index 80% rename from src/plugins/kibana_utils/public/render_complete/render_complete_helper.ts rename to src/plugins/kibana_utils/public/render_complete/render_complete_listener.ts index 1230638a1d709..d9289b20d4bfa 100644 --- a/src/plugins/kibana_utils/public/render_complete/render_complete_helper.ts +++ b/src/plugins/kibana_utils/public/render_complete/render_complete_listener.ts @@ -17,9 +17,9 @@ * under the License. */ -const attributeName = 'data-render-complete'; +export class RenderCompleteListener { + private readonly attributeName = 'data-render-complete'; -export class RenderCompleteHelper { constructor(private readonly element: HTMLElement) { this.setup(); } @@ -30,23 +30,23 @@ export class RenderCompleteHelper { }; public setup = () => { - this.element.setAttribute(attributeName, 'false'); + this.element.setAttribute(this.attributeName, 'false'); this.element.addEventListener('renderStart', this.start); this.element.addEventListener('renderComplete', this.complete); }; public disable = () => { - this.element.setAttribute(attributeName, 'disabled'); + this.element.setAttribute(this.attributeName, 'disabled'); this.destroy(); }; private start = () => { - this.element.setAttribute(attributeName, 'false'); + this.element.setAttribute(this.attributeName, 'false'); return true; }; private complete = () => { - this.element.setAttribute(attributeName, 'true'); + this.element.setAttribute(this.attributeName, 'true'); return true; }; } diff --git a/src/plugins/vis_type_vega/public/data_model/search_api.ts b/src/plugins/vis_type_vega/public/data_model/search_api.ts index a213b59be2ad0..d2ce8c95b9f90 100644 --- a/src/plugins/vis_type_vega/public/data_model/search_api.ts +++ b/src/plugins/vis_type_vega/public/data_model/search_api.ts @@ -51,8 +51,10 @@ export class SearchAPI { searchRequests.map((request) => { const requestId = request.name; const params = getSearchParamsFromRequest(request, { - uiSettings: this.dependencies.uiSettings, - injectedMetadata: this.dependencies.injectedMetadata, + esShardTimeout: this.dependencies.injectedMetadata.getInjectedVar( + 'esShardTimeout' + ) as number, + getConfig: this.dependencies.uiSettings.get.bind(this.dependencies.uiSettings), }); if (this.inspectorAdapters) { diff --git a/src/plugins/vis_type_vega/public/vega_type.ts b/src/plugins/vis_type_vega/public/vega_type.ts index f49816017b684..46fd2fbc5587e 100644 --- a/src/plugins/vis_type_vega/public/vega_type.ts +++ b/src/plugins/vis_type_vega/public/vega_type.ts @@ -58,7 +58,6 @@ export const createVegaTypeDefinition = (dependencies: VegaVisualizationDependen getSupportedTriggers: () => { return [VIS_EVENT_TO_TRIGGER.applyFilter]; }, - stage: 'experimental', inspectorAdapters: createInspectorAdapters, }; }; diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts index c4d5f5206ee90..a434bf9756b64 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -36,7 +36,6 @@ import { IContainer, Adapters, } from '../../../../plugins/embeddable/public'; -import { dispatchRenderComplete } from '../../../../plugins/kibana_utils/public'; import { IExpressionLoaderParams, ExpressionsStart, @@ -85,7 +84,6 @@ export class VisualizeEmbeddable extends Embeddable; private subscriptions: Subscription[] = []; @@ -158,7 +156,7 @@ export class VisualizeEmbeddable extends Embeddable Boolean(this.getInspectorAdapters()); onContainerLoading = () => { - this.domNode.setAttribute('data-render-complete', 'false'); + this.renderComplete.dispatchInProgress(); this.updateOutput({ loading: true, error: undefined }); }; - onContainerRender = (count: number) => { - this.domNode.setAttribute('data-render-complete', 'true'); - this.domNode.setAttribute('data-rendering-count', count.toString()); + onContainerRender = () => { + this.renderComplete.dispatchComplete(); this.updateOutput({ loading: false, error: undefined }); - dispatchRenderComplete(this.domNode); }; onContainerError = (error: ExpressionRenderError) => { if (this.abortController) { this.abortController.abort(); } - this.domNode.setAttribute( - 'data-rendering-count', - this.domNode.getAttribute('data-rendering-count') + 1 - ); - this.domNode.setAttribute('data-render-complete', 'false'); + this.renderComplete.dispatchError(); this.updateOutput({ loading: false, error }); }; @@ -274,7 +256,6 @@ export class VisualizeEmbeddable extends Embeddable; diff --git a/src/plugins/visualize/kibana.json b/src/plugins/visualize/kibana.json index a6cc8d8f8af60..29fcd30184cb2 100644 --- a/src/plugins/visualize/kibana.json +++ b/src/plugins/visualize/kibana.json @@ -10,6 +10,7 @@ "savedObjects", "visualizations", "embeddable", + "dashboard", "uiActions" ], "optionalPlugins": ["home", "share"], diff --git a/src/plugins/visualize/public/application/types.ts b/src/plugins/visualize/public/application/types.ts index 65b88485b2f06..0a12dbc22a744 100644 --- a/src/plugins/visualize/public/application/types.ts +++ b/src/plugins/visualize/public/application/types.ts @@ -44,7 +44,7 @@ import { SharePluginStart } from 'src/plugins/share/public'; import { SavedObjectsStart, SavedObject } from 'src/plugins/saved_objects/public'; import { EmbeddableStart } from 'src/plugins/embeddable/public'; import { KibanaLegacyStart } from 'src/plugins/kibana_legacy/public'; -import { ConfigSchema } from '../../config'; +import { DashboardStart } from '../../../dashboard/public'; export type PureVisState = SavedVisState; @@ -111,7 +111,7 @@ export interface VisualizeServices extends CoreStart { createVisEmbeddableFromObject: VisualizationsStart['__LEGACY']['createVisEmbeddableFromObject']; restorePreviousUrl: () => void; scopedHistory: ScopedHistory; - featureFlagConfig: ConfigSchema; + dashboard: DashboardStart; } export interface SavedVisInstance { diff --git a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx index 87a6437192aa9..43121f2cffc41 100644 --- a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx @@ -75,7 +75,7 @@ export const getTopNavConfig = ( toastNotifications, visualizeCapabilities, i18n: { Context: I18nContext }, - featureFlagConfig, + dashboard, }: VisualizeServices ) => { const { vis, embeddableHandler } = visInstance; @@ -212,7 +212,7 @@ export const getTopNavConfig = ( }; if ( originatingApp === 'dashboards' && - featureFlagConfig.showNewVisualizeFlow && + dashboard.dashboardFeatureFlagConfig.allowByValueEmbeddables && !savedVis ) { return createVisReference(); @@ -292,7 +292,7 @@ export const getTopNavConfig = ( const isSaveAsButton = anchorElement.classList.contains('saveAsButton'); if ( originatingApp === 'dashboards' && - featureFlagConfig.showNewVisualizeFlow && + dashboard.dashboardFeatureFlagConfig.allowByValueEmbeddables && !isSaveAsButton ) { createVisReference(); diff --git a/src/plugins/visualize/public/plugin.ts b/src/plugins/visualize/public/plugin.ts index 8794593d6c958..29d6f978bd05e 100644 --- a/src/plugins/visualize/public/plugin.ts +++ b/src/plugins/visualize/public/plugin.ts @@ -48,6 +48,7 @@ import { VisualizeServices } from './application/types'; import { DEFAULT_APP_CATEGORIES } from '../../../core/public'; import { SavedObjectsStart } from '../../saved_objects/public'; import { EmbeddableStart } from '../../embeddable/public'; +import { DashboardStart } from '../../dashboard/public'; import { UiActionsStart, VISUALIZE_FIELD_TRIGGER } from '../../ui_actions/public'; import { setUISettings, @@ -67,6 +68,7 @@ export interface VisualizePluginStartDependencies { embeddable: EmbeddableStart; kibanaLegacy: KibanaLegacyStart; savedObjects: SavedObjectsStart; + dashboard: DashboardStart; uiActions: UiActionsStart; } @@ -77,10 +79,6 @@ export interface VisualizePluginSetupDependencies { share?: SharePluginSetup; } -export interface FeatureFlagConfig { - showNewVisualizeFlow: boolean; -} - export class VisualizePlugin implements Plugin { @@ -171,7 +169,6 @@ export class VisualizePlugin * this should be replaced to use only scoped history after moving legacy apps to browser routing */ const history = createHashHistory(); - const services: VisualizeServices = { ...coreStart, history, @@ -198,7 +195,7 @@ export class VisualizePlugin savedObjectsPublic: pluginsStart.savedObjects, scopedHistory: params.history, restorePreviousUrl, - featureFlagConfig: this.initializerContext.config.get(), + dashboard: pluginsStart.dashboard, }; params.element.classList.add('visAppWrapper'); diff --git a/src/plugins/visualize/server/index.ts b/src/plugins/visualize/server/index.ts index 6da0a513b1475..5cebef71d8d22 100644 --- a/src/plugins/visualize/server/index.ts +++ b/src/plugins/visualize/server/index.ts @@ -17,17 +17,8 @@ * under the License. */ -import { PluginInitializerContext, PluginConfigDescriptor } from 'kibana/server'; +import { PluginInitializerContext } from 'kibana/server'; import { VisualizeServerPlugin } from './plugin'; -import { ConfigSchema, configSchema } from '../config'; - -export const config: PluginConfigDescriptor = { - exposeToBrowser: { - showNewVisualizeFlow: true, - }, - schema: configSchema, -}; - export const plugin = (initContext: PluginInitializerContext) => new VisualizeServerPlugin(initContext); diff --git a/test/functional/apps/dashboard/embeddable_rendering.js b/test/functional/apps/dashboard/embeddable_rendering.js index c00f01d060f4a..73c36c7562e8b 100644 --- a/test/functional/apps/dashboard/embeddable_rendering.js +++ b/test/functional/apps/dashboard/embeddable_rendering.js @@ -33,6 +33,7 @@ export default function ({ getService, getPageObjects }) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const pieChart = getService('pieChart'); + const security = getService('security'); const dashboardExpect = getService('dashboardExpect'); const dashboardAddPanel = getService('dashboardAddPanel'); const PageObjects = getPageObjects([ @@ -100,6 +101,7 @@ export default function ({ getService, getPageObjects }) { describe('dashboard embeddable rendering', function describeIndexTests() { before(async () => { + await security.testUser.setRoles(['kibana_admin', 'animals', 'test_logstash_reader']); await esArchiver.load('dashboard/current/kibana'); await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', @@ -118,6 +120,7 @@ export default function ({ getService, getPageObjects }) { const currentUrl = await browser.getCurrentUrl(); const newUrl = currentUrl.replace(/\?.*$/, ''); await browser.get(newUrl, false); + await security.testUser.restoreDefaults(); }); it('adding visualizations', async () => { diff --git a/test/functional/apps/dashboard/legacy_urls.ts b/test/functional/apps/dashboard/legacy_urls.ts index e606649c1df9f..6bb8d808e8daa 100644 --- a/test/functional/apps/dashboard/legacy_urls.ts +++ b/test/functional/apps/dashboard/legacy_urls.ts @@ -35,6 +35,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const dashboardAddPanel = getService('dashboardAddPanel'); const listingTable = getService('listingTable'); const esArchiver = getService('esArchiver'); + const security = getService('security'); let kibanaLegacyBaseUrl: string; let kibanaVisualizeBaseUrl: string; @@ -42,6 +43,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('legacy urls', function describeIndexTests() { before(async function () { + await security.testUser.setRoles(['kibana_admin', 'animals']); await esArchiver.load('dashboard/current/kibana'); await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.clickNewDashboard(); @@ -61,6 +63,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { after(async function () { await PageObjects.dashboard.gotoDashboardLandingPage(); await listingTable.deleteItem('legacyTest', testDashboardId); + await security.testUser.restoreDefaults(); }); describe('kibana link redirect', () => { diff --git a/test/functional/config.js b/test/functional/config.js index 95e0c689089ef..15097d9346471 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -264,7 +264,7 @@ export default async function ({ readConfigFile }) { cluster: [], indices: [ { - names: ['animals-*'], + names: ['animals-*', 'dogbreeds'], privileges: ['read', 'view_index_metadata'], field_security: { grant: ['*'], except: [] }, }, diff --git a/test/functional/page_objects/vega_chart_page.ts b/test/functional/page_objects/vega_chart_page.ts index 1173c35af3384..6c64f9dda2efd 100644 --- a/test/functional/page_objects/vega_chart_page.ts +++ b/test/functional/page_objects/vega_chart_page.ts @@ -33,7 +33,6 @@ export function VegaChartPageProvider({ const find = getService('find'); const testSubjects = getService('testSubjects'); const browser = getService('browser'); - const { common } = getPageObjects(['common']); const retry = getService('retry'); class VegaChartPage { @@ -49,6 +48,15 @@ export function VegaChartPageProvider({ return find.byCssSelector('div.vgaVis__controls'); } + public getYAxisContainer() { + return find.byCssSelector('[aria-label^="Y-axis"]'); + } + + public async getAceGutterContainer() { + const editor = await this.getEditor(); + return editor.findByClassName('ace_gutter'); + } + public async getRawSpec() { // Adapted from console_page.js:getVisibleTextFromAceEditor(). Is there a common utilities file? const editor = await this.getEditor(); @@ -83,20 +91,16 @@ export function VegaChartPageProvider({ } public async typeInSpec(text: string) { - await this.focusEditor(); + const aceGutter = await this.getAceGutterContainer(); - let repeats = 20; - while (--repeats > 0) { - await browser.pressKeys(Key.ARROW_UP); - await common.sleep(50); - } - await browser.pressKeys(Key.ARROW_RIGHT); + await aceGutter.doubleClick(); + await browser.pressKeys(Key.LEFT); + await browser.pressKeys(Key.RIGHT); await browser.pressKeys(text); } public async cleanSpec() { - const editor = await this.getEditor(); - const aceGutter = await editor.findByClassName('ace_gutter'); + const aceGutter = await this.getAceGutterContainer(); await retry.try(async () => { await aceGutter.doubleClick(); @@ -107,11 +111,11 @@ export function VegaChartPageProvider({ } public async getYAxisLabels() { - const chart = await testSubjects.find('visualizationLoader'); - const yAxis = await chart.findByCssSelector('[aria-label^="Y-axis"]'); + const yAxis = await this.getYAxisContainer(); const tickGroup = await yAxis.findByClassName('role-axis-label'); const labels = await tickGroup.findAllByCssSelector('text'); const labelTexts: string[] = []; + for (const label of labels) { labelTexts.push(await label.getVisibleText()); } diff --git a/test/new_visualize_flow/config.js b/test/new_visualize_flow/config.js index a6440d16481d5..c4790a35404c9 100644 --- a/test/new_visualize_flow/config.js +++ b/test/new_visualize_flow/config.js @@ -37,7 +37,6 @@ export default async function ({ readConfigFile }) { ...commonConfig.get('kbnTestServer.serverArgs'), '--oss', '--telemetry.optIn=false', - '--visualize.showNewVisualizeFlow=true', ], }, diff --git a/x-pack/package.json b/x-pack/package.json index 5333c67f6ac0f..a70373db36603 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -203,7 +203,7 @@ "@babel/core": "^7.11.1", "@babel/register": "^7.10.5", "@babel/runtime": "^7.11.2", - "@elastic/apm-rum-react": "^1.2.2", + "@elastic/apm-rum-react": "^1.2.3", "@elastic/datemath": "5.0.3", "@elastic/ems-client": "7.9.3", "@elastic/eui": "27.4.0", diff --git a/x-pack/plugins/ingest_manager/server/routes/setup/handlers.test.ts b/x-pack/plugins/ingest_manager/server/routes/setup/handlers.test.ts new file mode 100644 index 0000000000000..ce826e78c454d --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/routes/setup/handlers.test.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { xpackMocks } from '../../../../../../x-pack/mocks'; +import { httpServerMock } from 'src/core/server/mocks'; +import { PostIngestSetupResponse } from '../../../common'; +import { RegistryError } from '../../errors'; +import { createAppContextStartContractMock } from '../../mocks'; +import { ingestManagerSetupHandler } from './handlers'; +import { appContextService } from '../../services/app_context'; +import { setupIngestManager } from '../../services/setup'; + +jest.mock('../../services/setup', () => { + return { + setupIngestManager: jest.fn(), + }; +}); + +const mockSetupIngestManager = setupIngestManager as jest.MockedFunction; + +describe('ingestManagerSetupHandler', () => { + let context: ReturnType; + let response: ReturnType; + let request: ReturnType; + + beforeEach(async () => { + context = xpackMocks.createRequestHandlerContext(); + response = httpServerMock.createResponseFactory(); + request = httpServerMock.createKibanaRequest({ + method: 'post', + path: '/api/ingest_manager/setup', + }); + // prevents `Logger not set.` and other appContext errors + appContextService.start(createAppContextStartContractMock()); + }); + + afterEach(async () => { + jest.clearAllMocks(); + appContextService.stop(); + }); + + it('POST /setup succeeds w/200 and body of resolved value', async () => { + mockSetupIngestManager.mockImplementation(() => Promise.resolve({ isIntialized: true })); + await ingestManagerSetupHandler(context, request, response); + + const expectedBody: PostIngestSetupResponse = { isInitialized: true }; + expect(response.customError).toHaveBeenCalledTimes(0); + expect(response.ok).toHaveBeenCalledWith({ body: expectedBody }); + }); + + it('POST /setup fails w/500 on custom error', async () => { + mockSetupIngestManager.mockImplementation(() => + Promise.reject(new Error('SO method mocked to throw')) + ); + await ingestManagerSetupHandler(context, request, response); + + expect(response.customError).toHaveBeenCalledTimes(1); + expect(response.customError).toHaveBeenCalledWith({ + statusCode: 500, + body: { + message: 'SO method mocked to throw', + }, + }); + }); + + it('POST /setup fails w/502 on RegistryError', async () => { + mockSetupIngestManager.mockImplementation(() => + Promise.reject(new RegistryError('Registry method mocked to throw')) + ); + + await ingestManagerSetupHandler(context, request, response); + expect(response.customError).toHaveBeenCalledTimes(1); + expect(response.customError).toHaveBeenCalledWith({ + statusCode: 502, + body: { + message: 'Registry method mocked to throw', + }, + }); + }); +}); diff --git a/x-pack/plugins/ingest_manager/server/routes/setup/index.ts b/x-pack/plugins/ingest_manager/server/routes/setup/index.ts index 1d1e7a2d721c9..fe51abec45b23 100644 --- a/x-pack/plugins/ingest_manager/server/routes/setup/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/setup/index.ts @@ -14,8 +14,7 @@ import { } from './handlers'; import { PostFleetSetupRequestSchema } from '../../types'; -export const registerRoutes = (router: IRouter, config: IngestManagerConfigType) => { - // Ingest manager setup +export const registerIngestManagerSetupRoute = (router: IRouter) => { router.post( { path: SETUP_API_ROUTE, @@ -26,12 +25,20 @@ export const registerRoutes = (router: IRouter, config: IngestManagerConfigType) }, ingestManagerSetupHandler ); +}; - if (!config.fleet.enabled) { - return; - } +export const registerCreateFleetSetupRoute = (router: IRouter) => { + router.post( + { + path: FLEET_SETUP_API_ROUTES.CREATE_PATTERN, + validate: PostFleetSetupRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-all`] }, + }, + createFleetSetupHandler + ); +}; - // Get Fleet setup +export const registerGetFleetStatusRoute = (router: IRouter) => { router.get( { path: FLEET_SETUP_API_ROUTES.INFO_PATTERN, @@ -40,14 +47,19 @@ export const registerRoutes = (router: IRouter, config: IngestManagerConfigType) }, getFleetStatusHandler ); +}; + +export const registerRoutes = (router: IRouter, config: IngestManagerConfigType) => { + // Ingest manager setup + registerIngestManagerSetupRoute(router); + + if (!config.fleet.enabled) { + return; + } + + // Get Fleet setup + registerGetFleetStatusRoute(router); // Create Fleet setup - router.post( - { - path: FLEET_SETUP_API_ROUTES.CREATE_PATTERN, - validate: PostFleetSetupRequestSchema, - options: { tags: [`access:${PLUGIN_ID}-all`] }, - }, - createFleetSetupHandler - ); + registerCreateFleetSetupRoute(router); }; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/requests.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/requests.test.ts new file mode 100644 index 0000000000000..f836a133a78a0 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/requests.test.ts @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { fetchUrl } from './requests'; +import { RegistryError } from '../../../errors'; +jest.mock('node-fetch'); + +const { Response, FetchError } = jest.requireActual('node-fetch'); +// eslint-disable-next-line @typescript-eslint/no-var-requires +const fetchMock = require('node-fetch') as jest.Mock; + +jest.setTimeout(120 * 1000); +describe('setupIngestManager', () => { + beforeEach(async () => {}); + + afterEach(async () => { + jest.clearAllMocks(); + }); + + describe('fetchUrl / getResponse errors', () => { + it('regular Errors do not retry. Becomes RegistryError', async () => { + fetchMock.mockImplementationOnce(() => { + throw new Error('mocked'); + }); + const promise = fetchUrl(''); + await expect(promise).rejects.toThrow(RegistryError); + expect(fetchMock).toHaveBeenCalledTimes(1); + }); + + it('TypeErrors do not retry. Becomes RegistryError', async () => { + fetchMock.mockImplementationOnce(() => { + // @ts-expect-error + null.f(); + }); + const promise = fetchUrl(''); + await expect(promise).rejects.toThrow(RegistryError); + expect(fetchMock).toHaveBeenCalledTimes(1); + }); + + describe('only system errors retry (like ECONNRESET)', () => { + it('they eventually succeed', async () => { + const successValue = JSON.stringify({ name: 'attempt 4 works', version: '1.2.3' }); + fetchMock + .mockImplementationOnce(() => { + throw new FetchError('message 1', 'system', { code: 'ESOMETHING' }); + }) + .mockImplementationOnce(() => { + throw new FetchError('message 2', 'system', { code: 'ESOMETHING' }); + }) + .mockImplementationOnce(() => { + throw new FetchError('message 3', 'system', { code: 'ESOMETHING' }); + }) + // this one succeeds + .mockImplementationOnce(() => Promise.resolve(new Response(successValue))) + .mockImplementationOnce(() => { + throw new FetchError('message 5', 'system', { code: 'ESOMETHING' }); + }) + .mockImplementationOnce(() => { + throw new FetchError('message 6', 'system', { code: 'ESOMETHING' }); + }); + + const promise = fetchUrl(''); + await expect(promise).resolves.toEqual(successValue); + // doesn't retry after success + expect(fetchMock).toHaveBeenCalledTimes(4); + const actualResultsOrder = fetchMock.mock.results.map(({ type }: { type: string }) => type); + expect(actualResultsOrder).toEqual(['throw', 'throw', 'throw', 'return']); + }); + + it('or error after 1 failure & 5 retries with RegistryError', async () => { + fetchMock + .mockImplementationOnce(() => { + throw new FetchError('message 1', 'system', { code: 'ESOMETHING' }); + }) + .mockImplementationOnce(() => { + throw new FetchError('message 2', 'system', { code: 'ESOMETHING' }); + }) + .mockImplementationOnce(() => { + throw new FetchError('message 3', 'system', { code: 'ESOMETHING' }); + }) + .mockImplementationOnce(() => { + throw new FetchError('message 4', 'system', { code: 'ESOMETHING' }); + }) + .mockImplementationOnce(() => { + throw new FetchError('message 5', 'system', { code: 'ESOMETHING' }); + }) + .mockImplementationOnce(() => { + throw new FetchError('message 6', 'system', { code: 'ESOMETHING' }); + }) + .mockImplementationOnce(() => { + throw new FetchError('message 7', 'system', { code: 'ESOMETHING' }); + }) + .mockImplementationOnce(() => { + throw new FetchError('message 8', 'system', { code: 'ESOMETHING' }); + }); + + const promise = fetchUrl(''); + await expect(promise).rejects.toThrow(RegistryError); + // doesn't retry after 1 failure & 5 failed retries + expect(fetchMock).toHaveBeenCalledTimes(6); + const actualResultsOrder = fetchMock.mock.results.map(({ type }: { type: string }) => type); + expect(actualResultsOrder).toEqual(['throw', 'throw', 'throw', 'throw', 'throw', 'throw']); + }); + }); + }); +}); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/requests.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/requests.ts index abf77ddddfd7a..5939dc204aae6 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/requests.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/requests.ts @@ -4,20 +4,49 @@ * you may not use this file except in compliance with the Elastic License. */ -import fetch, { Response } from 'node-fetch'; +import fetch, { FetchError, Response } from 'node-fetch'; +import pRetry from 'p-retry'; import { streamToString } from './streams'; import { RegistryError } from '../../../errors'; +type FailedAttemptErrors = pRetry.FailedAttemptError | FetchError | Error; + +// not sure what to call this function, but we're not exporting it +async function registryFetch(url: string) { + const response = await fetch(url); + + if (response.ok) { + return response; + } else { + // 4xx & 5xx responses + // exit without retry & throw RegistryError + throw new pRetry.AbortError( + new RegistryError(`Error connecting to package registry at ${url}: ${response.statusText}`) + ); + } +} + export async function getResponse(url: string): Promise { try { - const response = await fetch(url); - if (response.ok) { - return response; - } else { - throw new RegistryError( - `Error connecting to package registry at ${url}: ${response.statusText}` - ); - } + // we only want to retry certain failures like network issues + // the rest should only try the one time then fail as they do now + const response = await pRetry(() => registryFetch(url), { + factor: 2, + retries: 5, + onFailedAttempt: (error) => { + // we only want to retry certain types of errors, like `ECONNREFUSED` and other operational errors + // and let the others through without retrying + // + // throwing in onFailedAttempt will abandon all retries & fail the request + // we only want to retry system errors, so throw a RegistryError for everything else + if (!isSystemError(error)) { + throw new RegistryError( + `Error connecting to package registry at ${url}: ${error.message}` + ); + } + }, + }); + return response; } catch (e) { throw new RegistryError(`Error connecting to package registry at ${url}: ${e.message}`); } @@ -31,3 +60,14 @@ export async function getResponseStream(url: string): Promise { return getResponseStream(url).then(streamToString); } + +// node-fetch throws a FetchError for those types of errors and +// "All errors originating from Node.js core are marked with error.type = 'system'" +// https://github.com/node-fetch/node-fetch/blob/master/docs/ERROR-HANDLING.md#error-handling-with-node-fetch +function isFetchError(error: FailedAttemptErrors): error is FetchError { + return error instanceof FetchError || error.name === 'FetchError'; +} + +function isSystemError(error: FailedAttemptErrors): boolean { + return isFetchError(error) && error.type === 'system'; +} diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/streams.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/streams.ts index 97d6f7b40a588..3801303cf726f 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/streams.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/streams.ts @@ -11,7 +11,8 @@ export function bufferToStream(buffer: Buffer): PassThrough { return stream; } -export function streamToString(stream: NodeJS.ReadableStream): Promise { +export function streamToString(stream: NodeJS.ReadableStream | Buffer): Promise { + if (stream instanceof Buffer) return Promise.resolve(stream.toString()); return new Promise((resolve, reject) => { const body: string[] = []; stream.on('data', (chunk: string) => body.push(chunk)); diff --git a/x-pack/plugins/ingest_manager/server/services/setup.test.ts b/x-pack/plugins/ingest_manager/server/services/setup.test.ts index 474b2fde23c81..bb01862aaf317 100644 --- a/x-pack/plugins/ingest_manager/server/services/setup.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/setup.test.ts @@ -4,41 +4,59 @@ * you may not use this file except in compliance with the Elastic License. */ +import { xpackMocks } from '../../../../../x-pack/mocks'; +import { createAppContextStartContractMock } from '../mocks'; +import { appContextService } from './app_context'; import { setupIngestManager } from './setup'; -import { savedObjectsClientMock } from 'src/core/server/mocks'; -describe('setupIngestManager', () => { - it('returned promise should reject if errors thrown', async () => { - const { savedObjectsClient, callClusterMock } = makeErrorMocks(); - const setupPromise = setupIngestManager(savedObjectsClient, callClusterMock); - await expect(setupPromise).rejects.toThrow('mocked'); +const mockedMethodThrowsError = () => + jest.fn().mockImplementation(() => { + throw new Error('SO method mocked to throw'); }); -}); -function makeErrorMocks() { - jest.mock('./app_context'); // else fails w/"Logger not set." - jest.mock('./epm/registry/registry_url', () => { - return { - fetchUrl: () => { - throw new Error('mocked registry#fetchUrl'); - }, - }; +class CustomTestError extends Error {} +const mockedMethodThrowsCustom = () => + jest.fn().mockImplementation(() => { + throw new CustomTestError('method mocked to throw'); }); - const callClusterMock = jest.fn(); - const savedObjectsClient = savedObjectsClientMock.create(); - savedObjectsClient.find = jest.fn().mockImplementation(() => { - throw new Error('mocked SO#find'); - }); - savedObjectsClient.get = jest.fn().mockImplementation(() => { - throw new Error('mocked SO#get'); +describe('setupIngestManager', () => { + let context: ReturnType; + + beforeEach(async () => { + context = xpackMocks.createRequestHandlerContext(); + // prevents `Logger not set.` and other appContext errors + appContextService.start(createAppContextStartContractMock()); }); - savedObjectsClient.update = jest.fn().mockImplementation(() => { - throw new Error('mocked SO#update'); + + afterEach(async () => { + jest.clearAllMocks(); + appContextService.stop(); }); - return { - savedObjectsClient, - callClusterMock, - }; -} + describe('should reject with any error thrown underneath', () => { + it('SO client throws plain Error', async () => { + const soClient = context.core.savedObjects.client; + soClient.create = mockedMethodThrowsError(); + soClient.find = mockedMethodThrowsError(); + soClient.get = mockedMethodThrowsError(); + soClient.update = mockedMethodThrowsError(); + + const setupPromise = setupIngestManager(soClient, jest.fn()); + await expect(setupPromise).rejects.toThrow('SO method mocked to throw'); + await expect(setupPromise).rejects.toThrow(Error); + }); + + it('SO client throws other error', async () => { + const soClient = context.core.savedObjects.client; + soClient.create = mockedMethodThrowsCustom(); + soClient.find = mockedMethodThrowsCustom(); + soClient.get = mockedMethodThrowsCustom(); + soClient.update = mockedMethodThrowsCustom(); + + const setupPromise = setupIngestManager(soClient, jest.fn()); + await expect(setupPromise).rejects.toThrow('method mocked to throw'); + await expect(setupPromise).rejects.toThrow(CustomTestError); + }); + }); +}); diff --git a/x-pack/plugins/ingest_manager/server/services/setup.ts b/x-pack/plugins/ingest_manager/server/services/setup.ts index 727b49cebc608..fb4430f8cf727 100644 --- a/x-pack/plugins/ingest_manager/server/services/setup.ts +++ b/x-pack/plugins/ingest_manager/server/services/setup.ts @@ -26,116 +26,101 @@ import { packagePolicyService } from './package_policy'; import { generateEnrollmentAPIKey } from './api_keys'; import { settingsService } from '.'; import { appContextService } from './app_context'; +import { awaitIfPending } from './setup_utils'; const FLEET_ENROLL_USERNAME = 'fleet_enroll'; const FLEET_ENROLL_ROLE = 'fleet_enroll'; -// the promise which tracks the setup -let setupIngestStatus: Promise | undefined; -// default resolve & reject to guard against "undefined is not a function" errors -let onSetupResolve = () => {}; -let onSetupReject = (error: Error) => {}; +export interface SetupStatus { + isIntialized: true | undefined; +} export async function setupIngestManager( soClient: SavedObjectsClientContract, callCluster: CallESAsCurrentUser -) { - // installation in progress - if (setupIngestStatus) { - await setupIngestStatus; - } else { - // create the initial promise - setupIngestStatus = new Promise((res, rej) => { - onSetupResolve = res; - onSetupReject = rej; - }); +): Promise { + return awaitIfPending(async () => createSetupSideEffects(soClient, callCluster)); +} + +async function createSetupSideEffects( + soClient: SavedObjectsClientContract, + callCluster: CallESAsCurrentUser +): Promise { + const [installedPackages, defaultOutput, defaultAgentPolicy] = await Promise.all([ + // packages installed by default + ensureInstalledDefaultPackages(soClient, callCluster), + outputService.ensureDefaultOutput(soClient), + agentPolicyService.ensureDefaultAgentPolicy(soClient), + ensureDefaultIndices(callCluster), + settingsService.getSettings(soClient).catch((e: any) => { + if (e.isBoom && e.output.statusCode === 404) { + const http = appContextService.getHttpSetup(); + const serverInfo = http.getServerInfo(); + const basePath = http.basePath; + + const cloud = appContextService.getCloud(); + const cloudId = cloud?.isCloudEnabled && cloud.cloudId; + const cloudUrl = cloudId && decodeCloudId(cloudId)?.kibanaUrl; + const flagsUrl = appContextService.getConfig()?.fleet?.kibana?.host; + const defaultUrl = url.format({ + protocol: serverInfo.protocol, + hostname: serverInfo.hostname, + port: serverInfo.port, + pathname: basePath.serverBasePath, + }); + + return settingsService.saveSettings(soClient, { + agent_auto_upgrade: true, + package_auto_upgrade: true, + kibana_url: cloudUrl || flagsUrl || defaultUrl, + }); + } + + return Promise.reject(e); + }), + ]); + + // ensure default packages are added to the default conifg + const agentPolicyWithPackagePolicies = await agentPolicyService.get( + soClient, + defaultAgentPolicy.id, + true + ); + if (!agentPolicyWithPackagePolicies) { + throw new Error('Policy not found'); + } + if ( + agentPolicyWithPackagePolicies.package_policies.length && + typeof agentPolicyWithPackagePolicies.package_policies[0] === 'string' + ) { + throw new Error('Policy not found'); } - try { - const [installedPackages, defaultOutput, defaultAgentPolicy] = await Promise.all([ - // packages installed by default - ensureInstalledDefaultPackages(soClient, callCluster), - outputService.ensureDefaultOutput(soClient), - agentPolicyService.ensureDefaultAgentPolicy(soClient), - ensureDefaultIndices(callCluster), - settingsService.getSettings(soClient).catch((e: any) => { - if (e.isBoom && e.output.statusCode === 404) { - const http = appContextService.getHttpSetup(); - const serverInfo = http.getServerInfo(); - const basePath = http.basePath; - - const cloud = appContextService.getCloud(); - const cloudId = cloud?.isCloudEnabled && cloud.cloudId; - const cloudUrl = cloudId && decodeCloudId(cloudId)?.kibanaUrl; - const flagsUrl = appContextService.getConfig()?.fleet?.kibana?.host; - const defaultUrl = url.format({ - protocol: serverInfo.protocol, - hostname: serverInfo.hostname, - port: serverInfo.port, - pathname: basePath.serverBasePath, - }); - - return settingsService.saveSettings(soClient, { - agent_auto_upgrade: true, - package_auto_upgrade: true, - kibana_url: cloudUrl || flagsUrl || defaultUrl, - }); - } - - return Promise.reject(e); - }), - ]); - - // ensure default packages are added to the default conifg - const agentPolicyWithPackagePolicies = await agentPolicyService.get( - soClient, - defaultAgentPolicy.id, - true + for (const installedPackage of installedPackages) { + const packageShouldBeInstalled = DEFAULT_AGENT_POLICIES_PACKAGES.some( + (packageName) => installedPackage.name === packageName ); - if (!agentPolicyWithPackagePolicies) { - throw new Error('Policy not found'); - } - if ( - agentPolicyWithPackagePolicies.package_policies.length && - typeof agentPolicyWithPackagePolicies.package_policies[0] === 'string' - ) { - throw new Error('Policy not found'); + if (!packageShouldBeInstalled) { + continue; } - for (const installedPackage of installedPackages) { - const packageShouldBeInstalled = DEFAULT_AGENT_POLICIES_PACKAGES.some( - (packageName) => installedPackage.name === packageName - ); - if (!packageShouldBeInstalled) { - continue; + + const isInstalled = agentPolicyWithPackagePolicies.package_policies.some( + (d: PackagePolicy | string) => { + return typeof d !== 'string' && d.package?.name === installedPackage.name; } + ); - const isInstalled = agentPolicyWithPackagePolicies.package_policies.some( - (d: PackagePolicy | string) => { - return typeof d !== 'string' && d.package?.name === installedPackage.name; - } + if (!isInstalled) { + await addPackageToAgentPolicy( + soClient, + callCluster, + installedPackage, + agentPolicyWithPackagePolicies, + defaultOutput ); - - if (!isInstalled) { - await addPackageToAgentPolicy( - soClient, - callCluster, - installedPackage, - agentPolicyWithPackagePolicies, - defaultOutput - ); - } } - - // if everything works, resolve/succeed - onSetupResolve(); - } catch (error) { - // if anything errors, reject/fail - onSetupReject(error); } - // be sure to return the promise because it has the resolved/rejected status attached to it - // otherwise, we effectively return success every time even if there are errors - // because `return undefined` -> `Promise.resolve(undefined)` in an `async` function - return setupIngestStatus; + return { isIntialized: true }; } export async function setupFleet( diff --git a/x-pack/plugins/ingest_manager/server/services/setup_utils.test.ts b/x-pack/plugins/ingest_manager/server/services/setup_utils.test.ts new file mode 100644 index 0000000000000..8d71fc48a2129 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/setup_utils.test.ts @@ -0,0 +1,149 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { awaitIfPending } from './setup_utils'; + +async function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +describe('awaitIfPending', () => { + it('first promise called blocks others', async () => { + const fnA = jest.fn().mockImplementation(async () => {}); + const fnB = jest.fn().mockImplementation(async () => {}); + const fnC = jest.fn().mockImplementation(async () => {}); + const fnD = jest.fn().mockImplementation(async () => {}); + const promises = [ + awaitIfPending(fnA), + awaitIfPending(fnB), + awaitIfPending(fnC), + awaitIfPending(fnD), + ]; + await Promise.all(promises); + + expect(fnA).toHaveBeenCalledTimes(1); + expect(fnB).toHaveBeenCalledTimes(0); + expect(fnC).toHaveBeenCalledTimes(0); + expect(fnD).toHaveBeenCalledTimes(0); + }); + + describe('first promise created, not necessarily first fulfilled, sets value for all in queue', () => { + it('succeeds', async () => { + const fnA = jest.fn().mockImplementation(async () => { + await sleep(1000); + return 'called first'; + }); + const fnB = jest.fn().mockImplementation(async () => 'called second'); + const fnC = jest.fn().mockImplementation(async () => 'called third'); + const fnD = jest.fn().mockImplementation(async () => 'called fourth'); + const promises = [ + awaitIfPending(fnA), + awaitIfPending(fnB), + awaitIfPending(fnC), + awaitIfPending(fnD), + ]; + + expect(fnA).toHaveBeenCalledTimes(1); + expect(fnB).toHaveBeenCalledTimes(0); + expect(fnC).toHaveBeenCalledTimes(0); + expect(fnD).toHaveBeenCalledTimes(0); + await expect(Promise.all(promises)).resolves.toEqual([ + 'called first', + 'called first', + 'called first', + 'called first', + ]); + }); + + it('throws', async () => { + const expectedError = new Error('error is called first'); + const fnA = jest.fn().mockImplementation(async () => { + await sleep(1000); + throw expectedError; + }); + const fnB = jest.fn().mockImplementation(async () => 'called second'); + const fnC = jest.fn().mockImplementation(async () => 'called third'); + const fnD = jest.fn().mockImplementation(async () => 'called fourth'); + const promises = [ + awaitIfPending(fnA), + awaitIfPending(fnB), + awaitIfPending(fnC), + awaitIfPending(fnD), + ]; + + await expect(Promise.all(promises)).rejects.toThrow(expectedError); + await expect(Promise.allSettled(promises)).resolves.toEqual([ + { status: 'rejected', reason: expectedError }, + { status: 'rejected', reason: expectedError }, + { status: 'rejected', reason: expectedError }, + { status: 'rejected', reason: expectedError }, + ]); + + expect(fnA).toHaveBeenCalledTimes(1); + expect(fnB).toHaveBeenCalledTimes(0); + expect(fnC).toHaveBeenCalledTimes(0); + expect(fnD).toHaveBeenCalledTimes(0); + }); + }); + + it('does not block other calls after batch is fulfilled. can call again for a new result', async () => { + const fnA = jest + .fn() + .mockImplementationOnce(async () => 'fnA first') + .mockImplementationOnce(async () => 'fnA second') + .mockImplementation(async () => 'fnA default/2+'); + const fnB = jest.fn().mockImplementation(async () => {}); + const fnC = jest.fn().mockImplementation(async () => {}); + const fnD = jest.fn().mockImplementation(async () => {}); + let promises = [ + awaitIfPending(fnA), + awaitIfPending(fnB), + awaitIfPending(fnC), + awaitIfPending(fnD), + ]; + let results = await Promise.all(promises); + + expect(fnA).toHaveBeenCalledTimes(1); + expect(fnB).toHaveBeenCalledTimes(0); + expect(fnC).toHaveBeenCalledTimes(0); + expect(fnD).toHaveBeenCalledTimes(0); + expect(results).toEqual(['fnA first', 'fnA first', 'fnA first', 'fnA first']); + + promises = [awaitIfPending(fnA), awaitIfPending(fnB), awaitIfPending(fnC), awaitIfPending(fnD)]; + results = await Promise.all(promises); + expect(fnA).toHaveBeenCalledTimes(2); + expect(fnB).toHaveBeenCalledTimes(0); + expect(fnC).toHaveBeenCalledTimes(0); + expect(fnD).toHaveBeenCalledTimes(0); + expect(results).toEqual(['fnA second', 'fnA second', 'fnA second', 'fnA second']); + + promises = [awaitIfPending(fnA), awaitIfPending(fnB), awaitIfPending(fnC), awaitIfPending(fnD)]; + results = await Promise.all(promises); + expect(fnA).toHaveBeenCalledTimes(3); + expect(fnB).toHaveBeenCalledTimes(0); + expect(fnC).toHaveBeenCalledTimes(0); + expect(fnD).toHaveBeenCalledTimes(0); + expect(results).toEqual([ + 'fnA default/2+', + 'fnA default/2+', + 'fnA default/2+', + 'fnA default/2+', + ]); + + promises = [awaitIfPending(fnA), awaitIfPending(fnB), awaitIfPending(fnC), awaitIfPending(fnD)]; + results = await Promise.all(promises); + expect(fnA).toHaveBeenCalledTimes(4); + expect(fnB).toHaveBeenCalledTimes(0); + expect(fnC).toHaveBeenCalledTimes(0); + expect(fnD).toHaveBeenCalledTimes(0); + expect(results).toEqual([ + 'fnA default/2+', + 'fnA default/2+', + 'fnA default/2+', + 'fnA default/2+', + ]); + }); +}); diff --git a/x-pack/plugins/ingest_manager/server/services/setup_utils.ts b/x-pack/plugins/ingest_manager/server/services/setup_utils.ts new file mode 100644 index 0000000000000..3c752bd410c5a --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/setup_utils.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// the promise which tracks the setup +let status: Promise | undefined; +let isPending = false; +// default resolve to guard against "undefined is not a function" errors +let onResolve = (value?: unknown) => {}; +let onReject = (reason: any) => {}; + +export async function awaitIfPending(asyncFunction: Function): Promise { + // pending successful or failed attempt + if (isPending) { + // don't run concurrent installs + // return a promise which will eventually resolve/reject + return status; + } else { + // create the initial promise + status = new Promise((res, rej) => { + isPending = true; + onResolve = res; + onReject = rej; + }); + } + try { + const result = await asyncFunction().catch(onReject); + onResolve(result); + } catch (error) { + // if something fails + onReject(error); + } + isPending = false; + return status; +} diff --git a/x-pack/plugins/maps/public/elasticsearch_geo_utils.d.ts b/x-pack/plugins/maps/common/elasticsearch_geo_utils.d.ts similarity index 70% rename from x-pack/plugins/maps/public/elasticsearch_geo_utils.d.ts rename to x-pack/plugins/maps/common/elasticsearch_geo_utils.d.ts index 964afb589187f..44250360e9d00 100644 --- a/x-pack/plugins/maps/public/elasticsearch_geo_utils.d.ts +++ b/x-pack/plugins/maps/common/elasticsearch_geo_utils.d.ts @@ -4,8 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { MapExtent } from '../common/descriptor_types'; +import { MapExtent } from './descriptor_types'; export function scaleBounds(bounds: MapExtent, scaleFactor: number): MapExtent; export function turfBboxToBounds(turfBbox: unknown): MapExtent; + +export function clampToLatBounds(lat: number): number; + +export function clampToLonBounds(lon: number): number; diff --git a/x-pack/plugins/maps/public/elasticsearch_geo_utils.js b/x-pack/plugins/maps/common/elasticsearch_geo_utils.js similarity index 98% rename from x-pack/plugins/maps/public/elasticsearch_geo_utils.js rename to x-pack/plugins/maps/common/elasticsearch_geo_utils.js index b32125e6eb614..f2bf83ae18bb0 100644 --- a/x-pack/plugins/maps/public/elasticsearch_geo_utils.js +++ b/x-pack/plugins/maps/common/elasticsearch_geo_utils.js @@ -16,10 +16,12 @@ import { LON_INDEX, LAT_INDEX, } from '../common/constants'; -import { getEsSpatialRelationLabel } from '../common/i18n_getters'; -import { SPATIAL_FILTER_TYPE } from './kibana_services'; +import { getEsSpatialRelationLabel } from './i18n_getters'; +import { FILTERS } from '../../../../src/plugins/data/common'; import turfCircle from '@turf/circle'; +const SPATIAL_FILTER_TYPE = FILTERS.SPATIAL_FILTER; + function ensureGeoField(type) { const expectedTypes = [ES_GEO_FIELD_TYPE.GEO_POINT, ES_GEO_FIELD_TYPE.GEO_SHAPE]; if (!expectedTypes.includes(type)) { diff --git a/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js b/x-pack/plugins/maps/common/elasticsearch_geo_utils.test.js similarity index 97% rename from x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js rename to x-pack/plugins/maps/common/elasticsearch_geo_utils.test.js index 6a4b4b78c829e..a8d5d650740cd 100644 --- a/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js +++ b/x-pack/plugins/maps/common/elasticsearch_geo_utils.test.js @@ -4,14 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('ui/new_platform'); - -jest.mock('./kibana_services', () => { - return { - SPATIAL_FILTER_TYPE: 'spatial_filter', - }; -}); - import { hitsToGeoJson, geoPointToGeometry, @@ -22,7 +14,7 @@ import { makeESBbox, scaleBounds, } from './elasticsearch_geo_utils'; -import { indexPatterns } from '../../../../src/plugins/data/public'; +import _ from 'lodash'; const geoFieldName = 'location'; @@ -173,19 +165,14 @@ describe('hitsToGeoJson', () => { }); describe('dot in geoFieldName', () => { - const indexPatternMock = { - fields: { - getByName: (name) => { - const fields = { - ['my.location']: { - type: 'geo_point', - }, - }; - return fields[name]; - }, - }, + // This essentially should test the implmentation of index-pattern.flattenHit, rather than anything in geo_utils. + // Leaving this here for reference. + const geoFieldName = 'my.location'; + const indexPatternFlattenHit = (hit) => { + return { + [geoFieldName]: _.get(hit._source, geoFieldName), + }; }; - const indexPatternFlattenHit = indexPatterns.flattenHitWrapper(indexPatternMock); it('Should handle geoField being an object', () => { const hits = [ diff --git a/x-pack/plugins/maps/public/actions/data_request_actions.ts b/x-pack/plugins/maps/public/actions/data_request_actions.ts index a22e8d582bc5e..6f5ed680ac64f 100644 --- a/x-pack/plugins/maps/public/actions/data_request_actions.ts +++ b/x-pack/plugins/maps/public/actions/data_request_actions.ts @@ -41,7 +41,7 @@ import { ILayer } from '../classes/layers/layer'; import { IVectorLayer } from '../classes/layers/vector_layer/vector_layer'; import { DataMeta, MapExtent, MapFilters } from '../../common/descriptor_types'; import { DataRequestAbortError } from '../classes/util/data_request'; -import { scaleBounds, turfBboxToBounds } from '../elasticsearch_geo_utils'; +import { scaleBounds, turfBboxToBounds } from '../../common/elasticsearch_geo_utils'; const FIT_TO_BOUNDS_SCALE_FACTOR = 0.1; diff --git a/x-pack/plugins/maps/public/actions/map_actions.ts b/x-pack/plugins/maps/public/actions/map_actions.ts index 7ba58307e1952..f408896853155 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.ts +++ b/x-pack/plugins/maps/public/actions/map_actions.ts @@ -54,7 +54,7 @@ import { MapRefreshConfig, } from '../../common/descriptor_types'; import { INITIAL_LOCATION } from '../../common/constants'; -import { scaleBounds } from '../elasticsearch_geo_utils'; +import { scaleBounds } from '../../common/elasticsearch_geo_utils'; export function setMapInitError(errorMessage: string) { return { diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/convert_to_geojson.js b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/convert_to_geojson.js index a95a8be4b24c8..35dbebdfd3c8a 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/convert_to_geojson.js +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/convert_to_geojson.js @@ -8,7 +8,7 @@ import _ from 'lodash'; import { RENDER_AS } from '../../../../common/constants'; import { getTileBoundingBox } from './geo_tile_utils'; import { extractPropertiesFromBucket } from '../../util/es_agg_utils'; -import { clamp } from '../../../elasticsearch_geo_utils'; +import { clamp } from '../../../../common/elasticsearch_geo_utils'; const GRID_BUCKET_KEYS_TO_IGNORE = ['key', 'gridCentroid']; diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js index a4dba71307b71..a6322ff3ba784 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js @@ -21,7 +21,7 @@ import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { AbstractESAggSource, DEFAULT_METRIC } from '../es_agg_source'; import { DataRequestAbortError } from '../../util/data_request'; import { registerSource } from '../source_registry'; -import { makeESBbox } from '../../../elasticsearch_geo_utils'; +import { makeESBbox } from '../../../../common/elasticsearch_geo_utils'; export const MAX_GEOTILE_LEVEL = 29; diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/geo_tile_utils.js b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/geo_tile_utils.js index 251e33b9579cb..89b24522e4275 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/geo_tile_utils.js +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/geo_tile_utils.js @@ -6,7 +6,7 @@ import _ from 'lodash'; import { DECIMAL_DEGREES_PRECISION } from '../../../../common/constants'; -import { clampToLatBounds } from '../../../elasticsearch_geo_utils'; +import { clampToLatBounds } from '../../../../common/elasticsearch_geo_utils'; const ZOOM_TILE_KEY_INDEX = 0; const X_TILE_KEY_INDEX = 1; diff --git a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js index 79eccf09b2888..92b0c717f6724 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js @@ -16,7 +16,7 @@ import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { convertToLines } from './convert_to_lines'; import { AbstractESAggSource, DEFAULT_METRIC } from '../es_agg_source'; import { registerSource } from '../source_registry'; -import { turfBboxToBounds } from '../../../elasticsearch_geo_utils'; +import { turfBboxToBounds } from '../../../../common/elasticsearch_geo_utils'; import { DataRequestAbortError } from '../../util/data_request'; const MAX_GEOTILE_LEVEL = 29; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js index 256becf70ffb0..6d61c4a7455b2 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js @@ -9,7 +9,7 @@ import React from 'react'; import { AbstractESSource } from '../es_source'; import { getSearchService } from '../../../kibana_services'; -import { hitsToGeoJson } from '../../../elasticsearch_geo_utils'; +import { hitsToGeoJson } from '../../../../common/elasticsearch_geo_utils'; import { UpdateSourceEditor } from './update_source_editor'; import { SOURCE_TYPES, diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js index 866e3c76c2a3f..8cc2aa018979b 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js @@ -11,7 +11,7 @@ import { getTimeFilter, getSearchService, } from '../../../kibana_services'; -import { createExtentFilter } from '../../../elasticsearch_geo_utils'; +import { createExtentFilter } from '../../../../common/elasticsearch_geo_utils'; import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import uuid from 'uuid/v4'; diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js index e1779c1afbf47..b0ce52b4db7ab 100644 --- a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js +++ b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js @@ -10,7 +10,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { URL_MAX_LENGTH } from '../../../../../../../src/core/public'; -import { createSpatialFilterWithGeometry } from '../../../elasticsearch_geo_utils'; +import { createSpatialFilterWithGeometry } from '../../../../common/elasticsearch_geo_utils'; import { GEO_JSON_TYPE } from '../../../../common/constants'; import { GeometryFilterForm } from '../../../components/geometry_filter_form'; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js b/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js index 2daa4b2c900f5..6de936fa4a8f1 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js @@ -15,7 +15,7 @@ import { createSpatialFilterWithGeometry, getBoundingBoxGeometry, roundCoordinates, -} from '../../../../elasticsearch_geo_utils'; +} from '../../../../../common/elasticsearch_geo_utils'; import { DrawTooltip } from './draw_tooltip'; const DRAW_RECTANGLE = 'draw_rectangle'; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/plugins/maps/public/connected_components/map/mb/view.js index d85959c3a08a4..5a38f6039ae4b 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/view.js @@ -19,7 +19,7 @@ import sprites1 from '@elastic/maki/dist/sprite@1.png'; import sprites2 from '@elastic/maki/dist/sprite@2.png'; import { DrawControl } from './draw_control'; import { TooltipControl } from './tooltip_control'; -import { clampToLatBounds, clampToLonBounds } from '../../../elasticsearch_geo_utils'; +import { clampToLatBounds, clampToLonBounds } from '../../../../common/elasticsearch_geo_utils'; import { getInitialView } from './get_initial_view'; import { getPreserveDrawingBuffer } from '../../../kibana_services'; diff --git a/x-pack/plugins/maps/public/kibana_services.ts b/x-pack/plugins/maps/public/kibana_services.ts index f8f89ebaed102..239a2898a06fc 100644 --- a/x-pack/plugins/maps/public/kibana_services.ts +++ b/x-pack/plugins/maps/public/kibana_services.ts @@ -5,14 +5,11 @@ */ import _ from 'lodash'; -import { esFilters } from '../../../../src/plugins/data/public'; import { MapsLegacyConfigType } from '../../../../src/plugins/maps_legacy/public'; import { MapsConfigType } from '../config'; import { MapsPluginStartDependencies } from './plugin'; import { CoreStart } from '../../../../src/core/public'; -export const SPATIAL_FILTER_TYPE = esFilters.FILTERS.SPATIAL_FILTER; - let licenseId: string | undefined; export const setLicenseId = (latestLicenseId: string | undefined) => (licenseId = latestLicenseId); export const getLicenseId = () => licenseId; diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.ts b/x-pack/plugins/maps/public/selectors/map_selectors.ts index 40ffda3f31c26..d48ee24027561 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.ts @@ -32,7 +32,7 @@ import { SPATIAL_FILTERS_LAYER_ID, } from '../../common/constants'; // @ts-ignore -import { extractFeaturesFromFilters } from '../elasticsearch_geo_utils'; +import { extractFeaturesFromFilters } from '../../common/elasticsearch_geo_utils'; import { MapStoreState } from '../reducers/store'; import { DataRequestDescriptor, diff --git a/x-pack/plugins/monitoring/server/license_service.ts b/x-pack/plugins/monitoring/server/license_service.ts index fb45abc22afa4..ecadf9d2d85db 100644 --- a/x-pack/plugins/monitoring/server/license_service.ts +++ b/x-pack/plugins/monitoring/server/license_service.ts @@ -33,14 +33,13 @@ export class LicenseService { let rawLicense: Readonly | undefined; let licenseSubscription: Subscription | undefined = license$.subscribe((nextRawLicense) => { rawLicense = nextRawLicense; + if (!rawLicense?.isAvailable) { + log.warn( + `X-Pack Monitoring Cluster Alerts will not be available: ${rawLicense?.getUnavailableReason()}` + ); + } }); - if (!rawLicense?.isAvailable) { - log.warn( - `X-Pack Monitoring Cluster Alerts will not be available: ${rawLicense?.getUnavailableReason()}` - ); - } - return { refresh, license$, diff --git a/x-pack/plugins/monitoring/server/plugin.test.ts b/x-pack/plugins/monitoring/server/plugin.test.ts new file mode 100644 index 0000000000000..13da0a3c9242e --- /dev/null +++ b/x-pack/plugins/monitoring/server/plugin.test.ts @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Plugin } from './plugin'; +import { combineLatest } from 'rxjs'; +// @ts-ignore +import { initBulkUploader } from './kibana_monitoring'; +import { AlertsFactory } from './alerts'; + +jest.mock('rxjs', () => ({ + // @ts-ignore + ...jest.requireActual('rxjs'), + combineLatest: jest.fn(), +})); + +jest.mock('./es_client/instantiate_client', () => ({ + instantiateClient: jest.fn(), +})); + +jest.mock('./license_service', () => ({ + LicenseService: jest.fn().mockImplementation(() => ({ + setup: jest.fn().mockImplementation(() => ({ + refresh: jest.fn(), + })), + })), +})); + +jest.mock('./kibana_monitoring', () => ({ + initBulkUploader: jest.fn(), +})); + +describe('Monitoring plugin', () => { + const initializerContext = { + logger: { + get: jest.fn().mockImplementation(() => ({ + info: jest.fn(), + })), + }, + config: { + create: jest.fn().mockImplementation(() => ({ + pipe: jest.fn().mockImplementation(() => ({ + toPromise: jest.fn(), + })), + })), + legacy: { + globalConfig$: {}, + }, + }, + env: { + packageInfo: { + version: '1.0.0', + }, + }, + }; + + const coreSetup = { + http: { + createRouter: jest.fn(), + getServerInfo: jest.fn().mockImplementation(() => ({ + port: 5601, + })), + basePath: { + serverBasePath: '', + }, + }, + uuid: { + getInstanceUuid: jest.fn(), + }, + elasticsearch: { + legacy: { + client: {}, + createClient: jest.fn(), + }, + }, + }; + + const setupPlugins = { + usageCollection: { + getCollectorByType: jest.fn(), + makeStatsCollector: jest.fn(), + registerCollector: jest.fn(), + }, + alerts: { + registerType: jest.fn(), + }, + }; + + let config = {}; + const defaultConfig = { + ui: { + elasticsearch: {}, + }, + kibana: { + collection: { + interval: 30000, + }, + }, + }; + + beforeEach(() => { + config = defaultConfig; + (combineLatest as jest.Mock).mockImplementation(() => { + return { + pipe: jest.fn().mockImplementation(() => { + return { + toPromise: jest.fn().mockImplementation(() => { + return [config, 2]; + }), + }; + }), + }; + }); + }); + + afterEach(() => { + (setupPlugins.alerts.registerType as jest.Mock).mockReset(); + }); + + it('always create the bulk uploader', async () => { + const setKibanaStatusGetter = jest.fn(); + (initBulkUploader as jest.Mock).mockImplementation(() => { + return { + setKibanaStatusGetter, + }; + }); + const plugin = new Plugin(initializerContext as any); + const contract = await plugin.setup(coreSetup as any, setupPlugins as any); + contract.registerLegacyAPI(null as any); + expect(setKibanaStatusGetter).toHaveBeenCalled(); + }); + + it('should register all alerts', async () => { + const alerts = AlertsFactory.getAll(); + const plugin = new Plugin(initializerContext as any); + await plugin.setup(coreSetup as any, setupPlugins as any); + expect(setupPlugins.alerts.registerType).toHaveBeenCalledTimes(alerts.length); + }); +}); diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 3aedb6831e7ab..043435c48a211 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -71,7 +71,7 @@ export class Plugin { private licenseService = {} as MonitoringLicenseService; private monitoringCore = {} as MonitoringCore; private legacyShimDependencies = {} as LegacyShimDependencies; - private bulkUploader = {} as IBulkUploader; + private bulkUploader: IBulkUploader = {} as IBulkUploader; constructor(initializerContext: PluginInitializerContext) { this.initializerContext = initializerContext; @@ -152,28 +152,28 @@ export class Plugin { registerCollectors(plugins.usageCollection, config); } - // If collection is enabled, create the bulk uploader + // Always create the bulk uploader const kibanaMonitoringLog = this.getLogger(KIBANA_MONITORING_LOGGING_TAG); + const bulkUploader = (this.bulkUploader = initBulkUploader({ + elasticsearch: core.elasticsearch, + config, + log: kibanaMonitoringLog, + kibanaStats: { + uuid: core.uuid.getInstanceUuid(), + name: serverInfo.name, + index: get(legacyConfig, 'kibana.index'), + host: serverInfo.hostname, + locale: i18n.getLocale(), + port: serverInfo.port.toString(), + transport_address: `${serverInfo.hostname}:${serverInfo.port}`, + version: this.initializerContext.env.packageInfo.version, + snapshot: snapshotRegex.test(this.initializerContext.env.packageInfo.version), + }, + })); + + // If collection is enabled, start it const kibanaCollectionEnabled = config.kibana.collection.enabled; if (kibanaCollectionEnabled) { - // Start kibana internal collection - const bulkUploader = (this.bulkUploader = initBulkUploader({ - elasticsearch: core.elasticsearch, - config, - log: kibanaMonitoringLog, - kibanaStats: { - uuid: core.uuid.getInstanceUuid(), - name: serverInfo.name, - index: get(legacyConfig, 'kibana.index'), - host: serverInfo.hostname, - locale: i18n.getLocale(), - port: serverInfo.port.toString(), - transport_address: `${serverInfo.hostname}:${serverInfo.port}`, - version: this.initializerContext.env.packageInfo.version, - snapshot: snapshotRegex.test(this.initializerContext.env.packageInfo.version), - }, - })); - // Do not use `this.licenseService` as that looks at the monitoring cluster // whereas we want to check the production cluster here if (plugins.licensing) { @@ -188,6 +188,10 @@ export class Plugin { bulkUploader.handleNotEnabled(); } }); + } else { + kibanaMonitoringLog.warn( + 'Internal collection for Kibana monitoring is disabled due to missing license information.' + ); } } else { kibanaMonitoringLog.info( diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts index 1cf879adc5415..ca191602dcf44 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts @@ -135,26 +135,36 @@ const expectPrivilegeCheck = async (fn: Function, args: Record) => ); }; -const expectObjectNamespaceFiltering = async (fn: Function, args: Record) => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementationOnce( - getMockCheckPrivilegesSuccess // privilege check for authorization - ); +const expectObjectNamespaceFiltering = async ( + fn: Function, + args: Record, + privilegeChecks = 1 +) => { + for (let i = 0; i < privilegeChecks; i++) { + clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementationOnce( + getMockCheckPrivilegesSuccess // privilege check for authorization + ); + } clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( getMockCheckPrivilegesFailure // privilege check for namespace filtering ); - const authorizedNamespace = args.options.namespace || 'default'; + const authorizedNamespace = args.options?.namespace || 'default'; const namespaces = ['some-other-namespace', authorizedNamespace]; const returnValue = { namespaces, foo: 'bar' }; // we don't know which base client method will be called; mock them all clientOpts.baseClient.create.mockReturnValue(returnValue as any); clientOpts.baseClient.get.mockReturnValue(returnValue as any); clientOpts.baseClient.update.mockReturnValue(returnValue as any); + clientOpts.baseClient.addToNamespaces.mockReturnValue(returnValue as any); + clientOpts.baseClient.deleteFromNamespaces.mockReturnValue(returnValue as any); const result = await fn.bind(client)(...Object.values(args)); expect(result).toEqual(expect.objectContaining({ namespaces: [authorizedNamespace, '?'] })); - expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenCalledTimes(2); + expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenCalledTimes( + privilegeChecks + 1 + ); expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenLastCalledWith( 'login:', namespaces @@ -369,6 +379,11 @@ describe('#addToNamespaces', () => { undefined // default namespace ); }); + + test(`filters namespaces that the user doesn't have access to`, async () => { + // this operation is unique because it requires two privilege checks before it executes + await expectObjectNamespaceFiltering(client.addToNamespaces, { type, id, namespaces }, 2); + }); }); describe('#bulkCreate', () => { @@ -682,6 +697,10 @@ describe('#deleteFromNamespaces', () => { namespaces ); }); + + test(`filters namespaces that the user doesn't have access to`, async () => { + await expectObjectNamespaceFiltering(client.deleteFromNamespaces, { type, id, namespaces }); + }); }); describe('#update', () => { diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts index 621299a0f025e..9fd8a732c4eab 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts @@ -164,7 +164,8 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra // result in a 404 error. await this.ensureAuthorized(type, 'update', namespace, args, 'addToNamespacesUpdate'); - return await this.baseClient.addToNamespaces(type, id, namespaces, options); + const result = await this.baseClient.addToNamespaces(type, id, namespaces, options); + return await this.redactSavedObjectNamespaces(result); } public async deleteFromNamespaces( @@ -177,7 +178,8 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra // To un-share an object, the user must have the "delete" permission in each of the target namespaces. await this.ensureAuthorized(type, 'delete', namespaces, args, 'deleteFromNamespaces'); - return await this.baseClient.deleteFromNamespaces(type, id, namespaces, options); + const result = await this.baseClient.deleteFromNamespaces(type, id, namespaces, options); + return await this.redactSavedObjectNamespaces(result); } public async bulkUpdate( diff --git a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts index cd4573817cc27..5f2de69689865 100644 --- a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts @@ -10,6 +10,7 @@ import { FIELDS_BROWSER_SELECTED_CATEGORY_TITLE, } from '../screens/fields_browser'; import { + EVENTS_PAGE, HEADER_SUBTITLE, HOST_GEO_CITY_NAME_HEADER, HOST_GEO_COUNTRY_NAME_HEADER, @@ -153,7 +154,7 @@ describe('Events Viewer', () => { }); }); - context.skip('Events columns', () => { + context('Events columns', () => { before(() => { loginAndWaitForPage(HOSTS_URL); openEvents(); @@ -171,6 +172,7 @@ describe('Events Viewer', () => { const expectedOrderAfterDragAndDrop = 'message@timestamphost.nameevent.moduleevent.datasetevent.actionuser.namesource.ipdestination.ip'; + cy.get(EVENTS_PAGE).scrollTo('bottom'); cy.get(HEADERS_GROUP).invoke('text').should('equal', originalColumnOrder); dragAndDropColumn({ column: 0, newPosition: 1 }); cy.get(HEADERS_GROUP).invoke('text').should('equal', expectedOrderAfterDragAndDrop); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts index 2fb265c55e3ad..383ebe2220585 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts @@ -13,8 +13,7 @@ import { TABLE_COLUMN_EVENTS_MESSAGE } from '../screens/hosts/external_events'; import { waitsForEventsToBeLoaded, openEventsViewerFieldsBrowser } from '../tasks/hosts/events'; import { removeColumn, resetFields } from '../tasks/timeline'; -// FLAKY: https://github.com/elastic/kibana/issues/72339 -describe.skip('persistent timeline', () => { +describe('persistent timeline', () => { before(() => { loginAndWaitForPage(HOSTS_URL); openEvents(); diff --git a/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts b/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts index 4b1ca19bd96fe..05f517b5de662 100644 --- a/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts +++ b/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts @@ -6,6 +6,8 @@ export const CLOSE_MODAL = '[data-test-subj="modal-inspect-close"]'; +export const EVENTS_PAGE = '[data-test-subj="pageContainer"]'; + export const EVENTS_VIEWER_FIELDS_BUTTON = '[data-test-subj="events-viewer-panel"] [data-test-subj="show-field-browser"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts index 68425731f149a..135dea35ca0d8 100644 --- a/x-pack/plugins/security_solution/cypress/screens/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts @@ -11,6 +11,8 @@ export const CREATE_NEW_TIMELINE = '[data-test-subj="timeline-new"]'; export const DRAGGABLE_HEADER = '[data-test-subj="events-viewer-panel"] [data-test-subj="headers-group"] [data-test-subj="draggable-header"]'; +export const HEADER = '[data-test-subj="header"]'; + export const HEADERS_GROUP = '[data-test-subj="headers-group"]'; export const ID_HEADER_FIELD = '[data-test-subj="timeline"] [data-test-subj="header-text-_id"]'; @@ -23,8 +25,7 @@ export const PIN_EVENT = '[data-test-subj="pin"]'; export const PROVIDER_BADGE = '[data-test-subj="providerBadge"]'; -export const REMOVE_COLUMN = - '[data-test-subj="events-viewer-panel"] [data-test-subj="remove-column"]'; +export const REMOVE_COLUMN = '[data-test-subj="remove-column"]'; export const RESET_FIELDS = '[data-test-subj="events-viewer-panel"] [data-test-subj="reset-fields"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts b/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts index 1d2c4aa8d0834..226178cd92f18 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts @@ -68,8 +68,6 @@ export const dragAndDropColumn = ({ .eq(column) .then((header) => drag(header)); - cy.wait(5000); // wait for DOM updates before moving - cy.get(DRAGGABLE_HEADER) .eq(newPosition) .then((targetPosition) => { diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index d5106f34cc9c5..9eeb9fc8bdf8a 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -9,6 +9,7 @@ import { DATE_PICKER_APPLY_BUTTON_TIMELINE } from '../screens/date_picker'; import { CLOSE_TIMELINE_BTN, CREATE_NEW_TIMELINE, + HEADER, ID_FIELD, ID_HEADER_FIELD, ID_TOGGLE_FIELD, @@ -114,7 +115,7 @@ export const dragAndDropIdToggleFieldToTimeline = () => { }; export const removeColumn = (column: number) => { - cy.get(REMOVE_COLUMN).first().should('exist'); + cy.get(HEADER).eq(column).click(); cy.get(REMOVE_COLUMN).eq(column).click({ force: true }); }; diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts index 4d0d75cd4595c..51c59212bef16 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts @@ -423,7 +423,7 @@ const ERROR_NAMESPACE_SPECIFIED = 'Spaces currently determines the namespaces'; test(`supplements options with the current namespace`, async () => { const { client, baseClient } = await createSpacesSavedObjectsClient(); - const expectedReturnValue = createMockResponse(); + const expectedReturnValue = { namespaces: ['foo', 'bar'] }; baseClient.addToNamespaces.mockReturnValue(Promise.resolve(expectedReturnValue)); const type = Symbol(); @@ -453,7 +453,7 @@ const ERROR_NAMESPACE_SPECIFIED = 'Spaces currently determines the namespaces'; test(`supplements options with the current namespace`, async () => { const { client, baseClient } = await createSpacesSavedObjectsClient(); - const expectedReturnValue = createMockResponse(); + const expectedReturnValue = { namespaces: ['foo', 'bar'] }; baseClient.deleteFromNamespaces.mockReturnValue(Promise.resolve(expectedReturnValue)); const type = Symbol(); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts index 70b6a8fe512e1..3c8fc78b7f872 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts @@ -77,7 +77,8 @@ export default function servicenowTest({ getService }: FtrProviderContext) { let proxyServer: any; let proxyHaveBeenCalled = false; - describe('ServiceNow', () => { + // FLAKY: https://github.com/elastic/kibana/issues/75522 + describe.skip('ServiceNow', () => { before(() => { servicenowSimulatorURL = kibanaServer.resolveUrl( getExternalServiceSimulatorPath(ExternalServiceSimulator.SERVICENOW) diff --git a/x-pack/test/api_integration/apis/ml/annotations/create_annotations.ts b/x-pack/test/api_integration/apis/ml/annotations/create_annotations.ts index aff1150997496..6b8fbe8350a43 100644 --- a/x-pack/test/api_integration/apis/ml/annotations/create_annotations.ts +++ b/x-pack/test/api_integration/apis/ml/annotations/create_annotations.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; import { USER } from '../../../../functional/services/ml/security_common'; import { Annotation } from '../../../../../plugins/ml/common/types/annotations'; import { createJobConfig, createAnnotationRequestBody } from './common_jobs'; diff --git a/x-pack/test/api_integration/apis/ml/annotations/delete_annotations.ts b/x-pack/test/api_integration/apis/ml/annotations/delete_annotations.ts index d3451c4d7da0c..5e55fb616cbfb 100644 --- a/x-pack/test/api_integration/apis/ml/annotations/delete_annotations.ts +++ b/x-pack/test/api_integration/apis/ml/annotations/delete_annotations.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; import { USER } from '../../../../functional/services/ml/security_common'; import { testSetupJobConfigs, jobIds, testSetupAnnotations } from './common_jobs'; diff --git a/x-pack/test/api_integration/apis/ml/annotations/get_annotations.ts b/x-pack/test/api_integration/apis/ml/annotations/get_annotations.ts index 29ad905bd3f2d..b720477abb350 100644 --- a/x-pack/test/api_integration/apis/ml/annotations/get_annotations.ts +++ b/x-pack/test/api_integration/apis/ml/annotations/get_annotations.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { omit } from 'lodash'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; import { USER } from '../../../../functional/services/ml/security_common'; import { testSetupJobConfigs, jobIds, testSetupAnnotations } from './common_jobs'; diff --git a/x-pack/test/api_integration/apis/ml/annotations/update_annotations.ts b/x-pack/test/api_integration/apis/ml/annotations/update_annotations.ts index bcfb7ab0825b8..a1e4bcac31807 100644 --- a/x-pack/test/api_integration/apis/ml/annotations/update_annotations.ts +++ b/x-pack/test/api_integration/apis/ml/annotations/update_annotations.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; import { USER } from '../../../../functional/services/ml/security_common'; import { ANNOTATION_TYPE } from '../../../../../plugins/ml/common/constants/annotations'; import { Annotation } from '../../../../../plugins/ml/common/types/annotations'; diff --git a/x-pack/test/api_integration/apis/ml/anomaly_detectors/create.ts b/x-pack/test/api_integration/apis/ml/anomaly_detectors/create.ts index 71703ed019dc5..8eb4d7f756fe5 100644 --- a/x-pack/test/api_integration/apis/ml/anomaly_detectors/create.ts +++ b/x-pack/test/api_integration/apis/ml/anomaly_detectors/create.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/api_integration/apis/ml/anomaly_detectors/get.ts b/x-pack/test/api_integration/apis/ml/anomaly_detectors/get.ts index e17870d8c6408..58202ea9b35b3 100644 --- a/x-pack/test/api_integration/apis/ml/anomaly_detectors/get.ts +++ b/x-pack/test/api_integration/apis/ml/anomaly_detectors/get.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/api_integration/apis/ml/calendars/create_calendars.ts b/x-pack/test/api_integration/apis/ml/calendars/create_calendars.ts index 82f4eee8cc328..e351eafea6e7f 100644 --- a/x-pack/test/api_integration/apis/ml/calendars/create_calendars.ts +++ b/x-pack/test/api_integration/apis/ml/calendars/create_calendars.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/api_integration/apis/ml/calendars/delete_calendars.ts b/x-pack/test/api_integration/apis/ml/calendars/delete_calendars.ts index eef8479b811b4..575c7c99dde17 100644 --- a/x-pack/test/api_integration/apis/ml/calendars/delete_calendars.ts +++ b/x-pack/test/api_integration/apis/ml/calendars/delete_calendars.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/api_integration/apis/ml/calendars/get_calendars.ts b/x-pack/test/api_integration/apis/ml/calendars/get_calendars.ts index 0b4f4a8f73ede..a5e65028196fe 100644 --- a/x-pack/test/api_integration/apis/ml/calendars/get_calendars.ts +++ b/x-pack/test/api_integration/apis/ml/calendars/get_calendars.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/api_integration/apis/ml/calendars/update_calendars.ts b/x-pack/test/api_integration/apis/ml/calendars/update_calendars.ts index 65832ac9ca81e..69a8eb3e06ee8 100644 --- a/x-pack/test/api_integration/apis/ml/calendars/update_calendars.ts +++ b/x-pack/test/api_integration/apis/ml/calendars/update_calendars.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts index 2a39bc14fbb7f..c6043b7a282d4 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; import { DataFrameAnalyticsConfig } from '../../../../../plugins/ml/public/application/data_frame_analytics/common'; import { DeepPartial } from '../../../../../plugins/ml/common/types/common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/get.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/get.ts index d3e4788ee41da..ebae5a80c2337 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/get.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/get.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; import { DataFrameAnalyticsConfig } from '../../../../../plugins/ml/public/application/data_frame_analytics/common'; import { DeepPartial } from '../../../../../plugins/ml/common/types/common'; diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/update.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/update.ts index 5dc781657619d..f4964308cd8c9 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/update.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/update.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; import { DataFrameAnalyticsConfig } from '../../../../../plugins/ml/public/application/data_frame_analytics/common'; import { DeepPartial } from '../../../../../plugins/ml/common/types/common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/api_integration/apis/ml/data_visualizer/get_field_histograms.ts b/x-pack/test/api_integration/apis/ml/data_visualizer/get_field_histograms.ts index 1a71894f8423d..299f5f93fd281 100644 --- a/x-pack/test/api_integration/apis/ml/data_visualizer/get_field_histograms.ts +++ b/x-pack/test/api_integration/apis/ml/data_visualizer/get_field_histograms.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/api_integration/apis/ml/data_visualizer/get_field_stats.ts b/x-pack/test/api_integration/apis/ml/data_visualizer/get_field_stats.ts index 5373da6a794c7..5795eac9637b1 100644 --- a/x-pack/test/api_integration/apis/ml/data_visualizer/get_field_stats.ts +++ b/x-pack/test/api_integration/apis/ml/data_visualizer/get_field_stats.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/api_integration/apis/ml/data_visualizer/get_overall_stats.ts b/x-pack/test/api_integration/apis/ml/data_visualizer/get_overall_stats.ts index d87ab16d71c18..fa83807be161a 100644 --- a/x-pack/test/api_integration/apis/ml/data_visualizer/get_overall_stats.ts +++ b/x-pack/test/api_integration/apis/ml/data_visualizer/get_overall_stats.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/api_integration/apis/ml/fields_service/field_cardinality.ts b/x-pack/test/api_integration/apis/ml/fields_service/field_cardinality.ts index ced4d937863ee..627d9454beeb6 100644 --- a/x-pack/test/api_integration/apis/ml/fields_service/field_cardinality.ts +++ b/x-pack/test/api_integration/apis/ml/fields_service/field_cardinality.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/api_integration/apis/ml/fields_service/time_field_range.ts b/x-pack/test/api_integration/apis/ml/fields_service/time_field_range.ts index 2128b1fe8d9e1..b1c086ddbb456 100644 --- a/x-pack/test/api_integration/apis/ml/fields_service/time_field_range.ts +++ b/x-pack/test/api_integration/apis/ml/fields_service/time_field_range.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/api_integration/apis/ml/filters/create_filters.ts b/x-pack/test/api_integration/apis/ml/filters/create_filters.ts index 233c95b190f02..dfec7798ffc0c 100644 --- a/x-pack/test/api_integration/apis/ml/filters/create_filters.ts +++ b/x-pack/test/api_integration/apis/ml/filters/create_filters.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/api_integration/apis/ml/filters/delete_filters.ts b/x-pack/test/api_integration/apis/ml/filters/delete_filters.ts index d0323360400be..7a55c2308eb9d 100644 --- a/x-pack/test/api_integration/apis/ml/filters/delete_filters.ts +++ b/x-pack/test/api_integration/apis/ml/filters/delete_filters.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/api_integration/apis/ml/filters/get_filters.ts b/x-pack/test/api_integration/apis/ml/filters/get_filters.ts index f0aa7aac7b9e4..5d7900ea5e9d9 100644 --- a/x-pack/test/api_integration/apis/ml/filters/get_filters.ts +++ b/x-pack/test/api_integration/apis/ml/filters/get_filters.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/api_integration/apis/ml/filters/update_filters.ts b/x-pack/test/api_integration/apis/ml/filters/update_filters.ts index 87eec99906c34..fbbb94d54c035 100644 --- a/x-pack/test/api_integration/apis/ml/filters/update_filters.ts +++ b/x-pack/test/api_integration/apis/ml/filters/update_filters.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/api_integration/apis/ml/job_validation/bucket_span_estimator.ts b/x-pack/test/api_integration/apis/ml/job_validation/bucket_span_estimator.ts index c556a6c28554b..a03432232a7d8 100644 --- a/x-pack/test/api_integration/apis/ml/job_validation/bucket_span_estimator.ts +++ b/x-pack/test/api_integration/apis/ml/job_validation/bucket_span_estimator.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/api_integration/apis/ml/job_validation/calculate_model_memory_limit.ts b/x-pack/test/api_integration/apis/ml/job_validation/calculate_model_memory_limit.ts index 409bd161e601b..30a387fe33b0e 100644 --- a/x-pack/test/api_integration/apis/ml/job_validation/calculate_model_memory_limit.ts +++ b/x-pack/test/api_integration/apis/ml/job_validation/calculate_model_memory_limit.ts @@ -6,7 +6,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/api_integration/apis/ml/job_validation/cardinality.ts b/x-pack/test/api_integration/apis/ml/job_validation/cardinality.ts index ed61f234a671d..ddf752bd876cb 100644 --- a/x-pack/test/api_integration/apis/ml/job_validation/cardinality.ts +++ b/x-pack/test/api_integration/apis/ml/job_validation/cardinality.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/api_integration/apis/ml/job_validation/validate.ts b/x-pack/test/api_integration/apis/ml/job_validation/validate.ts index 5e9b2d68bd6df..dab69498efc77 100644 --- a/x-pack/test/api_integration/apis/ml/job_validation/validate.ts +++ b/x-pack/test/api_integration/apis/ml/job_validation/validate.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; import pkg from '../../../../../../package.json'; export default ({ getService }: FtrProviderContext) => { diff --git a/x-pack/test/api_integration/apis/ml/jobs/categorization_field_examples.ts b/x-pack/test/api_integration/apis/ml/jobs/categorization_field_examples.ts index b99a4965adb9d..ccab65e1576a2 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/categorization_field_examples.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/categorization_field_examples.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; const start = 1554463535770; const end = 1574316073914; diff --git a/x-pack/test/api_integration/apis/ml/jobs/close_jobs.ts b/x-pack/test/api_integration/apis/ml/jobs/close_jobs.ts index f411595aca995..5b9c5393e81d9 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/close_jobs.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/close_jobs.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; import { USER } from '../../../../functional/services/ml/security_common'; import { JOB_STATE, DATAFEED_STATE } from '../../../../../plugins/ml/common/constants/states'; import { MULTI_METRIC_JOB_CONFIG, SINGLE_METRIC_JOB_CONFIG, DATAFEED_CONFIG } from './common_jobs'; diff --git a/x-pack/test/api_integration/apis/ml/jobs/delete_jobs.ts b/x-pack/test/api_integration/apis/ml/jobs/delete_jobs.ts index 4976b6441c37a..e8f733d2fc6ad 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/delete_jobs.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/delete_jobs.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; import { USER } from '../../../../functional/services/ml/security_common'; import { MULTI_METRIC_JOB_CONFIG, SINGLE_METRIC_JOB_CONFIG } from './common_jobs'; diff --git a/x-pack/test/api_integration/apis/ml/jobs/jobs_summary.ts b/x-pack/test/api_integration/apis/ml/jobs/jobs_summary.ts index 0a6e1ed75020a..f7ff5b118f6ae 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/jobs_summary.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/jobs_summary.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; import { USER } from '../../../../functional/services/ml/security_common'; import { MULTI_METRIC_JOB_CONFIG, SINGLE_METRIC_JOB_CONFIG } from './common_jobs'; diff --git a/x-pack/test/api_integration/apis/ml/modules/get_module.ts b/x-pack/test/api_integration/apis/ml/modules/get_module.ts index e2a5d3cd425dc..a3d060bb1faca 100644 --- a/x-pack/test/api_integration/apis/ml/modules/get_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/get_module.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; const moduleIds = [ 'apache_ecs', diff --git a/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts b/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts index 6634c4e2ed16c..d50148ec583a0 100644 --- a/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts index 6c3eda197f892..bc7fc691bc60d 100644 --- a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts @@ -12,7 +12,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; import { JOB_STATE, DATAFEED_STATE } from '../../../../../plugins/ml/common/constants/states'; import { Job } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/api_integration/apis/ml/results/get_anomalies_table_data.ts b/x-pack/test/api_integration/apis/ml/results/get_anomalies_table_data.ts index f769d0d878cb2..d1d7d2e8d78ce 100644 --- a/x-pack/test/api_integration/apis/ml/results/get_anomalies_table_data.ts +++ b/x-pack/test/api_integration/apis/ml/results/get_anomalies_table_data.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { USER } from '../../../../functional/services/ml/security_common'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { Datafeed, Job } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/api_integration/apis/ml/results/get_categorizer_stats.ts b/x-pack/test/api_integration/apis/ml/results/get_categorizer_stats.ts index a9d863b7526f9..d2a0625cf4e24 100644 --- a/x-pack/test/api_integration/apis/ml/results/get_categorizer_stats.ts +++ b/x-pack/test/api_integration/apis/ml/results/get_categorizer_stats.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; import { Datafeed } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs'; import { AnomalyCategorizerStatsDoc } from '../../../../../plugins/ml/common/types/anomalies'; diff --git a/x-pack/test/api_integration/apis/ml/results/get_stopped_partitions.ts b/x-pack/test/api_integration/apis/ml/results/get_stopped_partitions.ts index 424bc8c333aab..a46c8861258e9 100644 --- a/x-pack/test/api_integration/apis/ml/results/get_stopped_partitions.ts +++ b/x-pack/test/api_integration/apis/ml/results/get_stopped_partitions.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { Datafeed, Job } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; -import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/api_integration/apis/transform/delete_transforms.ts b/x-pack/test/api_integration/apis/transform/delete_transforms.ts index 136bb85dd5ac2..7f01d2741ad15 100644 --- a/x-pack/test/api_integration/apis/transform/delete_transforms.ts +++ b/x-pack/test/api_integration/apis/transform/delete_transforms.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { TransformEndpointRequest } from '../../../../plugins/transform/common'; import { FtrProviderContext } from '../../ftr_provider_context'; -import { COMMON_REQUEST_HEADERS } from '../../../functional/services/ml/common'; +import { COMMON_REQUEST_HEADERS } from '../../../functional/services/ml/common_api'; import { USER } from '../../../functional/services/transform/security_common'; async function asyncForEach(array: any[], callback: Function) { diff --git a/x-pack/test/api_integration/services/ml.ts b/x-pack/test/api_integration/services/ml.ts index be3b9732f83bf..a8780473fad67 100644 --- a/x-pack/test/api_integration/services/ml.ts +++ b/x-pack/test/api_integration/services/ml.ts @@ -7,16 +7,19 @@ import { FtrProviderContext } from '../../functional/ftr_provider_context'; import { MachineLearningAPIProvider } from '../../functional/services/ml/api'; +import { MachineLearningCommonAPIProvider } from '../../functional/services/ml/common_api'; import { MachineLearningSecurityCommonProvider } from '../../functional/services/ml/security_common'; import { MachineLearningTestResourcesProvider } from '../../functional/services/ml/test_resources'; export function MachineLearningProvider(context: FtrProviderContext) { const api = MachineLearningAPIProvider(context); + const commonAPI = MachineLearningCommonAPIProvider(context); const securityCommon = MachineLearningSecurityCommonProvider(context); const testResources = MachineLearningTestResourcesProvider(context); return { api, + commonAPI, securityCommon, testResources, }; diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/advanced_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection/advanced_job.ts index a8836a463e652..18a7a4b26a752 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/advanced_job.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/advanced_job.ts @@ -33,60 +33,6 @@ interface PickFieldsConfig { summaryCountField?: string; } -// type guards -// Detector -const isDetectorWithField = (arg: any): arg is Required> => { - return arg.hasOwnProperty('field'); -}; -const isDetectorWithByField = (arg: any): arg is Required> => { - return arg.hasOwnProperty('byField'); -}; -const isDetectorWithOverField = (arg: any): arg is Required> => { - return arg.hasOwnProperty('overField'); -}; -const isDetectorWithPartitionField = ( - arg: any -): arg is Required> => { - return arg.hasOwnProperty('partitionField'); -}; -const isDetectorWithExcludeFrequent = ( - arg: any -): arg is Required> => { - return arg.hasOwnProperty('excludeFrequent'); -}; -const isDetectorWithDescription = (arg: any): arg is Required> => { - return arg.hasOwnProperty('description'); -}; - -// DatafeedConfig -const isDatafeedConfigWithQueryDelay = ( - arg: any -): arg is Required> => { - return arg.hasOwnProperty('queryDelay'); -}; -const isDatafeedConfigWithFrequency = ( - arg: any -): arg is Required> => { - return arg.hasOwnProperty('frequency'); -}; -const isDatafeedConfigWithScrollSize = ( - arg: any -): arg is Required> => { - return arg.hasOwnProperty('scrollSize'); -}; - -// PickFieldsConfig -const isPickFieldsConfigWithCategorizationField = ( - arg: any -): arg is Required> => { - return arg.hasOwnProperty('categorizationField'); -}; -const isPickFieldsConfigWithSummaryCountField = ( - arg: any -): arg is Required> => { - return arg.hasOwnProperty('summaryCountField'); -}; - export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); @@ -290,88 +236,80 @@ export default function ({ getService }: FtrProviderContext) { for (const testData of testDataList) { describe(`${testData.suiteTitle}`, function () { - it('job creation loads the job management page', async () => { + it('job creation loads the advanced wizard for the source data', async () => { + await ml.testExecution.logTestStep('job creation navigates to job management'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); - }); - it('job creation loads the new job source selection page', async () => { + await ml.testExecution.logTestStep( + 'job creation loads the new job source selection page' + ); await ml.jobManagement.navigateToNewJobSourceSelection(); - }); - it('job creation loads the job type selection page', async () => { + await ml.testExecution.logTestStep('job creation loads the job type selection page'); await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob(testData.jobSource); - }); - it('job creation loads the advanced job wizard page', async () => { + await ml.testExecution.logTestStep('job creation loads the advanced job wizard page'); await ml.jobTypeSelection.selectAdvancedJob(); }); - it('job creation displays the configure datafeed step', async () => { + it('job creation navigates through the advanced wizard and sets all needed fields', async () => { + await ml.testExecution.logTestStep('job creation displays the configure datafeed step'); await ml.jobWizardCommon.assertConfigureDatafeedSectionExists(); - }); - it('job creation pre-fills the datafeed query editor', async () => { + await ml.testExecution.logTestStep('job creation pre-fills the datafeed query editor'); await ml.jobWizardAdvanced.assertDatafeedQueryEditorExists(); await ml.jobWizardAdvanced.assertDatafeedQueryEditorValue(defaultValues.datafeedQuery); - }); - it('job creation inputs the query delay', async () => { + await ml.testExecution.logTestStep('job creation inputs the query delay'); await ml.jobWizardAdvanced.assertQueryDelayInputExists(); await ml.jobWizardAdvanced.assertQueryDelayValue(defaultValues.queryDelay); - if (isDatafeedConfigWithQueryDelay(testData.datafeedConfig)) { - await ml.jobWizardAdvanced.setQueryDelay(testData.datafeedConfig.queryDelay); + if (testData.datafeedConfig.hasOwnProperty('queryDelay')) { + await ml.jobWizardAdvanced.setQueryDelay(testData.datafeedConfig.queryDelay!); } - }); - it('job creation inputs the frequency', async () => { + await ml.testExecution.logTestStep('job creation inputs the frequency'); await ml.jobWizardAdvanced.assertFrequencyInputExists(); await ml.jobWizardAdvanced.assertFrequencyValue(defaultValues.frequency); - if (isDatafeedConfigWithFrequency(testData.datafeedConfig)) { - await ml.jobWizardAdvanced.setFrequency(testData.datafeedConfig.frequency); + if (testData.datafeedConfig.hasOwnProperty('frequency')) { + await ml.jobWizardAdvanced.setFrequency(testData.datafeedConfig.frequency!); } - }); - it('job creation inputs the scroll size', async () => { + await ml.testExecution.logTestStep('job creation inputs the scroll size'); await ml.jobWizardAdvanced.assertScrollSizeInputExists(); await ml.jobWizardAdvanced.assertScrollSizeValue(defaultValues.scrollSize); - if (isDatafeedConfigWithScrollSize(testData.datafeedConfig)) { - await ml.jobWizardAdvanced.setScrollSize(testData.datafeedConfig.scrollSize); + if (testData.datafeedConfig.hasOwnProperty('scrollSize')) { + await ml.jobWizardAdvanced.setScrollSize(testData.datafeedConfig.scrollSize!); } - }); - it('job creation pre-fills the time field', async () => { + await ml.testExecution.logTestStep('job creation pre-fills the time field'); await ml.jobWizardAdvanced.assertTimeFieldInputExists(); await ml.jobWizardAdvanced.assertTimeFieldSelection([testData.expected.wizard.timeField]); - }); - it('job creation displays the pick fields step', async () => { + await ml.testExecution.logTestStep('job creation displays the pick fields step'); await ml.jobWizardCommon.advanceToPickFieldsSection(); - }); - it('job creation selects the categorization field', async () => { + await ml.testExecution.logTestStep('job creation selects the categorization field'); await ml.jobWizardAdvanced.assertCategorizationFieldInputExists(); - if (isPickFieldsConfigWithCategorizationField(testData.pickFieldsConfig)) { + if (testData.pickFieldsConfig.hasOwnProperty('categorizationField')) { await ml.jobWizardAdvanced.selectCategorizationField( - testData.pickFieldsConfig.categorizationField + testData.pickFieldsConfig.categorizationField! ); } else { await ml.jobWizardAdvanced.assertCategorizationFieldSelection([]); } - }); - it('job creation selects the summary count field', async () => { + await ml.testExecution.logTestStep('job creation selects the summary count field'); await ml.jobWizardAdvanced.assertSummaryCountFieldInputExists(); - if (isPickFieldsConfigWithSummaryCountField(testData.pickFieldsConfig)) { + if (testData.pickFieldsConfig.hasOwnProperty('summaryCountField')) { await ml.jobWizardAdvanced.selectSummaryCountField( - testData.pickFieldsConfig.summaryCountField + testData.pickFieldsConfig.summaryCountField! ); } else { await ml.jobWizardAdvanced.assertSummaryCountFieldSelection([]); } - }); - it('job creation adds detectors', async () => { + await ml.testExecution.logTestStep('job creation adds detectors'); for (const detector of testData.pickFieldsConfig.detectors) { await ml.jobWizardAdvanced.openCreateDetectorModal(); await ml.jobWizardAdvanced.assertDetectorFunctionInputExists(); @@ -390,128 +328,118 @@ export default function ({ getService }: FtrProviderContext) { await ml.jobWizardAdvanced.assertDetectorDescriptionValue(''); await ml.jobWizardAdvanced.selectDetectorFunction(detector.function); - if (isDetectorWithField(detector)) { - await ml.jobWizardAdvanced.selectDetectorField(detector.field); + if (detector.hasOwnProperty('field')) { + await ml.jobWizardAdvanced.selectDetectorField(detector.field!); } - if (isDetectorWithByField(detector)) { - await ml.jobWizardAdvanced.selectDetectorByField(detector.byField); + if (detector.hasOwnProperty('byField')) { + await ml.jobWizardAdvanced.selectDetectorByField(detector.byField!); } - if (isDetectorWithOverField(detector)) { - await ml.jobWizardAdvanced.selectDetectorOverField(detector.overField); + if (detector.hasOwnProperty('overField')) { + await ml.jobWizardAdvanced.selectDetectorOverField(detector.overField!); } - if (isDetectorWithPartitionField(detector)) { - await ml.jobWizardAdvanced.selectDetectorPartitionField(detector.partitionField); + if (detector.hasOwnProperty('partitionField')) { + await ml.jobWizardAdvanced.selectDetectorPartitionField(detector.partitionField!); } - if (isDetectorWithExcludeFrequent(detector)) { - await ml.jobWizardAdvanced.selectDetectorExcludeFrequent(detector.excludeFrequent); + if (detector.hasOwnProperty('excludeFrequent')) { + await ml.jobWizardAdvanced.selectDetectorExcludeFrequent(detector.excludeFrequent!); } - if (isDetectorWithDescription(detector)) { - await ml.jobWizardAdvanced.setDetectorDescription(detector.description); + if (detector.hasOwnProperty('description')) { + await ml.jobWizardAdvanced.setDetectorDescription(detector.description!); } await ml.jobWizardAdvanced.confirmAddDetectorModal(); } - }); - it('job creation displays detector entries', async () => { + await ml.testExecution.logTestStep('job creation displays detector entries'); for (const [index, detector] of testData.pickFieldsConfig.detectors.entries()) { await ml.jobWizardAdvanced.assertDetectorEntryExists( index, detector.identifier, - isDetectorWithDescription(detector) ? detector.description : undefined + detector.hasOwnProperty('description') ? detector.description! : undefined ); } - }); - it('job creation inputs the bucket span', async () => { + await ml.testExecution.logTestStep('job creation inputs the bucket span'); await ml.jobWizardCommon.assertBucketSpanInputExists(); await ml.jobWizardCommon.setBucketSpan(testData.pickFieldsConfig.bucketSpan); - }); - it('job creation inputs influencers', async () => { + await ml.testExecution.logTestStep('job creation inputs influencers'); await ml.jobWizardCommon.assertInfluencerInputExists(); await ml.jobWizardCommon.assertInfluencerSelection([]); for (const influencer of testData.pickFieldsConfig.influencers) { await ml.jobWizardCommon.addInfluencer(influencer); } - }); - it('job creation inputs the model memory limit', async () => { + await ml.testExecution.logTestStep('job creation inputs the model memory limit'); await ml.jobWizardCommon.assertModelMemoryLimitInputExists({ withAdvancedSection: false, }); await ml.jobWizardCommon.setModelMemoryLimit(testData.pickFieldsConfig.memoryLimit, { withAdvancedSection: false, }); - }); - it('job creation displays the job details step', async () => { + await ml.testExecution.logTestStep('job creation displays the job details step'); await ml.jobWizardCommon.advanceToJobDetailsSection(); - }); - it('job creation inputs the job id', async () => { + await ml.testExecution.logTestStep('job creation inputs the job id'); await ml.jobWizardCommon.assertJobIdInputExists(); await ml.jobWizardCommon.setJobId(testData.jobId); - }); - it('job creation inputs the job description', async () => { + await ml.testExecution.logTestStep('job creation inputs the job description'); await ml.jobWizardCommon.assertJobDescriptionInputExists(); await ml.jobWizardCommon.setJobDescription(testData.jobDescription); - }); - it('job creation inputs job groups', async () => { + await ml.testExecution.logTestStep('job creation inputs job groups'); await ml.jobWizardCommon.assertJobGroupInputExists(); for (const jobGroup of testData.jobGroups) { await ml.jobWizardCommon.addJobGroup(jobGroup); } await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroups); - }); - it('job creation opens the additional settings section', async () => { + await ml.testExecution.logTestStep('job creation opens the additional settings section'); await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); - }); - it('job creation adds a new custom url', async () => { + await ml.testExecution.logTestStep('job creation adds a new custom url'); await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); - }); - it('job creation assigns calendars', async () => { + await ml.testExecution.logTestStep('job creation assigns calendars'); await ml.jobWizardCommon.addCalendar(calendarId); - }); - it('job creation displays the model plot switch', async () => { + await ml.testExecution.logTestStep('job creation displays the model plot switch'); await ml.jobWizardCommon.assertModelPlotSwitchExists({ withAdvancedSection: false }); - }); - it('job creation enables the dedicated index switch', async () => { + await ml.testExecution.logTestStep('job creation enables the dedicated index switch'); await ml.jobWizardCommon.assertDedicatedIndexSwitchExists({ withAdvancedSection: false }); await ml.jobWizardCommon.activateDedicatedIndexSwitch({ withAdvancedSection: false }); - }); - it('job creation displays the validation step', async () => { + await ml.testExecution.logTestStep('job creation displays the validation step'); await ml.jobWizardCommon.advanceToValidationSection(); - }); - it('job creation displays the summary step', async () => { + await ml.testExecution.logTestStep('job creation displays the summary step'); await ml.jobWizardCommon.advanceToSummarySection(); }); - it('job creation creates the job and finishes processing', async () => { + it('job creation runs the job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep( + 'job creation creates the job and finishes processing' + ); await ml.jobWizardCommon.assertCreateJobButtonExists(); await ml.jobWizardAdvanced.createJob(); await ml.jobManagement.assertStartDatafeedModalExists(); await ml.jobManagement.confirmStartDatafeedModal(); await ml.jobManagement.waitForJobCompletion(testData.jobId); - }); - it('job creation displays the created job in the job list', async () => { + await ml.testExecution.logTestStep( + 'job creation displays the created job in the job list' + ); await ml.jobTable.refreshJobList(); await ml.jobTable.filterWithSearchString(testData.jobId); const rows = await ml.jobTable.parseJobTable(); expect(rows.filter((row) => row.id === testData.jobId)).to.have.length(1); - }); - it('job creation displays details for the created job in the job list', async () => { + await ml.testExecution.logTestStep( + 'job creation displays details for the created job in the job list' + ); await ml.jobTable.assertJobRowFields(testData.jobId, { id: testData.jobId, description: testData.jobDescription, @@ -530,84 +458,78 @@ export default function ({ getService }: FtrProviderContext) { ...testData.expected.modelSizeStats, } ); - }); - it('job creation has detector results', async () => { + await ml.testExecution.logTestStep('job creation has detector results'); for (let i = 0; i < testData.pickFieldsConfig.detectors.length; i++) { await ml.api.assertDetectorResultsExist(testData.jobId, i); } }); - it('job cloning clicks the clone action and loads the advanced wizard', async () => { + it('job cloning opens the existing job in the advanced wizard', async () => { + await ml.testExecution.logTestStep( + 'job cloning clicks the clone action and loads the advanced wizard' + ); await ml.jobTable.clickCloneJobAction(testData.jobId); await ml.jobTypeSelection.assertAdvancedJobWizardOpen(); }); - it('job cloning displays the configure datafeed step', async () => { + it('job cloning navigates through the advanced wizard, checks and sets all needed fields', async () => { + await ml.testExecution.logTestStep('job cloning displays the configure datafeed step'); await ml.jobWizardCommon.assertConfigureDatafeedSectionExists(); - }); - it('job cloning pre-fills the datafeed query editor', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the datafeed query editor'); await ml.jobWizardAdvanced.assertDatafeedQueryEditorExists(); await ml.jobWizardAdvanced.assertDatafeedQueryEditorValue(defaultValues.datafeedQuery); - }); - it('job cloning pre-fills the query delay', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the query delay'); await ml.jobWizardAdvanced.assertQueryDelayInputExists(); - if (isDatafeedConfigWithQueryDelay(testData.datafeedConfig)) { - await ml.jobWizardAdvanced.assertQueryDelayValue(testData.datafeedConfig.queryDelay); + if (testData.datafeedConfig.hasOwnProperty('queryDelay')) { + await ml.jobWizardAdvanced.assertQueryDelayValue(testData.datafeedConfig.queryDelay!); } - }); - it('job cloning pre-fills the frequency', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the frequency'); await ml.jobWizardAdvanced.assertFrequencyInputExists(); - if (isDatafeedConfigWithFrequency(testData.datafeedConfig)) { - await ml.jobWizardAdvanced.assertFrequencyValue(testData.datafeedConfig.frequency); + if (testData.datafeedConfig.hasOwnProperty('frequency')) { + await ml.jobWizardAdvanced.assertFrequencyValue(testData.datafeedConfig.frequency!); } - }); - it('job cloning pre-fills the scroll size', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the scroll size'); await ml.jobWizardAdvanced.assertScrollSizeInputExists(); await ml.jobWizardAdvanced.assertScrollSizeValue( - isDatafeedConfigWithScrollSize(testData.datafeedConfig) - ? testData.datafeedConfig.scrollSize + testData.datafeedConfig.hasOwnProperty('scrollSize') + ? testData.datafeedConfig.scrollSize! : defaultValues.scrollSize ); - }); - it('job creation pre-fills the time field', async () => { + await ml.testExecution.logTestStep('job creation pre-fills the time field'); await ml.jobWizardAdvanced.assertTimeFieldInputExists(); await ml.jobWizardAdvanced.assertTimeFieldSelection([testData.expected.wizard.timeField]); - }); - it('job cloning displays the pick fields step', async () => { + await ml.testExecution.logTestStep('job cloning displays the pick fields step'); await ml.jobWizardCommon.advanceToPickFieldsSection(); - }); - it('job cloning pre-fills the categorization field', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the categorization field'); await ml.jobWizardAdvanced.assertCategorizationFieldInputExists(); await ml.jobWizardAdvanced.assertCategorizationFieldSelection( - isPickFieldsConfigWithCategorizationField(testData.pickFieldsConfig) - ? [testData.pickFieldsConfig.categorizationField] + testData.pickFieldsConfig.hasOwnProperty('categorizationField') + ? [testData.pickFieldsConfig.categorizationField!] : [] ); - }); - it('job cloning pre-fills the summary count field', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the summary count field'); await ml.jobWizardAdvanced.assertSummaryCountFieldInputExists(); await ml.jobWizardAdvanced.assertSummaryCountFieldSelection( - isPickFieldsConfigWithSummaryCountField(testData.pickFieldsConfig) - ? [testData.pickFieldsConfig.summaryCountField] + testData.pickFieldsConfig.hasOwnProperty('summaryCountField') + ? [testData.pickFieldsConfig.summaryCountField!] : [] ); - }); - it('job cloning pre-fills detectors', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills detectors'); for (const [index, detector] of testData.pickFieldsConfig.detectors.entries()) { await ml.jobWizardAdvanced.assertDetectorEntryExists( index, detector.identifier, - isDetectorWithDescription(detector) ? detector.description : undefined + detector.hasOwnProperty('description') ? detector.description! : undefined ); await ml.jobWizardAdvanced.clickEditDetector(index); @@ -621,134 +543,121 @@ export default function ({ getService }: FtrProviderContext) { await ml.jobWizardAdvanced.assertDetectorFunctionSelection([detector.function]); await ml.jobWizardAdvanced.assertDetectorFieldSelection( - isDetectorWithField(detector) ? [detector.field] : [] + detector.hasOwnProperty('field') ? [detector.field!] : [] ); await ml.jobWizardAdvanced.assertDetectorByFieldSelection( - isDetectorWithByField(detector) ? [detector.byField] : [] + detector.hasOwnProperty('byField') ? [detector.byField!] : [] ); await ml.jobWizardAdvanced.assertDetectorOverFieldSelection( - isDetectorWithOverField(detector) ? [detector.overField] : [] + detector.hasOwnProperty('overField') ? [detector.overField!] : [] ); await ml.jobWizardAdvanced.assertDetectorPartitionFieldSelection( - isDetectorWithPartitionField(detector) ? [detector.partitionField] : [] + detector.hasOwnProperty('partitionField') ? [detector.partitionField!] : [] ); await ml.jobWizardAdvanced.assertDetectorExcludeFrequentSelection( - isDetectorWithExcludeFrequent(detector) ? [detector.excludeFrequent] : [] + detector.hasOwnProperty('excludeFrequent') ? [detector.excludeFrequent!] : [] ); // Currently, a description different form the identifier is generated for detectors with partition field await ml.jobWizardAdvanced.assertDetectorDescriptionValue( - isDetectorWithDescription(detector) - ? detector.description + detector.hasOwnProperty('description') + ? detector.description! : detector.identifier.replace('partition_field_name', 'partitionfield') ); await ml.jobWizardAdvanced.cancelAddDetectorModal(); } - }); - it('job cloning pre-fills the bucket span', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the bucket span'); await ml.jobWizardCommon.assertBucketSpanInputExists(); await ml.jobWizardCommon.assertBucketSpanValue(testData.pickFieldsConfig.bucketSpan); - }); - it('job cloning pre-fills influencers', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills influencers'); await ml.jobWizardCommon.assertInfluencerInputExists(); await ml.jobWizardCommon.assertInfluencerSelection(testData.pickFieldsConfig.influencers); - }); - - // MML during clone has changed in #61589 - // TODO: adjust test code to reflect the new behavior - it.skip('job cloning pre-fills the model memory limit', async () => { - await ml.jobWizardCommon.assertModelMemoryLimitInputExists({ - withAdvancedSection: false, - }); - await ml.jobWizardCommon.assertModelMemoryLimitValue( - testData.pickFieldsConfig.memoryLimit, - { - withAdvancedSection: false, - } - ); - }); - it('job cloning displays the job details step', async () => { + // MML during clone has changed in #61589 + // TODO: adjust test code to reflect the new behavior + // await ml.testExecution.logTestStep('job cloning pre-fills the model memory limit'); + // await ml.jobWizardCommon.assertModelMemoryLimitInputExists({ + // withAdvancedSection: false, + // }); + // await ml.jobWizardCommon.assertModelMemoryLimitValue( + // testData.pickFieldsConfig.memoryLimit, + // { + // withAdvancedSection: false, + // } + // ); + + await ml.testExecution.logTestStep('job cloning displays the job details step'); await ml.jobWizardCommon.advanceToJobDetailsSection(); - }); - it('job cloning does not pre-fill the job id', async () => { + await ml.testExecution.logTestStep('job cloning does not pre-fill the job id'); await ml.jobWizardCommon.assertJobIdInputExists(); await ml.jobWizardCommon.assertJobIdValue(''); - }); - it('job cloning inputs the clone job id', async () => { + await ml.testExecution.logTestStep('job cloning inputs the clone job id'); await ml.jobWizardCommon.setJobId(testData.jobIdClone); - }); - it('job cloning pre-fills the job description', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the job description'); await ml.jobWizardCommon.assertJobDescriptionInputExists(); await ml.jobWizardCommon.assertJobDescriptionValue(testData.jobDescription); - }); - it('job cloning pre-fills job groups', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills job groups'); await ml.jobWizardCommon.assertJobGroupInputExists(); await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroups); - }); - it('job cloning inputs the clone job group', async () => { + await ml.testExecution.logTestStep('job cloning inputs the clone job group'); await ml.jobWizardCommon.assertJobGroupInputExists(); await ml.jobWizardCommon.addJobGroup('clone'); await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroupsClone); - }); - it('job cloning opens the additional settings section', async () => { + await ml.testExecution.logTestStep('job cloning opens the additional settings section'); await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); - }); - it('job cloning persists custom urls', async () => { + await ml.testExecution.logTestStep('job cloning persists custom urls'); await ml.customUrls.assertCustomUrlItem(0, 'check-kibana-dashboard'); - }); - it('job cloning persists assigned calendars', async () => { + await ml.testExecution.logTestStep('job cloning persists assigned calendars'); await ml.jobWizardCommon.assertCalendarsSelection([calendarId]); - }); - it('job cloning pre-fills the model plot switch', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the model plot switch'); await ml.jobWizardCommon.assertModelPlotSwitchExists({ withAdvancedSection: false }); await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(false, { withAdvancedSection: false, }); - }); - it('job cloning pre-fills the dedicated index switch', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the dedicated index switch'); await ml.jobWizardCommon.assertDedicatedIndexSwitchExists({ withAdvancedSection: false }); await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true, { withAdvancedSection: false, }); - }); - it('job cloning displays the validation step', async () => { + await ml.testExecution.logTestStep('job cloning displays the validation step'); await ml.jobWizardCommon.advanceToValidationSection(); - }); - it('job cloning displays the summary step', async () => { + await ml.testExecution.logTestStep('job cloning displays the summary step'); await ml.jobWizardCommon.advanceToSummarySection(); }); - it('job cloning creates the job and finishes processing', async () => { + it('job cloning runs the clone job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep('job cloning creates the job and finishes processing'); await ml.jobWizardCommon.assertCreateJobButtonExists(); await ml.jobWizardAdvanced.createJob(); await ml.jobManagement.assertStartDatafeedModalExists(); await ml.jobManagement.confirmStartDatafeedModal(); await ml.jobManagement.waitForJobCompletion(testData.jobIdClone); - }); - it('job cloning displays the created job in the job list', async () => { + await ml.testExecution.logTestStep( + 'job cloning displays the created job in the job list' + ); await ml.jobTable.refreshJobList(); await ml.jobTable.filterWithSearchString(testData.jobIdClone); const rows = await ml.jobTable.parseJobTable(); expect(rows.filter((row) => row.id === testData.jobIdClone)).to.have.length(1); - }); - it('job creation displays details for the created job in the job list', async () => { + await ml.testExecution.logTestStep( + 'job cloning displays details for the created job in the job list' + ); await ml.jobTable.assertJobRowFields(testData.jobIdClone, { id: testData.jobIdClone, description: testData.jobDescription, @@ -767,9 +676,8 @@ export default function ({ getService }: FtrProviderContext) { ...testData.expected.modelSizeStats, } ); - }); - it('job creation has detector results', async () => { + await ml.testExecution.logTestStep('job cloning has detector results'); for (let i = 0; i < testData.pickFieldsConfig.detectors.length; i++) { await ml.api.assertDetectorResultsExist(testData.jobIdClone, i); } diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/annotations.ts b/x-pack/test/functional/apps/ml/anomaly_detection/annotations.ts index 202910622fb64..9e48c71ab0eba 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/annotations.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/annotations.ts @@ -56,7 +56,8 @@ export default function ({ getService }: FtrProviderContext) { await ml.api.cleanMlIndices(); }); - it('loads from job list row link', async () => { + it('displays error on broken annotation index and recovers after fix', async () => { + await ml.testExecution.logTestStep('loads from job list row link'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); @@ -66,43 +67,35 @@ export default function ({ getService }: FtrProviderContext) { expect(rows.filter((row) => row.id === JOB_CONFIG.job_id)).to.have.length(1); await ml.jobTable.clickOpenJobInSingleMetricViewerButton(JOB_CONFIG.job_id); - await ml.common.waitForMlLoadingIndicatorToDisappear(); - }); + await ml.commonUI.waitForMlLoadingIndicatorToDisappear(); - it('pre-fills the job selection', async () => { + await ml.testExecution.logTestStep('pre-fills the job selection'); await ml.jobSelection.assertJobSelection([JOB_CONFIG.job_id]); - }); - it('pre-fills the detector input', async () => { + await ml.testExecution.logTestStep('pre-fills the detector input'); await ml.singleMetricViewer.assertDetectorInputExsist(); await ml.singleMetricViewer.assertDetectorInputValue('0'); - }); - it('should display the annotations section showing an error', async () => { + await ml.testExecution.logTestStep('should display the annotations section showing an error'); await ml.singleMetricViewer.assertAnnotationsExists('error'); - }); - it('should navigate to anomaly explorer', async () => { + await ml.testExecution.logTestStep('should navigate to anomaly explorer'); await ml.navigation.navigateToAnomalyExplorerViaSingleMetricViewer(); - }); - it('should display the annotations section showing an error', async () => { + await ml.testExecution.logTestStep('should display the annotations section showing an error'); await ml.anomalyExplorer.assertAnnotationsPanelExists('error'); - }); - it('should display the annotations section without an error', async () => { + await ml.testExecution.logTestStep('should display the annotations section without an error'); // restores the aliases to point to the original working annotations index // so we can run tests against successfully loaded annotations sections. await ml.testResources.restoreAnnotationsIndexState(); await ml.anomalyExplorer.refreshPage(); await ml.anomalyExplorer.assertAnnotationsPanelExists('loaded'); - }); - it('should navigate to single metric viewer', async () => { + await ml.testExecution.logTestStep('should navigate to single metric viewer'); await ml.navigation.navigateToSingleMetricViewerViaAnomalyExplorer(); - }); - it('should display the annotations section without an error', async () => { + await ml.testExecution.logTestStep('should display the annotations section without an error'); await ml.singleMetricViewer.assertAnnotationsExists('loaded'); }); }); diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts b/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts index cbee36abef78d..cfbebd478fcb8 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts @@ -79,24 +79,26 @@ export default function ({ getService }: FtrProviderContext) { await ml.api.cleanMlIndices(); }); - it('loads from job list row link', async () => { + it('opens a job from job list link', async () => { + await ml.testExecution.logTestStep('navigate to job list'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); + await ml.testExecution.logTestStep('open job in anomaly explorer'); await ml.jobTable.waitForJobsToLoad(); await ml.jobTable.filterWithSearchString(testData.jobConfig.job_id); const rows = await ml.jobTable.parseJobTable(); expect(rows.filter((row) => row.id === testData.jobConfig.job_id)).to.have.length(1); await ml.jobTable.clickOpenJobInAnomalyExplorerButton(testData.jobConfig.job_id); - await ml.common.waitForMlLoadingIndicatorToDisappear(); + await ml.commonUI.waitForMlLoadingIndicatorToDisappear(); }); - it('pre-fills the job selection', async () => { + it('displays job results', async () => { + await ml.testExecution.logTestStep('pre-fills the job selection'); await ml.jobSelection.assertJobSelection([testData.jobConfig.job_id]); - }); - it('displays the influencers list', async () => { + await ml.testExecution.logTestStep('displays the influencers list'); await ml.anomalyExplorer.assertInfluencerListExists(); for (const influencerBlock of testData.expected.influencers) { await ml.anomalyExplorer.assertInfluencerFieldExists(influencerBlock.field); @@ -111,27 +113,26 @@ export default function ({ getService }: FtrProviderContext) { ); } } - }); - it('displays the swimlanes', async () => { + await ml.testExecution.logTestStep('displays the swimlanes'); await ml.anomalyExplorer.assertOverallSwimlaneExists(); await ml.anomalyExplorer.assertSwimlaneViewByExists(); - }); - it('should display the annotations panel', async () => { + await ml.testExecution.logTestStep('should display the annotations panel'); await ml.anomalyExplorer.assertAnnotationsPanelExists('loaded'); - }); - it('displays the anomalies table', async () => { + await ml.testExecution.logTestStep('displays the anomalies table'); await ml.anomaliesTable.assertTableExists(); - }); - it('anomalies table is not empty', async () => { + await ml.testExecution.logTestStep('anomalies table is not empty'); await ml.anomaliesTable.assertTableNotEmpty(); }); - // should be the last step because it navigates away from the Anomaly Explorer page - it('should allow to attach anomaly swimlane embeddable to the dashboard', async () => { + it('adds swim lane embeddable to a dashboard', async () => { + // should be the last step because it navigates away from the Anomaly Explorer page + await ml.testExecution.logTestStep( + 'should allow to attach anomaly swimlane embeddable to the dashboard' + ); await ml.anomalyExplorer.openAddToDashboardControl(); await ml.anomalyExplorer.addAndEditSwimlaneInDashboard('ML Test'); }); diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/categorization_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection/categorization_job.ts index 1581bd54f5c44..c410aff292ffa 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/categorization_job.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/categorization_job.ts @@ -89,49 +89,46 @@ export default function ({ getService }: FtrProviderContext) { await ml.api.cleanMlIndices(); }); - it('job creation loads the job management page', async () => { + it('job creation loads the categorization wizard for the source data', async () => { + await ml.testExecution.logTestStep('job creation loads the job management page'); + await ml.testExecution.logTestStep(''); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); - }); - it('job creation loads the new job source selection page', async () => { + await ml.testExecution.logTestStep('job creation loads the new job source selection page'); await ml.jobManagement.navigateToNewJobSourceSelection(); - }); - it('job creation loads the job type selection page', async () => { + await ml.testExecution.logTestStep('job creation loads the job type selection page'); await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob('ft_categorization'); - }); - it('job creation loads the categorization job wizard page', async () => { + await ml.testExecution.logTestStep('job creation loads the categorization job wizard page'); await ml.jobTypeSelection.selectCategorizationJob(); }); - it('job creation displays the time range step', async () => { + it('job creation navigates through the categorization wizard and sets all needed fields', async () => { + await ml.testExecution.logTestStep('job creation displays the time range step'); await ml.jobWizardCommon.assertTimeRangeSectionExists(); - }); - it('job creation sets the timerange', async () => { + await ml.testExecution.logTestStep('job creation sets the timerange'); await ml.jobWizardCommon.clickUseFullDataButton( 'Apr 5, 2019 @ 11:25:35.770', 'Nov 21, 2019 @ 06:01:13.914' ); - }); - it('job creation displays the event rate chart', async () => { + await ml.testExecution.logTestStep('job creation displays the event rate chart'); await ml.jobWizardCommon.assertEventRateChartExists(); await ml.jobWizardCommon.assertEventRateChartHasData(); - }); - it('job creation displays the pick fields step', async () => { + await ml.testExecution.logTestStep('job creation displays the pick fields step'); await ml.jobWizardCommon.advanceToPickFieldsSection(); - }); - it(`job creation selects ${detectorTypeIdentifier} detector type`, async () => { + await ml.testExecution.logTestStep( + `job creation selects ${detectorTypeIdentifier} detector type` + ); await ml.jobWizardCategorization.assertCategorizationDetectorTypeSelectionExists(); await ml.jobWizardCategorization.selectCategorizationDetectorType(detectorTypeIdentifier); - }); - it(`job creation selects the categorization field`, async () => { + await ml.testExecution.logTestStep(`job creation selects the categorization field`); await ml.jobWizardCategorization.assertCategorizationFieldInputExists(); await ml.jobWizardCategorization.selectCategorizationField(categorizationFieldIdentifier); await ml.jobWizardCategorization.assertCategorizationExamplesCallout( @@ -140,81 +137,67 @@ export default function ({ getService }: FtrProviderContext) { await ml.jobWizardCategorization.assertCategorizationExamplesTable( categorizationExampleCount ); - }); - it('job creation inputs the bucket span', async () => { + await ml.testExecution.logTestStep('job creation inputs the bucket span'); await ml.jobWizardCommon.assertBucketSpanInputExists(); await ml.jobWizardCommon.setBucketSpan(bucketSpan); - }); - it('job creation displays the job details step', async () => { + await ml.testExecution.logTestStep('job creation displays the job details step'); await ml.jobWizardCommon.advanceToJobDetailsSection(); - }); - it('job creation inputs the job id', async () => { + await ml.testExecution.logTestStep('job creation inputs the job id'); await ml.jobWizardCommon.assertJobIdInputExists(); await ml.jobWizardCommon.setJobId(jobId); - }); - it('job creation inputs the job description', async () => { + await ml.testExecution.logTestStep('job creation inputs the job description'); await ml.jobWizardCommon.assertJobDescriptionInputExists(); await ml.jobWizardCommon.setJobDescription(jobDescription); - }); - it('job creation inputs job groups', async () => { + await ml.testExecution.logTestStep('job creation inputs job groups'); await ml.jobWizardCommon.assertJobGroupInputExists(); for (const jobGroup of jobGroups) { await ml.jobWizardCommon.addJobGroup(jobGroup); } await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); - }); - it('job creation opens the additional settings section', async () => { + await ml.testExecution.logTestStep('job creation opens the additional settings section'); await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); - }); - it('job creation adds a new custom url', async () => { + await ml.testExecution.logTestStep('job creation adds a new custom url'); await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); - }); - it('job creation assigns calendars', async () => { + await ml.testExecution.logTestStep('job creation assigns calendars'); await ml.jobWizardCommon.addCalendar(calendarId); - }); - it('job creation opens the advanced section', async () => { + await ml.testExecution.logTestStep('job creation opens the advanced section'); await ml.jobWizardCommon.ensureAdvancedSectionOpen(); - }); - it('job creation displays the model plot switch', async () => { + await ml.testExecution.logTestStep('job creation displays the model plot switch'); await ml.jobWizardCommon.assertModelPlotSwitchExists(); await ml.jobWizardCommon.assertModelPlotSwitchEnabled(false); await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(false); - }); - it('job creation enables the dedicated index switch', async () => { + await ml.testExecution.logTestStep('job creation enables the dedicated index switch'); await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); await ml.jobWizardCommon.activateDedicatedIndexSwitch(); - }); - it('job creation inputs the model memory limit', async () => { + await ml.testExecution.logTestStep('job creation inputs the model memory limit'); await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit); - }); - it('job creation displays the validation step', async () => { + await ml.testExecution.logTestStep('job creation displays the validation step'); await ml.jobWizardCommon.advanceToValidationSection(); - }); - it('job creation displays the summary step', async () => { + await ml.testExecution.logTestStep('job creation displays the summary step'); await ml.jobWizardCommon.advanceToSummarySection(); }); - it('job creation creates the job and finishes processing', async () => { + it('job creation runs the job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep('job creation creates the job and finishes processing'); await ml.jobWizardCommon.assertCreateJobButtonExists(); await ml.jobWizardCommon.createJobAndWaitForCompletion(); - }); - it('job creation displays the created job in the job list', async () => { + await ml.testExecution.logTestStep('job creation displays the created job in the job list'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); @@ -222,9 +205,10 @@ export default function ({ getService }: FtrProviderContext) { await ml.jobTable.filterWithSearchString(jobId); const rows = await ml.jobTable.parseJobTable(); expect(rows.filter((row) => row.id === jobId)).to.have.length(1); - }); - it('job creation displays details for the created job in the job list', async () => { + await ml.testExecution.logTestStep( + 'job creation displays details for the created job in the job list' + ); await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId, jobGroups)); await ml.jobTable.assertJobRowDetailsCounts( @@ -232,123 +216,106 @@ export default function ({ getService }: FtrProviderContext) { getExpectedCounts(jobId), getExpectedModelSizeStats(jobId) ); - }); - it('job creation has detector results', async () => { + await ml.testExecution.logTestStep('job creation has detector results'); await ml.api.assertDetectorResultsExist(jobId, 0); }); - it('job cloning clicks the clone action and loads the single metric wizard', async () => { + it('job cloning opens the existing job in the categorization wizard', async () => { + await ml.testExecution.logTestStep( + 'job cloning clicks the clone action and loads the single metric wizard' + ); await ml.jobTable.clickCloneJobAction(jobId); await ml.jobTypeSelection.assertCategorizationJobWizardOpen(); }); - it('job cloning displays the time range step', async () => { + it('job cloning navigates through the categorization wizard, checks and sets all needed fields', async () => { + await ml.testExecution.logTestStep('job cloning displays the time range step'); await ml.jobWizardCommon.assertTimeRangeSectionExists(); - }); - it('job cloning sets the timerange', async () => { + await ml.testExecution.logTestStep('job cloning sets the timerange'); await ml.jobWizardCommon.clickUseFullDataButton( 'Apr 5, 2019 @ 11:25:35.770', 'Nov 21, 2019 @ 06:01:13.914' ); - }); - it('job cloning displays the event rate chart', async () => { + await ml.testExecution.logTestStep('job cloning displays the event rate chart'); await ml.jobWizardCommon.assertEventRateChartExists(); await ml.jobWizardCommon.assertEventRateChartHasData(); - }); - it('job cloning displays the pick fields step', async () => { + await ml.testExecution.logTestStep('job cloning displays the pick fields step'); await ml.jobWizardCommon.advanceToPickFieldsSection(); - }); - it('job cloning pre-fills field and aggregation', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills field and aggregation'); await ml.jobWizardCategorization.assertCategorizationDetectorTypeSelectionExists(); - }); - it('job cloning pre-fills the bucket span', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the bucket span'); await ml.jobWizardCommon.assertBucketSpanInputExists(); await ml.jobWizardCommon.assertBucketSpanValue(bucketSpan); - }); - it('job cloning displays the job details step', async () => { + await ml.testExecution.logTestStep('job cloning displays the job details step'); await ml.jobWizardCommon.advanceToJobDetailsSection(); - }); - it('job cloning does not pre-fill the job id', async () => { + await ml.testExecution.logTestStep('job cloning does not pre-fill the job id'); await ml.jobWizardCommon.assertJobIdInputExists(); await ml.jobWizardCommon.assertJobIdValue(''); - }); - it('job cloning inputs the clone job id', async () => { + await ml.testExecution.logTestStep('job cloning inputs the clone job id'); await ml.jobWizardCommon.setJobId(jobIdClone); - }); - it('job cloning pre-fills the job description', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the job description'); await ml.jobWizardCommon.assertJobDescriptionInputExists(); await ml.jobWizardCommon.assertJobDescriptionValue(jobDescription); - }); - it('job cloning pre-fills job groups', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills job groups'); await ml.jobWizardCommon.assertJobGroupInputExists(); await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); - }); - it('job cloning inputs the clone job group', async () => { + await ml.testExecution.logTestStep('job cloning inputs the clone job group'); await ml.jobWizardCommon.assertJobGroupInputExists(); await ml.jobWizardCommon.addJobGroup('clone'); await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); - }); - it('job cloning opens the additional settings section', async () => { + await ml.testExecution.logTestStep('job cloning opens the additional settings section'); await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); - }); - it('job cloning persists custom urls', async () => { + await ml.testExecution.logTestStep('job cloning persists custom urls'); await ml.customUrls.assertCustomUrlItem(0, 'check-kibana-dashboard'); - }); - it('job cloning persists assigned calendars', async () => { + await ml.testExecution.logTestStep('job cloning persists assigned calendars'); await ml.jobWizardCommon.assertCalendarsSelection([calendarId]); - }); - it('job cloning opens the advanced section', async () => { + await ml.testExecution.logTestStep('job cloning opens the advanced section'); await ml.jobWizardCommon.ensureAdvancedSectionOpen(); - }); - it('job cloning pre-fills the model plot switch', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the model plot switch'); await ml.jobWizardCommon.assertModelPlotSwitchExists(); await ml.jobWizardCommon.assertModelPlotSwitchEnabled(false); await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(false); - }); - it('job cloning pre-fills the dedicated index switch', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the dedicated index switch'); await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true); - }); - // MML during clone has changed in #61589 - // TODO: adjust test code to reflect the new behavior - it.skip('job cloning pre-fills the model memory limit', async () => { - await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); - await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit); - }); + // MML during clone has changed in #61589 + // TODO: adjust test code to reflect the new behavior + // await ml.testExecution.logTestStep('job cloning pre-fills the model memory limit'); + // await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + // await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit); - it('job cloning displays the validation step', async () => { + await ml.testExecution.logTestStep('job cloning displays the validation step'); await ml.jobWizardCommon.advanceToValidationSection(); - }); - it('job cloning displays the summary step', async () => { + await ml.testExecution.logTestStep('job cloning displays the summary step'); await ml.jobWizardCommon.advanceToSummarySection(); }); - it('job cloning creates the job and finishes processing', async () => { + it('job cloning runs the clone job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep('job cloning creates the job and finishes processing'); await ml.jobWizardCommon.assertCreateJobButtonExists(); await ml.jobWizardCommon.createJobAndWaitForCompletion(); - }); - it('job cloning displays the created job in the job list', async () => { + await ml.testExecution.logTestStep('job cloning displays the created job in the job list'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); @@ -356,9 +323,10 @@ export default function ({ getService }: FtrProviderContext) { await ml.jobTable.filterWithSearchString(jobIdClone); const rows = await ml.jobTable.parseJobTable(); expect(rows.filter((row) => row.id === jobIdClone)).to.have.length(1); - }); - it('job cloning displays details for the created job in the job list', async () => { + await ml.testExecution.logTestStep( + 'job cloning displays details for the created job in the job list' + ); await ml.jobTable.assertJobRowFields(jobIdClone, getExpectedRow(jobIdClone, jobGroupsClone)); await ml.jobTable.assertJobRowDetailsCounts( @@ -366,32 +334,32 @@ export default function ({ getService }: FtrProviderContext) { getExpectedCounts(jobIdClone), getExpectedModelSizeStats(jobIdClone) ); - }); - it('job cloning has detector results', async () => { + await ml.testExecution.logTestStep('job cloning has detector results'); await ml.api.assertDetectorResultsExist(jobId, 0); }); - it('job deletion has results for the job before deletion', async () => { + it('deletes the cloned job', async () => { + await ml.testExecution.logTestStep('job deletion has results for the job before deletion'); await ml.api.assertJobResultsExist(jobIdClone); - }); - it('job deletion triggers the delete action', async () => { + await ml.testExecution.logTestStep('job deletion triggers the delete action'); await ml.jobTable.clickDeleteJobAction(jobIdClone); - }); - it('job deletion confirms the delete modal', async () => { + await ml.testExecution.logTestStep('job deletion confirms the delete modal'); await ml.jobTable.confirmDeleteJobModal(); - }); - it('job deletion does not display the deleted job in the job list any more', async () => { + await ml.testExecution.logTestStep( + 'job deletion does not display the deleted job in the job list any more' + ); await ml.jobTable.waitForJobsToLoad(); await ml.jobTable.filterWithSearchString(jobIdClone); const rows = await ml.jobTable.parseJobTable(); expect(rows.filter((row) => row.id === jobIdClone)).to.have.length(0); - }); - it('job deletion does not have results for the deleted job any more', async () => { + await ml.testExecution.logTestStep( + 'job deletion does not have results for the deleted job any more' + ); await ml.api.assertNoJobResultsExist(jobIdClone); }); }); diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/date_nanos_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection/date_nanos_job.ts index 50622604c4e5c..22b4c4a1fdfe3 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/date_nanos_job.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/date_nanos_job.ts @@ -32,55 +32,6 @@ interface PickFieldsConfig { summaryCountField?: string; } -// type guards -// Detector -const isDetectorWithField = (arg: any): arg is Required> => { - return arg.hasOwnProperty('field'); -}; -const isDetectorWithByField = (arg: any): arg is Required> => { - return arg.hasOwnProperty('byField'); -}; -const isDetectorWithOverField = (arg: any): arg is Required> => { - return arg.hasOwnProperty('overField'); -}; -const isDetectorWithPartitionField = ( - arg: any -): arg is Required> => { - return arg.hasOwnProperty('partitionField'); -}; -const isDetectorWithExcludeFrequent = ( - arg: any -): arg is Required> => { - return arg.hasOwnProperty('excludeFrequent'); -}; -const isDetectorWithDescription = (arg: any): arg is Required> => { - return arg.hasOwnProperty('description'); -}; - -// DatafeedConfig -const isDatafeedConfigWithQueryDelay = ( - arg: any -): arg is Required> => { - return arg.hasOwnProperty('queryDelay'); -}; -const isDatafeedConfigWithFrequency = ( - arg: any -): arg is Required> => { - return arg.hasOwnProperty('frequency'); -}; -const isDatafeedConfigWithScrollSize = ( - arg: any -): arg is Required> => { - return arg.hasOwnProperty('scrollSize'); -}; - -// PickFieldsConfig -const isPickFieldsConfigWithSummaryCountField = ( - arg: any -): arg is Required> => { - return arg.hasOwnProperty('summaryCountField'); -}; - export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); @@ -183,77 +134,70 @@ export default function ({ getService }: FtrProviderContext) { for (const testData of testDataList) { describe(`${testData.suiteTitle}`, function () { - it('job creation loads the job management page', async () => { + it('loads the advanced wizard for the source data', async () => { + await ml.testExecution.logTestStep('job creation loads the job management page'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); - }); - it('job creation loads the new job source selection page', async () => { + await ml.testExecution.logTestStep( + 'job creation loads the new job source selection page' + ); await ml.jobManagement.navigateToNewJobSourceSelection(); - }); - it('job creation loads the job type selection page', async () => { + await ml.testExecution.logTestStep('job creation loads the job type selection page'); await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob(testData.jobSource); - }); - it('job creation loads the advanced job wizard page', async () => { + await ml.testExecution.logTestStep('job creation loads the advanced job wizard page'); await ml.jobTypeSelection.selectAdvancedJob(); }); - it('job creation displays the configure datafeed step', async () => { + it('navigates through the advanced wizard and sets all needed fields', async () => { + await ml.testExecution.logTestStep('job creation displays the configure datafeed step'); await ml.jobWizardCommon.assertConfigureDatafeedSectionExists(); - }); - it('job creation pre-fills the datafeed query editor', async () => { + await ml.testExecution.logTestStep('job creation pre-fills the datafeed query editor'); await ml.jobWizardAdvanced.assertDatafeedQueryEditorExists(); await ml.jobWizardAdvanced.assertDatafeedQueryEditorValue(defaultValues.datafeedQuery); - }); - it('job creation inputs the query delay', async () => { + await ml.testExecution.logTestStep('job creation inputs the query delay'); await ml.jobWizardAdvanced.assertQueryDelayInputExists(); await ml.jobWizardAdvanced.assertQueryDelayValue(defaultValues.queryDelay); - if (isDatafeedConfigWithQueryDelay(testData.datafeedConfig)) { - await ml.jobWizardAdvanced.setQueryDelay(testData.datafeedConfig.queryDelay); + if (testData.datafeedConfig.hasOwnProperty('queryDelay')) { + await ml.jobWizardAdvanced.setQueryDelay(testData.datafeedConfig.queryDelay!); } - }); - it('job creation inputs the frequency', async () => { + await ml.testExecution.logTestStep('job creation inputs the frequency'); await ml.jobWizardAdvanced.assertFrequencyInputExists(); await ml.jobWizardAdvanced.assertFrequencyValue(defaultValues.frequency); - if (isDatafeedConfigWithFrequency(testData.datafeedConfig)) { - await ml.jobWizardAdvanced.setFrequency(testData.datafeedConfig.frequency); + if (testData.datafeedConfig.hasOwnProperty('frequency')) { + await ml.jobWizardAdvanced.setFrequency(testData.datafeedConfig.frequency!); } - }); - it('job creation inputs the scroll size', async () => { + await ml.testExecution.logTestStep('job creation inputs the scroll size'); await ml.jobWizardAdvanced.assertScrollSizeInputExists(); await ml.jobWizardAdvanced.assertScrollSizeValue(defaultValues.scrollSize); - if (isDatafeedConfigWithScrollSize(testData.datafeedConfig)) { - await ml.jobWizardAdvanced.setScrollSize(testData.datafeedConfig.scrollSize); + if (testData.datafeedConfig.hasOwnProperty('scrollSize')) { + await ml.jobWizardAdvanced.setScrollSize(testData.datafeedConfig.scrollSize!); } - }); - it('job creation pre-fills the time field', async () => { + await ml.testExecution.logTestStep('ob creation pre-fills the time field'); await ml.jobWizardAdvanced.assertTimeFieldInputExists(); await ml.jobWizardAdvanced.assertTimeFieldSelection([testData.expected.wizard.timeField]); - }); - it('job creation displays the pick fields step', async () => { + await ml.testExecution.logTestStep('job creation displays the pick fields step'); await ml.jobWizardCommon.advanceToPickFieldsSection(); - }); - it('job creation selects the summary count field', async () => { + await ml.testExecution.logTestStep('job creation selects the summary count field'); await ml.jobWizardAdvanced.assertSummaryCountFieldInputExists(); - if (isPickFieldsConfigWithSummaryCountField(testData.pickFieldsConfig)) { + if (testData.pickFieldsConfig.hasOwnProperty('summaryCountField')) { await ml.jobWizardAdvanced.selectSummaryCountField( - testData.pickFieldsConfig.summaryCountField + testData.pickFieldsConfig.summaryCountField! ); } else { await ml.jobWizardAdvanced.assertSummaryCountFieldSelection([]); } - }); - it('job creation adds detectors', async () => { + await ml.testExecution.logTestStep('job creation adds detectors'); for (const detector of testData.pickFieldsConfig.detectors) { await ml.jobWizardAdvanced.openCreateDetectorModal(); await ml.jobWizardAdvanced.assertDetectorFunctionInputExists(); @@ -272,120 +216,112 @@ export default function ({ getService }: FtrProviderContext) { await ml.jobWizardAdvanced.assertDetectorDescriptionValue(''); await ml.jobWizardAdvanced.selectDetectorFunction(detector.function); - if (isDetectorWithField(detector)) { - await ml.jobWizardAdvanced.selectDetectorField(detector.field); + if (detector.hasOwnProperty('field')) { + await ml.jobWizardAdvanced.selectDetectorField(detector.field!); } - if (isDetectorWithByField(detector)) { - await ml.jobWizardAdvanced.selectDetectorByField(detector.byField); + if (detector.hasOwnProperty('byField')) { + await ml.jobWizardAdvanced.selectDetectorByField(detector.byField!); } - if (isDetectorWithOverField(detector)) { - await ml.jobWizardAdvanced.selectDetectorOverField(detector.overField); + if (detector.hasOwnProperty('overField')) { + await ml.jobWizardAdvanced.selectDetectorOverField(detector.overField!); } - if (isDetectorWithPartitionField(detector)) { - await ml.jobWizardAdvanced.selectDetectorPartitionField(detector.partitionField); + if (detector.hasOwnProperty('partitionField')) { + await ml.jobWizardAdvanced.selectDetectorPartitionField(detector.partitionField!); } - if (isDetectorWithExcludeFrequent(detector)) { - await ml.jobWizardAdvanced.selectDetectorExcludeFrequent(detector.excludeFrequent); + if (detector.hasOwnProperty('excludeFrequent')) { + await ml.jobWizardAdvanced.selectDetectorExcludeFrequent(detector.excludeFrequent!); } - if (isDetectorWithDescription(detector)) { - await ml.jobWizardAdvanced.setDetectorDescription(detector.description); + if (detector.hasOwnProperty('description')) { + await ml.jobWizardAdvanced.setDetectorDescription(detector.description!); } await ml.jobWizardAdvanced.confirmAddDetectorModal(); } - }); - it('job creation displays detector entries', async () => { + await ml.testExecution.logTestStep('job creation displays detector entries'); for (const [index, detector] of testData.pickFieldsConfig.detectors.entries()) { await ml.jobWizardAdvanced.assertDetectorEntryExists( index, detector.identifier, - isDetectorWithDescription(detector) ? detector.description : undefined + detector.hasOwnProperty('description') ? detector.description! : undefined ); } - }); - it('job creation inputs the bucket span', async () => { + await ml.testExecution.logTestStep('job creation inputs the bucket span'); await ml.jobWizardCommon.assertBucketSpanInputExists(); await ml.jobWizardCommon.setBucketSpan(testData.pickFieldsConfig.bucketSpan); - }); - it('job creation inputs influencers', async () => { + await ml.testExecution.logTestStep('job creation inputs influencers'); await ml.jobWizardCommon.assertInfluencerInputExists(); await ml.jobWizardCommon.assertInfluencerSelection([]); for (const influencer of testData.pickFieldsConfig.influencers) { await ml.jobWizardCommon.addInfluencer(influencer); } - }); - it('job creation inputs the model memory limit', async () => { + await ml.testExecution.logTestStep('job creation inputs the model memory limit'); await ml.jobWizardCommon.assertModelMemoryLimitInputExists({ withAdvancedSection: false, }); await ml.jobWizardCommon.setModelMemoryLimit(testData.pickFieldsConfig.memoryLimit, { withAdvancedSection: false, }); - }); - it('job creation displays the job details step', async () => { + await ml.testExecution.logTestStep('job creation displays the job details step'); await ml.jobWizardCommon.advanceToJobDetailsSection(); - }); - it('job creation inputs the job id', async () => { + await ml.testExecution.logTestStep('job creation inputs the job id'); await ml.jobWizardCommon.assertJobIdInputExists(); await ml.jobWizardCommon.setJobId(testData.jobId); - }); - it('job creation inputs the job description', async () => { + await ml.testExecution.logTestStep('job creation inputs the job description'); await ml.jobWizardCommon.assertJobDescriptionInputExists(); await ml.jobWizardCommon.setJobDescription(testData.jobDescription); - }); - it('job creation inputs job groups', async () => { + await ml.testExecution.logTestStep('job creation inputs job groups'); await ml.jobWizardCommon.assertJobGroupInputExists(); for (const jobGroup of testData.jobGroups) { await ml.jobWizardCommon.addJobGroup(jobGroup); } await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroups); - }); - it('job creation opens the additional settings section', async () => { + await ml.testExecution.logTestStep('job creation opens the additional settings section'); await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); - }); - it('job creation displays the model plot switch', async () => { + await ml.testExecution.logTestStep('job creation displays the model plot switch'); await ml.jobWizardCommon.assertModelPlotSwitchExists({ withAdvancedSection: false }); - }); - it('job creation enables the dedicated index switch', async () => { + await ml.testExecution.logTestStep('job creation enables the dedicated index switch'); await ml.jobWizardCommon.assertDedicatedIndexSwitchExists({ withAdvancedSection: false }); await ml.jobWizardCommon.activateDedicatedIndexSwitch({ withAdvancedSection: false }); - }); - it('job creation displays the validation step', async () => { + await ml.testExecution.logTestStep('job creation displays the validation step'); await ml.jobWizardCommon.advanceToValidationSection(); - }); - it('job creation displays the summary step', async () => { + await ml.testExecution.logTestStep('job creation displays the summary step'); await ml.jobWizardCommon.advanceToSummarySection(); }); - it('job creation creates the job and finishes processing', async () => { + it('runs the job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep( + 'job creation creates the job and finishes processing' + ); await ml.jobWizardCommon.assertCreateJobButtonExists(); await ml.jobWizardAdvanced.createJob(); await ml.jobManagement.assertStartDatafeedModalExists(); await ml.jobManagement.confirmStartDatafeedModal(); await ml.jobManagement.waitForJobCompletion(testData.jobId); - }); - it('job creation displays the created job in the job list', async () => { + await ml.testExecution.logTestStep( + 'job creation displays the created job in the job list' + ); await ml.jobTable.refreshJobList(); await ml.jobTable.filterWithSearchString(testData.jobId); const rows = await ml.jobTable.parseJobTable(); expect(rows.filter((row) => row.id === testData.jobId)).to.have.length(1); - }); - it('job creation displays details for the created job in the job list', async () => { + await ml.testExecution.logTestStep( + 'job creation displays details for the created job in the job list' + ); await ml.jobTable.assertJobRowFields(testData.jobId, { id: testData.jobId, description: testData.jobDescription, @@ -404,9 +340,8 @@ export default function ({ getService }: FtrProviderContext) { ...testData.expected.modelSizeStats, } ); - }); - it('job creation has detector results', async () => { + await ml.testExecution.logTestStep('job creation has detector results'); for (let i = 0; i < testData.pickFieldsConfig.detectors.length; i++) { await ml.api.assertDetectorResultsExist(testData.jobId, i); } diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/multi_metric_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection/multi_metric_job.ts index 85477b105abe9..8702cfd734454 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/multi_metric_job.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/multi_metric_job.ts @@ -86,52 +86,50 @@ export default function ({ getService }: FtrProviderContext) { await ml.api.cleanMlIndices(); }); - it('job creation loads the job management page', async () => { + it('job creation loads the multi metric wizard for the source data', async () => { + await ml.testExecution.logTestStep('job creation loads the job management page'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); - }); - it('job creation loads the new job source selection page', async () => { + await ml.testExecution.logTestStep('job creation loads the new job source selection page'); await ml.jobManagement.navigateToNewJobSourceSelection(); - }); - it('job creation loads the job type selection page', async () => { + await ml.testExecution.logTestStep('job creation loads the job type selection page'); await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob('ft_farequote'); - }); - it('job creation loads the multi metric job wizard page', async () => { + await ml.testExecution.logTestStep('job creation loads the multi metric job wizard page'); await ml.jobTypeSelection.selectMultiMetricJob(); }); - it('job creation displays the time range step', async () => { + it('job creation navigates through the multi metric wizard and sets all needed fields', async () => { + await ml.testExecution.logTestStep('job creation displays the time range step'); await ml.jobWizardCommon.assertTimeRangeSectionExists(); - }); - it('job creation sets the timerange', async () => { + await ml.testExecution.logTestStep('job creation sets the timerange'); await ml.jobWizardCommon.clickUseFullDataButton( 'Feb 7, 2016 @ 00:00:00.000', 'Feb 11, 2016 @ 23:59:54.000' ); - }); - it('job creation displays the event rate chart', async () => { + await ml.testExecution.logTestStep('job creation displays the event rate chart'); await ml.jobWizardCommon.assertEventRateChartExists(); await ml.jobWizardCommon.assertEventRateChartHasData(); - }); - it('job creation displays the pick fields step', async () => { + await ml.testExecution.logTestStep('job creation displays the pick fields step'); await ml.jobWizardCommon.advanceToPickFieldsSection(); - }); - it('job creation selects detectors and displays detector previews', async () => { + await ml.testExecution.logTestStep( + 'job creation selects detectors and displays detector previews' + ); for (const [index, aggAndFieldIdentifier] of aggAndFieldIdentifiers.entries()) { await ml.jobWizardCommon.assertAggAndFieldInputExists(); await ml.jobWizardCommon.selectAggAndField(aggAndFieldIdentifier, false); await ml.jobWizardCommon.assertDetectorPreviewExists(aggAndFieldIdentifier, index, 'LINE'); } - }); - it('job creation inputs the split field and displays split cards', async () => { + await ml.testExecution.logTestStep( + 'job creation inputs the split field and displays split cards' + ); await ml.jobWizardMultiMetric.assertSplitFieldInputExists(); await ml.jobWizardMultiMetric.selectSplitField(splitField); @@ -140,84 +138,69 @@ export default function ({ getService }: FtrProviderContext) { await ml.jobWizardMultiMetric.assertDetectorSplitNumberOfBackCards(9); await ml.jobWizardCommon.assertInfluencerSelection([splitField]); - }); - it('job creation displays the influencer field', async () => { + await ml.testExecution.logTestStep('job creation displays the influencer field'); await ml.jobWizardCommon.assertInfluencerInputExists(); await ml.jobWizardCommon.assertInfluencerSelection([splitField]); - }); - it('job creation inputs the bucket span', async () => { + await ml.testExecution.logTestStep('job creation inputs the bucket span'); await ml.jobWizardCommon.assertBucketSpanInputExists(); await ml.jobWizardCommon.setBucketSpan(bucketSpan); - }); - it('job creation displays the job details step', async () => { + await ml.testExecution.logTestStep('job creation displays the job details step'); await ml.jobWizardCommon.advanceToJobDetailsSection(); - }); - it('job creation inputs the job id', async () => { + await ml.testExecution.logTestStep('job creation inputs the job id'); await ml.jobWizardCommon.assertJobIdInputExists(); await ml.jobWizardCommon.setJobId(jobId); - }); - it('job creation inputs the job description', async () => { + await ml.testExecution.logTestStep('job creation inputs the job description'); await ml.jobWizardCommon.assertJobDescriptionInputExists(); await ml.jobWizardCommon.setJobDescription(jobDescription); - }); - it('job creation inputs job groups', async () => { + await ml.testExecution.logTestStep('job creation inputs job groups'); await ml.jobWizardCommon.assertJobGroupInputExists(); for (const jobGroup of jobGroups) { await ml.jobWizardCommon.addJobGroup(jobGroup); } await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); - }); - it('job creation opens the additional settings section', async () => { + await ml.testExecution.logTestStep('job creation opens the additional settings section'); await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); - }); - it('job creation adds a new custom url', async () => { + await ml.testExecution.logTestStep('job creation adds a new custom url'); await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); - }); - it('job creation assigns calendars', async () => { + await ml.testExecution.logTestStep('job creation assigns calendars'); await ml.jobWizardCommon.addCalendar(calendarId); - }); - it('job creation opens the advanced section', async () => { + await ml.testExecution.logTestStep('job creation opens the advanced section'); await ml.jobWizardCommon.ensureAdvancedSectionOpen(); - }); - it('job creation displays the model plot switch', async () => { + await ml.testExecution.logTestStep('job creation displays the model plot switch'); await ml.jobWizardCommon.assertModelPlotSwitchExists(); - }); - it('job creation enables the dedicated index switch', async () => { + await ml.testExecution.logTestStep('job creation enables the dedicated index switch'); await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); await ml.jobWizardCommon.activateDedicatedIndexSwitch(); - }); - it('job creation inputs the model memory limit', async () => { + await ml.testExecution.logTestStep('job creation inputs the model memory limit'); await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit); - }); - it('job creation displays the validation step', async () => { + await ml.testExecution.logTestStep('job creation displays the validation step'); await ml.jobWizardCommon.advanceToValidationSection(); - }); - it('job creation displays the summary step', async () => { + await ml.testExecution.logTestStep('job creation displays the summary step'); await ml.jobWizardCommon.advanceToSummarySection(); }); - it('job creation creates the job and finishes processing', async () => { + it('job creation runs the job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep('job creation creates the job and finishes processing'); await ml.jobWizardCommon.assertCreateJobButtonExists(); await ml.jobWizardCommon.createJobAndWaitForCompletion(); - }); - it('job creation displays the created job in the job list', async () => { + await ml.testExecution.logTestStep('job creation displays the created job in the job list'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); @@ -225,9 +208,10 @@ export default function ({ getService }: FtrProviderContext) { await ml.jobTable.filterWithSearchString(jobId); const rows = await ml.jobTable.parseJobTable(); expect(rows.filter((row) => row.id === jobId)).to.have.length(1); - }); - it('job creation displays details for the created job in the job list', async () => { + await ml.testExecution.logTestStep( + 'job creation displays details for the created job in the job list' + ); await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId, jobGroups)); await ml.jobTable.assertJobRowDetailsCounts( @@ -235,40 +219,41 @@ export default function ({ getService }: FtrProviderContext) { getExpectedCounts(jobId), getExpectedModelSizeStats(jobId) ); - }); - it('job creation has detector results', async () => { + await ml.testExecution.logTestStep('job creation has detector results'); for (let i = 0; i < aggAndFieldIdentifiers.length; i++) { await ml.api.assertDetectorResultsExist(jobId, i); } }); - it('job cloning clicks the clone action and loads the multi metric wizard', async () => { + it('job cloning opens the existing job in the multi metric wizard', async () => { + await ml.testExecution.logTestStep( + 'job cloning clicks the clone action and loads the multi metric wizard' + ); await ml.jobTable.clickCloneJobAction(jobId); await ml.jobTypeSelection.assertMultiMetricJobWizardOpen(); }); - it('job cloning displays the time range step', async () => { + it('job cloning navigates through the multi metric wizard, checks and sets all needed fields', async () => { + await ml.testExecution.logTestStep('job cloning displays the time range step'); await ml.jobWizardCommon.assertTimeRangeSectionExists(); - }); - it('job cloning sets the timerange', async () => { + await ml.testExecution.logTestStep('job cloning sets the timerange'); await ml.jobWizardCommon.clickUseFullDataButton( 'Feb 7, 2016 @ 00:00:00.000', 'Feb 11, 2016 @ 23:59:54.000' ); - }); - it('job cloning displays the event rate chart', async () => { + await ml.testExecution.logTestStep('job cloning displays the event rate chart'); await ml.jobWizardCommon.assertEventRateChartExists(); await ml.jobWizardCommon.assertEventRateChartHasData(); - }); - it('job cloning displays the pick fields step', async () => { + await ml.testExecution.logTestStep('job cloning displays the pick fields step'); await ml.jobWizardCommon.advanceToPickFieldsSection(); - }); - it('job cloning pre-fills detectors and shows preview with split cards', async () => { + await ml.testExecution.logTestStep( + 'job cloning pre-fills detectors and shows preview with split cards' + ); for (const [index, aggAndFieldIdentifier] of aggAndFieldIdentifiers.entries()) { await ml.jobWizardCommon.assertDetectorPreviewExists(aggAndFieldIdentifier, index, 'LINE'); } @@ -276,99 +261,81 @@ export default function ({ getService }: FtrProviderContext) { await ml.jobWizardMultiMetric.assertDetectorSplitExists(splitField); await ml.jobWizardMultiMetric.assertDetectorSplitFrontCardTitle('AAL'); await ml.jobWizardMultiMetric.assertDetectorSplitNumberOfBackCards(9); - }); - it('job cloning pre-fills the split field', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the split field'); await ml.jobWizardMultiMetric.assertSplitFieldInputExists(); await ml.jobWizardMultiMetric.assertSplitFieldSelection([splitField]); - }); - it('job cloning pre-fills influencers', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills influencers'); await ml.jobWizardCommon.assertInfluencerInputExists(); await ml.jobWizardCommon.assertInfluencerSelection([splitField]); - }); - it('job cloning pre-fills the bucket span', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the bucket span'); await ml.jobWizardCommon.assertBucketSpanInputExists(); await ml.jobWizardCommon.assertBucketSpanValue(bucketSpan); - }); - it('job cloning displays the job details step', async () => { + await ml.testExecution.logTestStep('job cloning displays the job details step'); await ml.jobWizardCommon.advanceToJobDetailsSection(); - }); - it('job cloning does not pre-fill the job id', async () => { + await ml.testExecution.logTestStep('job cloning does not pre-fill the job id'); await ml.jobWizardCommon.assertJobIdInputExists(); await ml.jobWizardCommon.assertJobIdValue(''); - }); - it('job cloning inputs the clone job id', async () => { + await ml.testExecution.logTestStep('job cloning inputs the clone job id'); await ml.jobWizardCommon.setJobId(jobIdClone); - }); - it('job cloning pre-fills the job description', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the job description'); await ml.jobWizardCommon.assertJobDescriptionInputExists(); await ml.jobWizardCommon.assertJobDescriptionValue(jobDescription); - }); - it('job cloning pre-fills job groups', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills job groups'); await ml.jobWizardCommon.assertJobGroupInputExists(); await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); - }); - it('job cloning inputs the clone job group', async () => { + await ml.testExecution.logTestStep('job cloning inputs the clone job group'); await ml.jobWizardCommon.assertJobGroupInputExists(); await ml.jobWizardCommon.addJobGroup('clone'); await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); - }); - it('job cloning opens the additional settings section', async () => { + await ml.testExecution.logTestStep('job cloning opens the additional settings section'); await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); - }); - it('job cloning persists custom urls', async () => { + await ml.testExecution.logTestStep('job cloning persists custom urls'); await ml.customUrls.assertCustomUrlItem(0, 'check-kibana-dashboard'); - }); - it('job cloning persists assigned calendars', async () => { + await ml.testExecution.logTestStep('job cloning persists assigned calendars'); await ml.jobWizardCommon.assertCalendarsSelection([calendarId]); - }); - it('job cloning opens the advanced section', async () => { + await ml.testExecution.logTestStep('job cloning opens the advanced section'); await ml.jobWizardCommon.ensureAdvancedSectionOpen(); - }); - it('job cloning pre-fills the model plot switch', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the model plot switch'); await ml.jobWizardCommon.assertModelPlotSwitchExists(); await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(false); - }); - it('job cloning pre-fills the dedicated index switch', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the dedicated index switch'); await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true); - }); - // MML during clone has changed in #61589 - // TODO: adjust test code to reflect the new behavior - it.skip('job cloning pre-fills the model memory limit', async () => { - await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); - await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit); - }); + // MML during clone has changed in #61589 + // TODO: adjust test code to reflect the new behavior + // await ml.testExecution.logTestStep('job cloning pre-fills the model memory limit'); + // await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + // await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit); - it('job cloning displays the validation step', async () => { + await ml.testExecution.logTestStep('job cloning displays the validation step'); await ml.jobWizardCommon.advanceToValidationSection(); - }); - it('job cloning displays the summary step', async () => { + await ml.testExecution.logTestStep('job cloning displays the summary step'); await ml.jobWizardCommon.advanceToSummarySection(); }); - it('job cloning creates the job and finishes processing', async () => { + it('job cloning runs the clone job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep('job cloning creates the job and finishes processing'); await ml.jobWizardCommon.assertCreateJobButtonExists(); await ml.jobWizardCommon.createJobAndWaitForCompletion(); - }); - it('job cloning displays the created job in the job list', async () => { + await ml.testExecution.logTestStep('job cloning displays the created job in the job list'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); @@ -376,9 +343,10 @@ export default function ({ getService }: FtrProviderContext) { await ml.jobTable.filterWithSearchString(jobIdClone); const rows = await ml.jobTable.parseJobTable(); expect(rows.filter((row) => row.id === jobIdClone)).to.have.length(1); - }); - it('job cloning displays details for the created job in the job list', async () => { + await ml.testExecution.logTestStep( + 'job cloning displays details for the created job in the job list' + ); await ml.jobTable.assertJobRowFields(jobIdClone, getExpectedRow(jobIdClone, jobGroupsClone)); await ml.jobTable.assertJobRowDetailsCounts( @@ -386,9 +354,8 @@ export default function ({ getService }: FtrProviderContext) { getExpectedCounts(jobIdClone), getExpectedModelSizeStats(jobIdClone) ); - }); - it('job cloning has detector results', async () => { + await ml.testExecution.logTestStep('job cloning has detector results'); for (let i = 0; i < aggAndFieldIdentifiers.length; i++) { await ml.api.assertDetectorResultsExist(jobId, i); } diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/population_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection/population_job.ts index c6de7f8a2bd39..3ec78eccf3de4 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/population_job.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/population_job.ts @@ -100,57 +100,54 @@ export default function ({ getService }: FtrProviderContext) { await ml.api.cleanMlIndices(); }); - it('job creation loads the job management page', async () => { + it('job creation loads the population wizard for the source data', async () => { + await ml.testExecution.logTestStep('job creation loads the job management page'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); - }); - it('job creation loads the new job source selection page', async () => { + await ml.testExecution.logTestStep('job creation loads the new job source selection page'); await ml.jobManagement.navigateToNewJobSourceSelection(); - }); - it('job creation loads the job type selection page', async () => { + await ml.testExecution.logTestStep('job creation loads the job type selection page'); await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob('ft_ecommerce'); - }); - it('job creation loads the population job wizard page', async () => { + await ml.testExecution.logTestStep('job creation loads the population job wizard page'); await ml.jobTypeSelection.selectPopulationJob(); }); - it('job creation displays the time range step', async () => { + it('job creation navigates through the population wizard and sets all needed fields', async () => { + await ml.testExecution.logTestStep('job creation displays the time range step'); await ml.jobWizardCommon.assertTimeRangeSectionExists(); - }); - it('job creation sets the timerange', async () => { + await ml.testExecution.logTestStep('job creation sets the timerange'); await ml.jobWizardCommon.clickUseFullDataButton( 'Jun 12, 2019 @ 00:04:19.000', 'Jul 12, 2019 @ 23:45:36.000' ); - }); - it('job creation displays the event rate chart', async () => { + await ml.testExecution.logTestStep('job creation displays the event rate chart'); await ml.jobWizardCommon.assertEventRateChartExists(); await ml.jobWizardCommon.assertEventRateChartHasData(); - }); - it('job creation displays the pick fields step', async () => { + await ml.testExecution.logTestStep('job creation displays the pick fields step'); await ml.jobWizardCommon.advanceToPickFieldsSection(); - }); - it('job creation selects the population field', async () => { + await ml.testExecution.logTestStep('job creation selects the population field'); await ml.jobWizardPopulation.assertPopulationFieldInputExists(); await ml.jobWizardPopulation.selectPopulationField(populationField); - }); - it('job creation selects detectors and displays detector previews', async () => { + await ml.testExecution.logTestStep( + 'job creation selects detectors and displays detector previews' + ); for (const [index, detector] of detectors.entries()) { await ml.jobWizardCommon.assertAggAndFieldInputExists(); await ml.jobWizardCommon.selectAggAndField(detector.identifier, false); await ml.jobWizardCommon.assertDetectorPreviewExists(detector.identifier, index, 'SCATTER'); } - }); - it('job creation inputs detector split fields and displays split cards', async () => { + await ml.testExecution.logTestStep( + 'job creation inputs detector split fields and displays split cards' + ); for (const [index, detector] of detectors.entries()) { await ml.jobWizardPopulation.assertDetectorSplitFieldInputExists(index); await ml.jobWizardPopulation.selectDetectorSplitField(index, detector.splitField); @@ -165,86 +162,71 @@ export default function ({ getService }: FtrProviderContext) { detector.numberOfBackCards ); } - }); - it('job creation displays the influencer field', async () => { + await ml.testExecution.logTestStep('job creation displays the influencer field'); await ml.jobWizardCommon.assertInfluencerInputExists(); await ml.jobWizardCommon.assertInfluencerSelection( [populationField].concat(detectors.map((detector) => detector.splitField)) ); - }); - it('job creation inputs the bucket span', async () => { + await ml.testExecution.logTestStep('job creation inputs the bucket span'); await ml.jobWizardCommon.assertBucketSpanInputExists(); await ml.jobWizardCommon.setBucketSpan(bucketSpan); - }); - it('job creation displays the job details step', async () => { + await ml.testExecution.logTestStep('job creation displays the job details step'); await ml.jobWizardCommon.advanceToJobDetailsSection(); - }); - it('job creation inputs the job id', async () => { + await ml.testExecution.logTestStep('job creation inputs the job id'); await ml.jobWizardCommon.assertJobIdInputExists(); await ml.jobWizardCommon.setJobId(jobId); - }); - it('job creation inputs the job description', async () => { + await ml.testExecution.logTestStep('job creation inputs the job description'); await ml.jobWizardCommon.assertJobDescriptionInputExists(); await ml.jobWizardCommon.setJobDescription(jobDescription); - }); - it('job creation inputs job groups', async () => { + await ml.testExecution.logTestStep('job creation inputs job groups'); await ml.jobWizardCommon.assertJobGroupInputExists(); for (const jobGroup of jobGroups) { await ml.jobWizardCommon.addJobGroup(jobGroup); } await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); - }); - it('job creation opens the additional settings section', async () => { + await ml.testExecution.logTestStep('job creation opens the additional settings section'); await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); - }); - it('job creation adds a new custom url', async () => { + await ml.testExecution.logTestStep('job creation adds a new custom url'); await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); - }); - it('job creation assigns calendars', async () => { + await ml.testExecution.logTestStep('job creation assigns calendars'); await ml.jobWizardCommon.addCalendar(calendarId); - }); - it('job creation opens the advanced section', async () => { + await ml.testExecution.logTestStep('job creation opens the advanced section'); await ml.jobWizardCommon.ensureAdvancedSectionOpen(); - }); - it('job creation displays the model plot switch', async () => { + await ml.testExecution.logTestStep('job creation displays the model plot switch'); await ml.jobWizardCommon.assertModelPlotSwitchExists(); - }); - it('job creation enables the dedicated index switch', async () => { + await ml.testExecution.logTestStep('job creation enables the dedicated index switch'); await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); await ml.jobWizardCommon.activateDedicatedIndexSwitch(); - }); - it('job creation inputs the model memory limit', async () => { + await ml.testExecution.logTestStep('job creation inputs the model memory limit'); await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit); - }); - it('job creation displays the validation step', async () => { + await ml.testExecution.logTestStep('job creation displays the validation step'); await ml.jobWizardCommon.advanceToValidationSection(); - }); - it('job creation displays the summary step', async () => { + await ml.testExecution.logTestStep('job creation displays the summary step'); await ml.jobWizardCommon.advanceToSummarySection(); }); - it('job creation creates the job and finishes processing', async () => { + it('job creation runs the job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep('job creation creates the job and finishes processing'); await ml.jobWizardCommon.assertCreateJobButtonExists(); await ml.jobWizardCommon.createJobAndWaitForCompletion(); - }); - it('job creation displays the created job in the job list', async () => { + await ml.testExecution.logTestStep('job creation displays the created job in the job list'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); @@ -252,9 +234,10 @@ export default function ({ getService }: FtrProviderContext) { await ml.jobTable.filterWithSearchString(jobId); const rows = await ml.jobTable.parseJobTable(); expect(rows.filter((row) => row.id === jobId)).to.have.length(1); - }); - it('job creation displays details for the created job in the job list', async () => { + await ml.testExecution.logTestStep( + 'job creation displays details for the created job in the job list' + ); await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId, jobGroups)); await ml.jobTable.assertJobRowDetailsCounts( @@ -262,45 +245,45 @@ export default function ({ getService }: FtrProviderContext) { getExpectedCounts(jobId), getExpectedModelSizeStats(jobId) ); - }); - it('job creation has detector results', async () => { + await ml.testExecution.logTestStep('job creation has detector results'); for (let i = 0; i < detectors.length; i++) { await ml.api.assertDetectorResultsExist(jobId, i); } }); - it('job cloning clicks the clone action and loads the population wizard', async () => { + it('job cloning opens the existing job in the population wizard', async () => { + await ml.testExecution.logTestStep( + 'job cloning clicks the clone action and loads the population wizard' + ); await ml.jobTable.clickCloneJobAction(jobId); await ml.jobTypeSelection.assertPopulationJobWizardOpen(); }); - it('job cloning displays the time range step', async () => { + it('job cloning navigates through the population wizard, checks and sets all needed fields', async () => { + await ml.testExecution.logTestStep('job cloning displays the time range step'); await ml.jobWizardCommon.assertTimeRangeSectionExists(); - }); - it('job cloning sets the timerange', async () => { + await ml.testExecution.logTestStep('job cloning sets the timerange'); await ml.jobWizardCommon.clickUseFullDataButton( 'Jun 12, 2019 @ 00:04:19.000', 'Jul 12, 2019 @ 23:45:36.000' ); - }); - it('job cloning displays the event rate chart', async () => { + await ml.testExecution.logTestStep('job cloning displays the event rate chart'); await ml.jobWizardCommon.assertEventRateChartExists(); await ml.jobWizardCommon.assertEventRateChartHasData(); - }); - it('job cloning displays the pick fields step', async () => { + await ml.testExecution.logTestStep('job cloning displays the pick fields step'); await ml.jobWizardCommon.advanceToPickFieldsSection(); - }); - it('job cloning pre-fills the population field', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the population field'); await ml.jobWizardPopulation.assertPopulationFieldInputExists(); await ml.jobWizardPopulation.assertPopulationFieldSelection([populationField]); - }); - it('job cloning pre-fills detectors and shows preview with split cards', async () => { + await ml.testExecution.logTestStep( + 'job cloning pre-fills detectors and shows preview with split cards' + ); for (const [index, detector] of detectors.entries()) { await ml.jobWizardCommon.assertDetectorPreviewExists(detector.identifier, index, 'SCATTER'); @@ -317,96 +300,79 @@ export default function ({ getService }: FtrProviderContext) { detector.numberOfBackCards ); } - }); - it('job cloning pre-fills influencers', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills influencers'); await ml.jobWizardCommon.assertInfluencerInputExists(); await ml.jobWizardCommon.assertInfluencerSelection( [populationField].concat(detectors.map((detector) => detector.splitField)) ); - }); - it('job cloning pre-fills the bucket span', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the bucket span'); await ml.jobWizardCommon.assertBucketSpanInputExists(); await ml.jobWizardCommon.assertBucketSpanValue(bucketSpan); - }); - it('job cloning displays the job details step', async () => { + await ml.testExecution.logTestStep('job cloning displays the job details step'); await ml.jobWizardCommon.advanceToJobDetailsSection(); - }); - it('job cloning does not pre-fill the job id', async () => { + await ml.testExecution.logTestStep('job cloning does not pre-fill the job id'); await ml.jobWizardCommon.assertJobIdInputExists(); await ml.jobWizardCommon.assertJobIdValue(''); - }); - it('job cloning inputs the clone job id', async () => { + await ml.testExecution.logTestStep('job cloning inputs the clone job id'); await ml.jobWizardCommon.setJobId(jobIdClone); - }); - it('job cloning pre-fills the job description', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the job description'); await ml.jobWizardCommon.assertJobDescriptionInputExists(); await ml.jobWizardCommon.assertJobDescriptionValue(jobDescription); - }); - it('job cloning pre-fills job groups', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills job groups'); await ml.jobWizardCommon.assertJobGroupInputExists(); await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); - }); - it('job cloning inputs the clone job group', async () => { + await ml.testExecution.logTestStep('job cloning inputs the clone job group'); await ml.jobWizardCommon.assertJobGroupInputExists(); await ml.jobWizardCommon.addJobGroup('clone'); await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); - }); - it('job cloning opens the additional settings section', async () => { + await ml.testExecution.logTestStep('job cloning opens the additional settings section'); await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); - }); - it('job cloning persists custom urls', async () => { + await ml.testExecution.logTestStep('job cloning persists custom urls'); await ml.customUrls.assertCustomUrlItem(0, 'check-kibana-dashboard'); - }); - it('job cloning persists assigned calendars', async () => { + await ml.testExecution.logTestStep('job cloning persists assigned calendars'); await ml.jobWizardCommon.assertCalendarsSelection([calendarId]); - }); - it('job cloning opens the advanced section', async () => { + await ml.testExecution.logTestStep('job cloning opens the advanced section'); await ml.jobWizardCommon.ensureAdvancedSectionOpen(); - }); - it('job cloning pre-fills the model plot switch', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the model plot switch'); await ml.jobWizardCommon.assertModelPlotSwitchExists(); await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(false); - }); - it('job cloning pre-fills the dedicated index switch', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the dedicated index switch'); await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true); - }); - // MML during clone has changed in #61589 - // TODO: adjust test code to reflect the new behavior - it.skip('job cloning pre-fills the model memory limit', async () => { - await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); - await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit); - }); + // MML during clone has changed in #61589 + // TODO: adjust test code to reflect the new behavior + // await ml.testExecution.logTestStep('job cloning pre-fills the model memory limit'); + // await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + // await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit); - it('job cloning displays the validation step', async () => { + await ml.testExecution.logTestStep('job cloning displays the validation step'); await ml.jobWizardCommon.advanceToValidationSection(); - }); - it('job cloning displays the summary step', async () => { + await ml.testExecution.logTestStep('job cloning displays the summary step'); await ml.jobWizardCommon.advanceToSummarySection(); }); - it('job cloning creates the job and finishes processing', async () => { + it('job cloning runs the clone job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep('job cloning creates the job and finishes processing'); await ml.jobWizardCommon.assertCreateJobButtonExists(); await ml.jobWizardCommon.createJobAndWaitForCompletion(); - }); - it('job cloning displays the created job in the job list', async () => { + await ml.testExecution.logTestStep('job cloning displays the created job in the job list'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); @@ -414,9 +380,10 @@ export default function ({ getService }: FtrProviderContext) { await ml.jobTable.filterWithSearchString(jobIdClone); const rows = await ml.jobTable.parseJobTable(); expect(rows.filter((row) => row.id === jobIdClone)).to.have.length(1); - }); - it('job cloning displays details for the created job in the job list', async () => { + await ml.testExecution.logTestStep( + 'job cloning displays details for the created job in the job list' + ); await ml.jobTable.assertJobRowFields(jobIdClone, getExpectedRow(jobIdClone, jobGroupsClone)); await ml.jobTable.assertJobRowDetailsCounts( @@ -424,9 +391,8 @@ export default function ({ getService }: FtrProviderContext) { getExpectedCounts(jobIdClone), getExpectedModelSizeStats(jobIdClone) ); - }); - it('job cloning has detector results', async () => { + await ml.testExecution.logTestStep('job cloning has detector results'); for (let i = 0; i < detectors.length; i++) { await ml.api.assertDetectorResultsExist(jobId, i); } diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/saved_search_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection/saved_search_job.ts index 6f40ec5427b74..170b88efd70f5 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/saved_search_job.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/saved_search_job.ts @@ -286,44 +286,43 @@ export default function ({ getService }: FtrProviderContext) { for (const testData of testDataList) { describe(` ${testData.suiteTitle}`, function () { - it('job creation loads the job management page', async () => { + it('job creation loads the multi metric wizard for the source data', async () => { + await ml.testExecution.logTestStep('job creation loads the job management page'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); - }); - it('job creation loads the new job source selection page', async () => { + await ml.testExecution.logTestStep( + 'job creation loads the new job source selection page' + ); await ml.jobManagement.navigateToNewJobSourceSelection(); - }); - it('job creation loads the job type selection page', async () => { + await ml.testExecution.logTestStep('job creation loads the job type selection page'); await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob(testData.jobSource); - }); - it('job creation loads the multi metric job wizard page', async () => { + await ml.testExecution.logTestStep('job creation loads the multi metric job wizard page'); await ml.jobTypeSelection.selectMultiMetricJob(); }); - it('job creation displays the time range step', async () => { + it('job creation navigates through the multi metric wizard and sets all needed fields', async () => { + await ml.testExecution.logTestStep('job creation displays the time range step'); await ml.jobWizardCommon.assertTimeRangeSectionExists(); - }); - it('job creation sets the timerange', async () => { + await ml.testExecution.logTestStep('job creation sets the timerange'); await ml.jobWizardCommon.clickUseFullDataButton( 'Feb 7, 2016 @ 00:00:00.000', 'Feb 11, 2016 @ 23:59:54.000' ); - }); - it('job creation displays the event rate chart', async () => { + await ml.testExecution.logTestStep('job creation displays the event rate chart'); await ml.jobWizardCommon.assertEventRateChartExists(); await ml.jobWizardCommon.assertEventRateChartHasData(); - }); - it('job creation displays the pick fields step', async () => { + await ml.testExecution.logTestStep('job creation displays the pick fields step'); await ml.jobWizardCommon.advanceToPickFieldsSection(); - }); - it('job creation selects detectors and displays detector previews', async () => { + await ml.testExecution.logTestStep( + 'job creation selects detectors and displays detector previews' + ); for (const [index, aggAndFieldIdentifier] of testData.aggAndFieldIdentifiers.entries()) { await ml.jobWizardCommon.assertAggAndFieldInputExists(); await ml.jobWizardCommon.selectAggAndField(aggAndFieldIdentifier, false); @@ -333,9 +332,10 @@ export default function ({ getService }: FtrProviderContext) { 'LINE' ); } - }); - it('job creation inputs the split field and displays split cards', async () => { + await ml.testExecution.logTestStep( + 'job creation inputs the split field and displays split cards' + ); await ml.jobWizardMultiMetric.assertSplitFieldInputExists(); await ml.jobWizardMultiMetric.selectSplitField(testData.splitField); @@ -348,72 +348,64 @@ export default function ({ getService }: FtrProviderContext) { ); await ml.jobWizardCommon.assertInfluencerSelection([testData.splitField]); - }); - it('job creation displays the influencer field', async () => { + await ml.testExecution.logTestStep('job creation displays the influencer field'); await ml.jobWizardCommon.assertInfluencerInputExists(); await ml.jobWizardCommon.assertInfluencerSelection([testData.splitField]); - }); - it('job creation inputs the bucket span', async () => { + await ml.testExecution.logTestStep('job creation inputs the bucket span'); await ml.jobWizardCommon.assertBucketSpanInputExists(); await ml.jobWizardCommon.setBucketSpan(testData.bucketSpan); - }); - it('job creation displays the job details step', async () => { + await ml.testExecution.logTestStep('job creation displays the job details step'); await ml.jobWizardCommon.advanceToJobDetailsSection(); - }); - it('job creation inputs the job id', async () => { + await ml.testExecution.logTestStep('job creation inputs the job id'); await ml.jobWizardCommon.assertJobIdInputExists(); await ml.jobWizardCommon.setJobId(testData.jobId); - }); - it('job creation inputs the job description', async () => { + await ml.testExecution.logTestStep('job creation inputs the job description'); await ml.jobWizardCommon.assertJobDescriptionInputExists(); await ml.jobWizardCommon.setJobDescription(testData.jobDescription); - }); - it('job creation inputs job groups', async () => { + await ml.testExecution.logTestStep('job creation inputs job groups'); await ml.jobWizardCommon.assertJobGroupInputExists(); for (const jobGroup of testData.jobGroups) { await ml.jobWizardCommon.addJobGroup(jobGroup); } await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroups); - }); - it('job creation opens the advanced section', async () => { + await ml.testExecution.logTestStep('job creation opens the advanced section'); await ml.jobWizardCommon.ensureAdvancedSectionOpen(); - }); - it('job creation displays the model plot switch', async () => { + await ml.testExecution.logTestStep('job creation displays the model plot switch'); await ml.jobWizardCommon.assertModelPlotSwitchExists(); - }); - it('job creation enables the dedicated index switch', async () => { + await ml.testExecution.logTestStep('job creation enables the dedicated index switch'); await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); await ml.jobWizardCommon.activateDedicatedIndexSwitch(); - }); - it('job creation inputs the model memory limit', async () => { + await ml.testExecution.logTestStep('job creation inputs the model memory limit'); await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); await ml.jobWizardCommon.setModelMemoryLimit(testData.memoryLimit); - }); - it('job creation displays the validation step', async () => { + await ml.testExecution.logTestStep('job creation displays the validation step'); await ml.jobWizardCommon.advanceToValidationSection(); - }); - it('job creation displays the summary step', async () => { + await ml.testExecution.logTestStep('job creation displays the summary step'); await ml.jobWizardCommon.advanceToSummarySection(); }); - it('job creation creates the job and finishes processing', async () => { + it('job creation runs the job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep( + 'job creation creates the job and finishes processing' + ); await ml.jobWizardCommon.assertCreateJobButtonExists(); await ml.jobWizardCommon.createJobAndWaitForCompletion(); - }); - it('job creation displays the created job in the job list', async () => { + await ml.testExecution.logTestStep( + 'job creation displays the created job in the job list' + ); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); @@ -421,9 +413,10 @@ export default function ({ getService }: FtrProviderContext) { await ml.jobTable.filterWithSearchString(testData.jobId); const rows = await ml.jobTable.parseJobTable(); expect(rows.filter((row) => row.id === testData.jobId)).to.have.length(1); - }); - it('job creation displays details for the created job in the job list', async () => { + await ml.testExecution.logTestStep( + 'job creation displays details for the created job in the job list' + ); await ml.jobTable.assertJobRowFields(testData.jobId, { id: testData.jobId, description: testData.jobDescription, @@ -442,9 +435,8 @@ export default function ({ getService }: FtrProviderContext) { ...testData.expected.modelSizeStats, } ); - }); - it('has detector results', async () => { + await ml.testExecution.logTestStep('has detector results'); for (let i = 0; i < testData.aggAndFieldIdentifiers.length; i++) { await ml.api.assertDetectorResultsExist(testData.jobId, i); } diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_job.ts index 58f3960153bc6..ba5628661bfc2 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_job.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_job.ts @@ -85,120 +85,101 @@ export default function ({ getService }: FtrProviderContext) { await ml.api.cleanMlIndices(); }); - it('job creation loads the job management page', async () => { + it('job creation loads the single metric wizard for the source data', async () => { + await ml.testExecution.logTestStep('job creation loads the job management page'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); - }); - it('job creation loads the new job source selection page', async () => { + await ml.testExecution.logTestStep('job creation loads the new job source selection page'); await ml.jobManagement.navigateToNewJobSourceSelection(); - }); - it('job creation loads the job type selection page', async () => { + await ml.testExecution.logTestStep('job creation loads the job type selection page'); await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob('ft_farequote'); - }); - it('job creation loads the single metric job wizard page', async () => { + await ml.testExecution.logTestStep('job creation loads the single metric job wizard page'); await ml.jobTypeSelection.selectSingleMetricJob(); }); - it('job creation displays the time range step', async () => { + it('job creation navigates through the single metric wizard and sets all needed fields', async () => { + await ml.testExecution.logTestStep('job creation displays the time range step'); await ml.jobWizardCommon.assertTimeRangeSectionExists(); - }); - it('job creation sets the timerange', async () => { + await ml.testExecution.logTestStep('job creation sets the timerange'); await ml.jobWizardCommon.clickUseFullDataButton( 'Feb 7, 2016 @ 00:00:00.000', 'Feb 11, 2016 @ 23:59:54.000' ); - }); - it('job creation displays the event rate chart', async () => { + await ml.testExecution.logTestStep('job creation displays the event rate chart'); await ml.jobWizardCommon.assertEventRateChartExists(); await ml.jobWizardCommon.assertEventRateChartHasData(); - }); - it('job creation displays the pick fields step', async () => { + await ml.testExecution.logTestStep('job creation displays the pick fields step'); await ml.jobWizardCommon.advanceToPickFieldsSection(); - }); - it('job creation selects field and aggregation', async () => { + await ml.testExecution.logTestStep('job creation selects field and aggregation'); await ml.jobWizardCommon.assertAggAndFieldInputExists(); await ml.jobWizardCommon.selectAggAndField(aggAndFieldIdentifier, true); await ml.jobWizardCommon.assertAnomalyChartExists('LINE'); - }); - it('job creation inputs the bucket span', async () => { + await ml.testExecution.logTestStep('job creation inputs the bucket span'); await ml.jobWizardCommon.assertBucketSpanInputExists(); await ml.jobWizardCommon.setBucketSpan(bucketSpan); - }); - it('job creation displays the job details step', async () => { + await ml.testExecution.logTestStep('job creation displays the job details step'); await ml.jobWizardCommon.advanceToJobDetailsSection(); - }); - it('job creation inputs the job id', async () => { + await ml.testExecution.logTestStep('job creation inputs the job id'); await ml.jobWizardCommon.assertJobIdInputExists(); await ml.jobWizardCommon.setJobId(jobId); - }); - it('job creation inputs the job description', async () => { + await ml.testExecution.logTestStep('job creation inputs the job description'); await ml.jobWizardCommon.assertJobDescriptionInputExists(); await ml.jobWizardCommon.setJobDescription(jobDescription); - }); - it('job creation inputs job groups', async () => { + await ml.testExecution.logTestStep('job creation inputs job groups'); await ml.jobWizardCommon.assertJobGroupInputExists(); for (const jobGroup of jobGroups) { await ml.jobWizardCommon.addJobGroup(jobGroup); } await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); - }); - it('job creation opens the additional settings section', async () => { + await ml.testExecution.logTestStep('job creation opens the additional settings section'); await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); - }); - it('job creation adds a new custom url', async () => { + await ml.testExecution.logTestStep('job creation adds a new custom url'); await ml.jobWizardCommon.addCustomUrl({ label: 'check-kibana-dashboard' }); - }); - it('job creation assigns calendars', async () => { + await ml.testExecution.logTestStep('job creation assigns calendars'); await ml.jobWizardCommon.addCalendar(calendarId); - }); - it('job creation opens the advanced section', async () => { + await ml.testExecution.logTestStep('job creation opens the advanced section'); await ml.jobWizardCommon.ensureAdvancedSectionOpen(); - }); - it('job creation displays the model plot switch', async () => { + await ml.testExecution.logTestStep('job creation displays the model plot switch'); await ml.jobWizardCommon.assertModelPlotSwitchExists(); - }); - it('job creation enables the dedicated index switch', async () => { + await ml.testExecution.logTestStep('job creation enables the dedicated index switch'); await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); await ml.jobWizardCommon.activateDedicatedIndexSwitch(); - }); - it('job creation inputs the model memory limit', async () => { + await ml.testExecution.logTestStep('job creation inputs the model memory limit'); await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit); - }); - it('job creation displays the validation step', async () => { + await ml.testExecution.logTestStep('job creation displays the validation step'); await ml.jobWizardCommon.advanceToValidationSection(); - }); - it('job creation displays the summary step', async () => { + await ml.testExecution.logTestStep('job creation displays the summary step'); await ml.jobWizardCommon.advanceToSummarySection(); }); - it('job creation creates the job and finishes processing', async () => { + it('job creation runs the job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep('job creation creates the job and finishes processing'); await ml.jobWizardCommon.assertCreateJobButtonExists(); await ml.jobWizardCommon.createJobAndWaitForCompletion(); - }); - it('job creation displays the created job in the job list', async () => { + await ml.testExecution.logTestStep('job creation displays the created job in the job list'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); @@ -206,9 +187,10 @@ export default function ({ getService }: FtrProviderContext) { await ml.jobTable.filterWithSearchString(jobId); const rows = await ml.jobTable.parseJobTable(); expect(rows.filter((row) => row.id === jobId)).to.have.length(1); - }); - it('job creation displays details for the created job in the job list', async () => { + await ml.testExecution.logTestStep( + 'job creation displays details for the created job in the job list' + ); await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId, jobGroups)); await ml.jobTable.assertJobRowDetailsCounts( @@ -216,124 +198,107 @@ export default function ({ getService }: FtrProviderContext) { getExpectedCounts(jobId), getExpectedModelSizeStats(jobId) ); - }); - it('job creation has detector results', async () => { + await ml.testExecution.logTestStep('job creation has detector results'); await ml.api.assertDetectorResultsExist(jobId, 0); }); - it('job cloning clicks the clone action and loads the single metric wizard', async () => { + it('job cloning opens the existing job in the single metric wizard', async () => { + await ml.testExecution.logTestStep( + 'job cloning clicks the clone action and loads the single metric wizard' + ); await ml.jobTable.clickCloneJobAction(jobId); await ml.jobTypeSelection.assertSingleMetricJobWizardOpen(); }); - it('job cloning displays the time range step', async () => { + it('job cloning navigates through the single metric wizard, checks and sets all needed fields', async () => { + await ml.testExecution.logTestStep('job cloning displays the time range step'); await ml.jobWizardCommon.assertTimeRangeSectionExists(); - }); - it('job cloning sets the timerange', async () => { + await ml.testExecution.logTestStep('job cloning sets the timerange'); await ml.jobWizardCommon.clickUseFullDataButton( 'Feb 7, 2016 @ 00:00:00.000', 'Feb 11, 2016 @ 23:59:54.000' ); - }); - it('job cloning displays the event rate chart', async () => { + await ml.testExecution.logTestStep('job cloning displays the event rate chart'); await ml.jobWizardCommon.assertEventRateChartExists(); await ml.jobWizardCommon.assertEventRateChartHasData(); - }); - it('job cloning displays the pick fields step', async () => { + await ml.testExecution.logTestStep('job cloning displays the pick fields step'); await ml.jobWizardCommon.advanceToPickFieldsSection(); - }); - it('job cloning pre-fills field and aggregation', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills field and aggregation'); await ml.jobWizardCommon.assertAggAndFieldInputExists(); await ml.jobWizardCommon.assertAggAndFieldSelection([aggAndFieldIdentifier]); await ml.jobWizardCommon.assertAnomalyChartExists('LINE'); - }); - it('job cloning pre-fills the bucket span', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the bucket span'); await ml.jobWizardCommon.assertBucketSpanInputExists(); await ml.jobWizardCommon.assertBucketSpanValue(bucketSpan); - }); - it('job cloning displays the job details step', async () => { + await ml.testExecution.logTestStep('job cloning displays the job details step'); await ml.jobWizardCommon.advanceToJobDetailsSection(); - }); - it('job cloning does not pre-fill the job id', async () => { + await ml.testExecution.logTestStep('job cloning does not pre-fill the job id'); await ml.jobWizardCommon.assertJobIdInputExists(); await ml.jobWizardCommon.assertJobIdValue(''); - }); - it('job cloning inputs the clone job id', async () => { + await ml.testExecution.logTestStep('job cloning inputs the clone job id'); await ml.jobWizardCommon.setJobId(jobIdClone); - }); - it('job cloning pre-fills the job description', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the job description'); await ml.jobWizardCommon.assertJobDescriptionInputExists(); await ml.jobWizardCommon.assertJobDescriptionValue(jobDescription); - }); - it('job cloning pre-fills job groups', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills job groups'); await ml.jobWizardCommon.assertJobGroupInputExists(); await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); - }); - it('job cloning inputs the clone job group', async () => { + await ml.testExecution.logTestStep('job cloning inputs the clone job group'); await ml.jobWizardCommon.assertJobGroupInputExists(); await ml.jobWizardCommon.addJobGroup('clone'); await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); - }); - it('job cloning opens the additional settings section', async () => { + await ml.testExecution.logTestStep('job cloning opens the additional settings section'); await ml.jobWizardCommon.ensureAdditionalSettingsSectionOpen(); - }); - it('job cloning persists custom urls', async () => { + await ml.testExecution.logTestStep('job cloning persists custom urls'); await ml.customUrls.assertCustomUrlItem(0, 'check-kibana-dashboard'); - }); - it('job cloning persists assigned calendars', async () => { + await ml.testExecution.logTestStep('job cloning persists assigned calendars'); await ml.jobWizardCommon.assertCalendarsSelection([calendarId]); - }); - it('job cloning opens the advanced section', async () => { + await ml.testExecution.logTestStep('job cloning opens the advanced section'); await ml.jobWizardCommon.ensureAdvancedSectionOpen(); - }); - it('job cloning pre-fills the model plot switch', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the model plot switch'); await ml.jobWizardCommon.assertModelPlotSwitchExists(); await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(true); - }); - it('job cloning pre-fills the dedicated index switch', async () => { + await ml.testExecution.logTestStep('job cloning pre-fills the dedicated index switch'); await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true); - }); - // MML during clone has changed in #61589 - // TODO: adjust test code to reflect the new behavior - it.skip('job cloning pre-fills the model memory limit', async () => { - await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); - await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit); - }); + // MML during clone has changed in #61589 + // TODO: adjust test code to reflect the new behavior + // await ml.testExecution.logTestStep('job cloning pre-fills the model memory limit'); + // await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + // await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit); - it('job cloning displays the validation step', async () => { + await ml.testExecution.logTestStep('job cloning displays the validation step'); await ml.jobWizardCommon.advanceToValidationSection(); - }); - it('job cloning displays the summary step', async () => { + await ml.testExecution.logTestStep('job cloning displays the summary step'); await ml.jobWizardCommon.advanceToSummarySection(); }); - it('job cloning creates the job and finishes processing', async () => { + it('job cloning runs the clone job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep('job cloning creates the job and finishes processing'); await ml.jobWizardCommon.assertCreateJobButtonExists(); await ml.jobWizardCommon.createJobAndWaitForCompletion(); - }); - it('job cloning displays the created job in the job list', async () => { + await ml.testExecution.logTestStep('job cloning displays the created job in the job list'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); @@ -341,9 +306,10 @@ export default function ({ getService }: FtrProviderContext) { await ml.jobTable.filterWithSearchString(jobIdClone); const rows = await ml.jobTable.parseJobTable(); expect(rows.filter((row) => row.id === jobIdClone)).to.have.length(1); - }); - it('job cloning displays details for the created job in the job list', async () => { + await ml.testExecution.logTestStep( + 'job cloning displays details for the created job in the job list' + ); await ml.jobTable.assertJobRowFields(jobIdClone, getExpectedRow(jobIdClone, jobGroupsClone)); await ml.jobTable.assertJobRowDetailsCounts( @@ -351,32 +317,32 @@ export default function ({ getService }: FtrProviderContext) { getExpectedCounts(jobIdClone), getExpectedModelSizeStats(jobIdClone) ); - }); - it('job cloning has detector results', async () => { + await ml.testExecution.logTestStep('job cloning has detector results'); await ml.api.assertDetectorResultsExist(jobId, 0); }); - it('job deletion has results for the job before deletion', async () => { + it('deletes the cloned job', async () => { + await ml.testExecution.logTestStep('job deletion has results for the job before deletion'); await ml.api.assertJobResultsExist(jobIdClone); - }); - it('job deletion triggers the delete action', async () => { + await ml.testExecution.logTestStep('job deletion triggers the delete action'); await ml.jobTable.clickDeleteJobAction(jobIdClone); - }); - it('job deletion confirms the delete modal', async () => { + await ml.testExecution.logTestStep('job deletion confirms the delete modal'); await ml.jobTable.confirmDeleteJobModal(); - }); - it('job deletion does not display the deleted job in the job list any more', async () => { + await ml.testExecution.logTestStep( + 'job deletion does not display the deleted job in the job list any more' + ); await ml.jobTable.waitForJobsToLoad(); await ml.jobTable.filterWithSearchString(jobIdClone); const rows = await ml.jobTable.parseJobTable(); expect(rows.filter((row) => row.id === jobIdClone)).to.have.length(0); - }); - it('job deletion does not have results for the deleted job any more', async () => { + await ml.testExecution.logTestStep( + 'job deletion does not have results for the deleted job any more' + ); await ml.api.assertNoJobResultsExist(jobIdClone); }); }); diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_viewer.ts b/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_viewer.ts index 3855bd0c884cd..e1ab3f8e092c3 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_viewer.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_viewer.ts @@ -53,41 +53,39 @@ export default function ({ getService }: FtrProviderContext) { await ml.api.cleanMlIndices(); }); - it('loads from job list row link', async () => { + it('opens a job from job list link', async () => { + await ml.testExecution.logTestStep('navigate to job list'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); + await ml.testExecution.logTestStep('open job in single metric viewer'); await ml.jobTable.waitForJobsToLoad(); await ml.jobTable.filterWithSearchString(JOB_CONFIG.job_id); const rows = await ml.jobTable.parseJobTable(); expect(rows.filter((row) => row.id === JOB_CONFIG.job_id)).to.have.length(1); await ml.jobTable.clickOpenJobInSingleMetricViewerButton(JOB_CONFIG.job_id); - await ml.common.waitForMlLoadingIndicatorToDisappear(); + await ml.commonUI.waitForMlLoadingIndicatorToDisappear(); }); - it('pre-fills the job selection', async () => { + it('displays job results', async () => { + await ml.testExecution.logTestStep('pre-fills the job selection'); await ml.jobSelection.assertJobSelection([JOB_CONFIG.job_id]); - }); - it('pre-fills the detector input', async () => { + await ml.testExecution.logTestStep('pre-fills the detector input'); await ml.singleMetricViewer.assertDetectorInputExsist(); await ml.singleMetricViewer.assertDetectorInputValue('0'); - }); - it('displays the chart', async () => { + await ml.testExecution.logTestStep('displays the chart'); await ml.singleMetricViewer.assertChartExsist(); - }); - it('should display the annotations section', async () => { + await ml.testExecution.logTestStep('should display the annotations section'); await ml.singleMetricViewer.assertAnnotationsExists('loaded'); - }); - it('displays the anomalies table', async () => { + await ml.testExecution.logTestStep('displays the anomalies table'); await ml.anomaliesTable.assertTableExists(); - }); - it('anomalies table is not empty', async () => { + await ml.testExecution.logTestStep('anomalies table is not empty'); await ml.anomaliesTable.assertTableNotEmpty(); }); }); diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts index a62bfdcde0572..6beefaafa3792 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts @@ -56,108 +56,96 @@ export default function ({ getService }: FtrProviderContext) { await ml.testResources.deleteIndexPatternByTitle(testData.destinationIndex); }); - it('loads the data frame analytics page', async () => { + it('loads the data frame analytics wizard', async () => { + await ml.testExecution.logTestStep('loads the data frame analytics page'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToDataFrameAnalytics(); - }); - it('loads the source selection modal', async () => { + await ml.testExecution.logTestStep('loads the source selection modal'); await ml.dataFrameAnalytics.startAnalyticsCreation(); - }); - it('selects the source data and loads the job wizard page', async () => { + await ml.testExecution.logTestStep( + 'selects the source data and loads the job wizard page' + ); await ml.jobSourceSelection.selectSourceForAnalyticsJob(testData.source); await ml.dataFrameAnalyticsCreation.assertConfigurationStepActive(); }); - it('selects the job type', async () => { + it('navigates through the wizard and sets all needed fields', async () => { + await ml.testExecution.logTestStep('selects the job type'); await ml.dataFrameAnalyticsCreation.assertJobTypeSelectExists(); await ml.dataFrameAnalyticsCreation.selectJobType(testData.jobType); - }); - it('inputs the dependent variable', async () => { + await ml.testExecution.logTestStep('inputs the dependent variable'); await ml.dataFrameAnalyticsCreation.assertDependentVariableInputExists(); await ml.dataFrameAnalyticsCreation.selectDependentVariable(testData.dependentVariable); - }); - it('inputs the training percent', async () => { + await ml.testExecution.logTestStep('inputs the training percent'); await ml.dataFrameAnalyticsCreation.assertTrainingPercentInputExists(); await ml.dataFrameAnalyticsCreation.setTrainingPercent(testData.trainingPercent); - }); - it('displays the source data preview', async () => { + await ml.testExecution.logTestStep('displays the source data preview'); await ml.dataFrameAnalyticsCreation.assertSourceDataPreviewExists(); - }); - it('displays the include fields selection', async () => { + await ml.testExecution.logTestStep('displays the include fields selection'); await ml.dataFrameAnalyticsCreation.assertIncludeFieldsSelectionExists(); - }); - it('continues to the additional options step', async () => { + await ml.testExecution.logTestStep('continues to the additional options step'); await ml.dataFrameAnalyticsCreation.continueToAdditionalOptionsStep(); - }); - it('accepts the suggested model memory limit', async () => { + await ml.testExecution.logTestStep('accepts the suggested model memory limit'); await ml.dataFrameAnalyticsCreation.assertModelMemoryInputExists(); await ml.dataFrameAnalyticsCreation.assertModelMemoryInputPopulated(); - }); - it('continues to the details step', async () => { + await ml.testExecution.logTestStep('continues to the details step'); await ml.dataFrameAnalyticsCreation.continueToDetailsStep(); - }); - it('inputs the job id', async () => { + await ml.testExecution.logTestStep('inputs the job id'); await ml.dataFrameAnalyticsCreation.assertJobIdInputExists(); await ml.dataFrameAnalyticsCreation.setJobId(testData.jobId); - }); - it('inputs the job description', async () => { + await ml.testExecution.logTestStep('inputs the job description'); await ml.dataFrameAnalyticsCreation.assertJobDescriptionInputExists(); await ml.dataFrameAnalyticsCreation.setJobDescription(testData.jobDescription); - }); - it('should default the set destination index to job id switch to true', async () => { + await ml.testExecution.logTestStep( + 'should default the set destination index to job id switch to true' + ); await ml.dataFrameAnalyticsCreation.assertDestIndexSameAsIdSwitchExists(); await ml.dataFrameAnalyticsCreation.assertDestIndexSameAsIdCheckState(true); - }); - it('should input the destination index', async () => { + await ml.testExecution.logTestStep('should input the destination index'); await ml.dataFrameAnalyticsCreation.setDestIndexSameAsIdCheckState(false); await ml.dataFrameAnalyticsCreation.assertDestIndexInputExists(); await ml.dataFrameAnalyticsCreation.setDestIndex(testData.destinationIndex); - }); - it('sets the create index pattern switch', async () => { + await ml.testExecution.logTestStep('sets the create index pattern switch'); await ml.dataFrameAnalyticsCreation.assertCreateIndexPatternSwitchExists(); await ml.dataFrameAnalyticsCreation.setCreateIndexPatternSwitchState( testData.createIndexPattern ); - }); - it('continues to the create step', async () => { + await ml.testExecution.logTestStep('continues to the create step'); await ml.dataFrameAnalyticsCreation.continueToCreateStep(); }); - it('creates and starts the analytics job', async () => { + it('runs the analytics job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep('creates and starts the analytics job'); await ml.dataFrameAnalyticsCreation.assertCreateButtonExists(); await ml.dataFrameAnalyticsCreation.assertStartJobCheckboxCheckState(true); await ml.dataFrameAnalyticsCreation.createAnalyticsJob(testData.jobId); - }); - it('finishes analytics processing', async () => { + await ml.testExecution.logTestStep('finishes analytics processing'); await ml.dataFrameAnalytics.waitForAnalyticsCompletion(testData.jobId); - }); - it('displays the analytics table', async () => { + await ml.testExecution.logTestStep('displays the analytics table'); await ml.dataFrameAnalyticsCreation.navigateToJobManagementPage(); await ml.dataFrameAnalytics.assertAnalyticsTableExists(); - }); - it('displays the stats bar', async () => { + await ml.testExecution.logTestStep('displays the stats bar'); await ml.dataFrameAnalytics.assertAnalyticsStatsBarExists(); - }); - it('displays the created job in the analytics table', async () => { + await ml.testExecution.logTestStep('displays the created job in the analytics table'); await ml.dataFrameAnalyticsTable.refreshAnalyticsTable(); await ml.dataFrameAnalyticsTable.filterWithSearchString(testData.jobId); const rows = await ml.dataFrameAnalyticsTable.parseAnalyticsTable(); @@ -166,9 +154,10 @@ export default function ({ getService }: FtrProviderContext) { 1, `Filtered analytics table should have 1 row for job id '${testData.jobId}' (got matching items '${filteredRows}')` ); - }); - it('displays details for the created job in the analytics table', async () => { + await ml.testExecution.logTestStep( + 'displays details for the created job in the analytics table' + ); await ml.dataFrameAnalyticsTable.assertAnalyticsRowFields(testData.jobId, { id: testData.jobId, description: testData.jobDescription, @@ -180,25 +169,28 @@ export default function ({ getService }: FtrProviderContext) { }); }); - it('should open the edit form for the created job in the analytics table', async () => { + it('edits the analytics job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep( + 'should open the edit form for the created job in the analytics table' + ); await ml.dataFrameAnalyticsTable.openEditFlyout(testData.jobId); - }); - it('should input the description in the edit form', async () => { + await ml.testExecution.logTestStep('should input the description in the edit form'); await ml.dataFrameAnalyticsEdit.assertJobDescriptionEditInputExists(); await ml.dataFrameAnalyticsEdit.setJobDescriptionEdit(editedDescription); - }); - it('should input the model memory limit in the edit form', async () => { + await ml.testExecution.logTestStep( + 'should input the model memory limit in the edit form' + ); await ml.dataFrameAnalyticsEdit.assertJobMmlEditInputExists(); await ml.dataFrameAnalyticsEdit.setJobMmlEdit('21mb'); - }); - it('should submit the edit job form', async () => { + await ml.testExecution.logTestStep('should submit the edit job form'); await ml.dataFrameAnalyticsEdit.updateAnalyticsJob(); - }); - it('displays details for the edited job in the analytics table', async () => { + await ml.testExecution.logTestStep( + 'displays details for the edited job in the analytics table' + ); await ml.dataFrameAnalyticsTable.assertAnalyticsRowFields(testData.jobId, { id: testData.jobId, description: editedDescription, @@ -208,14 +200,14 @@ export default function ({ getService }: FtrProviderContext) { status: testData.expected.row.status, progress: testData.expected.row.progress, }); - }); - it('creates the destination index and writes results to it', async () => { + await ml.testExecution.logTestStep( + 'creates the destination index and writes results to it' + ); await ml.api.assertIndicesExist(testData.destinationIndex); await ml.api.assertIndicesNotEmpty(testData.destinationIndex); - }); - it('displays the results view for created job', async () => { + await ml.testExecution.logTestStep('displays the results view for created job'); await ml.dataFrameAnalyticsTable.openResultsView(); await ml.dataFrameAnalytics.assertClassificationEvaluatePanelElementsExists(); await ml.dataFrameAnalytics.assertClassificationTablePanelExists(); diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts index e8f0a69b397cd..5494f2f963d37 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts @@ -159,57 +159,62 @@ export default function ({ getService }: FtrProviderContext) { await ml.testResources.deleteIndexPatternByTitle(cloneDestIndex); }); - it('should open the wizard with a proper header', async () => { + it('opens the existing job in the data frame analytics job wizard', async () => { + await ml.testExecution.logTestStep('should open the wizard with a proper header'); const headerText = await ml.dataFrameAnalyticsCreation.getHeaderText(); expect(headerText).to.match(/Clone job/); await ml.dataFrameAnalyticsCreation.assertConfigurationStepActive(); }); - it('should have correct init form values for config step', async () => { + it('navigates through the wizard, checks and sets all needed fields', async () => { + await ml.testExecution.logTestStep( + 'should have correct init form values for config step' + ); await ml.dataFrameAnalyticsCreation.assertInitialCloneJobConfigStep( testData.job as DataFrameAnalyticsConfig ); - }); - it('should continue to the additional options step', async () => { + await ml.testExecution.logTestStep('should continue to the additional options step'); await ml.dataFrameAnalyticsCreation.continueToAdditionalOptionsStep(); - }); - it('should have correct init form values for additional options step', async () => { + await ml.testExecution.logTestStep( + 'should have correct init form values for additional options step' + ); await ml.dataFrameAnalyticsCreation.assertInitialCloneJobAdditionalOptionsStep( testData.job.analysis as DataFrameAnalyticsConfig['analysis'] ); - }); - it('should continue to the details step', async () => { + await ml.testExecution.logTestStep('should continue to the details step'); await ml.dataFrameAnalyticsCreation.continueToDetailsStep(); - }); - it('should have correct init form values for details step', async () => { + await ml.testExecution.logTestStep( + 'should have correct init form values for details step' + ); await ml.dataFrameAnalyticsCreation.assertInitialCloneJobDetailsStep( testData.job as DataFrameAnalyticsConfig ); await ml.dataFrameAnalyticsCreation.setJobId(cloneJobId); await ml.dataFrameAnalyticsCreation.setDestIndex(cloneDestIndex); - }); - it('should continue to the create step', async () => { + await ml.testExecution.logTestStep('should continue to the create step'); await ml.dataFrameAnalyticsCreation.continueToCreateStep(); }); - it('should have enabled Create button on a valid form input', async () => { + it('runs the clone analytics job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep( + 'should have enabled Create button on a valid form input' + ); expect(await ml.dataFrameAnalyticsCreation.isCreateButtonDisabled()).to.be(false); - }); - it('should create a clone job', async () => { + await ml.testExecution.logTestStep('should create a clone job'); await ml.dataFrameAnalyticsCreation.createAnalyticsJob(cloneJobId); - }); - it('should finish analytics processing', async () => { + await ml.testExecution.logTestStep('should finish analytics processing'); await ml.dataFrameAnalytics.waitForAnalyticsCompletion(cloneJobId); - }); - it('should display the created job in the analytics table', async () => { + await ml.testExecution.logTestStep( + 'should display the created job in the analytics table' + ); await ml.dataFrameAnalyticsCreation.navigateToJobManagementPage(); await ml.dataFrameAnalyticsTable.refreshAnalyticsTable(); await ml.dataFrameAnalyticsTable.filterWithSearchString(cloneJobId); diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts index 5b89cec49db3e..e4bc7b940aaea 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts @@ -66,116 +66,102 @@ export default function ({ getService }: FtrProviderContext) { await ml.testResources.deleteIndexPatternByTitle(testData.destinationIndex); }); - it('loads the data frame analytics page', async () => { + it('loads the data frame analytics wizard', async () => { + await ml.testExecution.logTestStep('loads the data frame analytics page'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToDataFrameAnalytics(); - }); - it('loads the source selection modal', async () => { + await ml.testExecution.logTestStep('loads the source selection modal'); await ml.dataFrameAnalytics.startAnalyticsCreation(); - }); - it('selects the source data and loads the job wizard page', async () => { + await ml.testExecution.logTestStep( + 'selects the source data and loads the job wizard page' + ); await ml.jobSourceSelection.selectSourceForAnalyticsJob(testData.source); await ml.dataFrameAnalyticsCreation.assertConfigurationStepActive(); }); - it('selects the job type', async () => { + it('navigates through the wizard and sets all needed fields', async () => { + await ml.testExecution.logTestStep('selects the job type'); await ml.dataFrameAnalyticsCreation.assertJobTypeSelectExists(); await ml.dataFrameAnalyticsCreation.selectJobType(testData.jobType); - }); - it('does not display the dependent variable input', async () => { + await ml.testExecution.logTestStep('does not display the dependent variable input'); await ml.dataFrameAnalyticsCreation.assertDependentVariableInputMissing(); - }); - it('does not display the training percent input', async () => { + await ml.testExecution.logTestStep('does not display the training percent input'); await ml.dataFrameAnalyticsCreation.assertTrainingPercentInputMissing(); - }); - it('displays the source data preview', async () => { + await ml.testExecution.logTestStep('displays the source data preview'); await ml.dataFrameAnalyticsCreation.assertSourceDataPreviewExists(); - }); - it('enables the source data preview histogram charts', async () => { + await ml.testExecution.logTestStep('enables the source data preview histogram charts'); await ml.dataFrameAnalyticsCreation.enableSourceDataPreviewHistogramCharts(); - }); - it('displays the source data preview histogram charts', async () => { + await ml.testExecution.logTestStep('displays the source data preview histogram charts'); await ml.dataFrameAnalyticsCreation.assertSourceDataPreviewHistogramCharts( testData.expected.histogramCharts ); - }); - it('displays the include fields selection', async () => { + await ml.testExecution.logTestStep('displays the include fields selection'); await ml.dataFrameAnalyticsCreation.assertIncludeFieldsSelectionExists(); - }); - it('continues to the additional options step', async () => { + await ml.testExecution.logTestStep('continues to the additional options step'); await ml.dataFrameAnalyticsCreation.continueToAdditionalOptionsStep(); - }); - it('accepts the suggested model memory limit', async () => { + await ml.testExecution.logTestStep('accepts the suggested model memory limit'); await ml.dataFrameAnalyticsCreation.assertModelMemoryInputExists(); await ml.dataFrameAnalyticsCreation.assertModelMemoryInputPopulated(); - }); - it('continues to the details step', async () => { + await ml.testExecution.logTestStep('continues to the details step'); await ml.dataFrameAnalyticsCreation.continueToDetailsStep(); - }); - it('inputs the job id', async () => { + await ml.testExecution.logTestStep('inputs the job id'); await ml.dataFrameAnalyticsCreation.assertJobIdInputExists(); await ml.dataFrameAnalyticsCreation.setJobId(testData.jobId); - }); - it('inputs the job description', async () => { + await ml.testExecution.logTestStep('inputs the job description'); await ml.dataFrameAnalyticsCreation.assertJobDescriptionInputExists(); await ml.dataFrameAnalyticsCreation.setJobDescription(testData.jobDescription); - }); - it('should default the set destination index to job id switch to true', async () => { + await ml.testExecution.logTestStep( + 'should default the set destination index to job id switch to true' + ); await ml.dataFrameAnalyticsCreation.assertDestIndexSameAsIdSwitchExists(); await ml.dataFrameAnalyticsCreation.assertDestIndexSameAsIdCheckState(true); - }); - it('should input the destination index', async () => { + await ml.testExecution.logTestStep('should input the destination index'); await ml.dataFrameAnalyticsCreation.setDestIndexSameAsIdCheckState(false); await ml.dataFrameAnalyticsCreation.assertDestIndexInputExists(); await ml.dataFrameAnalyticsCreation.setDestIndex(testData.destinationIndex); - }); - it('sets the create index pattern switch', async () => { + await ml.testExecution.logTestStep('sets the create index pattern switch'); await ml.dataFrameAnalyticsCreation.assertCreateIndexPatternSwitchExists(); await ml.dataFrameAnalyticsCreation.setCreateIndexPatternSwitchState( testData.createIndexPattern ); - }); - it('continues to the create step', async () => { + await ml.testExecution.logTestStep('continues to the create step'); await ml.dataFrameAnalyticsCreation.continueToCreateStep(); }); - it('creates and starts the analytics job', async () => { + it('runs the analytics job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep('creates and starts the analytics job'); await ml.dataFrameAnalyticsCreation.assertCreateButtonExists(); await ml.dataFrameAnalyticsCreation.assertStartJobCheckboxCheckState(true); await ml.dataFrameAnalyticsCreation.createAnalyticsJob(testData.jobId); - }); - it('finishes analytics processing', async () => { + await ml.testExecution.logTestStep('finishes analytics processing'); await ml.dataFrameAnalytics.waitForAnalyticsCompletion(testData.jobId); - }); - it('displays the analytics table', async () => { + await ml.testExecution.logTestStep('displays the analytics table'); await ml.dataFrameAnalyticsCreation.navigateToJobManagementPage(); await ml.dataFrameAnalytics.assertAnalyticsTableExists(); - }); - it('displays the stats bar', async () => { + await ml.testExecution.logTestStep('displays the stats bar'); await ml.dataFrameAnalytics.assertAnalyticsStatsBarExists(); - }); - it('displays the created job in the analytics table', async () => { + await ml.testExecution.logTestStep('displays the created job in the analytics table'); await ml.dataFrameAnalyticsTable.refreshAnalyticsTable(); await ml.dataFrameAnalyticsTable.filterWithSearchString(testData.jobId); const rows = await ml.dataFrameAnalyticsTable.parseAnalyticsTable(); @@ -184,9 +170,10 @@ export default function ({ getService }: FtrProviderContext) { 1, `Filtered analytics table should have 1 row for job id '${testData.jobId}' (got matching items '${filteredRows}')` ); - }); - it('displays details for the created job in the analytics table', async () => { + await ml.testExecution.logTestStep( + 'displays details for the created job in the analytics table' + ); await ml.dataFrameAnalyticsTable.assertAnalyticsRowFields(testData.jobId, { id: testData.jobId, description: testData.jobDescription, @@ -198,25 +185,28 @@ export default function ({ getService }: FtrProviderContext) { }); }); - it('should open the edit form for the created job in the analytics table', async () => { + it('edits the analytics job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep( + 'should open the edit form for the created job in the analytics table' + ); await ml.dataFrameAnalyticsTable.openEditFlyout(testData.jobId); - }); - it('should input the description in the edit form', async () => { + await ml.testExecution.logTestStep('should input the description in the edit form'); await ml.dataFrameAnalyticsEdit.assertJobDescriptionEditInputExists(); await ml.dataFrameAnalyticsEdit.setJobDescriptionEdit(editedDescription); - }); - it('should input the model memory limit in the edit form', async () => { + await ml.testExecution.logTestStep( + 'should input the model memory limit in the edit form' + ); await ml.dataFrameAnalyticsEdit.assertJobMmlEditInputExists(); await ml.dataFrameAnalyticsEdit.setJobMmlEdit('21mb'); - }); - it('should submit the edit job form', async () => { + await ml.testExecution.logTestStep('should submit the edit job form'); await ml.dataFrameAnalyticsEdit.updateAnalyticsJob(); - }); - it('displays details for the edited job in the analytics table', async () => { + await ml.testExecution.logTestStep( + 'displays details for the edited job in the analytics table' + ); await ml.dataFrameAnalyticsTable.assertAnalyticsRowFields(testData.jobId, { id: testData.jobId, description: editedDescription, @@ -226,14 +216,14 @@ export default function ({ getService }: FtrProviderContext) { status: testData.expected.row.status, progress: testData.expected.row.progress, }); - }); - it('creates the destination index and writes results to it', async () => { + await ml.testExecution.logTestStep( + 'creates the destination index and writes results to it' + ); await ml.api.assertIndicesExist(testData.destinationIndex); await ml.api.assertIndicesNotEmpty(testData.destinationIndex); - }); - it('displays the results view for created job', async () => { + await ml.testExecution.logTestStep('displays the results view for created job'); await ml.dataFrameAnalyticsTable.openResultsView(); await ml.dataFrameAnalytics.assertOutlierTablePanelExists(); }); diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts index a67a348323347..af9c5417e4826 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts @@ -56,108 +56,96 @@ export default function ({ getService }: FtrProviderContext) { await ml.testResources.deleteIndexPatternByTitle(testData.destinationIndex); }); - it('loads the data frame analytics page', async () => { + it('loads the data frame analytics wizard', async () => { + await ml.testExecution.logTestStep('loads the data frame analytics page'); await ml.navigation.navigateToMl(); await ml.navigation.navigateToDataFrameAnalytics(); - }); - it('loads the source selection modal', async () => { + await ml.testExecution.logTestStep('loads the source selection modal'); await ml.dataFrameAnalytics.startAnalyticsCreation(); - }); - it('selects the source data and loads the job wizard page', async () => { + await ml.testExecution.logTestStep( + 'selects the source data and loads the job wizard page' + ); await ml.jobSourceSelection.selectSourceForAnalyticsJob(testData.source); await ml.dataFrameAnalyticsCreation.assertConfigurationStepActive(); }); - it('selects the job type', async () => { + it('navigates through the wizard and sets all needed fields', async () => { + await ml.testExecution.logTestStep('selects the job type'); await ml.dataFrameAnalyticsCreation.assertJobTypeSelectExists(); await ml.dataFrameAnalyticsCreation.selectJobType(testData.jobType); - }); - it('inputs the dependent variable', async () => { + await ml.testExecution.logTestStep('inputs the dependent variable'); await ml.dataFrameAnalyticsCreation.assertDependentVariableInputExists(); await ml.dataFrameAnalyticsCreation.selectDependentVariable(testData.dependentVariable); - }); - it('inputs the training percent', async () => { + await ml.testExecution.logTestStep('inputs the training percent'); await ml.dataFrameAnalyticsCreation.assertTrainingPercentInputExists(); await ml.dataFrameAnalyticsCreation.setTrainingPercent(testData.trainingPercent); - }); - it('displays the source data preview', async () => { + await ml.testExecution.logTestStep('displays the source data preview'); await ml.dataFrameAnalyticsCreation.assertSourceDataPreviewExists(); - }); - it('displays the include fields selection', async () => { + await ml.testExecution.logTestStep('displays the include fields selection'); await ml.dataFrameAnalyticsCreation.assertIncludeFieldsSelectionExists(); - }); - it('continues to the additional options step', async () => { + await ml.testExecution.logTestStep('continues to the additional options step'); await ml.dataFrameAnalyticsCreation.continueToAdditionalOptionsStep(); - }); - it('accepts the suggested model memory limit', async () => { + await ml.testExecution.logTestStep('accepts the suggested model memory limit'); await ml.dataFrameAnalyticsCreation.assertModelMemoryInputExists(); await ml.dataFrameAnalyticsCreation.assertModelMemoryInputPopulated(); - }); - it('continues to the details step', async () => { + await ml.testExecution.logTestStep('continues to the details step'); await ml.dataFrameAnalyticsCreation.continueToDetailsStep(); - }); - it('inputs the job id', async () => { + await ml.testExecution.logTestStep('inputs the job id'); await ml.dataFrameAnalyticsCreation.assertJobIdInputExists(); await ml.dataFrameAnalyticsCreation.setJobId(testData.jobId); - }); - it('inputs the job description', async () => { + await ml.testExecution.logTestStep('inputs the job description'); await ml.dataFrameAnalyticsCreation.assertJobDescriptionInputExists(); await ml.dataFrameAnalyticsCreation.setJobDescription(testData.jobDescription); - }); - it('should default the set destination index to job id switch to true', async () => { + await ml.testExecution.logTestStep( + 'should default the set destination index to job id switch to true' + ); await ml.dataFrameAnalyticsCreation.assertDestIndexSameAsIdSwitchExists(); await ml.dataFrameAnalyticsCreation.assertDestIndexSameAsIdCheckState(true); - }); - it('should input the destination index', async () => { + await ml.testExecution.logTestStep('should input the destination index'); await ml.dataFrameAnalyticsCreation.setDestIndexSameAsIdCheckState(false); await ml.dataFrameAnalyticsCreation.assertDestIndexInputExists(); await ml.dataFrameAnalyticsCreation.setDestIndex(testData.destinationIndex); - }); - it('sets the create index pattern switch', async () => { + await ml.testExecution.logTestStep('sets the create index pattern switch'); await ml.dataFrameAnalyticsCreation.assertCreateIndexPatternSwitchExists(); await ml.dataFrameAnalyticsCreation.setCreateIndexPatternSwitchState( testData.createIndexPattern ); - }); - it('continues to the create step', async () => { + await ml.testExecution.logTestStep('continues to the create step'); await ml.dataFrameAnalyticsCreation.continueToCreateStep(); }); - it('creates and starts the analytics job', async () => { + it('runs the analytics job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep('creates and starts the analytics job'); await ml.dataFrameAnalyticsCreation.assertCreateButtonExists(); await ml.dataFrameAnalyticsCreation.assertStartJobCheckboxCheckState(true); await ml.dataFrameAnalyticsCreation.createAnalyticsJob(testData.jobId); - }); - it('finishes analytics processing', async () => { + await ml.testExecution.logTestStep('finishes analytics processing'); await ml.dataFrameAnalytics.waitForAnalyticsCompletion(testData.jobId); - }); - it('displays the analytics table', async () => { + await ml.testExecution.logTestStep('displays the analytics table'); await ml.dataFrameAnalyticsCreation.navigateToJobManagementPage(); await ml.dataFrameAnalytics.assertAnalyticsTableExists(); - }); - it('displays the stats bar', async () => { + await ml.testExecution.logTestStep('displays the stats bar'); await ml.dataFrameAnalytics.assertAnalyticsStatsBarExists(); - }); - it('displays the created job in the analytics table', async () => { + await ml.testExecution.logTestStep('displays the created job in the analytics table'); await ml.dataFrameAnalyticsTable.refreshAnalyticsTable(); await ml.dataFrameAnalyticsTable.filterWithSearchString(testData.jobId); const rows = await ml.dataFrameAnalyticsTable.parseAnalyticsTable(); @@ -166,9 +154,10 @@ export default function ({ getService }: FtrProviderContext) { 1, `Filtered analytics table should have 1 row for job id '${testData.jobId}' (got matching items '${filteredRows}')` ); - }); - it('displays details for the created job in the analytics table', async () => { + await ml.testExecution.logTestStep( + 'displays details for the created job in the analytics table' + ); await ml.dataFrameAnalyticsTable.assertAnalyticsRowFields(testData.jobId, { id: testData.jobId, description: testData.jobDescription, @@ -180,25 +169,28 @@ export default function ({ getService }: FtrProviderContext) { }); }); - it('should open the edit form for the created job in the analytics table', async () => { + it('edits the analytics job and displays it correctly in the job list', async () => { + await ml.testExecution.logTestStep( + 'should open the edit form for the created job in the analytics table' + ); await ml.dataFrameAnalyticsTable.openEditFlyout(testData.jobId); - }); - it('should input the description in the edit form', async () => { + await ml.testExecution.logTestStep('should input the description in the edit form'); await ml.dataFrameAnalyticsEdit.assertJobDescriptionEditInputExists(); await ml.dataFrameAnalyticsEdit.setJobDescriptionEdit(editedDescription); - }); - it('should input the model memory limit in the edit form', async () => { + await ml.testExecution.logTestStep( + 'should input the model memory limit in the edit form' + ); await ml.dataFrameAnalyticsEdit.assertJobMmlEditInputExists(); await ml.dataFrameAnalyticsEdit.setJobMmlEdit('21mb'); - }); - it('should submit the edit job form', async () => { + await ml.testExecution.logTestStep('should submit the edit job form'); await ml.dataFrameAnalyticsEdit.updateAnalyticsJob(); - }); - it('displays details for the edited job in the analytics table', async () => { + await ml.testExecution.logTestStep( + 'displays details for the edited job in the analytics table' + ); await ml.dataFrameAnalyticsTable.assertAnalyticsRowFields(testData.jobId, { id: testData.jobId, description: editedDescription, @@ -208,14 +200,14 @@ export default function ({ getService }: FtrProviderContext) { status: testData.expected.row.status, progress: testData.expected.row.progress, }); - }); - it('creates the destination index and writes results to it', async () => { + await ml.testExecution.logTestStep( + 'creates the destination index and writes results to it' + ); await ml.api.assertIndicesExist(testData.destinationIndex); await ml.api.assertIndicesNotEmpty(testData.destinationIndex); - }); - it('displays the results view for created job', async () => { + await ml.testExecution.logTestStep('displays the results view for created job'); await ml.dataFrameAnalyticsTable.openResultsView(); await ml.dataFrameAnalytics.assertRegressionEvaluatePanelElementsExists(); await ml.dataFrameAnalytics.assertRegressionTablePanelExists(); diff --git a/x-pack/test/functional/apps/ml/data_visualizer/file_data_visualizer.ts b/x-pack/test/functional/apps/ml/data_visualizer/file_data_visualizer.ts index 3c9111c246630..4b3b0c38f2acf 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/file_data_visualizer.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/file_data_visualizer.ts @@ -47,40 +47,34 @@ export default function ({ getService }: FtrProviderContext) { await ml.api.deleteIndices(testData.indexName); }); - it('loads the data visualizer selector page', async () => { + it('displays and imports a file', async () => { + await ml.testExecution.logTestStep('loads the data visualizer selector page'); await ml.navigation.navigateToDataVisualizer(); - }); - it('loads the file upload page', async () => { + await ml.testExecution.logTestStep('loads the file upload page'); await ml.dataVisualizer.navigateToFileUpload(); - }); - it('selects a file and loads visualizer results', async () => { + await ml.testExecution.logTestStep('selects a file and loads visualizer results'); await ml.dataVisualizerFileBased.selectFile(testData.filePath); - }); - it('displays the components of the file details page', async () => { + await ml.testExecution.logTestStep('displays the components of the file details page'); await ml.dataVisualizerFileBased.assertFileTitle(testData.expected.results.title); await ml.dataVisualizerFileBased.assertFileContentPanelExists(); await ml.dataVisualizerFileBased.assertSummaryPanelExists(); await ml.dataVisualizerFileBased.assertFileStatsPanelExists(); - }); - it('loads the import settings page', async () => { + await ml.testExecution.logTestStep('loads the import settings page'); await ml.dataVisualizerFileBased.navigateToFileImport(); - }); - it('sets the index name', async () => { + await ml.testExecution.logTestStep('sets the index name'); await ml.dataVisualizerFileBased.setIndexName(testData.indexName); - }); - it('sets the create index pattern checkbox', async () => { + await ml.testExecution.logTestStep('sets the create index pattern checkbox'); await ml.dataVisualizerFileBased.setCreateIndexPatternCheckboxState( testData.createIndexPattern ); - }); - it('imports the file', async () => { + await ml.testExecution.logTestStep('imports the file'); await ml.dataVisualizerFileBased.startImportAndWaitForProcessing(); }); }); @@ -88,15 +82,14 @@ export default function ({ getService }: FtrProviderContext) { for (const testData of testDataListNegative) { describe(testData.suiteSuffix, function () { - it('loads the data visualizer selector page', async () => { + it('does not import an invalid file', async () => { + await ml.testExecution.logTestStep('loads the data visualizer selector page'); await ml.navigation.navigateToDataVisualizer(); - }); - it('loads the file upload page', async () => { + await ml.testExecution.logTestStep('loads the file upload page'); await ml.dataVisualizer.navigateToFileUpload(); - }); - it('selects a file and displays an error', async () => { + await ml.testExecution.logTestStep('selects a file and displays an error'); await ml.dataVisualizerFileBased.selectFile(testData.filePath, true); }); }); diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts index eb76a8b4298af..2dc1d9ec00eca 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts @@ -292,67 +292,74 @@ export default function ({ getService }: FtrProviderContext) { }; function runTests(testData: TestData) { - it(`${testData.suiteTitle} loads the saved search selection page`, async () => { + it(`${testData.suiteTitle} loads the source data in the data visualizer`, async () => { + await ml.testExecution.logTestStep( + `${testData.suiteTitle} loads the saved search selection page` + ); await ml.dataVisualizer.navigateToIndexPatternSelection(); - }); - it(`${testData.suiteTitle} loads the index data visualizer page`, async () => { + await ml.testExecution.logTestStep( + `${testData.suiteTitle} loads the index data visualizer page` + ); await ml.jobSourceSelection.selectSourceForIndexBasedDataVisualizer( testData.sourceIndexOrSavedSearch ); }); - it(`${testData.suiteTitle} displays the time range step`, async () => { + it(`${testData.suiteTitle} displays index details`, async () => { + await ml.testExecution.logTestStep(`${testData.suiteTitle} displays the time range step`); await ml.dataVisualizerIndexBased.assertTimeRangeSelectorSectionExists(); - }); - it(`${testData.suiteTitle} loads data for full time range`, async () => { + await ml.testExecution.logTestStep(`${testData.suiteTitle} loads data for full time range`); await ml.dataVisualizerIndexBased.clickUseFullDataButton(testData.expected.totalDocCount); - }); - it(`${testData.suiteTitle} displays the panels of fields`, async () => { + await ml.testExecution.logTestStep(`${testData.suiteTitle} displays the panels of fields`); await ml.dataVisualizerIndexBased.assertFieldsPanelsExist(testData.expected.fieldsPanelCount); - }); - if (testData.expected.metricCards !== undefined && testData.expected.metricCards.length > 0) { - it(`${testData.suiteTitle} displays the Metrics panel`, async () => { + if (testData.expected.metricCards !== undefined && testData.expected.metricCards.length > 0) { + await ml.testExecution.logTestStep(`${testData.suiteTitle} displays the Metrics panel`); await ml.dataVisualizerIndexBased.assertFieldsPanelForTypesExist([ ML_JOB_FIELD_TYPES.NUMBER, ]); // document_count not exposed as a type in the panel - }); - it(`${testData.suiteTitle} displays the expected metric field cards`, async () => { + await ml.testExecution.logTestStep( + `${testData.suiteTitle} displays the expected metric field cards` + ); for (const fieldCard of testData.expected.metricCards as FieldVisConfig[]) { await ml.dataVisualizerIndexBased.assertCardExists(fieldCard.type, fieldCard.fieldName); } - }); - it(`${testData.suiteTitle} filters metric fields cards with search`, async () => { + await ml.testExecution.logTestStep( + `${testData.suiteTitle} filters metric fields cards with search` + ); await ml.dataVisualizerIndexBased.filterFieldsPanelWithSearchString( ['number'], testData.metricFieldsFilter, testData.expected.metricFieldsFilterCardCount ); - }); - } + } - if ( - testData.expected.nonMetricCards !== undefined && - testData.expected.nonMetricCards.length > 0 - ) { - it(`${testData.suiteTitle} displays the non-metric Fields panel`, async () => { + if ( + testData.expected.nonMetricCards !== undefined && + testData.expected.nonMetricCards.length > 0 + ) { + await ml.testExecution.logTestStep( + `${testData.suiteTitle} displays the non-metric Fields panel` + ); await ml.dataVisualizerIndexBased.assertFieldsPanelForTypesExist( getFieldTypes(testData.expected.nonMetricCards as FieldVisConfig[]) ); - }); - it(`${testData.suiteTitle} displays the expected non-metric field cards`, async () => { + await ml.testExecution.logTestStep( + `${testData.suiteTitle} displays the expected non-metric field cards` + ); for (const fieldCard of testData.expected.nonMetricCards!) { await ml.dataVisualizerIndexBased.assertCardExists(fieldCard.type, fieldCard.fieldName); } - }); - it(`${testData.suiteTitle} sets the non metric field types input`, async () => { + await ml.testExecution.logTestStep( + `${testData.suiteTitle} sets the non metric field types input` + ); const fieldTypes: ML_JOB_FIELD_TYPES[] = getFieldTypes( testData.expected.nonMetricCards as FieldVisConfig[] ); @@ -362,16 +369,17 @@ export default function ({ getService }: FtrProviderContext) { testData.nonMetricFieldsTypeFilter, testData.expected.nonMetricFieldsTypeFilterCardCount ); - }); - it(`${testData.suiteTitle} filters non-metric fields cards with search`, async () => { + await ml.testExecution.logTestStep( + `${testData.suiteTitle} filters non-metric fields cards with search` + ); await ml.dataVisualizerIndexBased.filterFieldsPanelWithSearchString( getFieldTypes(testData.expected.nonMetricCards as FieldVisConfig[]), testData.nonMetricFieldsFilter, testData.expected.nonMetricFieldsFilterCardCount ); - }); - } + } + }); } describe('index based', function () { diff --git a/x-pack/test/functional/apps/ml/pages.ts b/x-pack/test/functional/apps/ml/pages.ts index 3691e6b1afcdc..5d084d5abe11e 100644 --- a/x-pack/test/functional/apps/ml/pages.ts +++ b/x-pack/test/functional/apps/ml/pages.ts @@ -16,53 +16,49 @@ export default function ({ getService }: FtrProviderContext) { await ml.securityUI.loginAsMlPowerUser(); }); - it('loads the home page', async () => { + it('loads the ML pages', async () => { + await ml.testExecution.logTestStep('home'); await ml.navigation.navigateToMl(); - }); - it('loads the overview page', async () => { + await ml.testExecution.logTestStep('loads the overview page'); await ml.navigation.navigateToOverview(); - }); - it('loads the anomaly detection area', async () => { + await ml.testExecution.logTestStep('loads the anomaly detection area'); await ml.navigation.navigateToAnomalyDetection(); - }); - it('loads the job management page', async () => { + await ml.testExecution.logTestStep('loads the job management page'); await ml.navigation.navigateToJobManagement(); await ml.jobManagement.assertJobStatsBarExists(); await ml.jobManagement.assertJobTableExists(); await ml.jobManagement.assertCreateNewJobButtonExists(); - }); - it('loads the settings page', async () => { + await ml.testExecution.logTestStep('loads the settings page'); await ml.navigation.navigateToSettings(); await ml.settings.assertSettingsManageCalendarsLinkExists(); await ml.settings.assertSettingsCreateCalendarLinkExists(); await ml.settings.assertSettingsManageFilterListsLinkExists(); await ml.settings.assertSettingsCreateFilterListLinkExists(); - }); - it('loads the data frame analytics page', async () => { + await ml.testExecution.logTestStep('loads the data frame analytics page'); await ml.navigation.navigateToDataFrameAnalytics(); await ml.dataFrameAnalytics.assertEmptyListMessageExists(); - }); - it('loads the data visualizer page', async () => { + await ml.testExecution.logTestStep('loads the data visualizer page'); await ml.navigation.navigateToDataVisualizer(); await ml.dataVisualizer.assertDataVisualizerImportDataCardExists(); await ml.dataVisualizer.assertDataVisualizerIndexDataCardExists(); - }); - it('should load the stack management with the ML menu item being present', async () => { + await ml.testExecution.logTestStep( + 'should load the stack management with the ML menu item being present' + ); await ml.navigation.navigateToStackManagement(); - }); - it('should load the jobs list page in stack management', async () => { + await ml.testExecution.logTestStep('should load the jobs list page in stack management'); await ml.navigation.navigateToStackManagementJobsListPage(); - }); - it('should load the analytics jobs list page in stack management', async () => { + await ml.testExecution.logTestStep( + 'should load the analytics jobs list page in stack management' + ); await ml.navigation.navigateToStackManagementJobsListPageAnalyticsTab(); }); }); diff --git a/x-pack/test/functional/apps/transform/cloning.ts b/x-pack/test/functional/apps/transform/cloning.ts index d94923d9a7cce..d5c972cb8bd1f 100644 --- a/x-pack/test/functional/apps/transform/cloning.ts +++ b/x-pack/test/functional/apps/transform/cloning.ts @@ -87,138 +87,130 @@ export default function ({ getService }: FtrProviderContext) { await transform.testResources.deleteIndexPatternByTitle(testData.destinationIndex); }); - it('should load the home page', async () => { + it('opens the existing transform in the wizard', async () => { + await transform.testExecution.logTestStep('should load the home page'); await transform.navigation.navigateTo(); await transform.management.assertTransformListPageExists(); - }); - it('should display the transforms table', async () => { + await transform.testExecution.logTestStep('should display the transforms table'); await transform.management.assertTransformsTableExists(); - }); - it('should display the original transform in the transform list', async () => { + await transform.testExecution.logTestStep( + 'should display the original transform in the transform list' + ); await transform.table.refreshTransformList(); await transform.table.filterWithSearchString(transformConfig.id); const rows = await transform.table.parseTransformTable(); expect(rows.filter((row) => row.id === transformConfig.id)).to.have.length(1); - }); - it('should show the actions popover', async () => { + await transform.testExecution.logTestStep('should show the actions popover'); await transform.table.assertTransformRowActions(false); - }); - it('should display the define pivot step', async () => { + await transform.testExecution.logTestStep('should display the define pivot step'); await transform.table.clickTransformRowAction('Clone'); await transform.wizard.assertDefineStepActive(); }); - it('should load the index preview', async () => { + it('navigates through the wizard, checks and sets all needed fields', async () => { + await transform.testExecution.logTestStep('should load the index preview'); await transform.wizard.assertIndexPreviewLoaded(); - }); - it('should show the index preview', async () => { + await transform.testExecution.logTestStep('should show the index preview'); await transform.wizard.assertIndexPreview( testData.expected.indexPreview.columns, testData.expected.indexPreview.rows ); - }); - it('should display the query input', async () => { + await transform.testExecution.logTestStep('should display the query input'); await transform.wizard.assertQueryInputExists(); await transform.wizard.assertQueryValue(''); - }); - it('should show the pre-filled group-by configuration', async () => { + await transform.testExecution.logTestStep( + 'should show the pre-filled group-by configuration' + ); await transform.wizard.assertGroupByEntryExists( testData.expected.groupBy.index, testData.expected.groupBy.label ); - }); - it('should show the pre-filled aggs configuration', async () => { + await transform.testExecution.logTestStep( + 'should show the pre-filled aggs configuration' + ); await transform.wizard.assertAggregationEntryExists( testData.expected.aggs.index, testData.expected.aggs.label ); - }); - it('should show the pivot preview', async () => { + await transform.testExecution.logTestStep('should show the pivot preview'); await transform.wizard.assertPivotPreviewChartHistogramButtonMissing(); await transform.wizard.assertPivotPreviewColumnValues( testData.expected.pivotPreview.column, testData.expected.pivotPreview.values ); - }); - it('should load the details step', async () => { + await transform.testExecution.logTestStep('should load the details step'); await transform.wizard.advanceToDetailsStep(); - }); - it('should input the transform id', async () => { + await transform.testExecution.logTestStep('should input the transform id'); await transform.wizard.assertTransformIdInputExists(); await transform.wizard.assertTransformIdValue(''); await transform.wizard.setTransformId(testData.transformId); - }); - it('should input the transform description', async () => { + await transform.testExecution.logTestStep('should input the transform description'); await transform.wizard.assertTransformDescriptionInputExists(); await transform.wizard.assertTransformDescriptionValue(''); await transform.wizard.setTransformDescription(testData.transformDescription); - }); - it('should input the destination index', async () => { + await transform.testExecution.logTestStep('should input the destination index'); await transform.wizard.assertDestinationIndexInputExists(); await transform.wizard.assertDestinationIndexValue(''); await transform.wizard.setDestinationIndex(testData.destinationIndex); - }); - it('should display the create index pattern switch', async () => { + await transform.testExecution.logTestStep( + 'should display the create index pattern switch' + ); await transform.wizard.assertCreateIndexPatternSwitchExists(); await transform.wizard.assertCreateIndexPatternSwitchCheckState(true); - }); - it('should display the continuous mode switch', async () => { + await transform.testExecution.logTestStep('should display the continuous mode switch'); await transform.wizard.assertContinuousModeSwitchExists(); await transform.wizard.assertContinuousModeSwitchCheckState(false); - }); - it('should load the create step', async () => { + await transform.testExecution.logTestStep('should load the create step'); await transform.wizard.advanceToCreateStep(); - }); - it('should display the create and start button', async () => { + await transform.testExecution.logTestStep('should display the create and start button'); await transform.wizard.assertCreateAndStartButtonExists(); await transform.wizard.assertCreateAndStartButtonEnabled(true); - }); - it('should display the create button', async () => { + await transform.testExecution.logTestStep('should display the create button'); await transform.wizard.assertCreateButtonExists(); await transform.wizard.assertCreateButtonEnabled(true); - }); - it('should display the copy to clipboard button', async () => { + await transform.testExecution.logTestStep('should display the copy to clipboard button'); await transform.wizard.assertCopyToClipboardButtonExists(); await transform.wizard.assertCopyToClipboardButtonEnabled(true); }); - it('should create the transform', async () => { + it('runs the clone transform and displays it correctly in the job list', async () => { + await transform.testExecution.logTestStep('should create the transform'); await transform.wizard.createTransform(); - }); - it('should start the transform and finish processing', async () => { + await transform.testExecution.logTestStep( + 'should start the transform and finish processing' + ); await transform.wizard.startTransform(); await transform.wizard.waitForProgressBarComplete(); - }); - it('should return to the management page', async () => { + await transform.testExecution.logTestStep('should return to the management page'); await transform.wizard.returnToManagement(); - }); - it('should display the transforms table', async () => { + await transform.testExecution.logTestStep('should display the transforms table'); await transform.management.assertTransformsTableExists(); - }); - it('should display the created transform in the transform list', async () => { + await transform.testExecution.logTestStep( + 'should display the created transform in the transform list' + ); await transform.table.refreshTransformList(); await transform.table.filterWithSearchString(testData.transformId); const rows = await transform.table.parseTransformTable(); diff --git a/x-pack/test/functional/apps/transform/creation_index_pattern.ts b/x-pack/test/functional/apps/transform/creation_index_pattern.ts index 7c9983101f607..daecc26186ac1 100644 --- a/x-pack/test/functional/apps/transform/creation_index_pattern.ts +++ b/x-pack/test/functional/apps/transform/creation_index_pattern.ts @@ -260,63 +260,54 @@ export default function ({ getService }: FtrProviderContext) { await transform.testResources.deleteIndexPatternByTitle(testData.destinationIndex); }); - it('loads the home page', async () => { + it('loads the wizard for the source data', async () => { + await transform.testExecution.logTestStep('loads the home page'); await transform.navigation.navigateTo(); await transform.management.assertTransformListPageExists(); - }); - it('displays the stats bar', async () => { + await transform.testExecution.logTestStep('displays the stats bar'); await transform.management.assertTransformStatsBarExists(); - }); - it('loads the source selection modal', async () => { + await transform.testExecution.logTestStep('loads the source selection modal'); await transform.management.startTransformCreation(); - }); - it('selects the source data', async () => { + await transform.testExecution.logTestStep('selects the source data'); await transform.sourceSelection.selectSource(testData.source); }); - it('displays the define pivot step', async () => { + it('navigates through the wizard and sets all needed fields', async () => { + await transform.testExecution.logTestStep('displays the define pivot step'); await transform.wizard.assertDefineStepActive(); - }); - it('loads the index preview', async () => { + await transform.testExecution.logTestStep('loads the index preview'); await transform.wizard.assertIndexPreviewLoaded(); - }); - it('shows the index preview', async () => { + await transform.testExecution.logTestStep('shows the index preview'); await transform.wizard.assertIndexPreview( testData.expected.indexPreview.columns, testData.expected.indexPreview.rows ); - }); - it('displays an empty pivot preview', async () => { + await transform.testExecution.logTestStep('displays an empty pivot preview'); await transform.wizard.assertPivotPreviewEmpty(); - }); - it('displays the query input', async () => { + await transform.testExecution.logTestStep('displays the query input'); await transform.wizard.assertQueryInputExists(); await transform.wizard.assertQueryValue(''); - }); - it('displays the advanced query editor switch', async () => { + await transform.testExecution.logTestStep('displays the advanced query editor switch'); await transform.wizard.assertAdvancedQueryEditorSwitchExists(); await transform.wizard.assertAdvancedQueryEditorSwitchCheckState(false); - }); - it('enables the index preview histogram charts', async () => { + await transform.testExecution.logTestStep('enables the index preview histogram charts'); await transform.wizard.enableIndexPreviewHistogramCharts(); - }); - it('displays the index preview histogram charts', async () => { + await transform.testExecution.logTestStep('displays the index preview histogram charts'); await transform.wizard.assertIndexPreviewHistogramCharts( testData.expected.histogramCharts ); - }); - it('adds the group by entries', async () => { + await transform.testExecution.logTestStep('adds the group by entries'); for (const [index, entry] of testData.groupByEntries.entries()) { await transform.wizard.assertGroupByInputExists(); await transform.wizard.assertGroupByInputValue([]); @@ -327,112 +318,97 @@ export default function ({ getService }: FtrProviderContext) { entry.intervalLabel ); } - }); - it('adds the aggregation entries', async () => { + await transform.testExecution.logTestStep('adds the aggregation entries'); await transform.wizard.addAggregationEntries(testData.aggregationEntries); - }); - it('displays the advanced pivot editor switch', async () => { + await transform.testExecution.logTestStep('displays the advanced pivot editor switch'); await transform.wizard.assertAdvancedPivotEditorSwitchExists(); await transform.wizard.assertAdvancedPivotEditorSwitchCheckState(false); - }); - it('displays the advanced configuration', async () => { + await transform.testExecution.logTestStep('displays the advanced configuration'); await transform.wizard.enabledAdvancedPivotEditor(); await transform.wizard.assertAdvancedPivotEditorContent( testData.expected.pivotAdvancedEditorValueArr ); - }); - it('loads the pivot preview', async () => { + await transform.testExecution.logTestStep('loads the pivot preview'); await transform.wizard.assertPivotPreviewLoaded(); - }); - it('shows the pivot preview', async () => { + await transform.testExecution.logTestStep('shows the pivot preview'); await transform.wizard.assertPivotPreviewChartHistogramButtonMissing(); await transform.wizard.assertPivotPreviewColumnValues( testData.expected.pivotPreview.column, testData.expected.pivotPreview.values ); - }); - it('loads the details step', async () => { + await transform.testExecution.logTestStep('loads the details step'); await transform.wizard.advanceToDetailsStep(); - }); - it('inputs the transform id', async () => { + await transform.testExecution.logTestStep('inputs the transform id'); await transform.wizard.assertTransformIdInputExists(); await transform.wizard.assertTransformIdValue(''); await transform.wizard.setTransformId(testData.transformId); - }); - it('inputs the transform description', async () => { + await transform.testExecution.logTestStep('inputs the transform description'); await transform.wizard.assertTransformDescriptionInputExists(); await transform.wizard.assertTransformDescriptionValue(''); await transform.wizard.setTransformDescription(testData.transformDescription); - }); - it('inputs the destination index', async () => { + await transform.testExecution.logTestStep('inputs the destination index'); await transform.wizard.assertDestinationIndexInputExists(); await transform.wizard.assertDestinationIndexValue(''); await transform.wizard.setDestinationIndex(testData.destinationIndex); - }); - it('displays the create index pattern switch', async () => { + await transform.testExecution.logTestStep('displays the create index pattern switch'); await transform.wizard.assertCreateIndexPatternSwitchExists(); await transform.wizard.assertCreateIndexPatternSwitchCheckState(true); - }); - it('displays the continuous mode switch', async () => { + await transform.testExecution.logTestStep('displays the continuous mode switch'); await transform.wizard.assertContinuousModeSwitchExists(); await transform.wizard.assertContinuousModeSwitchCheckState(false); - }); - it('loads the create step', async () => { + await transform.testExecution.logTestStep('loads the create step'); await transform.wizard.advanceToCreateStep(); - }); - it('displays the create and start button', async () => { + await transform.testExecution.logTestStep('displays the create and start button'); await transform.wizard.assertCreateAndStartButtonExists(); await transform.wizard.assertCreateAndStartButtonEnabled(true); - }); - it('displays the create button', async () => { + await transform.testExecution.logTestStep('displays the create button'); await transform.wizard.assertCreateButtonExists(); await transform.wizard.assertCreateButtonEnabled(true); - }); - it('displays the copy to clipboard button', async () => { + await transform.testExecution.logTestStep('displays the copy to clipboard button'); await transform.wizard.assertCopyToClipboardButtonExists(); await transform.wizard.assertCopyToClipboardButtonEnabled(true); }); - it('creates the transform', async () => { + it('runs the transform and displays it correctly in the job list', async () => { + await transform.testExecution.logTestStep('creates the transform'); await transform.wizard.createTransform(); - }); - it('starts the transform and finishes processing', async () => { + await transform.testExecution.logTestStep('starts the transform and finishes processing'); await transform.wizard.startTransform(); await transform.wizard.waitForProgressBarComplete(); - }); - it('returns to the management page', async () => { + await transform.testExecution.logTestStep('returns to the management page'); await transform.wizard.returnToManagement(); - }); - it('displays the transforms table', async () => { + await transform.testExecution.logTestStep('displays the transforms table'); await transform.management.assertTransformsTableExists(); - }); - it('displays the created transform in the transform list', async () => { + await transform.testExecution.logTestStep( + 'displays the created transform in the transform list' + ); await transform.table.refreshTransformList(); await transform.table.filterWithSearchString(testData.transformId); const rows = await transform.table.parseTransformTable(); expect(rows.filter((row) => row.id === testData.transformId)).to.have.length(1); - }); - it('transform creation displays details for the created transform in the transform list', async () => { + await transform.testExecution.logTestStep( + 'transform creation displays details for the created transform in the transform list' + ); await transform.table.assertTransformRowFields(testData.transformId, { id: testData.transformId, description: testData.transformDescription, diff --git a/x-pack/test/functional/apps/transform/creation_saved_search.ts b/x-pack/test/functional/apps/transform/creation_saved_search.ts index 54cc5b3f62933..d3cbc1159a9c7 100644 --- a/x-pack/test/functional/apps/transform/creation_saved_search.ts +++ b/x-pack/test/functional/apps/transform/creation_saved_search.ts @@ -79,51 +79,44 @@ export default function ({ getService }: FtrProviderContext) { await transform.testResources.deleteIndexPatternByTitle(testData.destinationIndex); }); - it('loads the home page', async () => { + it('loads the wizard for the source data', async () => { + await transform.testExecution.logTestStep('loads the home page'); await transform.navigation.navigateTo(); await transform.management.assertTransformListPageExists(); - }); - it('displays the stats bar', async () => { + await transform.testExecution.logTestStep('displays the stats bar'); await transform.management.assertTransformStatsBarExists(); - }); - it('loads the source selection modal', async () => { + await transform.testExecution.logTestStep('loads the source selection modal'); await transform.management.startTransformCreation(); - }); - it('selects the source data', async () => { + await transform.testExecution.logTestStep('selects the source data'); await transform.sourceSelection.selectSource(testData.source); }); - it('displays the define pivot step', async () => { + it('navigates through the wizard and sets all needed fields', async () => { + await transform.testExecution.logTestStep('displays the define pivot step'); await transform.wizard.assertDefineStepActive(); - }); - it('loads the index preview', async () => { + await transform.testExecution.logTestStep('loads the index preview'); await transform.wizard.assertIndexPreviewLoaded(); - }); - it('shows the filtered index preview', async () => { + await transform.testExecution.logTestStep('shows the filtered index preview'); await transform.wizard.assertIndexPreviewColumnValues( testData.expected.indexPreview.column, testData.expected.indexPreview.values ); - }); - it('displays an empty pivot preview', async () => { + await transform.testExecution.logTestStep('displays an empty pivot preview'); await transform.wizard.assertPivotPreviewEmpty(); - }); - it('hides the query input', async () => { + await transform.testExecution.logTestStep('hides the query input'); await transform.wizard.assertQueryInputMissing(); - }); - it('hides the advanced query editor switch', async () => { + await transform.testExecution.logTestStep('hides the advanced query editor switch'); await transform.wizard.assertAdvancedQueryEditorSwitchMissing(); - }); - it('adds the group by entries', async () => { + await transform.testExecution.logTestStep('adds the group by entries'); for (const [index, entry] of testData.groupByEntries.entries()) { await transform.wizard.assertGroupByInputExists(); await transform.wizard.assertGroupByInputValue([]); @@ -134,108 +127,94 @@ export default function ({ getService }: FtrProviderContext) { entry.intervalLabel ); } - }); - it('adds the aggregation entries', async () => { + await transform.testExecution.logTestStep('adds the aggregation entries'); for (const [index, agg] of testData.aggregationEntries.entries()) { await transform.wizard.assertAggregationInputExists(); await transform.wizard.assertAggregationInputValue([]); await transform.wizard.addAggregationEntry(index, agg.identifier, agg.label); } - }); - it('displays the advanced pivot editor switch', async () => { + await transform.testExecution.logTestStep('displays the advanced pivot editor switch'); await transform.wizard.assertAdvancedPivotEditorSwitchExists(); await transform.wizard.assertAdvancedPivotEditorSwitchCheckState(false); - }); - it('loads the pivot preview', async () => { + await transform.testExecution.logTestStep('loads the pivot preview'); await transform.wizard.assertPivotPreviewLoaded(); - }); - it('shows the pivot preview', async () => { + await transform.testExecution.logTestStep('shows the pivot preview'); await transform.wizard.assertPivotPreviewColumnValues( testData.expected.pivotPreview.column, testData.expected.pivotPreview.values ); - }); - it('loads the details step', async () => { + await transform.testExecution.logTestStep('loads the details step'); await transform.wizard.advanceToDetailsStep(); - }); - it('inputs the transform id', async () => { + await transform.testExecution.logTestStep('inputs the transform id'); await transform.wizard.assertTransformIdInputExists(); await transform.wizard.assertTransformIdValue(''); await transform.wizard.setTransformId(testData.transformId); - }); - it('inputs the transform description', async () => { + await transform.testExecution.logTestStep('inputs the transform description'); await transform.wizard.assertTransformDescriptionInputExists(); await transform.wizard.assertTransformDescriptionValue(''); await transform.wizard.setTransformDescription(testData.transformDescription); - }); - it('inputs the destination index', async () => { + await transform.testExecution.logTestStep('inputs the destination index'); await transform.wizard.assertDestinationIndexInputExists(); await transform.wizard.assertDestinationIndexValue(''); await transform.wizard.setDestinationIndex(testData.destinationIndex); - }); - it('displays the create index pattern switch', async () => { + await transform.testExecution.logTestStep('displays the create index pattern switch'); await transform.wizard.assertCreateIndexPatternSwitchExists(); await transform.wizard.assertCreateIndexPatternSwitchCheckState(true); - }); - it('displays the continuous mode switch', async () => { + await transform.testExecution.logTestStep('displays the continuous mode switch'); await transform.wizard.assertContinuousModeSwitchExists(); await transform.wizard.assertContinuousModeSwitchCheckState(false); - }); - it('loads the create step', async () => { + await transform.testExecution.logTestStep('loads the create step'); await transform.wizard.advanceToCreateStep(); - }); - it('displays the create and start button', async () => { + await transform.testExecution.logTestStep('displays the create and start button'); await transform.wizard.assertCreateAndStartButtonExists(); await transform.wizard.assertCreateAndStartButtonEnabled(true); - }); - it('displays the create button', async () => { + await transform.testExecution.logTestStep('displays the create button'); await transform.wizard.assertCreateButtonExists(); await transform.wizard.assertCreateButtonEnabled(true); - }); - it('displays the copy to clipboard button', async () => { + await transform.testExecution.logTestStep('displays the copy to clipboard button'); await transform.wizard.assertCopyToClipboardButtonExists(); await transform.wizard.assertCopyToClipboardButtonEnabled(true); }); - it('creates the transform', async () => { + it('runs the transform and displays it correctly in the job list', async () => { + await transform.testExecution.logTestStep('creates the transform'); await transform.wizard.createTransform(); - }); - it('starts the transform and finishes processing', async () => { + await transform.testExecution.logTestStep('starts the transform and finishes processing'); await transform.wizard.startTransform(); await transform.wizard.waitForProgressBarComplete(); - }); - it('returns to the management page', async () => { + await transform.testExecution.logTestStep('returns to the management page'); await transform.wizard.returnToManagement(); - }); - it('displays the transforms table', async () => { + await transform.testExecution.logTestStep('displays the transforms table'); await transform.management.assertTransformsTableExists(); - }); - it('displays the created transform in the transform list', async () => { + await transform.testExecution.logTestStep( + 'displays the created transform in the transform list' + ); await transform.table.refreshTransformList(); await transform.table.filterWithSearchString(testData.transformId); const rows = await transform.table.parseTransformTable(); expect(rows.filter((row) => row.id === testData.transformId)).to.have.length(1); - }); - it('transform creation displays details for the created transform in the transform list', async () => { + await transform.testExecution.logTestStep( + 'transform creation displays details for the created transform in the transform list' + ); await transform.table.assertTransformRowFields(testData.transformId, { id: testData.transformId, description: testData.transformDescription, @@ -243,13 +222,15 @@ export default function ({ getService }: FtrProviderContext) { mode: testData.expected.row.mode, progress: testData.expected.row.progress, }); - }); - it('expands the transform management table row and walks through available tabs', async () => { + await transform.testExecution.logTestStep( + 'expands the transform management table row and walks through available tabs' + ); await transform.table.assertTransformExpandedRow(); - }); - it('displays the transform preview in the expanded row', async () => { + await transform.testExecution.logTestStep( + 'displays the transform preview in the expanded row' + ); await transform.table.assertTransformsExpandedRowPreviewColumnValues( testData.expected.pivotPreview.column, testData.expected.pivotPreview.values diff --git a/x-pack/test/functional/apps/transform/editing.ts b/x-pack/test/functional/apps/transform/editing.ts index 44ecca17328a7..5582d279833e7 100644 --- a/x-pack/test/functional/apps/transform/editing.ts +++ b/x-pack/test/functional/apps/transform/editing.ts @@ -61,32 +61,32 @@ export default function ({ getService }: FtrProviderContext) { }; describe(`${testData.suiteTitle}`, function () { - it('should load the home page', async () => { + it('opens the edit flyout for an existing transform', async () => { + await transform.testExecution.logTestStep('should load the home page'); await transform.navigation.navigateTo(); await transform.management.assertTransformListPageExists(); - }); - it('should display the transforms table', async () => { + await transform.testExecution.logTestStep('should display the transforms table'); await transform.management.assertTransformsTableExists(); - }); - it('should display the original transform in the transform list', async () => { + await transform.testExecution.logTestStep( + 'should display the original transform in the transform list' + ); await transform.table.refreshTransformList(); await transform.table.filterWithSearchString(transformConfig.id); const rows = await transform.table.parseTransformTable(); expect(rows.filter((row) => row.id === transformConfig.id)).to.have.length(1); - }); - it('should show the actions popover', async () => { + await transform.testExecution.logTestStep('should show the actions popover'); await transform.table.assertTransformRowActions(false); - }); - it('should show the edit flyout', async () => { + await transform.testExecution.logTestStep('should show the edit flyout'); await transform.table.clickTransformRowAction('Edit'); await transform.editFlyout.assertTransformEditFlyoutExists(); }); - it('should update the transform description', async () => { + it('navigates through the edit flyout and sets all needed fields', async () => { + await transform.testExecution.logTestStep('should update the transform description'); await transform.editFlyout.assertTransformEditFlyoutInputExists('Description'); await transform.editFlyout.assertTransformEditFlyoutInputValue( 'Description', @@ -96,18 +96,18 @@ export default function ({ getService }: FtrProviderContext) { 'Description', testData.transformDescription ); - }); - it('should update the transform documents per second', async () => { + await transform.testExecution.logTestStep( + 'should update the transform documents per second' + ); await transform.editFlyout.assertTransformEditFlyoutInputExists('DocsPerSecond'); await transform.editFlyout.assertTransformEditFlyoutInputValue('DocsPerSecond', ''); await transform.editFlyout.setTransformEditFlyoutInputValue( 'DocsPerSecond', testData.transformDocsPerSecond ); - }); - it('should update the transform frequency', async () => { + await transform.testExecution.logTestStep('should update the transform frequency'); await transform.editFlyout.assertTransformEditFlyoutInputExists('Frequency'); await transform.editFlyout.assertTransformEditFlyoutInputValue('Frequency', ''); await transform.editFlyout.setTransformEditFlyoutInputValue( @@ -116,22 +116,24 @@ export default function ({ getService }: FtrProviderContext) { ); }); - it('should update the transform', async () => { + it('updates the transform and displays it correctly in the job list', async () => { + await transform.testExecution.logTestStep('should update the transform'); await transform.editFlyout.updateTransform(); - }); - it('should display the transforms table', async () => { + await transform.testExecution.logTestStep('should display the transforms table'); await transform.management.assertTransformsTableExists(); - }); - it('should display the updated transform in the transform list', async () => { + await transform.testExecution.logTestStep( + 'should display the updated transform in the transform list' + ); await transform.table.refreshTransformList(); await transform.table.filterWithSearchString(transformConfig.id); const rows = await transform.table.parseTransformTable(); expect(rows.filter((row) => row.id === transformConfig.id)).to.have.length(1); - }); - it('should display the updated transform in the transform list row cells', async () => { + await transform.testExecution.logTestStep( + 'should display the updated transform in the transform list row cells' + ); await transform.table.assertTransformRowFields(transformConfig.id, { id: transformConfig.id, description: testData.transformDescription, @@ -139,9 +141,10 @@ export default function ({ getService }: FtrProviderContext) { mode: testData.expected.row.mode, progress: testData.expected.row.progress, }); - }); - it('should display the messages tab and include an update message', async () => { + await transform.testExecution.logTestStep( + 'should display the messages tab and include an update message' + ); await transform.table.assertTransformExpandedRowMessages(testData.expected.messageText); }); }); diff --git a/x-pack/test/functional/services/ml/common_api.ts b/x-pack/test/functional/services/ml/common_api.ts new file mode 100644 index 0000000000000..e68dbf4bdcaf6 --- /dev/null +++ b/x-pack/test/functional/services/ml/common_api.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { ProvidedType } from '@kbn/test/types/ftr'; + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export const COMMON_REQUEST_HEADERS = { + 'kbn-xsrf': 'some-xsrf-token', +}; + +export type MlCommonAPI = ProvidedType; + +export function MachineLearningCommonAPIProvider({}: FtrProviderContext) { + return { + async getCommonRequestHeader() { + return COMMON_REQUEST_HEADERS; + }, + }; +} diff --git a/x-pack/test/functional/services/ml/common.ts b/x-pack/test/functional/services/ml/common_ui.ts similarity index 92% rename from x-pack/test/functional/services/ml/common.ts rename to x-pack/test/functional/services/ml/common_ui.ts index 3333a81f028fa..b66fd7087654d 100644 --- a/x-pack/test/functional/services/ml/common.ts +++ b/x-pack/test/functional/services/ml/common_ui.ts @@ -12,13 +12,9 @@ interface SetValueOptions { typeCharByChar?: boolean; } -export const COMMON_REQUEST_HEADERS = { - 'kbn-xsrf': 'some-xsrf-token', -}; +export type MlCommonUI = ProvidedType; -export type MlCommon = ProvidedType; - -export function MachineLearningCommonProvider({ getService }: FtrProviderContext) { +export function MachineLearningCommonUIProvider({ getService }: FtrProviderContext) { const log = getService('log'); const retry = getService('retry'); const testSubjects = getService('testSubjects'); diff --git a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts index cdd26b60d3be0..ffa1d9fd46c75 100644 --- a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts +++ b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { DataFrameAnalyticsConfig } from '../../../../plugins/ml/public/application/data_frame_analytics/common'; import { FtrProviderContext } from '../../ftr_provider_context'; -import { MlCommon } from './common'; +import { MlCommonUI } from './common_ui'; import { MlApi } from './api'; import { ClassificationAnalysis, @@ -32,7 +32,7 @@ const isClassificationAnalysis = (arg: any): arg is ClassificationAnalysis => { export function MachineLearningDataFrameAnalyticsCreationProvider( { getService }: FtrProviderContext, - mlCommon: MlCommon, + mlCommonUI: MlCommonUI, mlApi: MlApi ) { const testSubjects = getService('testSubjects'); @@ -113,16 +113,20 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( }, async setJobId(jobId: string) { - await mlCommon.setValueWithChecks('mlAnalyticsCreateJobFlyoutJobIdInput', jobId, { + await mlCommonUI.setValueWithChecks('mlAnalyticsCreateJobFlyoutJobIdInput', jobId, { clearWithKeyboard: true, }); await this.assertJobIdValue(jobId); }, async setJobDescription(jobDescription: string) { - await mlCommon.setValueWithChecks('mlDFAnalyticsJobCreationJobDescription', jobDescription, { - clearWithKeyboard: true, - }); + await mlCommonUI.setValueWithChecks( + 'mlDFAnalyticsJobCreationJobDescription', + jobDescription, + { + clearWithKeyboard: true, + } + ); await this.assertJobDescriptionValue(jobDescription); }, @@ -218,7 +222,7 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( }, async setDestIndex(destIndex: string) { - await mlCommon.setValueWithChecks( + await mlCommonUI.setValueWithChecks( 'mlAnalyticsCreateJobFlyoutDestinationIndexInput', destIndex, { @@ -405,7 +409,7 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( async setModelMemory(modelMemory: string) { await retry.tryForTime(15 * 1000, async () => { - await mlCommon.setValueWithChecks( + await mlCommonUI.setValueWithChecks( 'mlAnalyticsCreateJobWizardModelMemoryInput', modelMemory, { diff --git a/x-pack/test/functional/services/ml/data_frame_analytics_edit.ts b/x-pack/test/functional/services/ml/data_frame_analytics_edit.ts index fd06dd24d6f8b..6e446667875d9 100644 --- a/x-pack/test/functional/services/ml/data_frame_analytics_edit.ts +++ b/x-pack/test/functional/services/ml/data_frame_analytics_edit.ts @@ -6,11 +6,11 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; -import { MlCommon } from './common'; +import { MlCommonUI } from './common_ui'; export function MachineLearningDataFrameAnalyticsEditProvider( { getService }: FtrProviderContext, - mlCommon: MlCommon + mlCommonUI: MlCommonUI ) { const testSubjects = getService('testSubjects'); const retry = getService('retry'); @@ -43,14 +43,14 @@ export function MachineLearningDataFrameAnalyticsEditProvider( ); }, async setJobDescriptionEdit(jobDescription: string) { - await mlCommon.setValueWithChecks('mlAnalyticsEditFlyoutDescriptionInput', jobDescription, { + await mlCommonUI.setValueWithChecks('mlAnalyticsEditFlyoutDescriptionInput', jobDescription, { clearWithKeyboard: true, }); await this.assertJobDescriptionEditValue(jobDescription); }, async setJobMmlEdit(mml: string) { - await mlCommon.setValueWithChecks('mlAnalyticsEditFlyoutmodelMemoryLimitInput', mml, { + await mlCommonUI.setValueWithChecks('mlAnalyticsEditFlyoutmodelMemoryLimitInput', mml, { clearWithKeyboard: true, }); await this.assertJobMmlEditValue(mml); diff --git a/x-pack/test/functional/services/ml/data_visualizer_file_based.ts b/x-pack/test/functional/services/ml/data_visualizer_file_based.ts index 8c5e40dd5dbdd..14c6f8de7d329 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_file_based.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_file_based.ts @@ -7,11 +7,11 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; -import { MlCommon } from './common'; +import { MlCommonUI } from './common_ui'; export function MachineLearningDataVisualizerFileBasedProvider( { getService, getPageObjects }: FtrProviderContext, - mlCommon: MlCommon + mlCommonUI: MlCommonUI ) { const log = getService('log'); const retry = getService('retry'); @@ -75,7 +75,7 @@ export function MachineLearningDataVisualizerFileBasedProvider( }, async setIndexName(indexName: string) { - await mlCommon.setValueWithChecks('mlFileDataVisIndexNameInput', indexName, { + await mlCommonUI.setValueWithChecks('mlFileDataVisIndexNameInput', indexName, { clearWithKeyboard: true, }); await this.assertIndexNameValue(indexName); diff --git a/x-pack/test/functional/services/ml/index.ts b/x-pack/test/functional/services/ml/index.ts index fd36bb0f47f95..d7ff60440bf31 100644 --- a/x-pack/test/functional/services/ml/index.ts +++ b/x-pack/test/functional/services/ml/index.ts @@ -9,7 +9,8 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { MachineLearningAnomaliesTableProvider } from './anomalies_table'; import { MachineLearningAnomalyExplorerProvider } from './anomaly_explorer'; import { MachineLearningAPIProvider } from './api'; -import { MachineLearningCommonProvider } from './common'; +import { MachineLearningCommonAPIProvider } from './common_api'; +import { MachineLearningCommonUIProvider } from './common_ui'; import { MachineLearningCustomUrlsProvider } from './custom_urls'; import { MachineLearningDataFrameAnalyticsProvider } from './data_frame_analytics'; import { MachineLearningDataFrameAnalyticsCreationProvider } from './data_frame_analytics_creation'; @@ -33,10 +34,12 @@ import { MachineLearningSecurityCommonProvider } from './security_common'; import { MachineLearningSecurityUIProvider } from './security_ui'; import { MachineLearningSettingsProvider } from './settings'; import { MachineLearningSingleMetricViewerProvider } from './single_metric_viewer'; +import { MachineLearningTestExecutionProvider } from './test_execution'; import { MachineLearningTestResourcesProvider } from './test_resources'; export function MachineLearningProvider(context: FtrProviderContext) { - const common = MachineLearningCommonProvider(context); + const commonAPI = MachineLearningCommonAPIProvider(context); + const commonUI = MachineLearningCommonUIProvider(context); const anomaliesTable = MachineLearningAnomaliesTableProvider(context); const anomalyExplorer = MachineLearningAnomalyExplorerProvider(context); @@ -45,22 +48,22 @@ export function MachineLearningProvider(context: FtrProviderContext) { const dataFrameAnalytics = MachineLearningDataFrameAnalyticsProvider(context, api); const dataFrameAnalyticsCreation = MachineLearningDataFrameAnalyticsCreationProvider( context, - common, + commonUI, api ); - const dataFrameAnalyticsEdit = MachineLearningDataFrameAnalyticsEditProvider(context, common); + const dataFrameAnalyticsEdit = MachineLearningDataFrameAnalyticsEditProvider(context, commonUI); const dataFrameAnalyticsTable = MachineLearningDataFrameAnalyticsTableProvider(context); const dataVisualizer = MachineLearningDataVisualizerProvider(context); - const dataVisualizerFileBased = MachineLearningDataVisualizerFileBasedProvider(context, common); + const dataVisualizerFileBased = MachineLearningDataVisualizerFileBasedProvider(context, commonUI); const dataVisualizerIndexBased = MachineLearningDataVisualizerIndexBasedProvider(context); const jobManagement = MachineLearningJobManagementProvider(context, api); const jobSelection = MachineLearningJobSelectionProvider(context); const jobSourceSelection = MachineLearningJobSourceSelectionProvider(context); const jobTable = MachineLearningJobTableProvider(context); const jobTypeSelection = MachineLearningJobTypeSelectionProvider(context); - const jobWizardAdvanced = MachineLearningJobWizardAdvancedProvider(context, common); + const jobWizardAdvanced = MachineLearningJobWizardAdvancedProvider(context, commonUI); const jobWizardCategorization = MachineLearningJobWizardCategorizationProvider(context); - const jobWizardCommon = MachineLearningJobWizardCommonProvider(context, common, customUrls); + const jobWizardCommon = MachineLearningJobWizardCommonProvider(context, commonUI, customUrls); const jobWizardMultiMetric = MachineLearningJobWizardMultiMetricProvider(context); const jobWizardPopulation = MachineLearningJobWizardPopulationProvider(context); const navigation = MachineLearningNavigationProvider(context); @@ -68,13 +71,15 @@ export function MachineLearningProvider(context: FtrProviderContext) { const securityUI = MachineLearningSecurityUIProvider(context, securityCommon); const settings = MachineLearningSettingsProvider(context); const singleMetricViewer = MachineLearningSingleMetricViewerProvider(context); + const testExecution = MachineLearningTestExecutionProvider(context); const testResources = MachineLearningTestResourcesProvider(context); return { anomaliesTable, anomalyExplorer, api, - common, + commonAPI, + commonUI, customUrls, dataFrameAnalytics, dataFrameAnalyticsCreation, @@ -98,6 +103,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { securityUI, settings, singleMetricViewer, + testExecution, testResources, }; } diff --git a/x-pack/test/functional/services/ml/job_wizard_advanced.ts b/x-pack/test/functional/services/ml/job_wizard_advanced.ts index e4d2ecf66f646..dbb0c1ceeb14e 100644 --- a/x-pack/test/functional/services/ml/job_wizard_advanced.ts +++ b/x-pack/test/functional/services/ml/job_wizard_advanced.ts @@ -6,11 +6,11 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; -import { MlCommon } from './common'; +import { MlCommonUI } from './common_ui'; export function MachineLearningJobWizardAdvancedProvider( { getService }: FtrProviderContext, - mlCommon: MlCommon + mlCommonUI: MlCommonUI ) { const comboBox = getService('comboBox'); const testSubjects = getService('testSubjects'); @@ -54,7 +54,7 @@ export function MachineLearningJobWizardAdvancedProvider( }, async setQueryDelay(queryDelay: string) { - await mlCommon.setValueWithChecks('mlJobWizardInputQueryDelay', queryDelay, { + await mlCommonUI.setValueWithChecks('mlJobWizardInputQueryDelay', queryDelay, { clearWithKeyboard: true, typeCharByChar: true, }); @@ -74,7 +74,7 @@ export function MachineLearningJobWizardAdvancedProvider( }, async setFrequency(frequency: string) { - await mlCommon.setValueWithChecks('mlJobWizardInputFrequency', frequency, { + await mlCommonUI.setValueWithChecks('mlJobWizardInputFrequency', frequency, { clearWithKeyboard: true, typeCharByChar: true, }); @@ -94,7 +94,7 @@ export function MachineLearningJobWizardAdvancedProvider( }, async setScrollSize(scrollSize: string) { - await mlCommon.setValueWithChecks('mlJobWizardInputScrollSize', scrollSize, { + await mlCommonUI.setValueWithChecks('mlJobWizardInputScrollSize', scrollSize, { clearWithKeyboard: true, typeCharByChar: true, }); @@ -303,7 +303,7 @@ export function MachineLearningJobWizardAdvancedProvider( }, async setDetectorDescription(description: string) { - await mlCommon.setValueWithChecks('mlAdvancedDetectorDescriptionInput', description, { + await mlCommonUI.setValueWithChecks('mlAdvancedDetectorDescriptionInput', description, { clearWithKeyboard: true, }); await this.assertDetectorDescriptionValue(description); diff --git a/x-pack/test/functional/services/ml/job_wizard_common.ts b/x-pack/test/functional/services/ml/job_wizard_common.ts index 2843c36e08a1d..97253c5f45303 100644 --- a/x-pack/test/functional/services/ml/job_wizard_common.ts +++ b/x-pack/test/functional/services/ml/job_wizard_common.ts @@ -6,12 +6,12 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; -import { MlCommon } from './common'; +import { MlCommonUI } from './common_ui'; import { MlCustomUrls } from './custom_urls'; export function MachineLearningJobWizardCommonProvider( { getService }: FtrProviderContext, - mlCommon: MlCommon, + mlCommonUI: MlCommonUI, customUrls: MlCustomUrls ) { const comboBox = getService('comboBox'); @@ -128,7 +128,7 @@ export function MachineLearningJobWizardCommonProvider( }, async setBucketSpan(bucketSpan: string) { - await mlCommon.setValueWithChecks('mlJobWizardInputBucketSpan', bucketSpan, { + await mlCommonUI.setValueWithChecks('mlJobWizardInputBucketSpan', bucketSpan, { clearWithKeyboard: true, typeCharByChar: true, }); @@ -148,7 +148,7 @@ export function MachineLearningJobWizardCommonProvider( }, async setJobId(jobId: string) { - await mlCommon.setValueWithChecks('mlJobWizardInputJobId', jobId, { + await mlCommonUI.setValueWithChecks('mlJobWizardInputJobId', jobId, { clearWithKeyboard: true, }); await this.assertJobIdValue(jobId); @@ -169,7 +169,7 @@ export function MachineLearningJobWizardCommonProvider( }, async setJobDescription(jobDescription: string) { - await mlCommon.setValueWithChecks('mlJobWizardInputJobDescription', jobDescription, { + await mlCommonUI.setValueWithChecks('mlJobWizardInputJobDescription', jobDescription, { clearWithKeyboard: true, }); await this.assertJobDescriptionValue(jobDescription); @@ -219,11 +219,11 @@ export function MachineLearningJobWizardCommonProvider( async addCalendar(calendarId: string) { await this.ensureAdditionalSettingsSectionOpen(); - await comboBox.setCustom('mlJobWizardComboBoxCalendars > comboBoxInput', calendarId); + await comboBox.set('mlJobWizardComboBoxCalendars > comboBoxInput', calendarId); const actualCalendarSelection = await this.getSelectedCalendars(); expect(actualCalendarSelection).to.contain( calendarId, - `Expected calendar selection to conatin '${calendarId}' (got '${actualCalendarSelection}')` + `Expected calendar selection to contain '${calendarId}' (got '${actualCalendarSelection}')` ); }, @@ -372,7 +372,7 @@ export function MachineLearningJobWizardCommonProvider( subj = advancedSectionSelector(subj); } await retry.tryForTime(15 * 1000, async () => { - await mlCommon.setValueWithChecks(subj, modelMemoryLimit, { clearWithKeyboard: true }); + await mlCommonUI.setValueWithChecks(subj, modelMemoryLimit, { clearWithKeyboard: true }); await this.assertModelMemoryLimitValue(modelMemoryLimit, { withAdvancedSection: sectionOptions.withAdvancedSection, }); diff --git a/x-pack/test/functional/services/ml/test_execution.ts b/x-pack/test/functional/services/ml/test_execution.ts new file mode 100644 index 0000000000000..230c20b4d2d5a --- /dev/null +++ b/x-pack/test/functional/services/ml/test_execution.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function MachineLearningTestExecutionProvider({ getService }: FtrProviderContext) { + const log = getService('log'); + + return { + async logTestStep(stepTitle: string) { + await log.debug(`=== TEST STEP === ${stepTitle}`); + }, + }; +} diff --git a/x-pack/test/functional/services/ml/test_resources.ts b/x-pack/test/functional/services/ml/test_resources.ts index 675ec890b9edf..949635964c613 100644 --- a/x-pack/test/functional/services/ml/test_resources.ts +++ b/x-pack/test/functional/services/ml/test_resources.ts @@ -6,7 +6,7 @@ import { ProvidedType } from '@kbn/test/types/ftr'; import { savedSearches, dashboards } from './test_resources_data'; -import { COMMON_REQUEST_HEADERS } from './common'; +import { COMMON_REQUEST_HEADERS } from './common_api'; import { FtrProviderContext } from '../../ftr_provider_context'; export enum SavedObjectType { diff --git a/x-pack/test/functional/services/transform/index.ts b/x-pack/test/functional/services/transform/index.ts index 24091ba773218..83a227ce56604 100644 --- a/x-pack/test/functional/services/transform/index.ts +++ b/x-pack/test/functional/services/transform/index.ts @@ -14,6 +14,7 @@ import { TransformSecurityCommonProvider } from './security_common'; import { TransformSecurityUIProvider } from './security_ui'; import { TransformSourceSelectionProvider } from './source_selection'; import { TransformTableProvider } from './transform_table'; +import { TransformTestExecutionProvider } from './test_execution'; import { TransformWizardProvider } from './wizard'; import { MachineLearningTestResourcesProvider } from '../ml/test_resources'; @@ -27,6 +28,7 @@ export function TransformProvider(context: FtrProviderContext) { const securityUI = TransformSecurityUIProvider(context, securityCommon); const sourceSelection = TransformSourceSelectionProvider(context); const table = TransformTableProvider(context); + const testExecution = TransformTestExecutionProvider(context); const testResources = MachineLearningTestResourcesProvider(context); const wizard = TransformWizardProvider(context); @@ -39,6 +41,7 @@ export function TransformProvider(context: FtrProviderContext) { securityUI, sourceSelection, table, + testExecution, testResources, wizard, }; diff --git a/x-pack/test/functional/services/transform/test_execution.ts b/x-pack/test/functional/services/transform/test_execution.ts new file mode 100644 index 0000000000000..ee27f9ee5194b --- /dev/null +++ b/x-pack/test/functional/services/transform/test_execution.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function TransformTestExecutionProvider({ getService }: FtrProviderContext) { + const log = getService('log'); + + return { + async logTestStep(stepTitle: string) { + await log.debug(`=== TEST STEP === ${stepTitle}`); + }, + }; +} diff --git a/yarn.lock b/yarn.lock index 0bc1ef72651e3..d13db64ad8b79 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1120,29 +1120,29 @@ enabled "2.0.x" kuler "^2.0.0" -"@elastic/apm-rum-core@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@elastic/apm-rum-core/-/apm-rum-core-5.5.0.tgz#e05ffd87b95420c788ed3be7cfbbbce1ff54bcf5" - integrity sha512-fPx65oZD495WdHQ3YA8TnzqmjqlvSxoXm0tZqXQKzKVv7CMsNkolnEPSAXFl0W5pmAVRvw6T+vMmxcVIGsCD4Q== +"@elastic/apm-rum-core@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@elastic/apm-rum-core/-/apm-rum-core-5.6.0.tgz#d1f643eb00e590d5884598a20bb54efb1490ee13" + integrity sha512-hG+lITWBQd0mw00BQ1zYVRKDCh5b9FKFiht9fMXcT0SENOsT5J37RIbQHPdVawluT7/mhDF07t4fR8V0xRB1/g== dependencies: error-stack-parser "^1.3.5" opentracing "^0.14.3" promise-polyfill "^8.1.3" -"@elastic/apm-rum-react@^1.2.2": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@elastic/apm-rum-react/-/apm-rum-react-1.2.2.tgz#b92f1491bae62de0b4296264afe73171f17af022" - integrity sha512-KXM2qxG4p1GeDoud9jpmUA19uuQxW4M+CgtrNIXuNwITMIw46qRLyl5zOIvy9dqHodvLIvZ7RWsFtSZH4kZnAQ== +"@elastic/apm-rum-react@^1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@elastic/apm-rum-react/-/apm-rum-react-1.2.3.tgz#fdf28492daca0ee6aa67c53a457eea1f16739e1e" + integrity sha512-oCjF/L46OYDRLHKt60l7aU+DFE484dwb/kKN12VZCOgueDZm4BCJd7yaosBtWDhnw0tl0Iqc0X3r4U7pQ+g9aA== dependencies: - "@elastic/apm-rum" "^5.4.0" + "@elastic/apm-rum" "^5.5.0" hoist-non-react-statics "^3.3.0" -"@elastic/apm-rum@^5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@elastic/apm-rum/-/apm-rum-5.4.0.tgz#2d87d5ca19f7f4a021c03f075d9d767894e88b3c" - integrity sha512-X4uaJlM28pyDOsD06serggspbTyz7Za0zFr+OWUntI6tQKu++Tn8yGsr6L2WuXhKNGhyJmrAfh13pmy9ZGyFcg== +"@elastic/apm-rum@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@elastic/apm-rum/-/apm-rum-5.5.0.tgz#24a8b4db0fa328c1e54710d18837e1adba7e51e0" + integrity sha512-uEOJG7Lm0CLtGfXOLXSsiPLpTPvrNUqlWQEKf/D77lpHRVWxBb56xa4X4CK2on8V1XzHDufcYBPcBcKSGozTLw== dependencies: - "@elastic/apm-rum-core" "^5.5.0" + "@elastic/apm-rum-core" "^5.6.0" "@elastic/charts@19.8.1": version "19.8.1"