diff --git a/data/.empty b/data/.empty deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin.setup.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin.setup.md index 1ed6059c23062..20181a5208b52 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin.setup.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin.setup.md @@ -7,7 +7,7 @@ Signature: ```typescript -setup(core: CoreSetup, { bfetch, expressions, uiActions, usageCollection }: DataSetupDependencies): DataPublicPluginSetup; +setup(core: CoreSetup, { bfetch, expressions, uiActions, usageCollection, inspector }: DataSetupDependencies): DataPublicPluginSetup; ``` ## Parameters @@ -15,7 +15,7 @@ setup(core: CoreSetup, { bfetch, e | Parameter | Type | Description | | --- | --- | --- | | core | CoreSetup<DataStartDependencies, DataPublicPluginStart> | | -| { bfetch, expressions, uiActions, usageCollection } | DataSetupDependencies | | +| { bfetch, expressions, uiActions, usageCollection, inspector } | DataSetupDependencies | | Returns: diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.adapters.data.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.adapters.data.md deleted file mode 100644 index 0ddbcb3546d1e..0000000000000 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.adapters.data.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [Adapters](./kibana-plugin-plugins-embeddable-public.adapters.md) > [data](./kibana-plugin-plugins-embeddable-public.adapters.data.md) - -## Adapters.data property - -Signature: - -```typescript -data?: DataAdapter; -``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.adapters.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.adapters.md index 47484dc79d88c..8ba759e333fa3 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.adapters.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.adapters.md @@ -16,6 +16,5 @@ export interface Adapters | Property | Type | Description | | --- | --- | --- | -| [data](./kibana-plugin-plugins-embeddable-public.adapters.data.md) | DataAdapter | | | [requests](./kibana-plugin-plugins-embeddable-public.adapters.requests.md) | RequestAdapter | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.md index a03ea32482011..1b97c9e11f83c 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.md @@ -20,6 +20,7 @@ | [ExpressionsService](./kibana-plugin-plugins-expressions-public.expressionsservice.md) | ExpressionsService class is used for multiple purposes:1. It implements the same Expressions service that can be used on both: (1) server-side and (2) browser-side. 2. It implements the same Expressions service that users can fork/clone, thus have their own instance of the Expressions plugin. 3. ExpressionsService defines the public contracts of \*setup\* and \*start\* Kibana Platform life-cycles for ease-of-use on server-side and browser-side. 4. ExpressionsService creates a bound version of all exported contract functions. 5. Functions are bound the way there are:\`\`\`ts registerFunction = (...args: Parameters<Executor\['registerFunction'\]> ): ReturnType<Executor\['registerFunction'\]> => this.executor.registerFunction(...args); \`\`\`so that JSDoc appears in developers IDE when they use those plugins.expressions.registerFunction(. | | [ExpressionType](./kibana-plugin-plugins-expressions-public.expressiontype.md) | | | [FunctionsRegistry](./kibana-plugin-plugins-expressions-public.functionsregistry.md) | | +| [TablesAdapter](./kibana-plugin-plugins-expressions-public.tablesadapter.md) | | | [TypesRegistry](./kibana-plugin-plugins-expressions-public.typesregistry.md) | | ## Enumerations diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.tablesadapter.logdatatable.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.tablesadapter.logdatatable.md new file mode 100644 index 0000000000000..281f48918416b --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.tablesadapter.logdatatable.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [TablesAdapter](./kibana-plugin-plugins-expressions-public.tablesadapter.md) > [logDatatable](./kibana-plugin-plugins-expressions-public.tablesadapter.logdatatable.md) + +## TablesAdapter.logDatatable() method + +Signature: + +```typescript +logDatatable(name: string, datatable: Datatable): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | +| datatable | Datatable | | + +Returns: + +`void` + diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.tablesadapter.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.tablesadapter.md new file mode 100644 index 0000000000000..c489eff4cc252 --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.tablesadapter.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [TablesAdapter](./kibana-plugin-plugins-expressions-public.tablesadapter.md) + +## TablesAdapter class + +Signature: + +```typescript +export declare class TablesAdapter extends EventEmitter +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [tables](./kibana-plugin-plugins-expressions-public.tablesadapter.tables.md) | | {
[key: string]: Datatable;
} | | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [logDatatable(name, datatable)](./kibana-plugin-plugins-expressions-public.tablesadapter.logdatatable.md) | | | + diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.tablesadapter.tables.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.tablesadapter.tables.md new file mode 100644 index 0000000000000..ef5ada66e50b3 --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.tablesadapter.tables.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [TablesAdapter](./kibana-plugin-plugins-expressions-public.tablesadapter.md) > [tables](./kibana-plugin-plugins-expressions-public.tablesadapter.tables.md) + +## TablesAdapter.tables property + +Signature: + +```typescript +get tables(): { + [key: string]: Datatable; + }; +``` diff --git a/docs/management/images/tags/bulk-assign-selection.png b/docs/management/images/tags/bulk-assign-selection.png new file mode 100644 index 0000000000000..1c8687226b51f Binary files /dev/null and b/docs/management/images/tags/bulk-assign-selection.png differ diff --git a/docs/management/images/tags/create-tag.png b/docs/management/images/tags/create-tag.png new file mode 100644 index 0000000000000..a88e754457b9f Binary files /dev/null and b/docs/management/images/tags/create-tag.png differ diff --git a/docs/management/images/tags/manage-assignments-flyout.png b/docs/management/images/tags/manage-assignments-flyout.png new file mode 100644 index 0000000000000..a4e0b7a49d96a Binary files /dev/null and b/docs/management/images/tags/manage-assignments-flyout.png differ diff --git a/docs/management/images/tags/tag-management-section.png b/docs/management/images/tags/tag-management-section.png new file mode 100644 index 0000000000000..4aae3ea067820 Binary files /dev/null and b/docs/management/images/tags/tag-management-section.png differ diff --git a/docs/management/managing-tags.asciidoc b/docs/management/managing-tags.asciidoc new file mode 100644 index 0000000000000..3da98b2281fdc --- /dev/null +++ b/docs/management/managing-tags.asciidoc @@ -0,0 +1,75 @@ +[role="xpack"] +[[managing-tags]] +== Tags + +Tags enable you to categorize your saved objects. You can then easily filter for related objects based on shared tags. + +To begin, open the main menu, click *Stack Management*, then click *Tags*. + +[role="screenshot"] +image::images/tags/tag-management-section.png[Tags management section] + +[float] +=== Required permissions + +Access to *Tags* requires the `Tag Management` {kib} privilege. To add the privilege, open the menu, +click *Stack Management*, then click *Roles*. + +In addition: + +* The `read` privilege allows you to assign tags to the saved objects for which you have write permission. +* The `write` privilege enables you to create, edit, and delete tags. + + +NOTE: Having the `Tag Management` {kib} privilege is not required to +view tags assigned on objects the user has `read` access to, or to filter objects by tags +in {kib} applications or from the navigational search. + +[float] +[[settings-create-tag]] +=== Create a tag + +Create a tag to assign to your saved objects. + +. Click *Create tag*. ++ +[role="screenshot"] +image::images/tags/create-tag.png[Tag creation popin] +. Enter a name and select a color for the new tag. ++ +The name can include alphanumeric characters (English letters and digits), `:`, `-`, `_` and the space character, +and cannot be longer than 50 characters. +. Click *Create tag*. + +[float] +[[settings-assign-tag]] +=== Assign a tag to saved objects + +Assign or remove tags to one or more saved objects. You must have `write` permission +on the objects to which you assign the tags. + +. Click the action (...) icon in the tag row, and then select the *Manage assignments* action. ++ +[role="screenshot"] +image::images/tags/manage-assignments-flyout.png[Assign flyout] +. Select the objects to which you want to assign or remove tags. +. Click on *Save tag assignments*. + +TIP: To assign multiple tags to objects at once, select their checkboxes +and then select *Manage tag assignments* from the *selected tags* menu. + +[role="screenshot"] +image::images/tags/bulk-assign-selection.png[Bulk assign tags] + +[float] +[[settings-delete-tag]] +=== Delete a tag + +Delete a tag and remove it from any saved objects. + +. Click the action (...) icon in the tag row, and then select the *Delete* action. + +. Click *Delete tag*. + +TIP: To delete multiple tags at once, select their checkboxes in the list view, +and then select *Delete* action from the *selected tags* menu. \ No newline at end of file diff --git a/docs/user/management.asciidoc b/docs/user/management.asciidoc index 8933bf64d2736..9d2b0ae593b34 100644 --- a/docs/user/management.asciidoc +++ b/docs/user/management.asciidoc @@ -139,6 +139,9 @@ a| <> | Copy, edit, delete, import, and export your saved objects. These include dashboards, visualizations, maps, index patterns, Canvas workpads, and more. +| <> +|Create, manage, and assign tags to your saved objects. + | <> | Create spaces to organize your dashboards and other saved objects into categories. A space is isolated from all other spaces, @@ -196,6 +199,8 @@ include::{kib-repo-dir}/management/rollups/create_and_manage_rollups.asciidoc[] include::{kib-repo-dir}/management/managing-saved-objects.asciidoc[] +include::{kib-repo-dir}/management/managing-tags.asciidoc[] + include::security/index.asciidoc[] include::{kib-repo-dir}/management/snapshot-restore/index.asciidoc[] diff --git a/package.json b/package.json index 9bc363242a332..b4b3cbe22b715 100644 --- a/package.json +++ b/package.json @@ -609,7 +609,7 @@ "cpy": "^8.1.1", "cronstrue": "^1.51.0", "css-loader": "^3.4.2", - "cypress": "^6.0.1", + "cypress": "^6.1.0", "cypress-cucumber-preprocessor": "^2.5.2", "cypress-multi-reporters": "^1.4.0", "d3": "3.5.17", diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts index 8947a5ec2171c..937172272f179 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts @@ -226,7 +226,7 @@ describe('migration actions', () => { } `); }); - it('resolves right after waiting for index status to be green if clone target already existed', async () => { + it.skip('resolves right after waiting for index status to be green if clone target already existed', async () => { // Create a yellow index await client.indices.create({ index: 'yellow_then_green_index', @@ -872,7 +872,7 @@ describe('migration actions', () => { afterAll(async () => { await client.indices.delete({ index: 'yellow_then_green_index' }); }); - it('resolves right after waiting for an index status to be green if the index already existed', async () => { + it.skip('resolves right after waiting for an index status to be green if the index already existed', async () => { // Create a yellow index await client.indices.create( { diff --git a/src/dev/build/tasks/os_packages/package_scripts/post_install.sh b/src/dev/build/tasks/os_packages/package_scripts/post_install.sh index 68b4ca45ec911..bdc51d09a2aa5 100644 --- a/src/dev/build/tasks/os_packages/package_scripts/post_install.sh +++ b/src/dev/build/tasks/os_packages/package_scripts/post_install.sh @@ -77,7 +77,7 @@ case $1 in --gid "<%= group %>" \ --shell /sbin/nologin \ --comment "kibana service user" \ - "<%= user %>" \ + "<%= user %>" echo " OK" fi diff --git a/src/plugins/dashboard/public/application/actions/export_csv_action.tsx b/src/plugins/dashboard/public/application/actions/export_csv_action.tsx index 4f78a738095d2..a31ecbea88bab 100644 --- a/src/plugins/dashboard/public/application/actions/export_csv_action.tsx +++ b/src/plugins/dashboard/public/application/actions/export_csv_action.tsx @@ -65,7 +65,7 @@ export class ExportCSVAction implements ActionByType { } private hasDatatableContent = (adapters: Adapters | undefined) => { - return Object.keys(adapters?.tables || {}).length > 0; + return Object.keys(adapters?.tables || {}).length > 0 && adapters!.tables.allowCsvExport; }; private getFormatter = (): FormatFactory | undefined => { @@ -76,7 +76,7 @@ export class ExportCSVAction implements ActionByType { private getDataTableContent = (adapters: Adapters | undefined) => { if (this.hasDatatableContent(adapters)) { - return adapters?.tables; + return adapters?.tables.tables; } return; }; diff --git a/src/plugins/dashboard/public/dashboard_strings.ts b/src/plugins/dashboard/public/dashboard_strings.ts index 4aa552893ab9b..9bede02c75b94 100644 --- a/src/plugins/dashboard/public/dashboard_strings.ts +++ b/src/plugins/dashboard/public/dashboard_strings.ts @@ -134,7 +134,7 @@ export const dashboardExportCsvAction = { }), getUntitledFilename: () => i18n.translate('dashboard.actions.downloadOptionsUnsavedFilename', { - defaultMessage: 'unsaved', + defaultMessage: 'untitled', }), }; diff --git a/src/plugins/data/common/es_query/kuery/index.ts b/src/plugins/data/common/es_query/kuery/index.ts index 4184dea62ef2c..5b6cfab030acb 100644 --- a/src/plugins/data/common/es_query/kuery/index.ts +++ b/src/plugins/data/common/es_query/kuery/index.ts @@ -18,7 +18,7 @@ */ export { KQLSyntaxError } from './kuery_syntax_error'; -export { nodeTypes } from './node_types'; +export { nodeTypes, nodeBuilder } from './node_types'; export * from './ast'; export * from './types'; diff --git a/src/plugins/data/common/es_query/kuery/node_types/index.ts b/src/plugins/data/common/es_query/kuery/node_types/index.ts index 22e73e791df9a..bec42f89c5b71 100644 --- a/src/plugins/data/common/es_query/kuery/node_types/index.ts +++ b/src/plugins/data/common/es_query/kuery/node_types/index.ts @@ -24,6 +24,7 @@ import * as wildcard from './wildcard'; import { NodeTypes } from './types'; export { NodeTypes }; +export { nodeBuilder } from './node_builder'; export const nodeTypes: NodeTypes = { // This requires better typing of the different typings and their return types. diff --git a/src/plugins/inspector/public/views/data/index.ts b/src/plugins/data/common/es_query/kuery/node_types/node_builder.ts similarity index 53% rename from src/plugins/inspector/public/views/data/index.ts rename to src/plugins/data/common/es_query/kuery/node_types/node_builder.ts index d201ad89022be..4b77566dbc32b 100644 --- a/src/plugins/inspector/public/views/data/index.ts +++ b/src/plugins/data/common/es_query/kuery/node_types/node_builder.ts @@ -16,24 +16,23 @@ * specific language governing permissions and limitations * under the License. */ -import { lazy } from 'react'; -import { i18n } from '@kbn/i18n'; -import { InspectorViewDescription } from '../../types'; -import { Adapters } from '../../../common'; +import { KueryNode, nodeTypes } from '../types'; -const DataViewComponent = lazy(() => import('./components/data_view')); - -export const getDataViewDescription = (): InspectorViewDescription => ({ - title: i18n.translate('inspector.data.dataTitle', { - defaultMessage: 'Data', - }), - order: 10, - help: i18n.translate('inspector.data.dataDescriptionTooltip', { - defaultMessage: 'View the data behind the visualization', - }), - shouldShow(adapters: Adapters) { - return Boolean(adapters.data); +export const nodeBuilder = { + is: (fieldName: string, value: string | KueryNode) => { + return nodeTypes.function.buildNodeWithArgumentNodes('is', [ + nodeTypes.literal.buildNode(fieldName), + typeof value === 'string' ? nodeTypes.literal.buildNode(value) : value, + nodeTypes.literal.buildNode(false), + ]); + }, + or: ([first, ...args]: KueryNode[]): KueryNode => { + return args.length ? nodeTypes.function.buildNode('or', [first, nodeBuilder.or(args)]) : first; + }, + and: ([first, ...args]: KueryNode[]): KueryNode => { + return args.length + ? nodeTypes.function.buildNode('and', [first, nodeBuilder.and(args)]) + : first; }, - component: DataViewComponent, -}); +}; diff --git a/src/plugins/data/common/search/aggs/aggs_service.test.ts b/src/plugins/data/common/search/aggs/aggs_service.test.ts index 160860bcce591..eeaebecfe7f76 100644 --- a/src/plugins/data/common/search/aggs/aggs_service.test.ts +++ b/src/plugins/data/common/search/aggs/aggs_service.test.ts @@ -203,11 +203,12 @@ describe('Aggs service', () => { describe('start()', () => { test('exposes proper contract', () => { const start = service.start(startDeps); - expect(Object.keys(start).length).toBe(4); + expect(Object.keys(start).length).toBe(5); expect(start).toHaveProperty('calculateAutoTimeExpression'); expect(start).toHaveProperty('getDateMetaByDatatableColumn'); expect(start).toHaveProperty('createAggConfigs'); expect(start).toHaveProperty('types'); + expect(start).toHaveProperty('datatableUtilities'); }); test('types registry returns uninitialized type providers', () => { diff --git a/src/plugins/data/common/search/aggs/aggs_service.ts b/src/plugins/data/common/search/aggs/aggs_service.ts index b6afa708f9e6f..4b1756fabf1a7 100644 --- a/src/plugins/data/common/search/aggs/aggs_service.ts +++ b/src/plugins/data/common/search/aggs/aggs_service.ts @@ -18,7 +18,7 @@ */ import { ExpressionsServiceSetup } from 'src/plugins/expressions/common'; -import { IndexPattern, UI_SETTINGS } from '../../../common'; +import { CreateAggConfigParams, IndexPattern, UI_SETTINGS } from '../../../common'; import { GetConfigFn } from '../../types'; import { AggConfigs, @@ -29,6 +29,7 @@ import { } from './'; import { AggsCommonSetup, AggsCommonStart } from './types'; import { getDateMetaByDatatableColumn } from './utils/time_column_meta'; +import { getDatatableColumnUtilities } from './utils/datatable_column_meta'; /** @internal */ export const aggsRequiredUiSettings = [ @@ -88,6 +89,15 @@ export class AggsCommonService { const aggTypesStart = this.aggTypesRegistry.start(); const calculateAutoTimeExpression = getCalculateAutoTimeExpression(getConfig); + const createAggConfigs = ( + indexPattern: IndexPattern, + configStates?: CreateAggConfigParams[] + ) => { + return new AggConfigs(indexPattern, configStates, { + typesRegistry: aggTypesStart, + }); + }; + return { calculateAutoTimeExpression, getDateMetaByDatatableColumn: getDateMetaByDatatableColumn({ @@ -96,11 +106,12 @@ export class AggsCommonService { getConfig, isDefaultTimezone, }), - createAggConfigs: (indexPattern, configStates = [], schemas) => { - return new AggConfigs(indexPattern, configStates, { - typesRegistry: aggTypesStart, - }); - }, + datatableUtilities: getDatatableColumnUtilities({ + getIndexPattern, + createAggConfigs, + aggTypesStart, + }), + createAggConfigs, types: aggTypesStart, }; } diff --git a/src/plugins/data/common/search/aggs/types.ts b/src/plugins/data/common/search/aggs/types.ts index f3ae7d66dca96..1055777396f5f 100644 --- a/src/plugins/data/common/search/aggs/types.ts +++ b/src/plugins/data/common/search/aggs/types.ts @@ -94,6 +94,7 @@ import { CreateAggConfigParams, getCalculateAutoTimeExpression, METRIC_TYPES, + AggConfig, } from './'; export { IAggConfig, AggConfigSerialized } from './agg_config'; @@ -127,10 +128,14 @@ export interface AggsCommonStart { getDateMetaByDatatableColumn: ( column: DatatableColumn ) => Promise; + datatableUtilities: { + getIndexPattern: (column: DatatableColumn) => Promise; + getAggConfig: (column: DatatableColumn) => Promise; + isFilterable: (column: DatatableColumn) => boolean; + }; createAggConfigs: ( indexPattern: IndexPattern, - configStates?: CreateAggConfigParams[], - schemas?: Record + configStates?: CreateAggConfigParams[] ) => InstanceType; types: ReturnType; } diff --git a/src/plugins/data/common/search/aggs/utils/datatable_column_meta.ts b/src/plugins/data/common/search/aggs/utils/datatable_column_meta.ts new file mode 100644 index 0000000000000..38865f05727a9 --- /dev/null +++ b/src/plugins/data/common/search/aggs/utils/datatable_column_meta.ts @@ -0,0 +1,68 @@ +/* + * 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 { DatatableColumn } from 'src/plugins/expressions/common'; +import { IndexPattern } from '../../../index_patterns'; +import { AggConfigs, CreateAggConfigParams } from '../agg_configs'; +import { AggTypesRegistryStart } from '../agg_types_registry'; +import { IAggType } from '../agg_type'; + +export interface MetaByColumnDeps { + getIndexPattern: (id: string) => Promise; + createAggConfigs: ( + indexPattern: IndexPattern, + configStates?: CreateAggConfigParams[] + ) => InstanceType; + aggTypesStart: AggTypesRegistryStart; +} + +export const getDatatableColumnUtilities = (deps: MetaByColumnDeps) => { + const { getIndexPattern, createAggConfigs, aggTypesStart } = deps; + + const getIndexPatternFromDatatableColumn = async (column: DatatableColumn) => { + if (!column.meta.index) return; + + return await getIndexPattern(column.meta.index); + }; + + const getAggConfigFromDatatableColumn = async (column: DatatableColumn) => { + const indexPattern = await getIndexPatternFromDatatableColumn(column); + + if (!indexPattern) return; + + const aggConfigs = await createAggConfigs(indexPattern, [column.meta.sourceParams as any]); + return aggConfigs.aggs[0]; + }; + + const isFilterableAggDatatableColumn = (column: DatatableColumn) => { + if (column.meta.source !== 'esaggs') { + return false; + } + const aggType = (aggTypesStart.get(column.meta.sourceParams?.type as string) as any)( + {} + ) as IAggType; + return Boolean(aggType.createFilter); + }; + + return { + getIndexPattern: getIndexPatternFromDatatableColumn, + getAggConfig: getAggConfigFromDatatableColumn, + isFilterable: isFilterableAggDatatableColumn, + }; +}; diff --git a/src/plugins/data/common/search/expressions/esaggs/build_tabular_inspector_data.ts b/src/plugins/data/common/search/expressions/esaggs/build_tabular_inspector_data.ts deleted file mode 100644 index 2db3694884e2c..0000000000000 --- a/src/plugins/data/common/search/expressions/esaggs/build_tabular_inspector_data.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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 { set } from '@elastic/safer-lodash-set'; -import { - FormattedData, - TabularData, - TabularDataValue, -} from '../../../../../../plugins/inspector/common'; -import { Filter } from '../../../es_query'; -import { FormatFactory } from '../../../field_formats/utils'; -import { TabbedTable } from '../../tabify'; -import { createFilter } from './create_filter'; - -/** - * Type borrowed from the client-side FilterManager['addFilters']. - * - * We need to use a custom type to make this isomorphic since FilterManager - * doesn't exist on the server. - * - * @internal - */ -export type AddFilters = (filters: Filter[] | Filter, pinFilterStatus?: boolean) => void; - -/** - * This function builds tabular data from the response and attaches it to the - * inspector. It will only be called when the data view in the inspector is opened. - * - * @internal - */ -export async function buildTabularInspectorData( - table: TabbedTable, - { - addFilters, - deserializeFieldFormat, - }: { - addFilters?: AddFilters; - deserializeFieldFormat: FormatFactory; - } -): Promise { - const aggConfigs = table.columns.map((column) => column.aggConfig); - const rows = table.rows.map((row) => { - return table.columns.reduce>((prev, cur, colIndex) => { - const value = row[cur.id]; - - let format = cur.aggConfig.toSerializedFieldFormat(); - if (Object.keys(format).length < 1) { - // If no format exists, fall back to string as a default - format = { id: 'string' }; - } - const fieldFormatter = deserializeFieldFormat(format); - - prev[`col-${colIndex}-${cur.aggConfig.id}`] = new FormattedData( - value, - fieldFormatter.convert(value) - ); - return prev; - }, {}); - }); - - const columns = table.columns.map((col, colIndex) => { - const field = col.aggConfig.getField(); - const isCellContentFilterable = col.aggConfig.isFilterable() && (!field || field.filterable); - return { - name: col.name, - field: `col-${colIndex}-${col.aggConfig.id}`, - filter: - addFilters && - isCellContentFilterable && - ((value: TabularDataValue) => { - const rowIndex = rows.findIndex( - (row) => row[`col-${colIndex}-${col.aggConfig.id}`].raw === value.raw - ); - const filter = createFilter(aggConfigs, table, colIndex, rowIndex, value.raw); - - if (filter) { - addFilters(filter); - } - }), - filterOut: - addFilters && - isCellContentFilterable && - ((value: TabularDataValue) => { - const rowIndex = rows.findIndex( - (row) => row[`col-${colIndex}-${col.aggConfig.id}`].raw === value.raw - ); - const filter = createFilter(aggConfigs, table, colIndex, rowIndex, value.raw); - - if (filter) { - const notOther = value.raw !== '__other__'; - const notMissing = value.raw !== '__missing__'; - if (Array.isArray(filter)) { - filter.forEach((f) => set(f, 'meta.negate', notOther && notMissing)); - } else { - set(filter, 'meta.negate', notOther && notMissing); - } - addFilters(filter); - } - }), - }; - }); - - return { columns, rows }; -} diff --git a/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts b/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts index 6c53a8a09274a..2274fcfd6b8d5 100644 --- a/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts +++ b/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts @@ -34,7 +34,6 @@ import { AggsStart, AggExpressionType } from '../../aggs'; import { ISearchStartSearchSource } from '../../search_source'; import { KibanaContext } from '../kibana_context_type'; -import { AddFilters } from './build_tabular_inspector_data'; import { handleRequest, RequestHandlerParams } from './request_handler'; const name = 'esaggs'; @@ -59,7 +58,6 @@ export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition< /** @internal */ export interface EsaggsStartDependencies { - addFilters?: AddFilters; aggs: AggsStart; deserializeFieldFormat: FormatFactory; indexPatterns: IndexPatternsContract; diff --git a/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts b/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts index aba498f720ec1..78d169e8529c5 100644 --- a/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts +++ b/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts @@ -39,7 +39,6 @@ describe('esaggs expression function - public', () => { jest.clearAllMocks(); mockParams = { abortSignal: (jest.fn() as unknown) as jest.Mocked, - addFilters: jest.fn(), aggs: ({ aggs: [{ type: { name: 'terms', postFlightRequest: jest.fn().mockResolvedValue({}) } }], setTimeRange: jest.fn(), diff --git a/src/plugins/data/common/search/expressions/esaggs/request_handler.ts b/src/plugins/data/common/search/expressions/esaggs/request_handler.ts index 3c1745409ebcd..e4385526ee6e8 100644 --- a/src/plugins/data/common/search/expressions/esaggs/request_handler.ts +++ b/src/plugins/data/common/search/expressions/esaggs/request_handler.ts @@ -36,13 +36,9 @@ import { ISearchStartSearchSource } from '../../search_source'; import { tabifyAggResponse } from '../../tabify'; import { getRequestInspectorStats, getResponseInspectorStats } from '../utils'; -import type { AddFilters } from './build_tabular_inspector_data'; -import { buildTabularInspectorData } from './build_tabular_inspector_data'; - /** @internal */ export interface RequestHandlerParams { abortSignal?: AbortSignal; - addFilters?: AddFilters; aggs: IAggConfigs; deserializeFieldFormat: FormatFactory; filters?: Filter[]; @@ -59,7 +55,6 @@ export interface RequestHandlerParams { export const handleRequest = async ({ abortSignal, - addFilters, aggs, deserializeFieldFormat, filters, @@ -199,16 +194,5 @@ export const handleRequest = async ({ const tabifiedResponse = tabifyAggResponse(aggs, response, tabifyParams); - if (inspectorAdapters.data) { - inspectorAdapters.data.setTabularLoader( - () => - buildTabularInspectorData(tabifiedResponse, { - addFilters, - deserializeFieldFormat, - }), - { returnsFormattedValues: true } - ); - } - return tabifiedResponse; }; diff --git a/src/plugins/data/kibana.json b/src/plugins/data/kibana.json index 06b083e0ff3aa..a09ab12f0c6f0 100644 --- a/src/plugins/data/kibana.json +++ b/src/plugins/data/kibana.json @@ -7,7 +7,8 @@ "bfetch", "expressions", "uiActions", - "share" + "share", + "inspector" ], "optionalPlugins": ["usageCollection"], "extraPublicDirs": ["common"], diff --git a/src/plugins/data/public/index.scss b/src/plugins/data/public/index.scss index a51fde079f10b..467efa98934ec 100644 --- a/src/plugins/data/public/index.scss +++ b/src/plugins/data/public/index.scss @@ -1 +1,2 @@ @import './ui/index'; +@import './utils/table_inspector_view/index'; diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 458024151c585..eb3a053b78a2d 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -70,6 +70,7 @@ import { import { SavedObjectsClientPublicToCommon } from './index_patterns'; import { getIndexPatternLoad } from './index_patterns/expressions'; import { UsageCollectionSetup } from '../../usage_collection/public'; +import { getTableViewDescription } from './utils/table_inspector_view'; declare module '../../ui_actions/public' { export interface ActionContextMapping { @@ -104,7 +105,7 @@ export class DataPublicPlugin public setup( core: CoreSetup, - { bfetch, expressions, uiActions, usageCollection }: DataSetupDependencies + { bfetch, expressions, uiActions, usageCollection, inspector }: DataSetupDependencies ): DataPublicPluginSetup { const startServices = createStartServicesGetter(core.getStartServices); @@ -141,6 +142,15 @@ export class DataPublicPlugin expressions, }); + inspector.registerView( + getTableViewDescription(() => ({ + uiActions: startServices().plugins.uiActions, + uiSettings: startServices().core.uiSettings, + fieldFormats: startServices().self.fieldFormats, + isFilterable: startServices().self.search.aggs.datatableUtilities.isFilterable, + })) + ); + return { autocomplete: this.autocomplete.setup(core, { timefilter: queryService.timefilter }), search: searchService, diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 373aa4dee53fd..e5df6d860b404 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -20,6 +20,7 @@ import { CoreSetup } from 'src/core/public'; import { CoreSetup as CoreSetup_2 } from 'kibana/public'; import { CoreStart } from 'kibana/public'; import { CoreStart as CoreStart_2 } from 'src/core/public'; +import * as CSS from 'csstype'; import { Datatable as Datatable_2 } from 'src/plugins/expressions'; import { Datatable as Datatable_3 } from 'src/plugins/expressions/common'; import { DatatableColumn as DatatableColumn_2 } from 'src/plugins/expressions'; @@ -66,11 +67,12 @@ import { Plugin as Plugin_2 } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_3 } from 'kibana/public'; import { PopoverAnchorPosition } from '@elastic/eui'; +import * as PropTypes from 'prop-types'; import { PublicContract } from '@kbn/utility-types'; import { PublicMethodsOf } from '@kbn/utility-types'; import { PublicUiSettingsParams } from 'src/core/server/types'; import React from 'react'; -import * as React_2 from 'react'; +import * as React_3 from 'react'; import { RecursiveReadonly } from '@kbn/utility-types'; import { Reporter } from '@kbn/analytics'; import { RequestAdapter } from 'src/plugins/inspector/common'; @@ -1888,7 +1890,7 @@ export class Plugin implements Plugin_2); // (undocumented) - setup(core: CoreSetup, { bfetch, expressions, uiActions, usageCollection }: DataSetupDependencies): DataPublicPluginSetup; + setup(core: CoreSetup, { bfetch, expressions, uiActions, usageCollection, inspector }: DataSetupDependencies): DataPublicPluginSetup; // (undocumented) start(core: CoreStart_2, { uiActions }: DataStartDependencies): DataPublicPluginStart; // (undocumented) @@ -2560,7 +2562,7 @@ export const UI_SETTINGS: { // src/plugins/data/common/es_query/filters/phrases_filter.ts:31:3 - (ae-forgotten-export) The symbol "PhrasesFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:64:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts // src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:128:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/search/aggs/types.ts:145:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/search/aggs/types.ts:150:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts // src/plugins/data/common/search/search_source/search_source.ts:197:7 - (ae-forgotten-export) The symbol "SearchFieldValue" needs to be exported by the entry point index.d.ts // src/plugins/data/public/field_formats/field_formats_service.ts:67:3 - (ae-forgotten-export) The symbol "FormatFactory" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/search/aggs/aggs_service.test.ts b/src/plugins/data/public/search/aggs/aggs_service.test.ts index de747d234b441..bc4992384b0c2 100644 --- a/src/plugins/data/public/search/aggs/aggs_service.test.ts +++ b/src/plugins/data/public/search/aggs/aggs_service.test.ts @@ -88,11 +88,12 @@ describe('AggsService - public', () => { describe('start()', () => { test('exposes proper contract', () => { const start = service.start(startDeps); - expect(Object.keys(start).length).toBe(4); + expect(Object.keys(start).length).toBe(5); expect(start).toHaveProperty('calculateAutoTimeExpression'); expect(start).toHaveProperty('getDateMetaByDatatableColumn'); expect(start).toHaveProperty('createAggConfigs'); expect(start).toHaveProperty('types'); + expect(start).toHaveProperty('datatableUtilities'); }); test('types registry returns initialized agg types', () => { diff --git a/src/plugins/data/public/search/aggs/aggs_service.ts b/src/plugins/data/public/search/aggs/aggs_service.ts index 85e0f604bb8b5..7b5edac0280d9 100644 --- a/src/plugins/data/public/search/aggs/aggs_service.ts +++ b/src/plugins/data/public/search/aggs/aggs_service.ts @@ -102,6 +102,7 @@ export class AggsService { const { calculateAutoTimeExpression, getDateMetaByDatatableColumn, + datatableUtilities, types, } = this.aggsCommonService.start({ getConfig: this.getConfig!, @@ -148,7 +149,8 @@ export class AggsService { return { calculateAutoTimeExpression, getDateMetaByDatatableColumn, - createAggConfigs: (indexPattern, configStates = [], schemas) => { + datatableUtilities, + createAggConfigs: (indexPattern, configStates = []) => { return new AggConfigs(indexPattern, configStates, { typesRegistry }); }, types: typesRegistry, diff --git a/src/plugins/data/public/search/aggs/mocks.ts b/src/plugins/data/public/search/aggs/mocks.ts index abc930f00b594..bc02b48d67f7b 100644 --- a/src/plugins/data/public/search/aggs/mocks.ts +++ b/src/plugins/data/public/search/aggs/mocks.ts @@ -68,6 +68,11 @@ export const searchAggsSetupMock = (): AggsSetup => ({ export const searchAggsStartMock = (): AggsStart => ({ calculateAutoTimeExpression: getCalculateAutoTimeExpression(getConfig), getDateMetaByDatatableColumn: jest.fn(), + datatableUtilities: { + isFilterable: jest.fn(), + getAggConfig: jest.fn(), + getIndexPattern: jest.fn(), + }, createAggConfigs: jest.fn().mockImplementation((indexPattern, configStates = [], schemas) => { return new AggConfigs(indexPattern, configStates, { typesRegistry: mockAggTypesRegistry(), diff --git a/src/plugins/data/public/search/expressions/esaggs.test.ts b/src/plugins/data/public/search/expressions/esaggs.test.ts index 10ed22c861188..abb95ed05b12e 100644 --- a/src/plugins/data/public/search/expressions/esaggs.test.ts +++ b/src/plugins/data/public/search/expressions/esaggs.test.ts @@ -72,7 +72,6 @@ describe('esaggs expression function - public', () => { types: {}, }; startDependencies = { - addFilters: jest.fn(), aggs: ({ createAggConfigs: jest.fn().mockReturnValue({ foo: 'bar' }), } as unknown) as jest.Mocked, @@ -113,7 +112,6 @@ describe('esaggs expression function - public', () => { expect(handleEsaggsRequest).toHaveBeenCalledWith(null, args, { abortSignal: mockHandlers.abortSignal, - addFilters: startDependencies.addFilters, aggs: { foo: 'bar' }, deserializeFieldFormat: startDependencies.deserializeFieldFormat, filters: undefined, diff --git a/src/plugins/data/public/search/expressions/esaggs.ts b/src/plugins/data/public/search/expressions/esaggs.ts index 4a078bf9b2e55..d8d90ea464a73 100644 --- a/src/plugins/data/public/search/expressions/esaggs.ts +++ b/src/plugins/data/public/search/expressions/esaggs.ts @@ -49,7 +49,6 @@ export function getFunctionDefinition({ ...getEsaggsMeta(), async fn(input, args, { inspectorAdapters, abortSignal, getSearchSessionId }) { const { - addFilters, aggs, deserializeFieldFormat, indexPatterns, @@ -64,7 +63,6 @@ export function getFunctionDefinition({ return await handleEsaggsRequest(input, args, { abortSignal: (abortSignal as unknown) as AbortSignal, - addFilters, aggs: aggConfigs, deserializeFieldFormat, filters: get(input, 'filters', undefined), @@ -104,9 +102,8 @@ export function getEsaggs({ return getFunctionDefinition({ getStartDependencies: async () => { const [, , self] = await getStartServices(); - const { fieldFormats, indexPatterns, query, search } = self; + const { fieldFormats, indexPatterns, search } = self; return { - addFilters: query.filterManager.addFilters.bind(query.filterManager), aggs: search.aggs, deserializeFieldFormat: fieldFormats.deserialize.bind(fieldFormats), indexPatterns, diff --git a/src/plugins/data/public/types.ts b/src/plugins/data/public/types.ts index 4082fbe55094c..c7b66acfc6c7a 100644 --- a/src/plugins/data/public/types.ts +++ b/src/plugins/data/public/types.ts @@ -31,6 +31,7 @@ import { QuerySetup, QueryStart } from './query'; import { IndexPatternsContract } from './index_patterns'; import { IndexPatternSelectProps, StatefulSearchBarProps } from './ui'; import { UsageCollectionSetup } from '../../usage_collection/public'; +import { Setup as InspectorSetup } from '../../inspector/public'; export interface DataPublicPluginEnhancements { search: SearchEnhancements; @@ -40,6 +41,7 @@ export interface DataSetupDependencies { bfetch: BfetchPublicSetup; expressions: ExpressionsSetup; uiActions: UiActionsSetup; + inspector: InspectorSetup; usageCollection?: UsageCollectionSetup; } diff --git a/src/plugins/inspector/public/views/data/_data_table.scss b/src/plugins/data/public/utils/table_inspector_view/_data_table.scss similarity index 100% rename from src/plugins/inspector/public/views/data/_data_table.scss rename to src/plugins/data/public/utils/table_inspector_view/_data_table.scss diff --git a/src/plugins/inspector/public/views/data/_index.scss b/src/plugins/data/public/utils/table_inspector_view/_index.scss similarity index 100% rename from src/plugins/inspector/public/views/data/_index.scss rename to src/plugins/data/public/utils/table_inspector_view/_index.scss diff --git a/src/plugins/inspector/public/views/data/components/__snapshots__/data_view.test.tsx.snap b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap similarity index 68% rename from src/plugins/inspector/public/views/data/components/__snapshots__/data_view.test.tsx.snap rename to src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap index ec68b307734e3..4320fc186783b 100644 --- a/src/plugins/inspector/public/views/data/components/__snapshots__/data_view.test.tsx.snap +++ b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap @@ -1,17 +1,18 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Inspector Data View component should render empty state 1`] = ` -

@@ -272,7 +159,7 @@ exports[`Inspector Data View component should render empty state 1`] = `

@@ -295,7 +182,7 @@ exports[`Inspector Data View component should render empty state 1`] = ` > No data available @@ -316,7 +203,7 @@ exports[`Inspector Data View component should render empty state 1`] = `

The element did not provide any data. @@ -329,7 +216,7 @@ exports[`Inspector Data View component should render empty state 1`] = ` - + `; exports[`Inspector Data View component should render loading state 1`] = ` @@ -442,6 +329,20 @@ exports[`Inspector Data View component should render loading state 1`] = ` } } > +

loading
diff --git a/src/plugins/inspector/public/views/data/components/data_table.tsx b/src/plugins/data/public/utils/table_inspector_view/components/data_table.tsx similarity index 57% rename from src/plugins/inspector/public/views/data/components/data_table.tsx rename to src/plugins/data/public/utils/table_inspector_view/components/data_table.tsx index 69be069272f79..f4d1a8988da78 100644 --- a/src/plugins/inspector/public/views/data/components/data_table.tsx +++ b/src/plugins/data/public/utils/table_inspector_view/components/data_table.tsx @@ -35,8 +35,10 @@ import { i18n } from '@kbn/i18n'; import { DataDownloadOptions } from './download_options'; import { DataViewRow, DataViewColumn } from '../types'; -import { TabularData } from '../../../../common/adapters/data/types'; import { IUiSettingsClient } from '../../../../../../core/public'; +import { Datatable, DatatableColumn } from '../../../../../expressions/public'; +import { FieldFormatsStart } from '../../../field_formats'; +import { UiActionsStart } from '../../../../../ui_actions/public'; interface DataTableFormatState { columns: DataViewColumn[]; @@ -44,10 +46,21 @@ interface DataTableFormatState { } interface DataTableFormatProps { - data: TabularData; + data: Datatable; exportTitle: string; uiSettings: IUiSettingsClient; - isFormatted?: boolean; + fieldFormats: FieldFormatsStart; + uiActions: UiActionsStart; + isFilterable: (column: DatatableColumn) => boolean; +} + +interface RenderCellArguments { + table: Datatable; + columnIndex: number; + rowIndex: number; + formattedValue: string; + uiActions: UiActionsStart; + isFilterable: boolean; } export class DataTableFormat extends Component { @@ -55,25 +68,35 @@ export class DataTableFormat extends Component - {isFormatted ? value.formatted : value} + {formattedValue} - {dataColumn.filter && ( + {isFilterable && ( } @@ -81,23 +104,29 @@ export class DataTableFormat extends Component dataColumn.filter(value)} + onClick={() => { + const value = table.rows[rowIndex][column.id]; + const eventData = { table, column: columnIndex, row: rowIndex, value }; + uiActions.executeTriggerActions('VALUE_CLICK_TRIGGER', { + data: { data: [eventData] }, + }); + }} /> )} - {dataColumn.filterOut && ( + {isFilterable && ( } @@ -105,12 +134,21 @@ export class DataTableFormat extends Component dataColumn.filterOut(value)} + onClick={() => { + const value = table.rows[rowIndex][column.id]; + const eventData = { table, column: columnIndex, row: rowIndex, value }; + uiActions.executeTriggerActions('VALUE_CLICK_TRIGGER', { + data: { data: [eventData], negate: true }, + }); + }} /> @@ -121,7 +159,12 @@ export class DataTableFormat extends Component ({ - name: dataColumn.name, - field: dataColumn.field, - sortable: isFormatted ? (row: DataViewRow) => row[dataColumn.field].raw : true, - render: (value: any) => DataTableFormat.renderCell(dataColumn, value, isFormatted), - })); + const columns = data.columns.map((dataColumn: any, index: number) => { + const formatParams = { id: 'string', ...dataColumn.meta.params }; + const fieldFormatter = fieldFormats.deserialize(formatParams); + const filterable = isFilterable(dataColumn); + return { + originalColumn: () => dataColumn, + name: dataColumn.name, + field: dataColumn.id, + sortable: true, + render: (value: any) => { + const formattedValue = fieldFormatter.convert(value); + const rowIndex = data.rows.findIndex((row) => row[dataColumn.id] === value) || 0; + + return DataTableFormat.renderCell({ + table: data, + columnIndex: index, + rowIndex, + formattedValue, + uiActions, + isFilterable: filterable, + }); + }, + }; + }); return { columns, rows: data.rows }; } @@ -152,12 +213,12 @@ export class DataTableFormat extends Component diff --git a/src/plugins/inspector/public/views/data/components/data_view.test.tsx b/src/plugins/data/public/utils/table_inspector_view/components/data_view.test.tsx similarity index 77% rename from src/plugins/inspector/public/views/data/components/data_view.test.tsx rename to src/plugins/data/public/utils/table_inspector_view/components/data_view.test.tsx index 82bec5ee3fe8c..975a91548d799 100644 --- a/src/plugins/inspector/public/views/data/components/data_view.test.tsx +++ b/src/plugins/data/public/utils/table_inspector_view/components/data_view.test.tsx @@ -18,11 +18,11 @@ */ import React, { Suspense } from 'react'; -import { getDataViewDescription } from '../index'; -import { DataAdapter } from '../../../../common/adapters/data'; +import { getTableViewDescription } from '../index'; import { mountWithIntl } from '@kbn/test/jest'; +import { TablesAdapter } from '../../../../../expressions/common'; -jest.mock('../lib/export_csv', () => ({ +jest.mock('./export_csv', () => ({ exportAsCsv: jest.fn(), })); @@ -30,13 +30,18 @@ describe('Inspector Data View', () => { let DataView: any; beforeEach(() => { - DataView = getDataViewDescription(); + DataView = getTableViewDescription(() => ({ + uiActions: {} as any, + uiSettings: {} as any, + fieldFormats: {} as any, + isFilterable: jest.fn(), + })); }); it('should only show if data adapter is present', () => { - const adapter = new DataAdapter(); + const adapter = new TablesAdapter(); - expect(DataView.shouldShow({ data: adapter })).toBe(true); + expect(DataView.shouldShow({ tables: adapter })).toBe(true); expect(DataView.shouldShow({})).toBe(false); }); @@ -44,7 +49,7 @@ describe('Inspector Data View', () => { let adapters: any; beforeEach(() => { - adapters = { data: new DataAdapter() }; + adapters = { tables: new TablesAdapter() }; }); it('should render loading state', () => { @@ -60,9 +65,7 @@ describe('Inspector Data View', () => { it('should render empty state', async () => { const component = mountWithIntl(); // eslint-disable-line react/jsx-pascal-case - const tabularLoader = Promise.resolve(null); - adapters.data.setTabularLoader(() => tabularLoader); - await tabularLoader; + adapters.tables.logDatatable({ columns: [{ id: '1' }], rows: [{ '1': 123 }] }); // After the loader has resolved we'll still need one update, to "flush" the state changes component.update(); expect(component).toMatchSnapshot(); diff --git a/src/plugins/data/public/utils/table_inspector_view/components/data_view.tsx b/src/plugins/data/public/utils/table_inspector_view/components/data_view.tsx new file mode 100644 index 0000000000000..97dca45d742c9 --- /dev/null +++ b/src/plugins/data/public/utils/table_inspector_view/components/data_view.tsx @@ -0,0 +1,137 @@ +/* + * 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 React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiEmptyPrompt } from '@elastic/eui'; + +import { DataTableFormat } from './data_table'; +import { IUiSettingsClient } from '../../../../../../core/public'; +import { InspectorViewProps, Adapters } from '../../../../../inspector/public'; +import { UiActionsStart } from '../../../../../ui_actions/public'; +import { FieldFormatsStart } from '../../../field_formats'; +import { TablesAdapter, Datatable, DatatableColumn } from '../../../../../expressions/public'; + +interface DataViewComponentState { + datatable: Datatable; + adapters: Adapters; +} + +interface DataViewComponentProps extends InspectorViewProps { + uiSettings: IUiSettingsClient; + uiActions: UiActionsStart; + fieldFormats: FieldFormatsStart; + isFilterable: (column: DatatableColumn) => boolean; +} + +class DataViewComponent extends Component { + static propTypes = { + adapters: PropTypes.object.isRequired, + title: PropTypes.string.isRequired, + uiSettings: PropTypes.object, + uiActions: PropTypes.object.isRequired, + fieldFormats: PropTypes.object.isRequired, + isFilterable: PropTypes.func.isRequired, + }; + + state = {} as DataViewComponentState; + + static getDerivedStateFromProps( + nextProps: Readonly, + state: DataViewComponentState + ) { + if (state && nextProps.adapters === state.adapters) { + return null; + } + + const { tables } = nextProps.adapters.tables; + const keys = Object.keys(tables); + const datatable = keys.length ? tables[keys[0]] : undefined; + + return { + adapters: nextProps.adapters, + datatable, + }; + } + + onUpdateData = (tables: TablesAdapter['tables']) => { + const keys = Object.keys(tables); + const datatable = keys.length ? tables[keys[0]] : undefined; + + if (datatable) { + this.setState({ + datatable, + }); + } + }; + + componentDidMount() { + this.props.adapters.tables!.on('change', this.onUpdateData); + } + + componentWillUnmount() { + this.props.adapters.tables!.removeListener('change', this.onUpdateData); + } + + static renderNoData() { + return ( + + + + } + body={ + +

+ +

+
+ } + /> + ); + } + + render() { + if (!this.state.datatable) { + return DataViewComponent.renderNoData(); + } + + return ( + + ); + } +} + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export default DataViewComponent; diff --git a/src/plugins/data/public/utils/table_inspector_view/components/data_view_wrapper.tsx b/src/plugins/data/public/utils/table_inspector_view/components/data_view_wrapper.tsx new file mode 100644 index 0000000000000..d8b96da36628c --- /dev/null +++ b/src/plugins/data/public/utils/table_inspector_view/components/data_view_wrapper.tsx @@ -0,0 +1,47 @@ +/* + * 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 React, { lazy } from 'react'; +import { IUiSettingsClient } from 'kibana/public'; +import { UiActionsStart } from '../../../../../ui_actions/public'; +import { FieldFormatsStart } from '../../../field_formats'; +import { DatatableColumn } from '../../../../../expressions/common/expression_types/specs'; + +const DataViewComponent = lazy(() => import('./data_view')); + +export const getDataViewComponentWrapper = ( + getStartServices: () => { + uiActions: UiActionsStart; + fieldFormats: FieldFormatsStart; + uiSettings: IUiSettingsClient; + isFilterable: (column: DatatableColumn) => boolean; + } +) => { + return (props: any) => { + return ( + + ); + }; +}; diff --git a/src/plugins/inspector/public/views/data/components/download_options.tsx b/src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx similarity index 77% rename from src/plugins/inspector/public/views/data/components/download_options.tsx rename to src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx index cedb723091638..f849f598e9c69 100644 --- a/src/plugins/inspector/public/views/data/components/download_options.tsx +++ b/src/plugins/data/public/utils/table_inspector_view/components/download_options.tsx @@ -24,8 +24,8 @@ import { i18n } from '@kbn/i18n'; import { EuiButton, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; import { DataViewColumn, DataViewRow } from '../types'; - -import { exportAsCsv } from '../lib/export_csv'; +import { exportAsCsv } from './export_csv'; +import { FieldFormatsStart } from '../../../field_formats'; interface DataDownloadOptionsState { isPopoverOpen: boolean; @@ -38,6 +38,7 @@ interface DataDownloadOptionsProps { csvSeparator: string; quoteValues: boolean; isFormatted?: boolean; + fieldFormats: FieldFormatsStart; } class DataDownloadOptions extends Component { @@ -45,9 +46,9 @@ class DataDownloadOptions extends Component { + exportCsv = (isFormatted: boolean = true) => { let filename = this.props.title; if (!filename || filename.length === 0) { - filename = i18n.translate('inspector.data.downloadOptionsUnsavedFilename', { + filename = i18n.translate('data.inspector.table.downloadOptionsUnsavedFilename', { defaultMessage: 'unsaved', }); } @@ -79,38 +80,24 @@ class DataDownloadOptions extends Component { - this.exportCsv({ - valueFormatter: (item: any) => item.formatted, - }); + this.exportCsv(true); }; exportFormattedAsRawCsv = () => { - this.exportCsv({ - valueFormatter: (item: any) => item.raw, - }); + this.exportCsv(false); }; - renderUnformattedDownload() { - return ( - - - - ); - } - renderFormattedDownloads() { const button = ( @@ -121,14 +108,14 @@ class DataDownloadOptions extends Component } toolTipPosition="left" > , @@ -137,13 +124,13 @@ class DataDownloadOptions extends Component } toolTipPosition="left" > - + , ]; @@ -162,9 +149,7 @@ class DataDownloadOptions extends Component escape(col.name, quoteValues)); + const formatters = columns.map((column) => { + return fieldFormats.deserialize(column.originalColumn().meta.params); + }); + // Convert the array of row objects to an array of row arrays - const orderedFieldNames = columns.map((col) => col.field); const csvRows = rows.map((row) => { - return orderedFieldNames.map((field) => - escape(valueFormatter ? valueFormatter(row[field]) : row[field], quoteValues) - ); + return columns.map((column, i) => { + return escape( + isFormatted ? formatters[i].convert(row[column.field]) : row[column.field], + quoteValues + ); + }); }); return ( @@ -69,14 +77,18 @@ export function exportAsCsv({ filename, columns, rows, - valueFormatter, + isFormatted, csvSeparator, quoteValues, + fieldFormats, }: any) { const type = 'text/plain;charset=utf-8'; - const csv = new Blob([buildCsv(columns, rows, csvSeparator, quoteValues, valueFormatter)], { - type, - }); + const csv = new Blob( + [buildCsv(columns, rows, csvSeparator, quoteValues, isFormatted, fieldFormats)], + { + type, + } + ); saveAs(csv, filename); } diff --git a/src/plugins/data/public/utils/table_inspector_view/index.ts b/src/plugins/data/public/utils/table_inspector_view/index.ts new file mode 100644 index 0000000000000..3769298af05f3 --- /dev/null +++ b/src/plugins/data/public/utils/table_inspector_view/index.ts @@ -0,0 +1,46 @@ +/* + * 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 { IUiSettingsClient } from 'kibana/public'; +import { Adapters, InspectorViewDescription } from '../../../../inspector/public'; +import { getDataViewComponentWrapper } from './components/data_view_wrapper'; +import { UiActionsStart } from '../../../../ui_actions/public'; +import { FieldFormatsStart } from '../../field_formats'; +import { DatatableColumn } from '../../../../expressions/common/expression_types/specs'; + +export const getTableViewDescription = ( + getStartServices: () => { + uiActions: UiActionsStart; + fieldFormats: FieldFormatsStart; + isFilterable: (column: DatatableColumn) => boolean; + uiSettings: IUiSettingsClient; + } +): InspectorViewDescription => ({ + title: i18n.translate('data.inspector.table.dataTitle', { + defaultMessage: 'Data', + }), + order: 10, + help: i18n.translate('data.inspector.table..dataDescriptionTooltip', { + defaultMessage: 'View the data behind the visualization', + }), + shouldShow(adapters: Adapters) { + return Boolean(adapters.tables); + }, + component: getDataViewComponentWrapper(getStartServices), +}); diff --git a/src/plugins/inspector/public/views/data/types.ts b/src/plugins/data/public/utils/table_inspector_view/types.ts similarity index 70% rename from src/plugins/inspector/public/views/data/types.ts rename to src/plugins/data/public/utils/table_inspector_view/types.ts index 31de9eb3a152e..dc85c3c2e3135 100644 --- a/src/plugins/inspector/public/views/data/types.ts +++ b/src/plugins/data/public/utils/table_inspector_view/types.ts @@ -17,15 +17,20 @@ * under the License. */ -import { TabularDataRow } from '../../../common/adapters'; +import { Datatable, DatatableColumn, DatatableRow } from '../../../../expressions/common'; -type DataViewColumnRender = (value: string, _item: TabularDataRow) => string; +type DataViewColumnRender = (value: string, _item: DatatableRow) => string; export interface DataViewColumn { + originalColumn: () => DatatableColumn; name: string; field: string; - sortable: (item: TabularDataRow) => string | number; + sortable: (item: DatatableRow) => string | number; render: DataViewColumnRender; } -export type DataViewRow = TabularDataRow; +export type DataViewRow = DatatableRow; + +export interface TableInspectorAdapter { + [key: string]: Datatable; +} diff --git a/src/plugins/data/server/search/aggs/aggs_service.ts b/src/plugins/data/server/search/aggs/aggs_service.ts index c23f748b1eeb5..ae1cf3054ec3f 100644 --- a/src/plugins/data/server/search/aggs/aggs_service.ts +++ b/src/plugins/data/server/search/aggs/aggs_service.ts @@ -86,6 +86,7 @@ export class AggsService { const { calculateAutoTimeExpression, getDateMetaByDatatableColumn, + datatableUtilities, types, } = this.aggsCommonService.start({ getConfig, @@ -130,7 +131,8 @@ export class AggsService { return { calculateAutoTimeExpression, getDateMetaByDatatableColumn, - createAggConfigs: (indexPattern, configStates = [], schemas) => { + datatableUtilities, + createAggConfigs: (indexPattern, configStates = []) => { return new AggConfigs(indexPattern, configStates, { typesRegistry }); }, types: typesRegistry, diff --git a/src/plugins/data/server/search/aggs/mocks.ts b/src/plugins/data/server/search/aggs/mocks.ts index 7b7f3d3c40652..66a6aa2c7d803 100644 --- a/src/plugins/data/server/search/aggs/mocks.ts +++ b/src/plugins/data/server/search/aggs/mocks.ts @@ -70,6 +70,11 @@ export const searchAggsSetupMock = (): AggsSetup => ({ const commonStartMock = (): AggsCommonStart => ({ calculateAutoTimeExpression: getCalculateAutoTimeExpression(getConfig), getDateMetaByDatatableColumn: jest.fn(), + datatableUtilities: { + getIndexPattern: jest.fn(), + getAggConfig: jest.fn(), + isFilterable: jest.fn(), + }, createAggConfigs: jest.fn().mockImplementation((indexPattern, configStates = [], schemas) => { return new AggConfigs(indexPattern, configStates, { typesRegistry: mockAggTypesRegistry(), diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_exportable_embeddable.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_exportable_embeddable.tsx index 338eb4877a50a..00429c8df0cb1 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_exportable_embeddable.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_exportable_embeddable.tsx @@ -23,18 +23,21 @@ export class ContactCardExportableEmbeddable extends ContactCardEmbeddable { public getInspectorAdapters = () => { return { tables: { - layer1: { - type: 'datatable', - columns: [ - { id: 'firstName', name: 'First Name' }, - { id: 'originalLastName', name: 'Last Name' }, - ], - rows: [ - { - firstName: this.getInput().firstName, - orignialLastName: this.getInput().lastName, - }, - ], + allowCsvExport: true, + tables: { + layer1: { + type: 'datatable', + columns: [ + { id: 'firstName', name: 'First Name' }, + { id: 'originalLastName', name: 'Last Name' }, + ], + rows: [ + { + firstName: this.getInput().firstName, + orignialLastName: this.getInput().lastName, + }, + ], + }, }, }, }; diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index 60b694d628b78..03818fccda0bc 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -109,10 +109,6 @@ export const ACTION_EDIT_PANEL = "editPanel"; export interface Adapters { // (undocumented) [key: string]: any; - // Warning: (ae-forgotten-export) The symbol "DataAdapter" needs to be exported by the entry point index.d.ts - // - // (undocumented) - data?: DataAdapter; // Warning: (ae-forgotten-export) The symbol "RequestAdapter" needs to be exported by the entry point index.d.ts // // (undocumented) diff --git a/src/plugins/expressions/common/execution/execution.test.ts b/src/plugins/expressions/common/execution/execution.test.ts index 10a18d0cbf435..9819c721d7275 100644 --- a/src/plugins/expressions/common/execution/execution.test.ts +++ b/src/plugins/expressions/common/execution/execution.test.ts @@ -220,10 +220,10 @@ describe('Execution', () => { }); describe('inspector adapters', () => { - test('by default, "data" and "requests" inspector adapters are available', async () => { + test('by default, "tables" and "requests" inspector adapters are available', async () => { const { result } = (await run('introspectContext key="inspectorAdapters"')) as any; expect(result).toMatchObject({ - data: expect.any(Object), + tables: expect.any(Object), requests: expect.any(Object), }); }); diff --git a/src/plugins/expressions/common/execution/execution.ts b/src/plugins/expressions/common/execution/execution.ts index c5c7d82e223b0..609022f8a55c0 100644 --- a/src/plugins/expressions/common/execution/execution.ts +++ b/src/plugins/expressions/common/execution/execution.ts @@ -23,7 +23,7 @@ import { Executor } from '../executor'; import { createExecutionContainer, ExecutionContainer } from './container'; import { createError } from '../util'; import { abortSignalToPromise, Defer, now } from '../../../kibana_utils/common'; -import { RequestAdapter, DataAdapter, Adapters } from '../../../inspector/common'; +import { RequestAdapter, Adapters } from '../../../inspector/common'; import { isExpressionValueError, ExpressionValueError } from '../expression_types/specs/error'; import { ExpressionAstExpression, @@ -34,11 +34,12 @@ import { ExpressionAstNode, } from '../ast'; import { ExecutionContext, DefaultInspectorAdapters } from './types'; -import { getType, ExpressionValue } from '../expression_types'; +import { getType, ExpressionValue, Datatable } from '../expression_types'; import { ArgumentType, ExpressionFunction } from '../expression_functions'; import { getByAlias } from '../util/get_by_alias'; import { ExecutionContract } from './execution_contract'; import { ExpressionExecutionParams } from '../service'; +import { TablesAdapter } from '../util/tables_adapter'; /** * AbortController is not available in Node until v15, so we @@ -72,7 +73,7 @@ export interface ExecutionParams { const createDefaultInspectorAdapters = (): DefaultInspectorAdapters => ({ requests: new RequestAdapter(), - data: new DataAdapter(), + tables: new TablesAdapter(), }); export class Execution< @@ -166,6 +167,9 @@ export class Execution< ast, }); + const inspectorAdapters = + execution.params.inspectorAdapters || createDefaultInspectorAdapters(); + this.context = { getSearchContext: () => this.execution.params.searchContext || {}, getSearchSessionId: () => execution.params.searchSessionId, @@ -175,7 +179,10 @@ export class Execution< variables: execution.params.variables || {}, types: executor.getTypes(), abortSignal: this.abortController.signal, - inspectorAdapters: execution.params.inspectorAdapters || createDefaultInspectorAdapters(), + inspectorAdapters, + logDatatable: (name: string, datatable: Datatable) => { + inspectorAdapters.tables[name] = datatable; + }, ...(execution.params as any).extraContext, }; } diff --git a/src/plugins/expressions/common/execution/execution_contract.test.ts b/src/plugins/expressions/common/execution/execution_contract.test.ts index eaf7e6ea862eb..0a6704a8cb2f6 100644 --- a/src/plugins/expressions/common/execution/execution_contract.test.ts +++ b/src/plugins/expressions/common/execution/execution_contract.test.ts @@ -71,7 +71,7 @@ describe('ExecutionContract', () => { const execution = createExecution('foo bar=123'); const contract = new ExecutionContract(execution); expect(contract.inspect()).toMatchObject({ - data: expect.any(Object), + tables: expect.any(Object), requests: expect.any(Object), }); }); diff --git a/src/plugins/expressions/common/execution/types.ts b/src/plugins/expressions/common/execution/types.ts index a41f97118c4b2..a1b25c3802f4b 100644 --- a/src/plugins/expressions/common/execution/types.ts +++ b/src/plugins/expressions/common/execution/types.ts @@ -21,8 +21,9 @@ import type { KibanaRequest } from 'src/core/server'; import { ExpressionType, SerializableState } from '../expression_types'; -import { Adapters, DataAdapter, RequestAdapter } from '../../../inspector/common'; +import { Adapters, RequestAdapter } from '../../../inspector/common'; import { SavedObject, SavedObjectAttributes } from '../../../../core/public'; +import { TablesAdapter } from '../util/tables_adapter'; /** * `ExecutionContext` is an object available to all functions during a single execution; @@ -89,5 +90,5 @@ export interface ExecutionContext< */ export interface DefaultInspectorAdapters extends Adapters { requests: RequestAdapter; - data: DataAdapter; + tables: TablesAdapter; } diff --git a/src/plugins/expressions/common/util/index.ts b/src/plugins/expressions/common/util/index.ts index ee677d54ce968..ea900687650f8 100644 --- a/src/plugins/expressions/common/util/index.ts +++ b/src/plugins/expressions/common/util/index.ts @@ -19,3 +19,4 @@ export * from './create_error'; export * from './get_by_alias'; +export * from './tables_adapter'; diff --git a/src/plugins/inspector/common/adapters/data/formatted_data.ts b/src/plugins/expressions/common/util/tables_adapter.ts similarity index 66% rename from src/plugins/inspector/common/adapters/data/formatted_data.ts rename to src/plugins/expressions/common/util/tables_adapter.ts index 08c956f27d011..30b869818f999 100644 --- a/src/plugins/inspector/common/adapters/data/formatted_data.ts +++ b/src/plugins/expressions/common/util/tables_adapter.ts @@ -17,6 +17,18 @@ * under the License. */ -export class FormattedData { - constructor(public readonly raw: any, public readonly formatted: any) {} +import { EventEmitter } from 'events'; +import { Datatable } from '../expression_types/specs'; + +export class TablesAdapter extends EventEmitter { + private _tables: { [key: string]: Datatable } = {}; + + public logDatatable(name: string, datatable: Datatable): void { + this._tables[name] = datatable; + this.emit('change', this.tables); + } + + public get tables() { + return this._tables; + } } diff --git a/src/plugins/expressions/public/index.ts b/src/plugins/expressions/public/index.ts index 385055bc2fdc2..cd43c90d5ff45 100644 --- a/src/plugins/expressions/public/index.ts +++ b/src/plugins/expressions/public/index.ts @@ -117,4 +117,5 @@ export { ExpressionsService, ExpressionsServiceSetup, ExpressionsServiceStart, + TablesAdapter, } from '../common'; diff --git a/src/plugins/expressions/public/loader.test.ts b/src/plugins/expressions/public/loader.test.ts index 598b614a326a9..0508b36fad385 100644 --- a/src/plugins/expressions/public/loader.test.ts +++ b/src/plugins/expressions/public/loader.test.ts @@ -166,7 +166,7 @@ describe('ExpressionLoader', () => { it('inspect() returns correct inspector adapters', () => { const expressionDataHandler = new ExpressionLoader(element, expressionString, {}); - expect(expressionDataHandler.inspect()).toHaveProperty('data'); + expect(expressionDataHandler.inspect()).toHaveProperty('tables'); expect(expressionDataHandler.inspect()).toHaveProperty('requests'); }); }); diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md index 6eb0e71c58e3f..bb1f5dd9270d5 100644 --- a/src/plugins/expressions/public/public.api.md +++ b/src/plugins/expressions/public/public.api.md @@ -1096,6 +1096,18 @@ export interface SerializedFieldFormat> { // @public (undocumented) export type Style = ExpressionTypeStyle; +// Warning: (ae-missing-release-tag) "TablesAdapter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class TablesAdapter extends EventEmitter { + // (undocumented) + logDatatable(name: string, datatable: Datatable): void; + // (undocumented) + get tables(): { + [key: string]: Datatable; + }; + } + // Warning: (ae-missing-release-tag) "TextAlignment" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public diff --git a/src/plugins/inspector/common/adapters/data/data_adapter.ts b/src/plugins/inspector/common/adapters/data/data_adapter.ts deleted file mode 100644 index a21aa7db39145..0000000000000 --- a/src/plugins/inspector/common/adapters/data/data_adapter.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 { EventEmitter } from 'events'; -import { TabularCallback, TabularHolder, TabularLoaderOptions } from './types'; - -export class DataAdapter extends EventEmitter { - private tabular?: TabularCallback; - private tabularOptions?: TabularLoaderOptions; - - public setTabularLoader(callback: TabularCallback, options: TabularLoaderOptions = {}): void { - this.tabular = callback; - this.tabularOptions = options; - this.emit('change', 'tabular'); - } - - public getTabular(): Promise { - if (!this.tabular || !this.tabularOptions) { - return Promise.resolve({ data: null, options: {} }); - } - const options = this.tabularOptions; - return Promise.resolve(this.tabular()).then((data) => ({ data, options })); - } -} diff --git a/src/plugins/inspector/common/adapters/data/data_adapters.test.ts b/src/plugins/inspector/common/adapters/data/data_adapters.test.ts deleted file mode 100644 index 7cc52807548f0..0000000000000 --- a/src/plugins/inspector/common/adapters/data/data_adapters.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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 { DataAdapter } from './data_adapter'; - -describe('DataAdapter', () => { - let adapter: DataAdapter; - - beforeEach(() => { - adapter = new DataAdapter(); - }); - - describe('getTabular()', () => { - it('should return a null promise when called before initialized', () => { - expect(adapter.getTabular()).resolves.toEqual({ - data: null, - options: {}, - }); - }); - - it('should call the provided callback and resolve with its value', async () => { - const data = { columns: [], rows: [] }; - const spy = jest.fn(() => data); - adapter.setTabularLoader(spy); - expect(spy).not.toBeCalled(); - const result = await adapter.getTabular(); - expect(spy).toBeCalled(); - expect(result.data).toBe(data); - }); - - it('should pass through options specified via setTabularLoader', async () => { - const data = { columns: [], rows: [] }; - adapter.setTabularLoader(() => data, { returnsFormattedValues: true }); - const result = await adapter.getTabular(); - expect(result.options).toEqual({ returnsFormattedValues: true }); - }); - - it('should return options set when starting loading data', async () => { - const data = { columns: [], rows: [] }; - adapter.setTabularLoader(() => data, { returnsFormattedValues: true }); - const waitForResult = adapter.getTabular(); - adapter.setTabularLoader(() => data, { returnsFormattedValues: false }); - const result = await waitForResult; - expect(result.options).toEqual({ returnsFormattedValues: true }); - }); - }); - - it('should emit a "tabular" event when a new tabular loader is specified', () => { - const data = { columns: [], rows: [] }; - const spy = jest.fn(); - adapter.once('change', spy); - adapter.setTabularLoader(() => data); - expect(spy).toBeCalled(); - }); -}); diff --git a/src/plugins/inspector/common/adapters/data/index.ts b/src/plugins/inspector/common/adapters/data/index.ts deleted file mode 100644 index a8b1abcd8cd7e..0000000000000 --- a/src/plugins/inspector/common/adapters/data/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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. - */ - -export * from './data_adapter'; -export * from './formatted_data'; -export * from './types'; diff --git a/src/plugins/inspector/common/adapters/data/types.ts b/src/plugins/inspector/common/adapters/data/types.ts deleted file mode 100644 index 040724f4ae36e..0000000000000 --- a/src/plugins/inspector/common/adapters/data/types.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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. - */ - -export interface TabularDataValue { - formatted: string; - raw: unknown; -} - -export interface TabularDataColumn { - name: string; - field: string; - filter?: (value: TabularDataValue) => void; - filterOut?: (value: TabularDataValue) => void; -} - -export type TabularDataRow = Record; - -export interface TabularData { - columns: TabularDataColumn[]; - rows: TabularDataRow[]; -} - -export type TabularCallback = () => TabularData | Promise; - -export interface TabularHolder { - data: TabularData | null; - options: TabularLoaderOptions; -} - -export interface TabularLoaderOptions { - returnsFormattedValues?: boolean; -} diff --git a/src/plugins/inspector/common/adapters/index.ts b/src/plugins/inspector/common/adapters/index.ts index 0c6319a2905a8..152d7c54d7d84 100644 --- a/src/plugins/inspector/common/adapters/index.ts +++ b/src/plugins/inspector/common/adapters/index.ts @@ -17,6 +17,5 @@ * under the License. */ -export * from './data'; export * from './request'; export * from './types'; diff --git a/src/plugins/inspector/common/adapters/types.ts b/src/plugins/inspector/common/adapters/types.ts index b51c3e56c749f..ee56c994be469 100644 --- a/src/plugins/inspector/common/adapters/types.ts +++ b/src/plugins/inspector/common/adapters/types.ts @@ -17,14 +17,12 @@ * under the License. */ -import type { DataAdapter } from './data'; import type { RequestAdapter } from './request'; /** * The interface that the adapters used to open an inspector have to fullfill. */ export interface Adapters { - data?: DataAdapter; requests?: RequestAdapter; [key: string]: any; } diff --git a/src/plugins/inspector/common/index.ts b/src/plugins/inspector/common/index.ts index c5755b22095dc..f9f486521a76b 100644 --- a/src/plugins/inspector/common/index.ts +++ b/src/plugins/inspector/common/index.ts @@ -19,15 +19,9 @@ export { Adapters, - DataAdapter, - FormattedData, RequestAdapter, RequestStatistic, RequestStatistics, RequestStatus, RequestResponder, - TabularData, - TabularDataColumn, - TabularDataRow, - TabularDataValue, } from './adapters'; diff --git a/src/plugins/inspector/public/plugin.tsx b/src/plugins/inspector/public/plugin.tsx index 07ef7c8fbab0d..d3d867344a2a8 100644 --- a/src/plugins/inspector/public/plugin.tsx +++ b/src/plugins/inspector/public/plugin.tsx @@ -26,7 +26,7 @@ import { InspectorOptions, InspectorSession } from './types'; import { InspectorPanel } from './ui/inspector_panel'; import { Adapters } from '../common'; -import { getRequestsViewDescription, getDataViewDescription } from './views'; +import { getRequestsViewDescription } from './views'; export interface Setup { registerView: InspectorViewRegistry['register']; @@ -70,7 +70,6 @@ export class InspectorPublicPlugin implements Plugin { public async setup(core: CoreSetup) { this.views = new InspectorViewRegistry(); - this.views.register(getDataViewDescription()); this.views.register(getRequestsViewDescription()); return { diff --git a/src/plugins/inspector/public/test/is_available.test.ts b/src/plugins/inspector/public/test/is_available.test.ts index c38d9d7a3f825..1f5220fc07a63 100644 --- a/src/plugins/inspector/public/test/is_available.test.ts +++ b/src/plugins/inspector/public/test/is_available.test.ts @@ -18,19 +18,12 @@ */ import { inspectorPluginMock } from '../mocks'; -import { DataAdapter, RequestAdapter } from '../../common/adapters'; +import { RequestAdapter } from '../../common/adapters'; -const adapter1 = new DataAdapter(); const adapter2 = new RequestAdapter(); describe('inspector', () => { describe('isAvailable()', () => { - it('should return false if no view would be available', async () => { - const { doStart } = await inspectorPluginMock.createPlugin(); - const start = await doStart(); - expect(start.isAvailable({ adapter1 })).toBe(false); - }); - it('should return true if views would be available, false otherwise', async () => { const { setup, doStart } = await inspectorPluginMock.createPlugin(); @@ -44,7 +37,6 @@ describe('inspector', () => { const start = await doStart(); - expect(start.isAvailable({ adapter1 })).toBe(true); expect(start.isAvailable({ adapter2 })).toBe(false); }); }); diff --git a/src/plugins/inspector/public/views/_index.scss b/src/plugins/inspector/public/views/_index.scss index 620a33e965840..43fbc09e921cc 100644 --- a/src/plugins/inspector/public/views/_index.scss +++ b/src/plugins/inspector/public/views/_index.scss @@ -1,2 +1 @@ -@import './data/index'; @import './requests/index'; diff --git a/src/plugins/inspector/public/views/data/components/data_view.tsx b/src/plugins/inspector/public/views/data/components/data_view.tsx deleted file mode 100644 index 324094d8f93d0..0000000000000 --- a/src/plugins/inspector/public/views/data/components/data_view.tsx +++ /dev/null @@ -1,187 +0,0 @@ -/* - * 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 React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { - EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, - EuiLoadingChart, - EuiPanel, - EuiSpacer, - EuiText, -} from '@elastic/eui'; - -import { DataTableFormat } from './data_table'; -import { InspectorViewProps } from '../../../types'; -import { Adapters } from '../../../../common'; -import { - TabularLoaderOptions, - TabularData, - TabularHolder, -} from '../../../../common/adapters/data/types'; -import { IUiSettingsClient } from '../../../../../../core/public'; -import { withKibana, KibanaReactContextValue } from '../../../../../kibana_react/public'; - -interface DataViewComponentState { - tabularData: TabularData | null; - tabularOptions: TabularLoaderOptions; - adapters: Adapters; - tabularPromise: Promise | null; -} - -interface DataViewComponentProps extends InspectorViewProps { - kibana: KibanaReactContextValue<{ uiSettings: IUiSettingsClient }>; -} - -class DataViewComponent extends Component { - static propTypes = { - adapters: PropTypes.object.isRequired, - title: PropTypes.string.isRequired, - kibana: PropTypes.object, - }; - - state = {} as DataViewComponentState; - _isMounted = false; - - static getDerivedStateFromProps( - nextProps: DataViewComponentProps, - state: DataViewComponentState - ) { - if (state && nextProps.adapters === state.adapters) { - return null; - } - - return { - adapters: nextProps.adapters, - tabularData: null, - tabularOptions: {}, - tabularPromise: nextProps.adapters.data!.getTabular(), - }; - } - - onUpdateData = (type: string) => { - if (type === 'tabular') { - this.setState({ - tabularData: null, - tabularOptions: {}, - tabularPromise: this.props.adapters.data!.getTabular(), - }); - } - }; - - async finishLoadingData() { - const { tabularPromise } = this.state; - - if (tabularPromise) { - const tabularData: TabularHolder = await tabularPromise; - - if (this._isMounted) { - this.setState({ - tabularData: tabularData.data, - tabularOptions: tabularData.options, - tabularPromise: null, - }); - } - } - } - - componentDidMount() { - this._isMounted = true; - this.props.adapters.data!.on('change', this.onUpdateData); - this.finishLoadingData(); - } - - componentWillUnmount() { - this._isMounted = false; - this.props.adapters.data!.removeListener('change', this.onUpdateData); - } - - componentDidUpdate() { - this.finishLoadingData(); - } - - static renderNoData() { - return ( - - - - } - body={ - -

- -

-
- } - /> - ); - } - - static renderLoading() { - return ( - - - - - - -

- -

-
-
-
-
- ); - } - - render() { - if (this.state.tabularPromise) { - return DataViewComponent.renderLoading(); - } else if (!this.state.tabularData) { - return DataViewComponent.renderNoData(); - } - - return ( - - ); - } -} - -// default export required for React.Lazy -// eslint-disable-next-line import/no-default-export -export default withKibana(DataViewComponent); diff --git a/src/plugins/inspector/public/views/index.ts b/src/plugins/inspector/public/views/index.ts index c75ecfbd3e998..8aef30a68a327 100644 --- a/src/plugins/inspector/public/views/index.ts +++ b/src/plugins/inspector/public/views/index.ts @@ -17,5 +17,4 @@ * under the License. */ -export { getDataViewDescription } from './data'; export { getRequestsViewDescription } from './requests'; diff --git a/src/plugins/region_map/public/region_map_fn.js b/src/plugins/region_map/public/region_map_fn.js index fdb7c273720fa..cc99a5595d096 100644 --- a/src/plugins/region_map/public/region_map_fn.js +++ b/src/plugins/region_map/public/region_map_fn.js @@ -34,9 +34,12 @@ export const createRegionMapFn = () => ({ default: '"{}"', }, }, - fn(context, args) { + fn(context, args, handlers) { const visConfig = JSON.parse(args.visConfig); + if (handlers?.inspectorAdapters?.tables) { + handlers.inspectorAdapters.tables.logDatatable('default', context); + } return { type: 'render', as: 'visualization', diff --git a/src/plugins/region_map/public/region_map_fn.test.js b/src/plugins/region_map/public/region_map_fn.test.js index 32467541dee02..d83d04be6d38c 100644 --- a/src/plugins/region_map/public/region_map_fn.test.js +++ b/src/plugins/region_map/public/region_map_fn.test.js @@ -57,7 +57,11 @@ describe('interpreter/functions#regionmap', () => { }; it('returns an object with the correct structure', () => { - const actual = fn(context, { visConfig: JSON.stringify(visConfig) }); + const actual = fn( + context, + { visConfig: JSON.stringify(visConfig) }, + { logDatatable: jest.fn() } + ); expect(actual).toMatchSnapshot(); }); }); diff --git a/src/plugins/tile_map/public/tile_map_fn.js b/src/plugins/tile_map/public/tile_map_fn.js index 3253598d98d94..7a5f36be1eb9d 100644 --- a/src/plugins/tile_map/public/tile_map_fn.js +++ b/src/plugins/tile_map/public/tile_map_fn.js @@ -34,7 +34,7 @@ export const createTileMapFn = () => ({ default: '"{}"', }, }, - fn(context, args) { + fn(context, args, handlers) { const visConfig = JSON.parse(args.visConfig); const { geohash, metric, geocentroid } = visConfig.dimensions; const convertedData = convertToGeoJson(context, { @@ -47,6 +47,9 @@ export const createTileMapFn = () => ({ convertedData.meta.geohash = context.columns[geohash.accessor].meta; } + if (handlers?.inspectorAdapters?.tables) { + handlers.inspectorAdapters.tables.logDatatable('default', context); + } return { type: 'render', as: 'visualization', diff --git a/src/plugins/tile_map/public/tilemap_fn.test.js b/src/plugins/tile_map/public/tilemap_fn.test.js index df9fc10a7303c..895842ea1e8f4 100644 --- a/src/plugins/tile_map/public/tilemap_fn.test.js +++ b/src/plugins/tile_map/public/tilemap_fn.test.js @@ -80,13 +80,17 @@ describe('interpreter/functions#tilemap', () => { }); it('returns an object with the correct structure', () => { - const actual = fn(context, { visConfig: JSON.stringify(visConfig) }); + const actual = fn( + context, + { visConfig: JSON.stringify(visConfig) }, + { logDatatable: jest.fn() } + ); expect(actual).toMatchSnapshot(); }); it('calls response handler with correct values', () => { const { geohash, metric, geocentroid } = visConfig.dimensions; - fn(context, { visConfig: JSON.stringify(visConfig) }); + fn(context, { visConfig: JSON.stringify(visConfig) }, { logDatatable: jest.fn() }); expect(convertToGeoJson).toHaveBeenCalledTimes(1); expect(convertToGeoJson).toHaveBeenCalledWith(context, { geohash, diff --git a/src/plugins/vis_type_metric/public/metric_vis_fn.ts b/src/plugins/vis_type_metric/public/metric_vis_fn.ts index 20de22f50e63a..ae6dc6683852e 100644 --- a/src/plugins/vis_type_metric/public/metric_vis_fn.ts +++ b/src/plugins/vis_type_metric/public/metric_vis_fn.ts @@ -160,7 +160,7 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({ }), }, }, - fn(input, args) { + fn(input, args, handlers) { const dimensions: DimensionsVisParam = { metrics: args.metric, }; @@ -175,6 +175,9 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({ const fontSize = Number.parseInt(args.font.spec.fontSize || '', 10); + if (handlers?.inspectorAdapters?.tables) { + handlers.inspectorAdapters.tables.logDatatable('default', input); + } return { type: 'render', as: 'metric_vis', diff --git a/src/plugins/vis_type_table/public/table_vis_fn.ts b/src/plugins/vis_type_table/public/table_vis_fn.ts index 28990f28caf31..530f50ffd89b6 100644 --- a/src/plugins/vis_type_table/public/table_vis_fn.ts +++ b/src/plugins/vis_type_table/public/table_vis_fn.ts @@ -55,10 +55,13 @@ export const createTableVisFn = (): TableExpressionFunctionDefinition => ({ help: '', }, }, - fn(input, args) { + fn(input, args, handlers) { const visConfig = args.visConfig && JSON.parse(args.visConfig); const convertedData = tableVisResponseHandler(input, visConfig.dimensions); + if (handlers?.inspectorAdapters?.tables) { + handlers.inspectorAdapters.tables.logDatatable('default', input); + } return { type: 'render', as: 'table_vis', diff --git a/src/plugins/vis_type_tagcloud/public/tag_cloud_fn.ts b/src/plugins/vis_type_tagcloud/public/tag_cloud_fn.ts index ff59572e0817d..3ed203dd22ef4 100644 --- a/src/plugins/vis_type_tagcloud/public/tag_cloud_fn.ts +++ b/src/plugins/vis_type_tagcloud/public/tag_cloud_fn.ts @@ -95,7 +95,7 @@ export const createTagCloudFn = (): TagcloudExpressionFunctionDefinition => ({ }), }, }, - fn(input, args) { + fn(input, args, handlers) { const visParams = { scale: args.scale, orientation: args.orientation, @@ -109,6 +109,9 @@ export const createTagCloudFn = (): TagcloudExpressionFunctionDefinition => ({ visParams.bucket = args.bucket; } + if (handlers?.inspectorAdapters?.tables) { + handlers.inspectorAdapters.tables.logDatatable('default', input); + } return { type: 'render', as: 'tagloud_vis', diff --git a/src/plugins/vis_type_vislib/public/pie_fn.ts b/src/plugins/vis_type_vislib/public/pie_fn.ts index c9da9e9bd9fab..00aa73128c349 100644 --- a/src/plugins/vis_type_vislib/public/pie_fn.ts +++ b/src/plugins/vis_type_vislib/public/pie_fn.ts @@ -59,10 +59,14 @@ export const createPieVisFn = (): VisTypeVislibPieExpressionFunctionDefinition = help: 'vislib pie vis config', }, }, - fn(input, args) { + fn(input, args, handlers) { const visConfig = JSON.parse(args.visConfig) as PieVisParams; const visData = vislibSlicesResponseHandler(input, visConfig.dimensions); + if (handlers?.inspectorAdapters?.tables) { + handlers.inspectorAdapters.tables.logDatatable('default', input); + } + return { type: 'render', as: vislibVisName, diff --git a/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts b/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts index c5fa8f36f43e3..dc4a6314fb013 100644 --- a/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts +++ b/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts @@ -64,11 +64,15 @@ export const createVisTypeVislibVisFn = (): VisTypeVislibExpressionFunctionDefin help: 'vislib vis config', }, }, - fn(context, args) { + fn(context, args, handlers) { const visType = args.type; const visConfig = JSON.parse(args.visConfig) as BasicVislibParams; const visData = vislibSeriesResponseHandler(context, visConfig.dimensions); + if (handlers?.inspectorAdapters?.tables) { + handlers.inspectorAdapters.tables.logDatatable('default', context); + } + return { type: 'render', as: vislibVisName, diff --git a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.ts b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.ts index 5eb1e65965318..fb42416abe249 100644 --- a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.ts +++ b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.ts @@ -41,7 +41,7 @@ export interface Point { table: Table; column: number; row: number; - value: number; + value: string; title: string; }; parent: Aspect | null; @@ -94,7 +94,7 @@ export function getPoint( table: table.$parent.table, column: table.$parent.column, row: table.$parent.row, - value: table.$parent.key, + value: table.$parent.formattedKey, title: table.$parent.name, }, parent: series ? series[0] : null, diff --git a/src/plugins/vis_type_vislib/public/vislib/response_handler.js b/src/plugins/vis_type_vislib/public/vislib/response_handler.js index 871ce97ad4480..9028b53fbd003 100644 --- a/src/plugins/vis_type_vislib/public/vislib/response_handler.js +++ b/src/plugins/vis_type_vislib/public/vislib/response_handler.js @@ -34,14 +34,16 @@ function tableResponseHandler(table, dimensions) { table.rows.forEach((row, rowIndex) => { const splitValue = row[splitColumn.id]; + const formattedValue = splitColumnFormatter.convert(splitValue); if (!splitMap.hasOwnProperty(splitValue)) { splitMap[splitValue] = splitIndex++; const tableGroup = { $parent: converted, - title: `${splitColumnFormatter.convert(splitValue)}: ${splitColumn.name}`, + title: `${formattedValue}: ${splitColumn.name}`, name: splitColumn.name, key: splitValue, + formattedKey: formattedValue, column: splitColumnIndex, row: rowIndex, table, diff --git a/src/plugins/vis_type_vislib/public/vislib/types.ts b/src/plugins/vis_type_vislib/public/vislib/types.ts index ad59603663b84..5096015c99a90 100644 --- a/src/plugins/vis_type_vislib/public/vislib/types.ts +++ b/src/plugins/vis_type_vislib/public/vislib/types.ts @@ -33,6 +33,7 @@ export interface TableParent { column: number; row: number; key: number; + formattedKey: string; name: string; } export interface Table { diff --git a/test/functional/apps/dashboard/dashboard_listing.js b/test/functional/apps/dashboard/dashboard_listing.js index 175605bfa8253..45c4d2bffbfa7 100644 --- a/test/functional/apps/dashboard/dashboard_listing.js +++ b/test/functional/apps/dashboard/dashboard_listing.js @@ -134,7 +134,7 @@ export default function ({ getService, getPageObjects }) { expect(onDashboardLandingPage).to.equal(false); }); - it('title match is case insensitive', async function () { + it.skip('title match is case insensitive', async function () { await PageObjects.dashboard.gotoDashboardLandingPage(); const currentUrl = await browser.getCurrentUrl(); const newUrl = currentUrl + '&title=two%20words'; diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app/components/main.tsx b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app/components/main.tsx index 8c62fa246dd59..9ff5bc3d59379 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app/components/main.tsx +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app/components/main.tsx @@ -20,8 +20,12 @@ import React from 'react'; import { EuiPage, EuiPageBody, EuiPageContent, EuiPageContentHeader } from '@elastic/eui'; import { first } from 'rxjs/operators'; -import { IInterpreterRenderHandlers, ExpressionValue } from 'src/plugins/expressions'; -import { RequestAdapter, DataAdapter } from '../../../../../../../src/plugins/inspector/public'; +import { + IInterpreterRenderHandlers, + ExpressionValue, + TablesAdapter, +} from '../../../../../../../src/plugins/expressions/public'; +import { RequestAdapter } from '../../../../../../../src/plugins/inspector/public'; import { Adapters, ExpressionRenderHandler } from '../../types'; import { getExpressions } from '../../services'; @@ -58,7 +62,7 @@ class Main extends React.Component<{}, State> { this.setState({ expression }); const adapters: Adapters = { requests: new RequestAdapter(), - data: new DataAdapter(), + tables: new TablesAdapter(), }; return getExpressions() .execute(expression, context || { type: 'null' }, { diff --git a/vars/tasks.groovy b/vars/tasks.groovy index a01e63e218147..221e93fd7b839 100644 --- a/vars/tasks.groovy +++ b/vars/tasks.groovy @@ -115,15 +115,14 @@ def functionalXpack(Map params = [:]) { task(kibanaPipeline.functionalTestProcess('xpack-savedObjectsFieldMetrics', './test/scripts/jenkins_xpack_saved_objects_field_metrics.sh')) } - // FLAKY: https://github.com/elastic/kibana/issues/86080 - // whenChanged([ - // 'x-pack/plugins/security_solution/', - // 'x-pack/test/security_solution_cypress/', - // 'x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/', - // 'x-pack/plugins/triggers_actions_ui/public/application/context/actions_connectors_context.tsx', - // ]) { - // task(kibanaPipeline.functionalTestProcess('xpack-securitySolutionCypress', './test/scripts/jenkins_security_solution_cypress.sh')) - // } + whenChanged([ + 'x-pack/plugins/security_solution/', + 'x-pack/test/security_solution_cypress/', + 'x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/', + 'x-pack/plugins/triggers_actions_ui/public/application/context/actions_connectors_context.tsx', + ]) { + task(kibanaPipeline.functionalTestProcess('xpack-securitySolutionCypress', './test/scripts/jenkins_security_solution_cypress.sh')) + } } } diff --git a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts index 095823952722b..f21cd2b02943a 100644 --- a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts @@ -45,7 +45,7 @@ import { TaskManagerStartContract } from '../../../task_manager/server'; import { taskInstanceToAlertTaskInstance } from '../task_runner/alert_task_instance'; import { deleteTaskIfItExists } from '../lib/delete_task_if_it_exists'; import { RegistryAlertType } from '../alert_type_registry'; -import { AlertsAuthorization, WriteOperations, ReadOperations, and } from '../authorization'; +import { AlertsAuthorization, WriteOperations, ReadOperations } from '../authorization'; import { IEventLogClient } from '../../../../plugins/event_log/server'; import { parseIsoOrRelativeDate } from '../lib/iso_or_relative_date'; import { alertInstanceSummaryFromEventLog } from '../lib/alert_instance_summary_from_event_log'; @@ -56,6 +56,7 @@ import { retryIfConflicts } from '../lib/retry_if_conflicts'; import { partiallyUpdateAlert } from '../saved_objects'; import { markApiKeyForInvalidation } from '../invalidate_pending_api_keys/mark_api_key_for_invalidation'; import { alertAuditEvent, AlertAuditAction } from './audit_events'; +import { nodeBuilder } from '../../../../../src/plugins/data/common'; export interface RegistryAlertTypeWithAuth extends RegistryAlertType { authorizedConsumers: string[]; @@ -455,7 +456,7 @@ export class AlertsClient { ...options, filter: (authorizationFilter && options.filter - ? and([esKuery.fromKueryExpression(options.filter), authorizationFilter]) + ? nodeBuilder.and([esKuery.fromKueryExpression(options.filter), authorizationFilter]) : authorizationFilter) ?? options.filter, fields: fields ? this.includeFieldsRequiredForAuthentication(fields) : fields, type: 'alert', @@ -517,7 +518,7 @@ export class AlertsClient { ...options, filter: (authorizationFilter && filter - ? and([esKuery.fromKueryExpression(filter), authorizationFilter]) + ? nodeBuilder.and([esKuery.fromKueryExpression(filter), authorizationFilter]) : authorizationFilter) ?? filter, page: 1, perPage: 0, diff --git a/x-pack/plugins/alerts/server/authorization/alerts_authorization_kuery.ts b/x-pack/plugins/alerts/server/authorization/alerts_authorization_kuery.ts index f236ee7f3c258..adab78b69e8a2 100644 --- a/x-pack/plugins/alerts/server/authorization/alerts_authorization_kuery.ts +++ b/x-pack/plugins/alerts/server/authorization/alerts_authorization_kuery.ts @@ -5,36 +5,23 @@ */ import { remove } from 'lodash'; -import { nodeTypes } from '../../../../../src/plugins/data/common'; +import { nodeBuilder } from '../../../../../src/plugins/data/common'; import { KueryNode } from '../../../../../src/plugins/data/server'; import { RegistryAlertTypeWithAuth } from './alerts_authorization'; -export const is = (fieldName: string, value: string | KueryNode) => - nodeTypes.function.buildNodeWithArgumentNodes('is', [ - nodeTypes.literal.buildNode(fieldName), - typeof value === 'string' ? nodeTypes.literal.buildNode(value) : value, - nodeTypes.literal.buildNode(false), - ]); - -export const or = ([first, ...args]: KueryNode[]): KueryNode => - args.length ? nodeTypes.function.buildNode('or', [first, or(args)]) : first; - -export const and = ([first, ...args]: KueryNode[]): KueryNode => - args.length ? nodeTypes.function.buildNode('and', [first, and(args)]) : first; - export function asFiltersByAlertTypeAndConsumer( alertTypes: Set ): KueryNode { - return or( + return nodeBuilder.or( Array.from(alertTypes).reduce((filters, { id, authorizedConsumers }) => { ensureFieldIsSafeForQuery('alertTypeId', id); filters.push( - and([ - is(`alert.attributes.alertTypeId`, id), - or( + nodeBuilder.and([ + nodeBuilder.is(`alert.attributes.alertTypeId`, id), + nodeBuilder.or( Object.keys(authorizedConsumers).map((consumer) => { ensureFieldIsSafeForQuery('consumer', consumer); - return is(`alert.attributes.consumer`, consumer); + return nodeBuilder.is(`alert.attributes.consumer`, consumer); }) ), ]) diff --git a/x-pack/plugins/alerts/server/plugin.test.ts b/x-pack/plugins/alerts/server/plugin.test.ts index 0c9a09b11532b..6288d27c6ebe0 100644 --- a/x-pack/plugins/alerts/server/plugin.test.ts +++ b/x-pack/plugins/alerts/server/plugin.test.ts @@ -4,18 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - AlertingPlugin, - AlertingPluginsSetup, - AlertingPluginsStart, - PluginSetupContract, -} from './plugin'; +import { AlertingPlugin, AlertingPluginsSetup, PluginSetupContract } from './plugin'; import { coreMock, statusServiceMock } from '../../../../src/core/server/mocks'; import { licensingMock } from '../../licensing/server/mocks'; import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks'; import { taskManagerMock } from '../../task_manager/server/mocks'; import { eventLogServiceMock } from '../../event_log/server/event_log_service.mock'; -import { KibanaRequest, CoreSetup } from 'kibana/server'; +import { KibanaRequest } from 'kibana/server'; import { featuresPluginMock } from '../../features/server/mocks'; import { KibanaFeature } from '../../features/server'; import { AlertsConfig } from './config'; @@ -41,27 +36,20 @@ describe('Alerting Plugin', () => { }); plugin = new AlertingPlugin(context); - coreSetup = coreMock.createSetup(); const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup(); - const statusMock = statusServiceMock.createSetupContract(); - await plugin.setup( - ({ - ...coreSetup, - http: { - ...coreSetup.http, - route: jest.fn(), - }, - status: statusMock, - } as unknown) as CoreSetup, - ({ - licensing: licensingMock.createSetup(), - encryptedSavedObjects: encryptedSavedObjectsSetup, - taskManager: taskManagerMock.createSetup(), - eventLog: eventLogServiceMock.create(), - } as unknown) as AlertingPluginsSetup - ); - expect(statusMock.set).toHaveBeenCalledTimes(1); + const setupMocks = coreMock.createSetup(); + // need await to test number of calls of setupMocks.status.set, becuase it is under async function which awaiting core.getStartServices() + await plugin.setup(setupMocks, { + licensing: licensingMock.createSetup(), + encryptedSavedObjects: encryptedSavedObjectsSetup, + taskManager: taskManagerMock.createSetup(), + eventLog: eventLogServiceMock.create(), + actions: actionsMock.createSetup(), + statusService: statusServiceMock.createSetupContract(), + }); + + expect(setupMocks.status.set).toHaveBeenCalledTimes(1); expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true); expect(context.logger.get().warn).toHaveBeenCalledWith( 'APIs are disabled because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.' @@ -90,7 +78,7 @@ describe('Alerting Plugin', () => { actions: actionsMock.createSetup(), statusService: statusServiceMock.createSetupContract(), }; - setup = await plugin.setup(coreSetup, pluginsSetup); + setup = plugin.setup(coreSetup, pluginsSetup); }); it('should throw error when license type is invalid', async () => { @@ -120,13 +108,6 @@ describe('Alerting Plugin', () => { }); describe('start()', () => { - /** - * HACK: This test has put together to ensuire the function "getAlertsClientWithRequest" - * throws when needed. There's a lot of blockers for writing a proper test like - * misisng plugin start/setup mocks for taskManager and actions plugin, core.http.route - * is actually not a function in Kibana Platform, etc. This test contains what is needed - * to get to the necessary function within start(). - */ describe('getAlertsClientWithRequest()', () => { it('throws error when encryptedSavedObjects plugin has usingEphemeralEncryptionKey set to true', async () => { const context = coreMock.createPluginInitializerContext({ @@ -140,37 +121,24 @@ describe('Alerting Plugin', () => { }); const plugin = new AlertingPlugin(context); - const coreSetup = coreMock.createSetup(); const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup(); - await plugin.setup( - ({ - ...coreSetup, - http: { - ...coreSetup.http, - route: jest.fn(), - }, - } as unknown) as CoreSetup, - ({ - licensing: licensingMock.createSetup(), - encryptedSavedObjects: encryptedSavedObjectsSetup, - taskManager: taskManagerMock.createSetup(), - eventLog: eventLogServiceMock.create(), - } as unknown) as AlertingPluginsSetup - ); + plugin.setup(coreMock.createSetup(), { + licensing: licensingMock.createSetup(), + encryptedSavedObjects: encryptedSavedObjectsSetup, + taskManager: taskManagerMock.createSetup(), + eventLog: eventLogServiceMock.create(), + actions: actionsMock.createSetup(), + statusService: statusServiceMock.createSetupContract(), + }); - const startContract = plugin.start( - coreMock.createStart() as ReturnType, - ({ - actions: { - execute: jest.fn(), - getActionsClientWithRequest: jest.fn(), - getActionsAuthorizationWithRequest: jest.fn(), - }, - encryptedSavedObjects: encryptedSavedObjectsMock.createStart(), - features: mockFeatures(), - licensing: licensingMock.createStart(), - } as unknown) as AlertingPluginsStart - ); + const startContract = plugin.start(coreMock.createStart(), { + actions: actionsMock.createStart(), + encryptedSavedObjects: encryptedSavedObjectsMock.createStart(), + features: mockFeatures(), + licensing: licensingMock.createStart(), + eventLog: eventLogMock.createStart(), + taskManager: taskManagerMock.createStart(), + }); expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true); expect(() => @@ -192,40 +160,27 @@ describe('Alerting Plugin', () => { }); const plugin = new AlertingPlugin(context); - const coreSetup = coreMock.createSetup(); const encryptedSavedObjectsSetup = { ...encryptedSavedObjectsMock.createSetup(), usingEphemeralEncryptionKey: false, }; - await plugin.setup( - ({ - ...coreSetup, - http: { - ...coreSetup.http, - route: jest.fn(), - }, - } as unknown) as CoreSetup, - ({ - licensing: licensingMock.createSetup(), - encryptedSavedObjects: encryptedSavedObjectsSetup, - taskManager: taskManagerMock.createSetup(), - eventLog: eventLogServiceMock.create(), - } as unknown) as AlertingPluginsSetup - ); + plugin.setup(coreMock.createSetup(), { + licensing: licensingMock.createSetup(), + encryptedSavedObjects: encryptedSavedObjectsSetup, + taskManager: taskManagerMock.createSetup(), + eventLog: eventLogServiceMock.create(), + actions: actionsMock.createSetup(), + statusService: statusServiceMock.createSetupContract(), + }); - const startContract = plugin.start( - coreMock.createStart() as ReturnType, - ({ - actions: { - execute: jest.fn(), - getActionsClientWithRequest: jest.fn(), - getActionsAuthorizationWithRequest: jest.fn(), - }, - encryptedSavedObjects: encryptedSavedObjectsMock.createStart(), - features: mockFeatures(), - licensing: licensingMock.createStart(), - } as unknown) as AlertingPluginsStart - ); + const startContract = plugin.start(coreMock.createStart(), { + actions: actionsMock.createStart(), + encryptedSavedObjects: encryptedSavedObjectsMock.createStart(), + features: mockFeatures(), + licensing: licensingMock.createStart(), + eventLog: eventLogMock.createStart(), + taskManager: taskManagerMock.createStart(), + }); const fakeRequest = ({ headers: {}, @@ -242,7 +197,7 @@ describe('Alerting Plugin', () => { }, getSavedObjectsClient: jest.fn(), } as unknown) as KibanaRequest; - await startContract.getAlertsClientWithRequest(fakeRequest); + startContract.getAlertsClientWithRequest(fakeRequest); }); }); }); diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 976fb127604aa..2e04cce5ff670 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -21,12 +21,13 @@ import { ServiceOverviewErrorsTable } from './service_overview_errors_table'; import { ServiceOverviewInstancesTable } from './service_overview_instances_table'; import { ServiceOverviewThroughputChart } from './service_overview_throughput_chart'; import { ServiceOverviewTransactionsTable } from './service_overview_transactions_table'; +import { useShouldUseMobileLayout } from './use_should_use_mobile_layout'; /** * The height a chart should be if it's next to a table with 5 rows and a title. * Add the height of the pagination row. */ -export const chartHeight = 322; +export const chartHeight = 288; interface ServiceOverviewProps { agentName?: string; @@ -40,6 +41,11 @@ export function ServiceOverview({ useTrackPageview({ app: 'apm', path: 'service_overview' }); useTrackPageview({ app: 'apm', path: 'service_overview', delay: 15000 }); + // The default EuiFlexGroup breaks at 768, but we want to break at 992, so we + // observe the window width and set the flex directions of rows accordingly + const shouldUseMobileLayout = useShouldUseMobileLayout(); + const rowDirection = shouldUseMobileLayout ? 'column' : 'row'; + const { transactionType } = useApmServiceContext(); const transactionTypeLabel = i18n.translate( 'xpack.apm.serviceOverview.searchBar.transactionTypeLabel', @@ -58,11 +64,15 @@ export function ServiceOverview({
- - + + - + - + {!isRumAgentName(agentName) && ( - + )} - + @@ -89,11 +103,15 @@ export function ServiceOverview({ - - + + - + + {item.type === 'service' ? ( @@ -190,9 +190,9 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { })); return ( - + - +

diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx index 1736ab518354f..da74a6fc0004d 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx @@ -4,27 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ import { + EuiBasicTable, EuiBasicTableColumn, EuiFlexGroup, EuiFlexItem, EuiTitle, - EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; -import styled from 'styled-components'; -import { EuiBasicTable } from '@elastic/eui'; import { asInteger } from '../../../../../common/utils/formatters'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { callApmApi } from '../../../../services/rest/createCallApmApi'; -import { px, truncate, unit } from '../../../../style/variables'; +import { px, unit } from '../../../../style/variables'; import { SparkPlot } from '../../../shared/charts/spark_plot'; import { ErrorDetailLink } from '../../../shared/Links/apm/ErrorDetailLink'; import { ErrorOverviewLink } from '../../../shared/Links/apm/ErrorOverviewLink'; import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; import { TimestampTooltip } from '../../../shared/TimestampTooltip'; +import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; import { ServiceOverviewTableContainer } from '../service_overview_table_container'; import { TableLinkFlexItem } from '../table_link_flex_item'; @@ -51,18 +50,6 @@ const DEFAULT_SORT = { field: 'occurrences' as const, }; -const ErrorDetailLinkWrapper = styled.div` - width: 100%; - .euiToolTipAnchor { - width: 100% !important; - } -`; - -const StyledErrorDetailLink = styled(ErrorDetailLink)` - display: block; - ${truncate('100%')} -`; - export function ServiceOverviewErrorsTable({ serviceName }: Props) { const { urlParams: { start, end }, @@ -88,16 +75,17 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { }), render: (_, { name, group_id: errorGroupId }) => { return ( - - - {name} - - - + + } + /> ); }, }, @@ -205,9 +193,9 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { } = data; return ( - + - +

diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx index c9b4801883160..51a4ef649a3ba 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx @@ -236,7 +236,7 @@ export function ServiceOverviewInstancesTable({ serviceName }: Props) { status === FETCH_STATUS.LOADING || status === FETCH_STATUS.PENDING; return ( - +

diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_table_container.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_table_container.tsx index e5113cebd3dcb..76db81a70550d 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_table_container.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_table_container.tsx @@ -4,24 +4,33 @@ * you may not use this file except in compliance with the Elastic License. */ +import React, { ReactNode } from 'react'; import styled from 'styled-components'; +import { useShouldUseMobileLayout } from './use_should_use_mobile_layout'; /** * The height for a table on the overview page. Is the height of a 5-row basic * table. */ -const tableHeight = 298; +const tableHeight = 282; /** * A container for the table. Sets height and flex properties on the EUI Basic * Table contained within and the first child div of that. This makes it so the * pagination controls always stay fixed at the bottom in the same position. * + * Only do this when we're at a non-mobile breakpoint. + * * Hide the empty message when we don't yet have any items and are still loading. */ -export const ServiceOverviewTableContainer = styled.div<{ +const ServiceOverviewTableContainerDiv = styled.div<{ isEmptyAndLoading: boolean; + shouldUseMobileLayout: boolean; }>` + ${({ shouldUseMobileLayout }) => + shouldUseMobileLayout + ? '' + : ` height: ${tableHeight}px; display: flex; flex-direction: column; @@ -34,10 +43,29 @@ export const ServiceOverviewTableContainer = styled.div<{ > :first-child { flex-grow: 1; } - } + `} .euiTableRowCell { visibility: ${({ isEmptyAndLoading }) => isEmptyAndLoading ? 'hidden' : 'visible'}; } `; + +export function ServiceOverviewTableContainer({ + children, + isEmptyAndLoading, +}: { + children?: ReactNode; + isEmptyAndLoading: boolean; +}) { + const shouldUseMobileLayout = useShouldUseMobileLayout(); + + return ( + + {children} + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx index 2ef2c48f0164a..6345d546c716f 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx @@ -5,6 +5,7 @@ */ import { + EuiBasicTable, EuiBasicTableColumn, EuiFlexGroup, EuiFlexItem, @@ -12,24 +13,21 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; -import styled from 'styled-components'; -import { EuiToolTip } from '@elastic/eui'; import { ValuesType } from 'utility-types'; -import { EuiBasicTable } from '@elastic/eui'; -import { useLatencyAggregationType } from '../../../../hooks/use_latency_Aggregation_type'; import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; import { asDuration, asPercent, asTransactionRate, } from '../../../../../common/utils/formatters'; -import { px, truncate, unit } from '../../../../style/variables'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { useLatencyAggregationType } from '../../../../hooks/use_latency_Aggregation_type'; import { APIReturnType, callApmApi, } from '../../../../services/rest/createCallApmApi'; +import { px, unit } from '../../../../style/variables'; import { TransactionDetailLink } from '../../../shared/Links/apm/TransactionDetailLink'; import { TransactionOverviewLink } from '../../../shared/Links/apm/TransactionOverviewLink'; import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; @@ -37,6 +35,7 @@ import { TableLinkFlexItem } from '../table_link_flex_item'; import { SparkPlot } from '../../../shared/charts/spark_plot'; import { ImpactBar } from '../../../shared/ImpactBar'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; +import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; import { ServiceOverviewTableContainer } from '../service_overview_table_container'; type ServiceTransactionGroupItem = ValuesType< @@ -56,18 +55,6 @@ const DEFAULT_SORT = { field: 'impact' as const, }; -const TransactionGroupLinkWrapper = styled.div` - width: 100%; - .euiToolTipAnchor { - width: 100% !important; - } -`; - -const StyledTransactionDetailLink = styled(TransactionDetailLink)` - display: block; - ${truncate('100%')} -`; - function getLatencyAggregationTypeLabel( latencyAggregationType?: LatencyAggregationType ) { @@ -194,17 +181,18 @@ export function ServiceOverviewTransactionsTable(props: Props) { ), render: (_, { name, transactionType: type }) => { return ( - - - {name} - - - + + } + /> ); }, }, @@ -279,9 +267,9 @@ export function ServiceOverviewTransactionsTable(props: Props) { ]; return ( - + - +

diff --git a/x-pack/plugins/apm/public/components/app/service_overview/use_should_use_mobile_layout.ts b/x-pack/plugins/apm/public/components/app/service_overview/use_should_use_mobile_layout.ts new file mode 100644 index 0000000000000..bd844a3f2e694 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_overview/use_should_use_mobile_layout.ts @@ -0,0 +1,27 @@ +/* + * 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 { isWithinMaxBreakpoint } from '@elastic/eui'; +import { useEffect, useState } from 'react'; + +export function useShouldUseMobileLayout() { + const [shouldUseMobileLayout, setShouldUseMobileLayout] = useState( + isWithinMaxBreakpoint(window.innerWidth, 'm') + ); + + useEffect(() => { + const resizeHandler = () => { + setShouldUseMobileLayout(isWithinMaxBreakpoint(window.innerWidth, 'm')); + }; + window.addEventListener('resize', resizeHandler); + + return () => { + window.removeEventListener('resize', resizeHandler); + }; + }); + + return shouldUseMobileLayout; +} diff --git a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx index be7c6babe8e00..475877c0edad3 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/index.tsx @@ -48,11 +48,11 @@ export function LatencyChart({ height }: Props) { const latencyFormatter = getDurationFormatter(latencyMaxY); return ( - + - +

diff --git a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx index e620acd56aadd..7b5ff4fd56e91 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx @@ -58,7 +58,7 @@ export function SparkPlot({ const colorValue = theme.eui[color]; return ( - + {!series || series.every((point) => point.y === null) ? ( diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index 29a0d1fdf4249..b8cbdab9e9e5e 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -44,6 +44,7 @@ export const config = { telemetryCollectionEnabled: schema.boolean({ defaultValue: true }), metricsInterval: schema.number({ defaultValue: 30 }), maxServiceEnvironments: schema.number({ defaultValue: 100 }), + maxServiceSelection: schema.number({ defaultValue: 50 }), }), }; @@ -76,6 +77,7 @@ export function mergeConfigs( apmConfig.serviceMapMaxTracesPerRequest, 'xpack.apm.ui.enabled': apmConfig.ui.enabled, 'xpack.apm.maxServiceEnvironments': apmConfig.maxServiceEnvironments, + 'xpack.apm.maxServiceSelection': apmConfig.maxServiceSelection, 'xpack.apm.ui.maxTraceItems': apmConfig.ui.maxTraceItems, 'xpack.apm.ui.transactionGroupBucketSize': apmConfig.ui.transactionGroupBucketSize, diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts index 3ca583c30c29e..137acf1f32904 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts @@ -21,7 +21,8 @@ export async function getServiceNames({ setup: Setup; searchAggregatedTransactions: boolean; }) { - const { apmEventClient } = setup; + const { apmEventClient, config } = setup; + const maxServiceSelection = config['xpack.apm.maxServiceSelection']; const params = { apm: { @@ -40,7 +41,7 @@ export async function getServiceNames({ services: { terms: { field: SERVICE_NAME, - size: 50, + size: maxServiceSelection, min_doc_count: 0, }, }, diff --git a/x-pack/plugins/apm/server/utils/test_helpers.tsx b/x-pack/plugins/apm/server/utils/test_helpers.tsx index 9093b16fada0d..208dd8b91f13a 100644 --- a/x-pack/plugins/apm/server/utils/test_helpers.tsx +++ b/x-pack/plugins/apm/server/utils/test_helpers.tsx @@ -79,6 +79,9 @@ export async function inspectSearchParams( case 'xpack.apm.maxServiceEnvironments': return 100; + + case 'xpack.apm.maxServiceSelection': + return 50; } }, } diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index 96d66157c48ec..d426e73b48510 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -14,11 +14,14 @@ import { SavedObjectsClientContract, Logger, SavedObject, + SavedObjectsBulkUpdateObject, } from '../../../../../../src/core/server'; import { IKibanaSearchRequest, IKibanaSearchResponse, ISearchOptions, + KueryNode, + nodeBuilder, tapFirst, } from '../../../../../../src/plugins/data/common'; import { @@ -81,6 +84,18 @@ export class BackgroundSessionService implements ISessionService { } }; + /** + * Compiles a KQL Query to fetch sessions by ID. + * Done as a performance optimization workaround. + */ + private sessionIdsAsFilters(sessionIds: string[]): KueryNode { + return nodeBuilder.or( + sessionIds.map((id) => { + return nodeBuilder.is(`${BACKGROUND_SESSION_TYPE}.attributes.sessionId`, id); + }) + ); + } + /** * Gets all {@link SessionSavedObjectAttributes | Background Searches} that * currently being tracked by the service. @@ -90,17 +105,14 @@ export class BackgroundSessionService implements ISessionService { * context of a user's session. */ private async getAllMappedSavedObjects() { - const activeMappingIds = Array.from(this.sessionSearchMap.keys()) - .map((sessionId) => `"${sessionId}"`) - .join(' | '); + const filter = this.sessionIdsAsFilters(Array.from(this.sessionSearchMap.keys())); const res = await this.internalSavedObjectsClient.find({ perPage: INMEM_MAX_SESSIONS, // If there are more sessions in memory, they will be synced when some items are cleared out. type: BACKGROUND_SESSION_TYPE, - search: activeMappingIds, - searchFields: ['sessionId'], + filter, namespaces: ['*'], }); - this.logger.debug(`getAllMappedSavedObjects | Got ${res.saved_objects.length} items`); + this.logger.warn(`getAllMappedSavedObjects | Got ${res.saved_objects.length} items`); return res.saved_objects; } @@ -135,6 +147,9 @@ export class BackgroundSessionService implements ISessionService { updatedSessions.forEach((updatedSavedObject) => { const sessionInfo = this.sessionSearchMap.get(updatedSavedObject.id)!; if (updatedSavedObject.error) { + this.logger.warn( + `monitorMappedIds | update error ${JSON.stringify(updatedSavedObject.error) || ''}` + ); // Retry next time sessionInfo.retryCount++; } else if (updatedSavedObject.attributes.idMapping) { @@ -164,7 +179,9 @@ export class BackgroundSessionService implements ISessionService { if (!activeMappingObjects.length) return []; this.logger.debug(`updateAllSavedObjects | Updating ${activeMappingObjects.length} items`); - const updatedSessions = activeMappingObjects + const updatedSessions: Array< + SavedObjectsBulkUpdateObject + > = activeMappingObjects .filter((so) => !so.error) .map((sessionSavedObject) => { const sessionInfo = this.sessionSearchMap.get(sessionSavedObject.id); @@ -173,7 +190,10 @@ export class BackgroundSessionService implements ISessionService { ...sessionSavedObject.attributes.idMapping, ...idMapping, }; - return sessionSavedObject; + return { + ...sessionSavedObject, + namespace: sessionSavedObject.namespaces?.[0], + }; }); const updateResults = await this.internalSavedObjectsClient.bulkUpdate( diff --git a/x-pack/plugins/enterprise_search/README.md b/x-pack/plugins/enterprise_search/README.md index 3fd6154b1d265..0caea251ec6fb 100644 --- a/x-pack/plugins/enterprise_search/README.md +++ b/x-pack/plugins/enterprise_search/README.md @@ -31,8 +31,21 @@ To debug Kea state in-browser, Kea recommends [Redux Devtools](https://kea.js.or Documentation: https://www.elastic.co/guide/en/kibana/current/development-tests.html#_unit_testing +Jest tests can be run directly from the `x-pack/plugins/enterprise_search` folder. This also works for any subfolders or subcomponents. + +```bash +yarn test:jest +yarn test:jest --watch ``` -yarn test:jest x-pack/plugins/enterprise_search --watch + +Unfortunately coverage collection does not work as automatically, and requires using our handy jest.sh script if you want to run tests on a specific folder and only get coverage numbers for that folder: + +```bash +# Running the jest.sh script from the `x-pack/plugins/enterprise_search` folder (vs. kibana root) +# will save you time and allow you to Tab to complete folder dir names +sh jest.sh {YOUR_COMPONENT_DIR} +sh jest.sh public/applications/shared/kibana +sh jest.sh server/routes/app_search ``` ### E2E tests diff --git a/x-pack/plugins/enterprise_search/jest.config.js b/x-pack/plugins/enterprise_search/jest.config.js index db6a25a1f7efd..395297f1723c0 100644 --- a/x-pack/plugins/enterprise_search/jest.config.js +++ b/x-pack/plugins/enterprise_search/jest.config.js @@ -8,4 +8,11 @@ module.exports = { preset: '@kbn/test', rootDir: '../../..', roots: ['/x-pack/plugins/enterprise_search'], + collectCoverage: true, + coverageReporters: ['text'], + collectCoverageFrom: [ + '/x-pack/plugins/enterprise_search/**/*.{ts,tsx}', + '!/x-pack/plugins/enterprise_search/public/*.ts', + '!/x-pack/plugins/enterprise_search/server/*.ts', + ], }; diff --git a/x-pack/plugins/enterprise_search/jest.sh b/x-pack/plugins/enterprise_search/jest.sh new file mode 100644 index 0000000000000..d7aa0b07fb89c --- /dev/null +++ b/x-pack/plugins/enterprise_search/jest.sh @@ -0,0 +1,18 @@ +#! /bin/bash + +# Whether to run Jest on the entire enterprise_search plugin or a specific component/folder +FOLDER="${1:-all}" +if [[ $FOLDER && $FOLDER != "all" ]] +then + FOLDER=${FOLDER%/} # Strip any trailing slash + FOLDER="${FOLDER}/ --collectCoverageFrom='/x-pack/plugins/enterprise_search/${FOLDER}/**/*.{ts,tsx}'" +else + FOLDER='' +fi + +# Pass all remaining arguments (e.g., ...rest) from the 2nd arg onwards +# as an open-ended string. Appends onto to the end the Jest CLI command +# @see https://jestjs.io/docs/en/cli#options +ARGS="${*:2}" + +yarn test:jest $FOLDER $ARGS diff --git a/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap b/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap index e18acbfea8f48..680429a4f5946 100644 --- a/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap +++ b/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap @@ -83,6 +83,7 @@ Array [ "lens", "map", "tag", + "background-session", ], }, "ui": Array [ @@ -204,6 +205,7 @@ Array [ "search", "query", "index-pattern", + "background-session", "url", ], "read": Array [], @@ -565,6 +567,7 @@ Array [ "lens", "map", "tag", + "background-session", ], }, "ui": Array [ @@ -686,6 +689,7 @@ Array [ "search", "query", "index-pattern", + "background-session", "url", ], "read": Array [], diff --git a/x-pack/plugins/features/server/oss_features.ts b/x-pack/plugins/features/server/oss_features.ts index 46dd5fd086b4a..209e26821aedd 100644 --- a/x-pack/plugins/features/server/oss_features.ts +++ b/x-pack/plugins/features/server/oss_features.ts @@ -28,7 +28,7 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS app: ['discover', 'kibana'], catalogue: ['discover'], savedObject: { - all: ['search', 'query', 'index-pattern'], + all: ['search', 'query', 'index-pattern', 'background-session'], read: [], }, ui: ['show', 'save', 'saveQuery'], @@ -156,6 +156,7 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS 'lens', 'map', 'tag', + 'background-session', ], }, ui: ['createNew', 'show', 'showWriteControls', 'saveQuery'], diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/package_policies_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/package_policies_panel.tsx index 4061b86f1f740..4d8cb5a16034f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/package_policies_panel.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/package_policies_panel.tsx @@ -13,7 +13,7 @@ import { EuiTableFieldDataColumnType, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedRelative } from '@kbn/i18n/react'; +import { FormattedRelative, FormattedMessage } from '@kbn/i18n/react'; import { useGetPackageInstallStatus } from '../../hooks'; import { InstallStatus } from '../../../../types'; import { useLink } from '../../../../hooks'; @@ -37,6 +37,7 @@ const IntegrationDetailsLink = memo<{ + {description} + + ); + }, }, { field: 'packagePolicy.policy_id', @@ -172,7 +180,7 @@ export const PackagePoliciesPanel = ({ name, version }: PackagePoliciesPanelProp truncateText: true, render(updatedAt: PackagePolicyAndAgentPolicy['packagePolicy']['updated_at']) { return ( - + ); @@ -182,6 +190,24 @@ export const PackagePoliciesPanel = ({ name, version }: PackagePoliciesPanelProp [] ); + const noItemsMessage = useMemo(() => { + return isLoading ? ( + + ) : undefined; + }, [isLoading]); + + const tablePagination = useMemo(() => { + return { + pageIndex: pagination.currentPage - 1, + pageSize: pagination.pageSize, + totalItemCount: data?.total ?? 0, + pageSizeOptions, + }; + }, [data?.total, pageSizeOptions, pagination.currentPage, pagination.pageSize]); + // if they arrive at this page and the package is not installed, send them to overview // this happens if they arrive with a direct url or they uninstall while on this tab if (packageInstallStatus.status !== InstallStatus.installed) { @@ -192,15 +218,11 @@ export const PackagePoliciesPanel = ({ name, version }: PackagePoliciesPanelProp ); }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts index 0fa1ddeee820d..d7cf391376076 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts @@ -20,6 +20,7 @@ export interface DataStreamsTabTestBed extends TestBed { clickEmptyPromptIndexTemplateLink: () => void; clickIncludeStatsSwitch: () => void; toggleViewFilterAt: (index: number) => void; + sortTableOnStorageSize: () => void; clickReloadButton: () => void; clickNameAt: (index: number) => void; clickIndicesAt: (index: number) => void; @@ -94,6 +95,14 @@ export const setup = async (overridingDependencies: any = {}): Promise { + const { find, component } = testBed; + act(() => { + find('tableHeaderCell_storageSizeBytes_3.tableHeaderSortButton').simulate('click'); + }); + component.update(); + }; + const clickReloadButton = () => { const { find } = testBed; find('reloadButton').simulate('click'); @@ -205,6 +214,7 @@ export const setup = async (overridingDependencies: any = {}): Promise): DataSt health: 'green', indexTemplateName: 'indexTemplate', storageSize: '1b', + storageSizeBytes: 1, maxTimeStamp: 420, privileges: { delete_index: true, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts index 93899dece3308..b71e058e711cd 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts @@ -120,11 +120,21 @@ describe('Data Streams tab', () => { createNonDataStreamIndex('non-data-stream-index'), ]); - const dataStreamForDetailPanel = createDataStreamPayload({ name: 'dataStream1' }); + const dataStreamForDetailPanel = createDataStreamPayload({ + name: 'dataStream1', + storageSize: '5b', + storageSizeBytes: 5, + }); + setLoadDataStreamsResponse([ dataStreamForDetailPanel, - createDataStreamPayload({ name: 'dataStream2' }), + createDataStreamPayload({ + name: 'dataStream2', + storageSize: '1kb', + storageSizeBytes: 1000, + }), ]); + setLoadDataStreamResponse(dataStreamForDetailPanel); const indexTemplate = fixtures.getTemplate({ name: 'indexTemplate' }); @@ -181,8 +191,28 @@ describe('Data Streams tab', () => { // The table renders with the stats columns though. const { tableCellsValues } = table.getMetaData('dataStreamTable'); expect(tableCellsValues).toEqual([ - ['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '1b', '1', 'Delete'], - ['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1b', '1', 'Delete'], + ['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '5b', '1', 'Delete'], + ['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1kb', '1', 'Delete'], + ]); + }); + + test('sorting on stats sorts by bytes value instead of human readable value', async () => { + // Guards against regression of #86122. + const { actions, table, component } = testBed; + + await act(async () => { + actions.clickIncludeStatsSwitch(); + }); + component.update(); + + actions.sortTableOnStorageSize(); + + // The table sorts by the underlying byte values in ascending order, instead of sorting by + // the human-readable string values. + const { tableCellsValues } = table.getMetaData('dataStreamTable'); + expect(tableCellsValues).toEqual([ + ['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '5b', '1', 'Delete'], + ['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1kb', '1', 'Delete'], ]); }); diff --git a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts index 333cb4b97f2aa..c1a3a728d4238 100644 --- a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts +++ b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts @@ -16,6 +16,7 @@ export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataS template, ilm_policy: ilmPolicyName, store_size: storageSize, + store_size_bytes: storageSizeBytes, maximum_timestamp: maxTimeStamp, _meta, privileges, @@ -37,6 +38,7 @@ export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataS indexTemplateName: template, ilmPolicyName, storageSize, + storageSizeBytes, maxTimeStamp, _meta, privileges, diff --git a/x-pack/plugins/index_management/common/types/data_streams.ts b/x-pack/plugins/index_management/common/types/data_streams.ts index fca10f85ab63c..57abc0e188e16 100644 --- a/x-pack/plugins/index_management/common/types/data_streams.ts +++ b/x-pack/plugins/index_management/common/types/data_streams.ts @@ -36,6 +36,7 @@ export interface DataStreamFromEs { template: string; ilm_policy?: string; store_size?: string; + store_size_bytes?: number; maximum_timestamp?: number; privileges: PrivilegesFromEs; hidden: boolean; @@ -57,6 +58,7 @@ export interface DataStream { indexTemplateName: string; ilmPolicyName?: string; storageSize?: string; + storageSizeBytes?: number; maxTimeStamp?: number; _meta?: Meta; privileges: Privileges; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx index f74147f996701..dfad10ab62449 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx @@ -90,12 +90,14 @@ export const DataStreamTable: React.FunctionComponent = ({ }); columns.push({ - field: 'storageSize', + field: 'storageSizeBytes', name: i18n.translate('xpack.idxMgmt.dataStreamList.table.storageSizeColumnTitle', { defaultMessage: 'Storage size', }), truncateText: true, sortable: true, + render: (storageSizeBytes: DataStream['storageSizeBytes'], dataStream: DataStream) => + dataStream.storageSize, }); } diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts index 4124d8e897b5b..4256107fc1818 100644 --- a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts @@ -23,6 +23,7 @@ interface PrivilegesFromEs { interface StatsFromEs { data_stream: string; store_size: string; + store_size_bytes: number; maximum_timestamp: number; } @@ -40,7 +41,7 @@ const enhanceDataStreams = ({ if (dataStreamsStats) { // eslint-disable-next-line @typescript-eslint/naming-convention - const { store_size, maximum_timestamp } = + const { store_size, store_size_bytes, maximum_timestamp } = dataStreamsStats.find( ({ data_stream: statsName }: { data_stream: string }) => statsName === dataStream.name ) || {}; @@ -48,6 +49,7 @@ const enhanceDataStreams = ({ enhancedDataStream = { ...enhancedDataStream, store_size, + store_size_bytes, maximum_timestamp, }; } diff --git a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts index f430e6b5e4d90..879d2d95d7946 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts @@ -89,7 +89,7 @@ export const useLogSource = ({ sourceId, fetch }: { sourceId: string; fetch: Htt const derivedIndexPattern = useMemo( () => ({ fields: sourceStatus?.logIndexFields ?? [], - title: sourceConfiguration?.configuration.name ?? 'unknown', + title: sourceConfiguration?.configuration.logAlias ?? 'unknown', }), [sourceConfiguration, sourceStatus] ); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx index b0e6ab751f02e..4541a55dbeca7 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx @@ -8,6 +8,7 @@ import { EuiPortal, EuiTabs, EuiTab, EuiPanel, EuiTitle, EuiSpacer } from '@elas import { FormattedMessage } from '@kbn/i18n/react'; import React, { useMemo, useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; +import { EuiOutsideClickDetector } from '@elastic/eui'; import { euiStyled } from '../../../../../../../observability/public'; import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../../../../lib/lib'; import { InventoryItemType } from '../../../../../../common/inventory_models/types'; @@ -70,52 +71,58 @@ export const NodeContextPopover = ({ return ( - - - - - -

{node.name}

-
-
- - - - - - - - - - - - - - -
- - - {tabs.map((tab, i) => ( - setSelectedTab(i)}> - {tab.name} - - ))} - -
- {tabs[selectedTab].content} -
+ + + + + + +

{node.name}

+
+
+ + + + + + + + + + + + + + +
+ + + {tabs.map((tab, i) => ( + setSelectedTab(i)} + > + {tab.name} + + ))} + +
+ {tabs[selectedTab].content} +
+
); }; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx index 33deb0356660c..deb48027512cc 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx @@ -290,7 +290,7 @@ describe('workspace_panel', () => { const onData = expressionRendererMock.mock.calls[0][0].onData$!; const tableData = { table1: { columns: [], rows: [] } }; - onData(undefined, { tables: tableData }); + onData(undefined, { tables: { tables: tableData } }); expect(dispatch).toHaveBeenCalledWith({ type: 'UPDATE_ACTIVE_DATA', tables: tableData }); }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 52d1b2729c7ab..8820f26479cf9 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -51,9 +51,9 @@ import { import { VIS_EVENT_TO_TRIGGER } from '../../../../../../../src/plugins/visualizations/public'; import { WorkspacePanelWrapper } from './workspace_panel_wrapper'; import { DropIllustration } from '../../../assets/drop_illustration'; -import { LensInspectorAdapters } from '../../types'; import { getOriginalRequestErrorMessage } from '../../error_helper'; import { validateDatasourceAndVisualization } from '../state_helpers'; +import { DefaultInspectorAdapters } from '../../../../../../../src/plugins/expressions/common'; export interface WorkspacePanelProps { activeVisualizationId: string | null; @@ -382,11 +382,11 @@ export const InnerVisualizationWrapper = ({ ); const onData$ = useCallback( - (data: unknown, inspectorAdapters?: LensInspectorAdapters) => { + (data: unknown, inspectorAdapters?: Partial) => { if (inspectorAdapters && inspectorAdapters.tables) { dispatch({ type: 'UPDATE_ACTIVE_DATA', - tables: inspectorAdapters.tables, + tables: inspectorAdapters.tables.tables, }); } }, diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx index 6c86ae5cff2c8..190470e6c4235 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx @@ -20,7 +20,7 @@ import { PaletteOutput } from 'src/plugins/charts/public'; import { Subscription } from 'rxjs'; import { toExpression, Ast } from '@kbn/interpreter/common'; -import { RenderMode } from 'src/plugins/expressions'; +import { DefaultInspectorAdapters, RenderMode } from 'src/plugins/expressions'; import { map, distinctUntilChanged, skip } from 'rxjs/operators'; import isEqual from 'fast-deep-equal'; import { @@ -50,7 +50,6 @@ import { IndexPatternsContract } from '../../../../../../src/plugins/data/public import { getEditPath, DOC_TYPE } from '../../../common'; import { IBasePath } from '../../../../../../src/core/public'; import { LensAttributeService } from '../../lens_attribute_service'; -import { LensInspectorAdapters } from '../types'; export type LensSavedObjectAttributes = Omit; @@ -92,7 +91,7 @@ export class Embeddable private subscription: Subscription; private autoRefreshFetchSubscription: Subscription; private isInitialized = false; - private activeData: LensInspectorAdapters | undefined; + private activeData: Partial | undefined; private externalSearchContext: { timeRange?: TimeRange; @@ -229,7 +228,7 @@ export class Embeddable private updateActiveData = ( data: unknown, - inspectorAdapters?: LensInspectorAdapters | undefined + inspectorAdapters?: Partial | undefined ) => { this.activeData = inspectorAdapters; }; diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx index 2fc1cfee82fd3..e2607886a4219 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx @@ -14,9 +14,8 @@ import { ReactExpressionRendererProps, } from 'src/plugins/expressions/public'; import { ExecutionContextSearch } from 'src/plugins/data/public'; -import { RenderMode } from 'src/plugins/expressions'; +import { DefaultInspectorAdapters, RenderMode } from 'src/plugins/expressions'; import { getOriginalRequestErrorMessage } from '../error_helper'; -import { LensInspectorAdapters } from '../types'; export interface ExpressionWrapperProps { ExpressionRenderer: ReactExpressionRendererType; @@ -25,7 +24,10 @@ export interface ExpressionWrapperProps { searchContext: ExecutionContextSearch; searchSessionId?: string; handleEvent: (event: ExpressionRendererEvent) => void; - onData$: (data: unknown, inspectorAdapters?: LensInspectorAdapters | undefined) => void; + onData$: ( + data: unknown, + inspectorAdapters?: Partial | undefined + ) => void; renderMode?: RenderMode; hasCompatibleActions?: ReactExpressionRendererProps['hasCompatibleActions']; } diff --git a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts index 02c07d809e09f..5521d585262dd 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts @@ -7,8 +7,12 @@ import moment from 'moment'; import { mergeTables } from './merge_tables'; import { ExpressionValueSearchContext } from 'src/plugins/data/public'; -import { Datatable, ExecutionContext } from 'src/plugins/expressions'; -import { LensInspectorAdapters } from './types'; +import { + Datatable, + ExecutionContext, + DefaultInspectorAdapters, + TablesAdapter, +} from 'src/plugins/expressions'; describe('lens_merge_tables', () => { const sampleTable1: Datatable = { @@ -50,12 +54,15 @@ describe('lens_merge_tables', () => { }); it('should store the current tables in the tables inspector', () => { - const adapters: LensInspectorAdapters = { tables: {} }; + const adapters: DefaultInspectorAdapters = { + tables: new TablesAdapter(), + requests: {} as never, + }; mergeTables.fn(null, { layerIds: ['first', 'second'], tables: [sampleTable1, sampleTable2] }, { inspectorAdapters: adapters, - } as ExecutionContext); - expect(adapters.tables!.first).toBe(sampleTable1); - expect(adapters.tables!.second).toBe(sampleTable2); + } as ExecutionContext); + expect(adapters.tables!.tables.first).toBe(sampleTable1); + expect(adapters.tables!.tables.second).toBe(sampleTable2); }); it('should pass the date range along', () => { diff --git a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts index 109b30144fc20..b13efa41ff6c4 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts @@ -14,7 +14,7 @@ import { ExpressionValueSearchContext, search } from '../../../../../src/plugins const { toAbsoluteDates } = search.aggs; import { LensMultiTable } from '../types'; -import { LensInspectorAdapters } from './types'; +import { Adapters } from '../../../../../src/plugins/inspector/common'; interface MergeTables { layerIds: string[]; @@ -26,7 +26,7 @@ export const mergeTables: ExpressionFunctionDefinition< ExpressionValueSearchContext | null, MergeTables, LensMultiTable, - ExecutionContext + ExecutionContext > = { name: 'lens_merge_tables', type: 'lens_multitable', @@ -48,17 +48,14 @@ export const mergeTables: ExpressionFunctionDefinition< }, inputTypes: ['kibana_context', 'null'], fn(input, { layerIds, tables }, context) { - if (!context.inspectorAdapters) { - context.inspectorAdapters = {}; - } - if (!context.inspectorAdapters.tables) { - context.inspectorAdapters.tables = {}; - } const resultTables: Record = {}; tables.forEach((table, index) => { resultTables[layerIds[index]] = table; // adapter is always defined at that point because we make sure by the beginning of the function - context.inspectorAdapters.tables![layerIds[index]] = table; + if (context?.inspectorAdapters?.tables) { + context.inspectorAdapters.tables.allowCsvExport = true; + context.inspectorAdapters.tables.logDatatable(layerIds[index], table); + } }); return { type: 'lens_multitable', diff --git a/x-pack/plugins/lens/public/editor_frame_service/types.ts b/x-pack/plugins/lens/public/editor_frame_service/types.ts index 2da95ec2fd66f..5117c1ef85e68 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/types.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/types.ts @@ -7,6 +7,3 @@ import { Datatable } from 'src/plugins/expressions'; export type TableInspectorAdapter = Record; -export interface LensInspectorAdapters { - tables?: TableInspectorAdapter; -} diff --git a/x-pack/plugins/ml/common/constants/messages.ts b/x-pack/plugins/ml/common/constants/messages.ts index 1027ee5bf9a89..0e7303c6d7317 100644 --- a/x-pack/plugins/ml/common/constants/messages.ts +++ b/x-pack/plugins/ml/common/constants/messages.ts @@ -90,6 +90,38 @@ export const getMessages = once(() => { url: 'https://www.elastic.co/guide/en/machine-learning/{{version}}/ml-configuring-aggregation.html', }, + cardinality_no_results: { + status: VALIDATION_STATUS.WARNING, + heading: i18n.translate( + 'xpack.ml.models.jobValidation.messages.cardinalityNoResultsHeading', + { + defaultMessage: 'Field cardinality', + } + ), + text: i18n.translate('xpack.ml.models.jobValidation.messages.cardinalityNoResultsMessage', { + defaultMessage: `Cardinality checks could not be run. The query to validate fields didn't return any documents.`, + }), + }, + cardinality_field_not_exists: { + status: VALIDATION_STATUS.WARNING, + heading: i18n.translate( + 'xpack.ml.models.jobValidation.messages.cardinalityFieldNotExistsHeading', + { + defaultMessage: 'Field cardinality', + } + ), + text: i18n.translate( + 'xpack.ml.models.jobValidation.messages.cardinalityFieldNotExistsMessage', + { + defaultMessage: `Cardinality checks could not be run for field {fieldName}. The query to validate the field didn't return any documents.`, + values: { + fieldName: '"{{fieldName}}"', + }, + } + ), + url: + 'https://www.elastic.co/guide/en/machine-learning/{{version}}/ml-configuring-aggregation.html', + }, cardinality_by_field: { status: VALIDATION_STATUS.WARNING, text: i18n.translate('xpack.ml.models.jobValidation.messages.cardinalityByFieldMessage', { diff --git a/x-pack/plugins/ml/common/types/ml_url_generator.ts b/x-pack/plugins/ml/common/types/ml_url_generator.ts index c91e3615514ff..fb432189c6dd3 100644 --- a/x-pack/plugins/ml/common/types/ml_url_generator.ts +++ b/x-pack/plugins/ml/common/types/ml_url_generator.ts @@ -90,7 +90,7 @@ export interface ExplorerAppState { mlExplorerSwimlane: { selectedType?: 'overall' | 'viewBy'; selectedLanes?: string[]; - selectedTimes?: number[]; + selectedTimes?: [number, number]; showTopFieldValues?: boolean; viewByFieldName?: string; viewByPerPage?: number; diff --git a/x-pack/plugins/ml/public/application/components/data_recognizer/data_recognizer.js b/x-pack/plugins/ml/public/application/components/data_recognizer/data_recognizer.js index 5790b8bd239e0..e4ed8f67fd783 100644 --- a/x-pack/plugins/ml/public/application/components/data_recognizer/data_recognizer.js +++ b/x-pack/plugins/ml/public/application/components/data_recognizer/data_recognizer.js @@ -29,6 +29,9 @@ export class DataRecognizer extends Component { // once the mount is complete, call the recognize endpoint to see if the index format is known to us, ml.recognizeIndex({ indexPatternTitle: this.indexPattern.title }) .then((resp) => { + // Sort results by title prior to display + resp.sort((res1, res2) => res1.title.localeCompare(res2.title)); + const results = resp.map((r) => ( { - shouldShowLoadingIndicator = false; - this.setState({ - ...this.state, - ui: { - ...this.state.ui, - iconType: statusToEuiIconType(getMostSevereMessageStatus(messages)), - isLoading: false, - isModalVisible: true, - }, - data: { - messages, - success: true, - }, - title: job.job_id, - }); - if (typeof this.props.setIsValid === 'function') { - this.props.setIsValid( - messages.some((m) => m.status === VALIDATION_STATUS.ERROR) === false + if (typeof duration === 'object' && duration.start !== null && duration.end !== null) { + let shouldShowLoadingIndicator = true; + + this.props.ml + .validateJob({ duration, fields, job }) + .then((messages) => { + shouldShowLoadingIndicator = false; + + const messagesContainError = messages.some((m) => m.status === VALIDATION_STATUS.ERROR); + + if (messagesContainError) { + messages.push({ + id: 'job_validation_includes_error', + text: i18n.translate('xpack.ml.validateJob.jobValidationIncludesErrorText', { + defaultMessage: + 'Job validation has failed, but you can still continue and create the job. Please be aware the job may encounter problems when running.', + }), + status: VALIDATION_STATUS.WARNING, + }); + } + + this.setState({ + ...this.state, + ui: { + ...this.state.ui, + iconType: statusToEuiIconType(getMostSevereMessageStatus(messages)), + isLoading: false, + isModalVisible: true, + }, + data: { + messages, + success: true, + }, + title: job.job_id, + }); + if (typeof this.props.setIsValid === 'function') { + this.props.setIsValid(!messagesContainError); + } + }) + .catch((error) => { + const { toasts } = this.props.kibana.services.notifications; + const toastNotificationService = toastNotificationServiceProvider(toasts); + toastNotificationService.displayErrorToast( + error, + i18n.translate('xpack.ml.jobService.validateJobErrorTitle', { + defaultMessage: 'Job Validation Error', + }) ); + }); + + // wait for 250ms before triggering the loading indicator + // to avoid flickering when there's a loading time below + // 250ms for the job validation data + const delay = 250; + setTimeout(() => { + if (shouldShowLoadingIndicator) { + this.setState({ + ...this.state, + ui: { + ...this.state.ui, + isLoading: true, + isModalVisible: false, + }, + }); } - }) - .catch((error) => { - const { toasts } = this.props.kibana.services.notifications; - const toastNotificationService = toastNotificationServiceProvider(toasts); - toastNotificationService.displayErrorToast( - error, - i18n.translate('xpack.ml.jobService.validateJobErrorTitle', { - defaultMessage: 'Job Validation Error', - }) - ); + }, delay); + } else { + this.setState({ + ...this.state, + ui: { + ...this.state.ui, + iconType: statusToEuiIconType(VALIDATION_STATUS.WARNING), + isLoading: false, + isModalVisible: true, + }, + data: { + messages: [ + { + id: 'job_validation_skipped', + text: i18n.translate('xpack.ml.validateJob.jobValidationSkippedText', { + defaultMessage: + 'Job validation could not be run because of insufficient sample data. Please be aware the job may encounter problems when running.', + }), + status: VALIDATION_STATUS.WARNING, + }, + ], + success: true, + }, + title: job.job_id, }); - - // wait for 250ms before triggering the loading indicator - // to avoid flickering when there's a loading time below - // 250ms for the job validation data - const delay = 250; - setTimeout(() => { - if (shouldShowLoadingIndicator) { - this.setState({ - ...this.state, - ui: { - ...this.state.ui, - isLoading: true, - isModalVisible: false, - }, - }); + if (typeof this.props.setIsValid === 'function') { + this.props.setIsValid(true); } - }, delay); + } } }; diff --git a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js index f2f785d91dcac..7e473f12ee50d 100644 --- a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js +++ b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js @@ -27,6 +27,7 @@ const job = { }; const getJobConfig = () => job; +const getDuration = () => ({ start: 0, end: 1 }); function prepareTest(messages) { const p = Promise.resolve(messages); @@ -40,7 +41,9 @@ function prepareTest(messages) { }, }; - const component = ; + const component = ( + + ); const wrapper = shallowWithIntl(component); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section_results.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section_results.tsx index e22e48733cc39..7ec967ebfb935 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section_results.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section_results.tsx @@ -26,6 +26,7 @@ import { SavedSearchQuery } from '../../../../../contexts/ml'; import { defaultSearchQuery, DataFrameAnalyticsConfig, + INDEX_STATUS, SEARCH_SIZE, getAnalysisType, } from '../../../../common'; @@ -54,11 +55,12 @@ const showingFirstDocs = i18n.translate( const getResultsSectionHeaderItems = ( columnsWithCharts: EuiDataGridColumn[], + status: INDEX_STATUS, tableItems: Array>, rowCount: number, colorRange?: ReturnType ): ExpandableSectionProps['headerItems'] => { - return columnsWithCharts.length > 0 && tableItems.length > 0 + return columnsWithCharts.length > 0 && (tableItems.length > 0 || status === INDEX_STATUS.LOADED) ? [ { id: 'explorationTableTotalDocs', @@ -109,11 +111,12 @@ export const ExpandableSectionResults: FC = ({ needsDestIndexPattern, searchQuery, }) => { - const { columnsWithCharts, tableItems } = indexData; + const { columnsWithCharts, status, tableItems } = indexData; // Results section header items and content const resultsSectionHeaderItems = getResultsSectionHeaderItems( columnsWithCharts, + status, tableItems, indexData.rowCount, colorRange @@ -137,14 +140,15 @@ export const ExpandableSectionResults: FC = ({ {(columnsWithCharts.length > 0 || searchQuery !== defaultSearchQuery) && indexPattern !== undefined && ( <> - {columnsWithCharts.length > 0 && tableItems.length > 0 && ( - - )} + {columnsWithCharts.length > 0 && + (tableItems.length > 0 || status === INDEX_STATUS.LOADED) && ( + + )} )} diff --git a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts index 5712f3c4843b4..297da075d6d60 100644 --- a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts +++ b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts @@ -26,7 +26,6 @@ import { loadTopInfluencers, AppStateSelectedCells, ExplorerJob, - TimeRangeBounds, } from '../explorer_utils'; import { ExplorerState } from '../reducers'; import { useMlKibana, useTimefilter } from '../../contexts/kibana'; @@ -34,6 +33,7 @@ import { AnomalyTimelineService } from '../../services/anomaly_timeline_service' import { mlResultsServiceProvider } from '../../services/results_service'; import { isViewBySwimLaneData } from '../swimlane_container'; import { ANOMALY_SWIM_LANE_HARD_LIMIT } from '../explorer_constants'; +import { TimefilterContract } from '../../../../../../../src/plugins/data/public'; // Memoize the data fetching methods. // wrapWithLastRefreshArg() wraps any given function and preprends a `lastRefresh` argument @@ -63,7 +63,6 @@ const memoizedLoadTopInfluencers = memoize(loadTopInfluencers); const memoizedLoadAnomaliesTableData = memoize(loadAnomaliesTableData); export interface LoadExplorerDataConfig { - bounds: TimeRangeBounds; influencersFilterQuery: any; lastRefresh: number; noInfluencersConfigured: boolean; @@ -82,7 +81,6 @@ export interface LoadExplorerDataConfig { export const isLoadExplorerDataConfig = (arg: any): arg is LoadExplorerDataConfig => { return ( arg !== undefined && - arg.bounds !== undefined && arg.selectedJobs !== undefined && arg.selectedJobs !== null && arg.viewBySwimlaneFieldName !== undefined @@ -92,7 +90,10 @@ export const isLoadExplorerDataConfig = (arg: any): arg is LoadExplorerDataConfi /** * Fetches the data necessary for the Anomaly Explorer using observables. */ -const loadExplorerDataProvider = (anomalyTimelineService: AnomalyTimelineService) => { +const loadExplorerDataProvider = ( + anomalyTimelineService: AnomalyTimelineService, + timefilter: TimefilterContract +) => { const memoizedLoadOverallData = memoize( anomalyTimelineService.loadOverallData, anomalyTimelineService @@ -107,7 +108,6 @@ const loadExplorerDataProvider = (anomalyTimelineService: AnomalyTimelineService } const { - bounds, lastRefresh, influencersFilterQuery, noInfluencersConfigured, @@ -125,6 +125,9 @@ const loadExplorerDataProvider = (anomalyTimelineService: AnomalyTimelineService const selectionInfluencers = getSelectionInfluencers(selectedCells, viewBySwimlaneFieldName); const jobIds = getSelectionJobIds(selectedCells, selectedJobs); + + const bounds = timefilter.getBounds(); + const timerange = getSelectionTimeRange( selectedCells, swimlaneBucketInterval.asSeconds(), @@ -287,12 +290,12 @@ export const useExplorerData = (): [Partial | undefined, (d: any) } = useMlKibana(); const loadExplorerData = useMemo(() => { - const service = new AnomalyTimelineService( + const anomalyTimelineService = new AnomalyTimelineService( timefilter, uiSettings, mlResultsServiceProvider(mlApiServices) ); - return loadExplorerDataProvider(service); + return loadExplorerDataProvider(anomalyTimelineService, timefilter); }, []); const loadExplorerData$ = useMemo(() => new Subject(), []); diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts b/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts index 7440bf3213413..3f5f016fc365a 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts +++ b/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts @@ -20,7 +20,6 @@ export const EXPLORER_ACTION = { CLEAR_INFLUENCER_FILTER_SETTINGS: 'clearInfluencerFilterSettings', CLEAR_JOBS: 'clearJobs', JOB_SELECTION_CHANGE: 'jobSelectionChange', - SET_BOUNDS: 'setBounds', SET_CHARTS: 'setCharts', SET_EXPLORER_DATA: 'setExplorerData', SET_FILTER_DATA: 'setFilterData', diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts b/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts index 8a95e5c6adbd6..2e718990d7498 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts +++ b/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts @@ -19,7 +19,7 @@ import { DeepPartial } from '../../../common/types/common'; import { jobSelectionActionCreator } from './actions'; import { ExplorerChartsData } from './explorer_charts/explorer_charts_container_service'; import { EXPLORER_ACTION } from './explorer_constants'; -import { AppStateSelectedCells, TimeRangeBounds } from './explorer_utils'; +import { AppStateSelectedCells } from './explorer_utils'; import { explorerReducer, getExplorerDefaultState, ExplorerState } from './reducers'; import { ExplorerAppState } from '../../../common/types/ml_url_generator'; @@ -115,9 +115,6 @@ export const explorerService = { updateJobSelection: (selectedJobIds: string[]) => { explorerAction$.next(jobSelectionActionCreator(selectedJobIds)); }, - setBounds: (payload: TimeRangeBounds) => { - explorerAction$.next({ type: EXPLORER_ACTION.SET_BOUNDS, payload }); - }, setCharts: (payload: ExplorerChartsData) => { explorerAction$.next({ type: EXPLORER_ACTION.SET_CHARTS, payload }); }, diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_utils.d.ts b/x-pack/plugins/ml/public/application/explorer/explorer_utils.d.ts index 94690b74a6f8f..143a281be6951 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_utils.d.ts +++ b/x-pack/plugins/ml/public/application/explorer/explorer_utils.d.ts @@ -186,7 +186,7 @@ export declare interface FilterData { export declare interface AppStateSelectedCells { type: SwimlaneType; lanes: string[]; - times: number[]; + times: [number, number]; showTopFieldValues?: boolean; viewByFieldName?: string; } diff --git a/x-pack/plugins/ml/public/application/explorer/hooks/use_selected_cells.ts b/x-pack/plugins/ml/public/application/explorer/hooks/use_selected_cells.ts index f940fdc2387e2..7a279afc22ae7 100644 --- a/x-pack/plugins/ml/public/application/explorer/hooks/use_selected_cells.ts +++ b/x-pack/plugins/ml/public/application/explorer/hooks/use_selected_cells.ts @@ -7,15 +7,19 @@ import { useCallback, useEffect, useMemo } from 'react'; import { Duration } from 'moment'; import { SWIMLANE_TYPE } from '../explorer_constants'; -import { AppStateSelectedCells, TimeRangeBounds } from '../explorer_utils'; +import { AppStateSelectedCells } from '../explorer_utils'; import { ExplorerAppState } from '../../../../common/types/ml_url_generator'; +import { useTimefilter } from '../../contexts/kibana'; export const useSelectedCells = ( appState: ExplorerAppState, setAppState: (update: Partial) => void, - timeBounds: TimeRangeBounds | undefined, bucketInterval: Duration | undefined ): [AppStateSelectedCells | undefined, (swimlaneSelectedCells: AppStateSelectedCells) => void] => { + const timeFilter = useTimefilter(); + + const timeBounds = timeFilter.getBounds(); + // keep swimlane selection, restore selectedCells from AppState const selectedCells = useMemo(() => { return appState?.mlExplorerSwimlane?.selectedType !== undefined @@ -73,12 +77,7 @@ export const useSelectedCells = ( * Reset it entirely when it out of range. */ useEffect(() => { - if ( - timeBounds === undefined || - selectedCells?.times === undefined || - bucketInterval === undefined - ) - return; + if (selectedCells?.times === undefined || bucketInterval === undefined) return; let [selectedFrom, selectedTo] = selectedCells.times; @@ -108,7 +107,33 @@ export const useSelectedCells = ( times: [selectedFrom, selectedTo], }); } - }, [timeBounds, selectedCells, bucketInterval]); + }, [ + timeBounds.min?.valueOf(), + timeBounds.max?.valueOf(), + selectedCells, + bucketInterval?.asMilliseconds(), + ]); return [selectedCells, setSelectedCells]; }; + +export interface SelectionTimeRange { + earliestMs: number; + latestMs: number; +} + +export function getTimeBoundsFromSelection( + selectedCells: AppStateSelectedCells | undefined +): SelectionTimeRange | undefined { + if (selectedCells?.times === undefined) { + return; + } + + // time property of the cell data is an array, with the elements being + // the start times of the first and last cell selected. + return { + earliestMs: selectedCells.times[0] * 1000, + // Subtract 1 ms so search does not include start of next bucket. + latestMs: selectedCells.times[1] * 1000 - 1, + }; +} diff --git a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts index 546f782f4d868..04aebe2a39d8a 100644 --- a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts +++ b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts @@ -12,7 +12,6 @@ import { Action } from '../../explorer_dashboard_service'; import { getClearedSelectedAnomaliesState, getDefaultSwimlaneData, - getSelectionTimeRange, getSwimlaneBucketInterval, getViewBySwimlaneOptions, } from '../../explorer_utils'; @@ -23,6 +22,7 @@ import { jobSelectionChange } from './job_selection_change'; import { ExplorerState } from './state'; import { setInfluencerFilterSettings } from './set_influencer_filter_settings'; import { setKqlQueryBarPlaceholder } from './set_kql_query_bar_placeholder'; +import { getTimeBoundsFromSelection } from '../../hooks/use_selected_cells'; export const explorerReducer = (state: ExplorerState, nextAction: Action): ExplorerState => { const { type, payload } = nextAction; @@ -48,10 +48,6 @@ export const explorerReducer = (state: ExplorerState, nextAction: Action): Explo nextState = jobSelectionChange(state, payload); break; - case EXPLORER_ACTION.SET_BOUNDS: - nextState = { ...state, bounds: payload }; - break; - case EXPLORER_ACTION.SET_CHARTS: nextState = { ...state, @@ -144,7 +140,7 @@ export const explorerReducer = (state: ExplorerState, nextAction: Action): Explo nextState = state; } - if (nextState.selectedJobs === null || nextState.bounds === undefined) { + if (nextState.selectedJobs === null) { return nextState; } @@ -164,21 +160,16 @@ export const explorerReducer = (state: ExplorerState, nextAction: Action): Explo selectedCells: nextState.selectedCells!, }); - const { bounds, selectedCells } = nextState; + const { selectedCells } = nextState; - const timerange = getSelectionTimeRange( - selectedCells, - swimlaneBucketInterval.asSeconds(), - bounds - ); + const timeRange = getTimeBoundsFromSelection(selectedCells); return { ...nextState, swimlaneBucketInterval, - viewByLoadedForTimeFormatted: - selectedCells !== undefined && selectedCells.showTopFieldValues === true - ? formatHumanReadableDateTime(timerange.earliestMs) - : null, + viewByLoadedForTimeFormatted: timeRange + ? formatHumanReadableDateTime(timeRange.earliestMs) + : null, viewBySwimlaneFieldName, viewBySwimlaneOptions, ...checkSelectedCells(nextState), diff --git a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts index 14b0a6033999c..0ae09a794bc5d 100644 --- a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts +++ b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts @@ -17,7 +17,6 @@ import { AnomaliesTableData, ExplorerJob, AppStateSelectedCells, - TimeRangeBounds, OverallSwimlaneData, SwimlaneData, ViewBySwimLaneData, @@ -27,7 +26,6 @@ import { SWIM_LANE_DEFAULT_PAGE_SIZE } from '../../explorer_constants'; export interface ExplorerState { annotations: AnnotationsTable; - bounds: TimeRangeBounds | undefined; chartsData: ExplorerChartsData; fieldFormatsLoading: boolean; filterActive: boolean; @@ -69,7 +67,6 @@ export function getExplorerDefaultState(): ExplorerState { annotationsData: [], aggregations: {}, }, - bounds: undefined, chartsData: getDefaultChartsData(), fieldFormatsLoading: false, filterActive: false, diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx index b166d90f040a6..101d4857a89b1 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx @@ -255,7 +255,7 @@ export const SwimlaneContainer: FC = ({ onBrushEnd: (e: HeatmapBrushEvent) => { onCellsSelection({ lanes: e.y as string[], - times: e.x.map((v) => (v as number) / 1000), + times: e.x.map((v) => (v as number) / 1000) as [number, number], type: swimlaneType, viewByFieldName: swimlaneData.fieldName, }); @@ -326,7 +326,7 @@ export const SwimlaneContainer: FC = ({ const startTime = (cell.datum.x as number) / 1000; const payload = { lanes: [String(cell.datum.y)], - times: [startTime, startTime + swimlaneData.interval], + times: [startTime, startTime + swimlaneData.interval] as [number, number], type: swimlaneType, viewByFieldName: swimlaneData.fieldName, }; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.tsx index 90ae44eb85e5b..0cb23c2b264bb 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.tsx @@ -17,6 +17,7 @@ import { EuiButtonEmpty, EuiButton, EuiLoadingSpinner, + EuiText, } from '@elastic/eui'; import { deleteJobs } from '../utils'; @@ -103,7 +104,7 @@ export const DeleteJobModal: FC = ({ setShowFunction, unsetShowFunction, ) : ( - <> + - ); - modal = ( - - - } - confirmButtonText={ - - } - buttonColor="danger" - defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} - className="eui-textBreakWord" - > - {this.state.deleting === true && ( -
- - -
- -
-
- )} - - {this.state.deleting === false && ( - -

- -

-
- )} -
-
- ); - } - - return
{modal}
; - } -} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/validation_step/validation.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/validation_step/validation.tsx index 3bde32f40eeb5..224e2eacb21e0 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/validation_step/validation.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/validation_step/validation.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, FC, useContext, useState, useEffect } from 'react'; +import React, { Fragment, FC, useContext, useEffect } from 'react'; import { WizardNav } from '../wizard_nav'; import { WIZARD_STEPS, StepProps } from '../step_types'; import { JobCreatorContext } from '../job_creator_context'; @@ -21,7 +21,6 @@ const idFilterList = [ export const ValidationStep: FC = ({ setCurrentStep, isCurrentStep }) => { const { jobCreator, jobCreatorUpdate, jobValidator } = useContext(JobCreatorContext); - const [nextActive, setNextActive] = useState(false); if (jobCreator.type === JOB_TYPE.ADVANCED) { // for advanced jobs, ignore time range warning as the @@ -50,13 +49,8 @@ export const ValidationStep: FC = ({ setCurrentStep, isCurrentStep }) }, []); // keep a record of the advanced validation in the jobValidator - // and disable the next button if any advanced checks have failed. - // note, it is not currently possible to get to a state where any of the - // advanced validation checks return an error because they are all - // caught in previous basic checks function setIsValid(valid: boolean) { jobValidator.advancedValid = valid; - setNextActive(valid); } return ( @@ -74,7 +68,7 @@ export const ValidationStep: FC = ({ setCurrentStep, isCurrentStep }) setCurrentStep(WIZARD_STEPS.JOB_DETAILS)} next={() => setCurrentStep(WIZARD_STEPS.SUMMARY)} - nextActive={nextActive} + nextActive={true} /> )} diff --git a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx index 2126cbceae6b1..15ce3847f4d9c 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx @@ -119,13 +119,6 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim from: globalState.time.from, to: globalState.time.to, }); - - const timefilterBounds = timefilter.getBounds(); - // Only if both min/max bounds are valid moment times set the bounds. - // An invalid string restored from globalState might return `undefined`. - if (timefilterBounds?.min !== undefined && timefilterBounds?.max !== undefined) { - explorerService.setBounds(timefilterBounds); - } } }, [globalState?.time?.from, globalState?.time?.to]); @@ -208,7 +201,6 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim const [selectedCells, setSelectedCells] = useSelectedCells( explorerUrlState, setExplorerUrlState, - explorerState?.bounds, explorerState?.swimlaneBucketInterval ); @@ -219,7 +211,6 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim const loadExplorerDataConfig = explorerState !== undefined ? { - bounds: explorerState.bounds, lastRefresh, influencersFilterQuery: explorerState.influencersFilterQuery, noInfluencersConfigured: explorerState.noInfluencersConfigured, diff --git a/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts b/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts index 8cfb066c9d092..2fe49064cb16d 100644 --- a/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts +++ b/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts @@ -19,7 +19,6 @@ import { Calendar } from '../../../common/types/calendars'; import { searchProvider } from './search'; import { DataFrameAnalyticsConfig } from '../../../common/types/data_frame_analytics'; -import { InferenceConfigResponse, TrainedModelStat } from '../../../common/types/trained_models'; import { MLJobNotFound } from './errors'; import { MlClient, @@ -131,50 +130,6 @@ export function getMlClient( } } - async function getFilterTrainedModels( - p: Parameters, - allowWildcards: boolean = false - ) { - let configs = []; - try { - const resp = await mlClient.getTrainedModels(...p); - configs = resp.body.trained_model_configs; - } catch (error) { - if (error.statusCode === 404) { - throw new MLJobNotFound(error.body.error.reason); - } - throw error.body ?? error; - } - - const modelIds = getTrainedModelIdsFromRequest(p); - - const modelJobIds: string[] = configs - .map((m) => m.metadata?.analytics_config.id) - .filter((id) => id !== undefined); - const filteredModelJobIds = await jobSavedObjectService.filterJobIdsForSpace( - 'data-frame-analytics', - modelJobIds - ); - - const filteredConfigs = configs.filter((m) => { - const jobId = m.metadata?.analytics_config.id; - return jobId === undefined || filteredModelJobIds.includes(jobId); - }); - const filteredConfigsIds = filteredConfigs.map((c) => c.model_id); - - if (modelIds.length > filteredConfigs.length) { - let missingIds = modelIds.filter((j) => filteredConfigsIds.indexOf(j) === -1); - if (allowWildcards === true && missingIds.join().match('\\*') !== null) { - // filter out wildcard ids from the error - missingIds = missingIds.filter((id) => id.match('\\*') === null); - } - if (missingIds.length) { - throw new MLJobNotFound(`No known trained model with model_id [${missingIds.join(',')}]`); - } - } - return filteredConfigs; - } - return { async closeJob(...p: Parameters) { await jobIdsCheck('anomaly-detector', p); @@ -228,7 +183,6 @@ export function getMlClient( return mlClient.deleteModelSnapshot(...p); }, async deleteTrainedModel(...p: Parameters) { - await getFilterTrainedModels(p, true); return mlClient.deleteTrainedModel(...p); }, async estimateModelMemory(...p: Parameters) { @@ -428,20 +382,10 @@ export function getMlClient( return mlClient.getRecords(...p); }, async getTrainedModels(...p: Parameters) { - const models = await getFilterTrainedModels(p, true); - return { body: { trained_model_configs: models } }; + return mlClient.getTrainedModels(...p); }, async getTrainedModelsStats(...p: Parameters) { - await getFilterTrainedModels(p, true); - const models = await getFilterTrainedModels(p); - const filteredModelIds = models.map((m) => m.model_id); - const { body: allModelStats } = await mlClient.getTrainedModelsStats<{ - trained_model_stats: TrainedModelStat[]; - }>(...p); - const modelStats = allModelStats.trained_model_stats.filter((m) => - filteredModelIds.includes(m.model_id) - ); - return { body: { trained_model_stats: modelStats } }; + return mlClient.getTrainedModelsStats(...p); }, async info(...p: Parameters) { return mlClient.info(...p); @@ -571,9 +515,3 @@ function getJobIdFromBody(p: any): string | undefined { const [params] = p; return params?.body?.job_id; } - -function getTrainedModelIdsFromRequest(p: any): string[] { - const [params] = p; - const ids = params?.model_id?.split(','); - return ids || []; -} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts index aeaf13ebf954e..67a95de3b3d71 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts @@ -249,6 +249,8 @@ export class DataRecognizer { }) ); + results.sort((res1, res2) => res1.id.localeCompare(res2.id)); + return results; } diff --git a/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts b/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts index d397d39d32b6b..8e88d1c1d0537 100644 --- a/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts @@ -12,7 +12,7 @@ import type { MlClient } from '../../lib/ml_client'; const callAs = { fieldCaps: () => Promise.resolve({ body: { fields: [] } }), - search: () => Promise.resolve({ body: { hits: { total: { value: 0, relation: 'eq' } } } }), + search: () => Promise.resolve({ body: { hits: { total: { value: 1, relation: 'eq' } } } }), }; const mlClusterClient = ({ diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts index 2e2a9e21aa959..3996f42c48926 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts @@ -189,6 +189,38 @@ describe('ML - validateCardinality', () => { ); }); + it('cardinality no results', () => { + const job = getJobConfig('partition_field_name'); + + const mockCardinality = cloneDeep(mockResponses); + mockCardinality.search.hits.total.value = 0; + mockCardinality.search.aggregations.airline_count.doc_count = 0; + + return validateCardinality( + mlClusterClientFactory(mockCardinality), + (job as unknown) as CombinedJob + ).then((messages) => { + const ids = messages.map((m) => m.id); + expect(ids).toStrictEqual(['cardinality_no_results']); + }); + }); + + it('cardinality field not exists', () => { + const job = getJobConfig('partition_field_name'); + + const mockCardinality = cloneDeep(mockResponses); + mockCardinality.search.hits.total.value = 1; + mockCardinality.search.aggregations.airline_count.doc_count = 0; + + return validateCardinality( + mlClusterClientFactory(mockCardinality), + (job as unknown) as CombinedJob + ).then((messages) => { + const ids = messages.map((m) => m.id); + expect(ids).toStrictEqual(['cardinality_field_not_exists']); + }); + }); + it('fields not aggregatable', () => { const job = getJobConfig('partition_field_name'); job.analysis_config.detectors.push({ diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts index 822d1a1081874..3afb403554666 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.ts @@ -132,35 +132,54 @@ const validateFactory = (client: IScopedClusterClient, job: CombinedJob): Valida datafeedConfig ); - uniqueFieldNames.forEach((uniqueFieldName) => { - const field = stats.aggregatableExistsFields.find( - (fieldData) => fieldData.fieldName === uniqueFieldName - ); - if (field !== undefined && typeof field === 'object' && field.stats) { - modelPlotCardinality += - modelPlotConfigFieldCount > 0 ? modelPlotConfigFieldCount : field.stats.cardinality!; - - if (isInvalid(field.stats.cardinality!)) { - messages.push({ - id: messageId || (`cardinality_${type}_field` as MessageId), - fieldName: uniqueFieldName, - }); - } - } else { - // only report uniqueFieldName as not aggregatable if it's not part - // of a valid categorization configuration and if it's not a scripted field or runtime mapping. - if ( - !isValidCategorizationConfig(job, uniqueFieldName) && - !isScriptField(job, uniqueFieldName) && - !isRuntimeMapping(job, uniqueFieldName) - ) { + if (stats.totalCount === 0) { + messages.push({ + id: 'cardinality_no_results', + }); + } else { + uniqueFieldNames.forEach((uniqueFieldName) => { + const aggregatableNotExistsField = stats.aggregatableNotExistsFields.find( + (fieldData) => fieldData.fieldName === uniqueFieldName + ); + + if (aggregatableNotExistsField !== undefined) { messages.push({ - id: 'field_not_aggregatable', + id: 'cardinality_field_not_exists', fieldName: uniqueFieldName, }); + } else { + const field = stats.aggregatableExistsFields.find( + (fieldData) => fieldData.fieldName === uniqueFieldName + ); + if (field !== undefined && typeof field === 'object' && field.stats) { + modelPlotCardinality += + modelPlotConfigFieldCount > 0 + ? modelPlotConfigFieldCount + : field.stats.cardinality!; + + if (isInvalid(field.stats.cardinality!)) { + messages.push({ + id: messageId || (`cardinality_${type}_field` as MessageId), + fieldName: uniqueFieldName, + }); + } + } else { + // only report uniqueFieldName as not aggregatable if it's not part + // of a valid categorization configuration and if it's not a scripted field or runtime mapping. + if ( + !isValidCategorizationConfig(job, uniqueFieldName) && + !isScriptField(job, uniqueFieldName) && + !isRuntimeMapping(job, uniqueFieldName) + ) { + messages.push({ + id: 'field_not_aggregatable', + fieldName: uniqueFieldName, + }); + } + } } - } - }); + }); + } } catch (e) { // checkAggregatableFieldsExist may return an error if 'fielddata' is // disabled for text fields (which is the default). If there was only diff --git a/x-pack/plugins/ml/server/routes/modules.ts b/x-pack/plugins/ml/server/routes/modules.ts index 0e5fde8a34c76..a9bceb61d5e66 100644 --- a/x-pack/plugins/ml/server/routes/modules.ts +++ b/x-pack/plugins/ml/server/routes/modules.ts @@ -133,10 +133,11 @@ export function dataRecognizer({ router, routeGuard }: RouteInitialization) { * @apiName RecognizeIndex * @apiDescription By supplying an index pattern, discover if any of the modules are a match for data in that index. * @apiSchema (params) modulesIndexPatternTitleSchema - * @apiSuccess {object[]} modules Array of objects describing the modules which match the index pattern. + * @apiSuccess {object[]} modules Array of objects describing the modules which match the index pattern, sorted by module ID. * @apiSuccessExample {json} Success-Response: * [{ * "id": "nginx_ecs", + * "title": "Nginx access logs", * "query": { * "bool": { * "filter": [ diff --git a/x-pack/plugins/ml/server/saved_objects/service.ts b/x-pack/plugins/ml/server/saved_objects/service.ts index bfc5b165fe555..207f10dc33d4b 100644 --- a/x-pack/plugins/ml/server/saved_objects/service.ts +++ b/x-pack/plugins/ml/server/saved_objects/service.ts @@ -114,6 +114,19 @@ export function jobSavedObjectServiceFactory( await savedObjectsClient.delete(ML_SAVED_OBJECT_TYPE, job.id, { force: true }); } + async function _forceDeleteJob(jobType: JobType, jobId: string, namespace: string) { + const id = savedObjectId({ + job_id: jobId, + datafeed_id: null, + type: jobType, + }); + + await internalSavedObjectsClient.delete(ML_SAVED_OBJECT_TYPE, id, { + namespace, + force: true, + }); + } + async function createAnomalyDetectionJob(jobId: string, datafeedId?: string) { await _createJob('anomaly-detector', jobId, datafeedId); } @@ -122,6 +135,10 @@ export function jobSavedObjectServiceFactory( await _deleteJob('anomaly-detector', jobId); } + async function forceDeleteAnomalyDetectionJob(jobId: string, namespace: string) { + await _forceDeleteJob('anomaly-detector', jobId, namespace); + } + async function createDataFrameAnalyticsJob(jobId: string) { await _createJob('data-frame-analytics', jobId); } @@ -130,6 +147,10 @@ export function jobSavedObjectServiceFactory( await _deleteJob('data-frame-analytics', jobId); } + async function forceDeleteDataFrameAnalyticsJob(jobId: string, namespace: string) { + await _forceDeleteJob('data-frame-analytics', jobId, namespace); + } + async function bulkCreateJobs(jobs: Array<{ job: JobObject; namespaces: string[] }>) { return await _bulkCreateJobs(jobs); } @@ -325,7 +346,9 @@ export function jobSavedObjectServiceFactory( createAnomalyDetectionJob, createDataFrameAnalyticsJob, deleteAnomalyDetectionJob, + forceDeleteAnomalyDetectionJob, deleteDataFrameAnalyticsJob, + forceDeleteDataFrameAnalyticsJob, addDatafeed, deleteDatafeed, filterJobsForSpace, diff --git a/x-pack/plugins/ml/server/saved_objects/sync.ts b/x-pack/plugins/ml/server/saved_objects/sync.ts index 16e0520567056..00dfdba178fe5 100644 --- a/x-pack/plugins/ml/server/saved_objects/sync.ts +++ b/x-pack/plugins/ml/server/saved_objects/sync.ts @@ -94,10 +94,14 @@ export function syncSavedObjectsFactory( results.savedObjectsDeleted[job.jobId] = { success: true }; } else { // Delete AD saved objects for jobs which no longer exist - const jobId = job.jobId; + const { jobId, namespaces } = job; tasks.push(async () => { try { - await jobSavedObjectService.deleteAnomalyDetectionJob(jobId); + if (namespaces !== undefined && namespaces.length) { + await jobSavedObjectService.forceDeleteAnomalyDetectionJob(jobId, namespaces[0]); + } else { + await jobSavedObjectService.deleteAnomalyDetectionJob(jobId); + } results.savedObjectsDeleted[job.jobId] = { success: true }; } catch (error) { results.savedObjectsDeleted[job.jobId] = { @@ -115,10 +119,14 @@ export function syncSavedObjectsFactory( results.savedObjectsDeleted[job.jobId] = { success: true }; } else { // Delete DFA saved objects for jobs which no longer exist - const jobId = job.jobId; + const { jobId, namespaces } = job; tasks.push(async () => { try { - await jobSavedObjectService.deleteDataFrameAnalyticsJob(jobId); + if (namespaces !== undefined && namespaces.length) { + await jobSavedObjectService.forceDeleteDataFrameAnalyticsJob(jobId, namespaces[0]); + } else { + await jobSavedObjectService.deleteDataFrameAnalyticsJob(jobId); + } results.savedObjectsDeleted[job.jobId] = { success: true }; } catch (error) { results.savedObjectsDeleted[job.jobId] = { diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts index a40c79acd8fd8..a15aad1bd8cc3 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts @@ -26,6 +26,7 @@ import { goToInProgressAlerts, } from '../tasks/alerts'; import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { cleanKibana } from '../tasks/common'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPage } from '../tasks/login'; @@ -34,16 +35,17 @@ import { DETECTIONS_URL } from '../urls/navigation'; describe('Alerts', () => { context('Closing alerts', () => { beforeEach(() => { + cleanKibana(); + removeSignalsIndex(); esArchiverLoad('alerts'); loginAndWaitForPage(DETECTIONS_URL); }); afterEach(() => { esArchiverUnload('alerts'); - removeSignalsIndex(); }); - it.skip('Closes and opens alerts', () => { + it('Closes and opens alerts', () => { waitForAlertsPanelToBeLoaded(); waitForAlertsToBeLoaded(); @@ -117,13 +119,11 @@ describe('Alerts', () => { `Showing ${expectedNumberOfOpenedAlerts.toString()} alerts` ); - cy.get( - '[data-test-subj="events-viewer-panel"] [data-test-subj="server-side-event-count"]' - ).should('have.text', expectedNumberOfOpenedAlerts.toString()); + cy.get(ALERTS_COUNT).should('have.text', expectedNumberOfOpenedAlerts.toString()); }); }); - it.skip('Closes one alert when more than one opened alerts are selected', () => { + it('Closes one alert when more than one opened alerts are selected', () => { waitForAlertsToBeLoaded(); cy.get(ALERTS_COUNT) @@ -163,16 +163,17 @@ describe('Alerts', () => { context('Opening alerts', () => { beforeEach(() => { + cleanKibana(); + removeSignalsIndex(); esArchiverLoad('closed_alerts'); loginAndWaitForPage(DETECTIONS_URL); }); afterEach(() => { esArchiverUnload('closed_alerts'); - removeSignalsIndex(); }); - it.skip('Open one alert when more than one closed alerts are selected', () => { + it('Open one alert when more than one closed alerts are selected', () => { waitForAlerts(); goToClosedAlerts(); waitForAlertsToBeLoaded(); @@ -215,6 +216,8 @@ describe('Alerts', () => { context('Marking alerts as in-progress', () => { beforeEach(() => { + cleanKibana(); + removeSignalsIndex(); esArchiverLoad('alerts'); loginAndWaitForPage(DETECTIONS_URL); }); @@ -224,7 +227,7 @@ describe('Alerts', () => { removeSignalsIndex(); }); - it.skip('Mark one alert in progress when more than one open alerts are selected', () => { + it('Mark one alert in progress when more than one open alerts are selected', () => { waitForAlerts(); waitForAlertsToBeLoaded(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_callouts_readonly.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_callouts_readonly.spec.ts index 23798fb38a794..fa48c0bc1abc6 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_callouts_readonly.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_callouts_readonly.spec.ts @@ -13,12 +13,12 @@ import { login, loginAndWaitForPageWithoutDateRange, waitForPageWithoutDateRange, - deleteRoleAndUser, } from '../tasks/login'; import { waitForAlertsIndexToBeCreated } from '../tasks/alerts'; import { goToRuleDetails } from '../tasks/alerts_detection_rules'; import { createCustomRule, deleteCustomRule, removeSignalsIndex } from '../tasks/api_calls/rules'; import { getCallOut, waitForCallOutToBeShown, dismissCallOut } from '../tasks/common/callouts'; +import { cleanKibana } from '../tasks/common'; const loadPageAsReadOnlyUser = (url: string) => { waitForPageWithoutDateRange(url, ROLES.reader); @@ -41,6 +41,8 @@ describe('Detections > Callouts indicating read-only access to resources', () => before(() => { // First, we have to open the app on behalf of a priviledged user in order to initialize it. // Otherwise the app will be disabled and show a "welcome"-like page. + cleanKibana(); + removeSignalsIndex(); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL, ROLES.platform_engineer); waitForAlertsIndexToBeCreated(); @@ -48,12 +50,6 @@ describe('Detections > Callouts indicating read-only access to resources', () => login(ROLES.reader); }); - after(() => { - deleteRoleAndUser(ROLES.reader); - deleteRoleAndUser(ROLES.platform_engineer); - removeSignalsIndex(); - }); - context('On Detections home page', () => { beforeEach(() => { loadPageAsReadOnlyUser(DETECTIONS_URL); @@ -95,7 +91,6 @@ describe('Detections > Callouts indicating read-only access to resources', () => context('On Rule Details page', () => { beforeEach(() => { createCustomRule(newRule); - loadPageAsReadOnlyUser(DETECTIONS_RULE_MANAGEMENT_URL); waitForPageTitleToBeShown(); goToRuleDetails(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_exceptions.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_exceptions.spec.ts index 9137d6383a15e..265f4d43c71c1 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_exceptions.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_exceptions.spec.ts @@ -16,7 +16,7 @@ import { goToOpenedAlerts, waitForAlertsIndexToBeCreated, } from '../tasks/alerts'; -import { createCustomRule, deleteCustomRule, removeSignalsIndex } from '../tasks/api_calls/rules'; +import { createCustomRule, removeSignalsIndex } from '../tasks/api_calls/rules'; import { goToRuleDetails } from '../tasks/alerts_detection_rules'; import { waitForAlertsToPopulate } from '../tasks/create_new_rule'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; @@ -33,10 +33,13 @@ import { import { refreshPage } from '../tasks/security_header'; import { DETECTIONS_URL } from '../urls/navigation'; +import { cleanKibana } from '../tasks/common'; describe.skip('Exceptions', () => { const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1'; beforeEach(() => { + cleanKibana(); + removeSignalsIndex(); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsIndexToBeCreated(); createCustomRule(newRule); @@ -58,9 +61,8 @@ describe.skip('Exceptions', () => { afterEach(() => { esArchiverUnload('auditbeat_for_exceptions'); esArchiverUnload('auditbeat_for_exceptions2'); - deleteCustomRule(); - removeSignalsIndex(); }); + context('From rule', () => { it('Creates an exception and deletes it', () => { goToExceptionsTab(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts index 2d21e3d333c07..4284b05205c69 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts @@ -33,15 +33,18 @@ import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DEFAULT_RULE_REFRESH_INTERVAL_VALUE } from '../../common/constants'; import { DETECTIONS_URL } from '../urls/navigation'; +import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { cleanKibana } from '../tasks/common'; describe('Alerts detection rules', () => { before(() => { + cleanKibana(); + removeSignalsIndex(); esArchiverLoad('prebuilt_rules_loaded'); }); after(() => { esArchiverUnload('prebuilt_rules_loaded'); - cy.clock().invoke('restore'); }); it('Sorts by activated rules', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts index 3ce507c791f0a..fb196fde3ae83 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts @@ -75,7 +75,6 @@ import { import { changeToThreeHundredRowsPerPage, deleteFirstRule, - deleteRule, deleteSelectedRules, editFirstRule, filterByCustomRules, @@ -86,7 +85,8 @@ import { waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; import { removeSignalsIndex } from '../tasks/api_calls/rules'; -import { createTimeline, deleteTimeline } from '../tasks/api_calls/timelines'; +import { createTimeline } from '../tasks/api_calls/timelines'; +import { cleanKibana } from '../tasks/common'; import { createAndActivateRule, fillAboutRule, @@ -115,17 +115,13 @@ describe('Custom detection rules creation', () => { const rule = { ...newRule }; before(() => { + cleanKibana(); + removeSignalsIndex(); createTimeline(newRule.timeline).then((response) => { rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); }); - after(() => { - deleteRule(); - deleteTimeline(rule.timeline.id!); - removeSignalsIndex(); - }); - it('Creates and activates a new rule', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); @@ -219,6 +215,8 @@ describe('Custom detection rules creation', () => { describe.skip('Custom detection rules deletion and edition', () => { beforeEach(() => { + cleanKibana(); + removeSignalsIndex(); esArchiverLoad('custom_rules'); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); @@ -227,7 +225,6 @@ describe.skip('Custom detection rules deletion and edition', () => { }); afterEach(() => { - removeSignalsIndex(); esArchiverUnload('custom_rules'); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts index 4c991a15b20b0..ebbbc65b12aeb 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts @@ -56,14 +56,15 @@ import { } from '../tasks/alerts'; import { changeToThreeHundredRowsPerPage, - deleteRule, filterByCustomRules, goToCreateNewRule, goToRuleDetails, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; -import { createTimeline, deleteTimeline } from '../tasks/api_calls/timelines'; +import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { createTimeline } from '../tasks/api_calls/timelines'; +import { cleanKibana } from '../tasks/common'; import { createAndActivateRule, fillAboutRuleAndContinue, @@ -77,7 +78,7 @@ import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; -describe('Detection rules, EQL', () => { +describe.skip('Detection rules, EQL', () => { const expectedUrls = eqlRule.referenceUrls.join(''); const expectedFalsePositives = eqlRule.falsePositivesExamples.join(''); const expectedTags = eqlRule.tags.join(''); @@ -88,16 +89,13 @@ describe('Detection rules, EQL', () => { const rule = { ...eqlRule }; before(() => { + cleanKibana(); + removeSignalsIndex(); createTimeline(eqlRule.timeline).then((response) => { rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); }); - after(() => { - deleteTimeline(rule.timeline.id!); - deleteRule(); - }); - it('Creates and activates a new EQL rule', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); @@ -183,16 +181,13 @@ describe('Detection rules, sequence EQL', () => { const rule = { ...eqlSequenceRule }; before(() => { + cleanKibana(); + removeSignalsIndex(); createTimeline(eqlSequenceRule.timeline).then((response) => { rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); }); - afterEach(() => { - deleteTimeline(eqlSequenceRule.timeline.id!); - deleteRule(); - }); - it('Creates and activates a new EQL rule with a sequence', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts index 8f9a0b4fc78fd..197c544d395a6 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts @@ -11,7 +11,8 @@ import { waitForAlertsPanelToBeLoaded, } from '../tasks/alerts'; import { exportFirstRule } from '../tasks/alerts_detection_rules'; -import { createCustomRule, deleteCustomRule } from '../tasks/api_calls/rules'; +import { createCustomRule, removeSignalsIndex } from '../tasks/api_calls/rules'; +import { cleanKibana } from '../tasks/common'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; @@ -19,6 +20,8 @@ import { DETECTIONS_URL } from '../urls/navigation'; describe('Export rules', () => { let ruleResponse: Cypress.Response; before(() => { + cleanKibana(); + removeSignalsIndex(); cy.intercept( 'POST', '/api/detection_engine/rules/_export?exclude_export_details=false&file_name=rules_export.ndjson' @@ -31,10 +34,6 @@ describe('Export rules', () => { }); }); - after(() => { - deleteCustomRule(); - }); - it('Exports a custom rule', () => { goToManageAlertsDetectionRules(); exportFirstRule(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_indicator_match.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_indicator_match.spec.ts index cbc4f21577864..4e97b619fc274 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_indicator_match.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_indicator_match.spec.ts @@ -59,7 +59,6 @@ import { } from '../tasks/alerts'; import { changeToThreeHundredRowsPerPage, - deleteRule, filterByCustomRules, goToCreateNewRule, goToRuleDetails, @@ -67,6 +66,7 @@ import { waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { cleanKibana } from '../tasks/common'; import { createAndActivateRule, fillAboutRuleAndContinue, @@ -90,6 +90,8 @@ describe('Detection rules, Indicator Match', () => { const expectedNumberOfAlerts = 1; beforeEach(() => { + cleanKibana(); + removeSignalsIndex(); esArchiverLoad('threat_indicator'); esArchiverLoad('threat_data'); }); @@ -97,11 +99,9 @@ describe('Detection rules, Indicator Match', () => { afterEach(() => { esArchiverUnload('threat_indicator'); esArchiverUnload('threat_data'); - deleteRule(); - removeSignalsIndex(); }); - it.skip('Creates and activates a new Indicator Match rule', () => { + it('Creates and activates a new Indicator Match rule', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts index 14f0ef46e7ac8..ae4cb204d6e17 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts @@ -46,13 +46,14 @@ import { } from '../tasks/alerts'; import { changeToThreeHundredRowsPerPage, - deleteRule, filterByCustomRules, goToCreateNewRule, goToRuleDetails, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; +import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { cleanKibana } from '../tasks/common'; import { createAndActivateRule, fillAboutRuleAndContinue, @@ -71,8 +72,9 @@ describe('Detection rules, machine learning', () => { const expectedMitre = formatMitreAttackDescription(machineLearningRule.mitre); const expectedNumberOfRules = 1; - after(() => { - deleteRule(); + before(() => { + cleanKibana(); + removeSignalsIndex(); }); it('Creates and activates a new ml rule', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts index 4d013ee7d7232..a543dca00b010 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts @@ -63,14 +63,15 @@ import { } from '../tasks/alerts'; import { changeToThreeHundredRowsPerPage, - deleteRule, filterByCustomRules, goToCreateNewRule, goToRuleDetails, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; -import { createTimeline, deleteTimeline } from '../tasks/api_calls/timelines'; +import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { createTimeline } from '../tasks/api_calls/timelines'; +import { cleanKibana } from '../tasks/common'; import { createAndActivateRule, fillAboutRuleWithOverrideAndContinue, @@ -94,16 +95,13 @@ describe.skip('Detection rules, override', () => { const rule = { ...newOverrideRule }; beforeEach(() => { + cleanKibana(); + removeSignalsIndex(); createTimeline(newOverrideRule.timeline).then((response) => { rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); }); - afterEach(() => { - deleteTimeline(rule.timeline.id!); - deleteRule(); - }); - it('Creates and activates a new custom rule with override option', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_prebuilt.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_prebuilt.spec.ts index e1451d78192b5..71c5647895385 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_prebuilt.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_prebuilt.spec.ts @@ -30,23 +30,23 @@ import { waitForPrebuiltDetectionRulesToBeLoaded, waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; -import { esArchiverLoadEmptyKibana, esArchiverUnloadEmptyKibana } from '../tasks/es_archiver'; +import { esArchiverLoadEmptyKibana } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; import { totalNumberOfPrebuiltRules } from '../objects/rule'; +import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { cleanKibana } from '../tasks/common'; describe('Alerts rules, prebuilt rules', () => { before(() => { + cleanKibana(); + removeSignalsIndex(); esArchiverLoadEmptyKibana(); }); - after(() => { - esArchiverUnloadEmptyKibana(); - }); - - it.skip('Loads prebuilt rules', () => { + it('Loads prebuilt rules', () => { const expectedNumberOfRules = totalNumberOfPrebuiltRules; const expectedElasticRulesBtnText = `Elastic rules (${expectedNumberOfRules})`; @@ -78,11 +78,12 @@ describe('Alerts rules, prebuilt rules', () => { }); }); -describe.skip('Deleting prebuilt rules', () => { +describe('Deleting prebuilt rules', () => { beforeEach(() => { const expectedNumberOfRules = totalNumberOfPrebuiltRules; const expectedElasticRulesBtnText = `Elastic rules (${expectedNumberOfRules})`; + cleanKibana(); esArchiverLoadEmptyKibana(); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); @@ -98,10 +99,6 @@ describe.skip('Deleting prebuilt rules', () => { waitForRulesToBeLoaded(); }); - afterEach(() => { - esArchiverUnloadEmptyKibana(); - }); - it('Does not allow to delete one rule when more than one is selected', () => { const numberOfRulesToBeSelected = 2; selectNumberOfRules(numberOfRulesToBeSelected); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts index cc6f1b3a8a0e0..812d0fa29f9b7 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts @@ -58,14 +58,15 @@ import { } from '../tasks/alerts'; import { changeToThreeHundredRowsPerPage, - deleteRule, filterByCustomRules, goToCreateNewRule, goToRuleDetails, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, } from '../tasks/alerts_detection_rules'; -import { createTimeline, deleteTimeline } from '../tasks/api_calls/timelines'; +import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { createTimeline } from '../tasks/api_calls/timelines'; +import { cleanKibana } from '../tasks/common'; import { createAndActivateRule, fillAboutRuleAndContinue, @@ -79,7 +80,7 @@ import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; -describe('Detection rules, threshold', () => { +describe.skip('Detection rules, threshold', () => { const expectedUrls = newThresholdRule.referenceUrls.join(''); const expectedFalsePositives = newThresholdRule.falsePositivesExamples.join(''); const expectedTags = newThresholdRule.tags.join(''); @@ -88,16 +89,13 @@ describe('Detection rules, threshold', () => { const rule = { ...newThresholdRule }; beforeEach(() => { + cleanKibana(); + removeSignalsIndex(); createTimeline(newThresholdRule.timeline).then((response) => { rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); }); - afterEach(() => { - deleteTimeline(rule.timeline.id!); - deleteRule(); - }); - it('Creates and activates a new threshold rule', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts index 2bfe72033135b..d5fba65a70149 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts @@ -7,6 +7,8 @@ import { PROVIDER_BADGE } from '../screens/timeline'; import { investigateFirstAlertInTimeline, waitForAlertsPanelToBeLoaded } from '../tasks/alerts'; +import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { cleanKibana } from '../tasks/common'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPage } from '../tasks/login'; @@ -14,6 +16,8 @@ import { DETECTIONS_URL } from '../urls/navigation'; describe('Alerts timeline', () => { beforeEach(() => { + cleanKibana(); + removeSignalsIndex(); esArchiverLoad('timeline_alerts'); loginAndWaitForPage(DETECTIONS_URL); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts index b755de8efe757..d53b98b6c103d 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts @@ -38,8 +38,9 @@ import { import { TIMELINE_DESCRIPTION, TIMELINE_QUERY, TIMELINE_TITLE } from '../screens/timeline'; import { goToCaseDetails, goToCreateNewCase } from '../tasks/all_cases'; -import { createTimeline, deleteTimeline } from '../tasks/api_calls/timelines'; -import { deleteCase, openCaseTimeline } from '../tasks/case_details'; +import { createTimeline } from '../tasks/api_calls/timelines'; +import { openCaseTimeline } from '../tasks/case_details'; +import { cleanKibana } from '../tasks/common'; import { attachTimeline, backToCases, @@ -47,7 +48,6 @@ import { fillCasesMandatoryfields, } from '../tasks/create_new_case'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; -import { closeTimeline } from '../tasks/timeline'; import { CASES_URL } from '../urls/navigation'; @@ -55,17 +55,12 @@ describe.skip('Cases', () => { const mycase = { ...case1 }; before(() => { + cleanKibana(); createTimeline(case1.timeline).then((response) => { mycase.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); }); - after(() => { - closeTimeline(); - deleteTimeline(mycase.timeline.id!); - deleteCase(); - }); - it('Creates a new case with timeline and opens the timeline', () => { loginAndWaitForPageWithoutDateRange(CASES_URL); goToCreateNewCase(); diff --git a/x-pack/plugins/security_solution/cypress/integration/cases_connector_options.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases_connector_options.spec.ts index 1d580b93af235..c41b79ef33653 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases_connector_options.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases_connector_options.spec.ts @@ -22,12 +22,13 @@ import { fillServiceNowConnectorOptions, } from '../tasks/create_new_case'; import { goToCreateNewCase } from '../tasks/all_cases'; -import { deleteCase } from '../tasks/case_details'; import { CASES_URL } from '../urls/navigation'; import { CONNECTOR_CARD_DETAILS, CONNECTOR_TITLE } from '../screens/case_details'; +import { cleanKibana } from '../tasks/common'; describe('Cases connector incident fields', () => { before(() => { + cleanKibana(); cy.intercept('GET', '/api/cases/configure/connectors/_find', mockConnectorsResponse); cy.intercept('POST', `/api/actions/action/${connectorIds.jira}/_execute`, (req) => { const response = @@ -45,10 +46,6 @@ describe('Cases connector incident fields', () => { }); }); - after(() => { - deleteCase(); - }); - it('Correct incident fields show when connector is changed', () => { loginAndWaitForPageWithoutDateRange(CASES_URL); goToCreateNewCase(); diff --git a/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts index acb56d1f24668..8bd9f5b09f2c8 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts @@ -8,6 +8,7 @@ import { serviceNowConnector } from '../objects/case'; import { SERVICE_NOW_MAPPING, TOASTER } from '../screens/configure_cases'; import { goToEditExternalConnection } from '../tasks/all_cases'; +import { cleanKibana } from '../tasks/common'; import { addServiceNowConnector, openAddNewConnectorOption, @@ -38,6 +39,7 @@ describe('Cases connectors', () => { version: 'WzEwNCwxXQ==', }; before(() => { + cleanKibana(); cy.intercept('POST', '/api/actions/action').as('createConnector'); cy.intercept('POST', '/api/cases/configure', (req) => { const connector = req.body.connector; 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 664de967b9aff..f7a19fa281bee 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 @@ -33,6 +33,7 @@ import { clearSearchBar, kqlSearch } from '../tasks/security_header'; import { HOSTS_URL } from '../urls/navigation'; import { resetFields } from '../tasks/timeline'; +import { cleanKibana } from '../tasks/common'; const defaultHeadersInDefaultEcsCategory = [ { id: '@timestamp' }, @@ -44,9 +45,10 @@ const defaultHeadersInDefaultEcsCategory = [ { id: 'destination.ip' }, ]; -describe('Events Viewer', () => { +describe.skip('Events Viewer', () => { context('Fields rendering', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); openEvents(); }); @@ -73,6 +75,7 @@ describe('Events Viewer', () => { context('Events viewer query modal', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); openEvents(); }); @@ -91,6 +94,7 @@ describe('Events Viewer', () => { context('Events viewer fields behaviour', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); openEvents(); }); @@ -122,6 +126,7 @@ describe('Events Viewer', () => { context('Events behaviour', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); openEvents(); waitsForEventsToBeLoaded(); @@ -144,6 +149,7 @@ describe('Events Viewer', () => { context('Events columns', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); openEvents(); cy.scrollTo('bottom'); diff --git a/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts b/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts index cb8949402b986..d99981b42d049 100644 --- a/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts @@ -16,6 +16,7 @@ import { FIELDS_BROWSER_SELECTED_CATEGORY_COUNT, FIELDS_BROWSER_SYSTEM_CATEGORIES_COUNT, } from '../screens/fields_browser'; +import { cleanKibana } from '../tasks/common'; import { addsHostGeoCityNameToTimeline, @@ -47,6 +48,7 @@ const defaultHeaders = [ describe('Fields Browser', () => { context.skip('Fields Browser rendering', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); openTimelineUsingToggle(); populateTimeline(); @@ -110,6 +112,7 @@ describe('Fields Browser', () => { context.skip('Editing the timeline', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); openTimelineUsingToggle(); populateTimeline(); diff --git a/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts b/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts index b84b668a28502..6321be1e26151 100644 --- a/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts @@ -9,6 +9,7 @@ import { INSPECT_MODAL, INSPECT_NETWORK_BUTTONS_IN_SECURITY, } from '../screens/inspect'; +import { cleanKibana } from '../tasks/common'; import { closesModal, openStatsAndTables } from '../tasks/inspect'; import { loginAndWaitForPage } from '../tasks/login'; @@ -20,6 +21,7 @@ import { HOSTS_URL, NETWORK_URL } from '../urls/navigation'; describe('Inspect', () => { context('Hosts stats and tables', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); }); afterEach(() => { @@ -36,6 +38,7 @@ describe('Inspect', () => { context('Network stats and tables', () => { before(() => { + cleanKibana(); loginAndWaitForPage(NETWORK_URL); }); afterEach(() => { diff --git a/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts b/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts index 7bdc461a7c73d..0d3890a5292e4 100644 --- a/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts @@ -5,6 +5,7 @@ */ import { KQL_INPUT } from '../screens/security_header'; +import { cleanKibana } from '../tasks/common'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; @@ -25,6 +26,10 @@ import { } from '../urls/ml_conditional_links'; describe('ml conditional links', () => { + before(() => { + cleanKibana(); + }); + it('sets the KQL from a single IP with a value for the query', () => { loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpKqlQuery); cy.get(KQL_INPUT) diff --git a/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts index 792eee3660429..c8e9af98fe6fd 100644 --- a/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts @@ -36,9 +36,11 @@ import { OVERVIEW_PAGE, TIMELINES_PAGE, } from '../screens/kibana_navigation'; +import { cleanKibana } from '../tasks/common'; describe('top-level navigation common to all pages in the Security app', () => { before(() => { + cleanKibana(); loginAndWaitForPage(TIMELINES_URL); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts b/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts index 0d12019adbc99..f72559b9f21e6 100644 --- a/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/overview.spec.ts @@ -13,8 +13,13 @@ import { OVERVIEW_URL } from '../urls/navigation'; import overviewFixture from '../fixtures/overview_search_strategy.json'; import emptyInstance from '../fixtures/empty_instance.json'; +import { cleanKibana } from '../tasks/common'; describe('Overview Page', () => { + before(() => { + cleanKibana(); + }); + it('Host stats render with correct values', () => { cy.stubSearchStrategyApi(overviewFixture, 'overviewHost'); loginAndWaitForPage(OVERVIEW_URL); diff --git a/x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts b/x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts index d755735982517..2896b2dbc36c6 100644 --- a/x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts @@ -6,6 +6,7 @@ import { PROCESS_NAME_FIELD, UNCOMMON_PROCESSES_TABLE } from '../screens/hosts/uncommon_processes'; import { FIRST_PAGE_SELECTOR, THIRD_PAGE_SELECTOR } from '../screens/pagination'; +import { cleanKibana } from '../tasks/common'; import { waitForAuthenticationsToBeLoaded } from '../tasks/hosts/authentications'; import { openAuthentications, openUncommonProcesses } from '../tasks/hosts/main'; @@ -18,6 +19,7 @@ import { HOSTS_PAGE_TAB_URLS } from '../urls/navigation'; describe('Pagination', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_PAGE_TAB_URLS.uncommonProcesses); waitForUncommonProcessesToBeLoaded(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts b/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts index 15ca92714d4a3..7fcbc10f88b44 100644 --- a/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/search_bar.spec.ts @@ -11,9 +11,11 @@ import { hostIpFilter } from '../objects/filter'; import { HOSTS_URL } from '../urls/navigation'; import { waitForAllHostsToBeLoaded } from '../tasks/hosts/all_hosts'; +import { cleanKibana } from '../tasks/common'; describe('SearchBar', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); waitForAllHostsToBeLoaded(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts b/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts index ead3fa6175107..b441d33d34baf 100644 --- a/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts @@ -26,8 +26,13 @@ import { import { openTimelineUsingToggle } from '../tasks/security_main'; import { populateTimeline } from '../tasks/timeline'; import { SERVER_SIDE_EVENT_COUNT } from '../screens/timeline'; +import { cleanKibana } from '../tasks/common'; describe('Sourcerer', () => { + before(() => { + cleanKibana(); + }); + beforeEach(() => { loginAndWaitForPage(HOSTS_URL); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts index 936151d13f920..74bf4f03b0b14 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts @@ -16,12 +16,14 @@ import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { TIMELINE_CASE_ID } from '../objects/case'; import { caseTimeline, timeline } from '../objects/timeline'; import { createTimeline, deleteTimeline } from '../tasks/api_calls/timelines'; +import { cleanKibana } from '../tasks/common'; describe('attach timeline to case', () => { const myTimeline = { ...timeline }; context('without cases created', () => { before(() => { + cleanKibana(); createTimeline(timeline).then((response) => { myTimeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); @@ -61,13 +63,10 @@ describe('attach timeline to case', () => { context('with cases created', () => { before(() => { + cleanKibana(); esArchiverLoad('case_and_timeline'); }); - after(() => { - esArchiverUnload('case_and_timeline'); - }); - it('attach timeline to an existing case', () => { loginAndWaitForTimeline(caseTimeline.id!); attachTimelineToExistingCase(); @@ -81,6 +80,7 @@ describe('attach timeline to case', () => { }](${origin}/app/security/timelines?timeline=(id:%27${caseTimeline.id!}%27,isOpen:!t))` ); }); + esArchiverUnload('case_and_timeline'); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts index 1fb751f344fd3..5d44c057c7383 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts @@ -25,7 +25,8 @@ import { TIMELINES_NOTES_COUNT, TIMELINES_FAVORITE, } from '../screens/timelines'; -import { deleteTimeline, getTimelineById } from '../tasks/api_calls/timelines'; +import { getTimelineById } from '../tasks/api_calls/timelines'; +import { cleanKibana } from '../tasks/common'; import { loginAndWaitForPage } from '../tasks/login'; import { openTimelineUsingToggle } from '../tasks/security_main'; @@ -50,8 +51,8 @@ import { OVERVIEW_URL } from '../urls/navigation'; describe.skip('Timelines', () => { let timelineId: string; - after(() => { - if (timelineId) deleteTimeline(timelineId); + before(() => { + cleanKibana(); }); it('Creates a timeline', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts index abf6ca38844e2..a103586e007e4 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts @@ -23,11 +23,13 @@ import { openTimelineUsingToggle } from '../tasks/security_main'; import { closeTimeline, createNewTimeline } from '../tasks/timeline'; import { HOSTS_URL } from '../urls/navigation'; +import { cleanKibana } from '../tasks/common'; // FLAKY: https://github.com/elastic/kibana/issues/85098 // FLAKY: https://github.com/elastic/kibana/issues/62060 describe.skip('timeline data providers', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); waitForAllHostsToBeLoaded(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_flyout_button.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_flyout_button.spec.ts index 33e8cc40b1239..acf245251d7e1 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_flyout_button.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_flyout_button.spec.ts @@ -5,6 +5,7 @@ */ import { TIMELINE_FLYOUT_HEADER, TIMELINE_DATA_PROVIDERS } from '../screens/timeline'; +import { cleanKibana } from '../tasks/common'; import { dragFirstHostToTimeline, waitForAllHostsToBeLoaded } from '../tasks/hosts/all_hosts'; import { loginAndWaitForPage } from '../tasks/login'; @@ -14,6 +15,7 @@ import { HOSTS_URL } from '../urls/navigation'; describe('timeline flyout button', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); waitForAllHostsToBeLoaded(); }); 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 d518f9e42f21f..8b84ae7815452 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 @@ -4,28 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { reload } from '../tasks/common'; +import { cleanKibana, reload } from '../tasks/common'; import { loginAndWaitForPage } from '../tasks/login'; import { HOSTS_URL } from '../urls/navigation'; import { openEvents } from '../tasks/hosts/main'; import { DRAGGABLE_HEADER } from '../screens/timeline'; import { TABLE_COLUMN_EVENTS_MESSAGE } from '../screens/hosts/external_events'; -import { waitsForEventsToBeLoaded, openEventsViewerFieldsBrowser } from '../tasks/hosts/events'; -import { removeColumn, resetFields } from '../tasks/timeline'; +import { waitsForEventsToBeLoaded } from '../tasks/hosts/events'; +import { removeColumn } from '../tasks/timeline'; // Failing: See https://github.com/elastic/kibana/issues/75794 describe.skip('persistent timeline', () => { before(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); openEvents(); waitsForEventsToBeLoaded(); }); - afterEach(() => { - openEventsViewerFieldsBrowser(); - resetFields(); - }); - it('persist the deletion of a column', () => { cy.get(DRAGGABLE_HEADER).then((header) => { const currentNumberOfTimelineColumns = header.length; diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts index 814fcee2b0c5f..52274329034b1 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts @@ -5,6 +5,7 @@ */ import { SERVER_SIDE_EVENT_COUNT } from '../screens/timeline'; +import { cleanKibana } from '../tasks/common'; import { loginAndWaitForPage } from '../tasks/login'; import { openTimelineUsingToggle } from '../tasks/security_main'; @@ -12,8 +13,9 @@ import { executeTimelineKQL } from '../tasks/timeline'; import { HOSTS_URL } from '../urls/navigation'; -describe('timeline search or filter KQL bar', () => { +describe.skip('timeline search or filter KQL bar', () => { beforeEach(() => { + cleanKibana(); loginAndWaitForPage(HOSTS_URL); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_template_creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_template_creation.spec.ts index fc7125288b743..f1aaa4ab8b980 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_template_creation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_template_creation.spec.ts @@ -23,6 +23,7 @@ import { TIMELINES_NOTES_COUNT, TIMELINES_FAVORITE, } from '../screens/timelines'; +import { cleanKibana } from '../tasks/common'; import { loginAndWaitForPage } from '../tasks/login'; import { openTimelineUsingToggle } from '../tasks/security_main'; @@ -44,6 +45,7 @@ import { OVERVIEW_URL } from '../urls/navigation'; describe('Timeline Templates', () => { before(() => { + cleanKibana(); cy.intercept('PATCH', '/api/timeline').as('timeline'); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_templates_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_templates_export.spec.ts index 8e1dc0f8aa1ee..015c0fc80e292 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_templates_export.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_templates_export.spec.ts @@ -12,16 +12,15 @@ import { } from '../objects/timeline'; import { TIMELINE_TEMPLATES_URL } from '../urls/navigation'; -import { - createTimelineTemplate, - deleteTimeline as deleteTimelineTemplate, -} from '../tasks/api_calls/timelines'; +import { createTimelineTemplate } from '../tasks/api_calls/timelines'; +import { cleanKibana } from '../tasks/common'; describe('Export timelines', () => { let templateResponse: Cypress.Response; let templateId: string; before(() => { + cleanKibana(); cy.intercept('POST', 'api/timeline/_export?file_name=timelines_export.ndjson').as('export'); createTimelineTemplate(timelineTemplate).then((response) => { templateResponse = response; @@ -29,10 +28,6 @@ describe('Export timelines', () => { }); }); - after(() => { - deleteTimelineTemplate(templateId); - }); - it('Exports a custom timeline template', () => { loginAndWaitForPageWithoutDateRange(TIMELINE_TEMPLATES_URL); exportTimeline(templateId!); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts index 80693f3dd8074..9a03936c3683f 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts @@ -11,7 +11,8 @@ import { TIMESTAMP_HEADER_FIELD, TIMESTAMP_TOGGLE_FIELD, } from '../screens/timeline'; -import { createTimeline, deleteTimeline } from '../tasks/api_calls/timelines'; +import { createTimeline } from '../tasks/api_calls/timelines'; +import { cleanKibana } from '../tasks/common'; import { loginAndWaitForPage } from '../tasks/login'; import { openTimelineUsingToggle } from '../tasks/security_main'; @@ -28,19 +29,14 @@ import { import { HOSTS_URL } from '../urls/navigation'; describe('toggle column in timeline', () => { - let timelineId: string; before(() => { + cleanKibana(); cy.intercept('POST', '/api/timeline/_export?file_name=timelines_export.ndjson').as('export'); createTimeline(timeline).then((response) => { - timelineId = response.body.data.persistTimeline.timeline.savedObjectId; loginAndWaitForPage(HOSTS_URL); }); }); - after(() => { - deleteTimeline(timelineId); - }); - beforeEach(() => { openTimelineUsingToggle(); populateTimeline(); diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts index 8e3cd3dfbde7a..064d98bf01b24 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts @@ -8,13 +8,15 @@ import { exportTimeline, waitForTimelinesPanelToBeLoaded } from '../tasks/timeli import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { TIMELINES_URL } from '../urls/navigation'; -import { createTimeline, deleteTimeline } from '../tasks/api_calls/timelines'; +import { createTimeline } from '../tasks/api_calls/timelines'; import { expectedExportedTimeline, timeline } from '../objects/timeline'; +import { cleanKibana } from '../tasks/common'; describe('Export timelines', () => { let timelineResponse: Cypress.Response; let timelineId: string; before(() => { + cleanKibana(); cy.intercept('POST', '/api/timeline/_export?file_name=timelines_export.ndjson').as('export'); createTimeline(timeline).then((response) => { timelineResponse = response; @@ -22,10 +24,6 @@ describe('Export timelines', () => { }); }); - after(() => { - deleteTimeline(timelineId); - }); - it('Exports a custom timeline', () => { loginAndWaitForPageWithoutDateRange(TIMELINES_URL); waitForTimelinesPanelToBeLoaded(); diff --git a/x-pack/plugins/security_solution/cypress/integration/url_compatibility.spec.ts b/x-pack/plugins/security_solution/cypress/integration/url_compatibility.spec.ts index 5b42897b065e3..58ef4cd2d96ba 100644 --- a/x-pack/plugins/security_solution/cypress/integration/url_compatibility.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/url_compatibility.spec.ts @@ -12,6 +12,7 @@ import { DATE_PICKER_START_DATE_POPOVER_BUTTON, DATE_PICKER_END_DATE_POPOVER_BUTTON, } from '../screens/date_picker'; +import { cleanKibana } from '../tasks/common'; const ABSOLUTE_DATE = { endTime: '2019-08-01T20:33:29.186Z', @@ -19,6 +20,10 @@ const ABSOLUTE_DATE = { }; describe('URL compatibility', () => { + before(() => { + cleanKibana(); + }); + it('Redirects to Detection alerts from old Detections URL', () => { loginAndWaitForPage(DETECTIONS); diff --git a/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts index b911fc5b81f98..a13ae62eb7f84 100644 --- a/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts @@ -38,6 +38,7 @@ import { ABSOLUTE_DATE_RANGE } from '../urls/state'; import { timeline } from '../objects/timeline'; import { TIMELINE } from '../screens/create_new_case'; +import { cleanKibana } from '../tasks/common'; const ABSOLUTE_DATE = { endTime: '2019-08-01T20:33:29.186Z', @@ -50,6 +51,10 @@ const ABSOLUTE_DATE = { // FLAKY: https://github.com/elastic/kibana/issues/61612 describe.skip('url state', () => { + before(() => { + cleanKibana(); + }); + it('sets the global start and end dates from the url', () => { loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.url); cy.get(DATE_PICKER_START_DATE_POPOVER_BUTTON).should( diff --git a/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts b/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts index 341ca31715356..0b1ab12f37c91 100644 --- a/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts @@ -26,10 +26,17 @@ import { exportValueList, } from '../tasks/lists'; import { VALUE_LISTS_TABLE, VALUE_LISTS_ROW, VALUE_LISTS_MODAL_ACTIVATOR } from '../screens/lists'; +import { removeSignalsIndex } from '../tasks/api_calls/rules'; +import { cleanKibana } from '../tasks/common'; describe('value lists', () => { describe('management modal', () => { + before(() => { + cleanKibana(); + }); + beforeEach(() => { + removeSignalsIndex(); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); @@ -39,6 +46,7 @@ describe('value lists', () => { }); afterEach(() => { + removeSignalsIndex(); deleteAllValueListsFromUI(); }); diff --git a/x-pack/plugins/security_solution/cypress/support/index.js b/x-pack/plugins/security_solution/cypress/support/index.js index d6abecad74b91..0d5538758413b 100644 --- a/x-pack/plugins/security_solution/cypress/support/index.js +++ b/x-pack/plugins/security_solution/cypress/support/index.js @@ -21,16 +21,13 @@ // Import commands.js using ES2015 syntax: import './commands'; -import 'cypress-promise/register'; Cypress.Cookies.defaults({ preserve: 'sid', }); -Cypress.on('uncaught:exception', (err) => { - if (err.message.includes('ResizeObserver loop limit exceeded')) { - return false; - } +Cypress.on('uncaught:exception', () => { + return false; }); // Alternatively you can use CommonJS syntax: diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts index edf493d82c843..34fc00428d2cd 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts @@ -36,9 +36,13 @@ export const deleteCustomRule = () => { }; export const removeSignalsIndex = () => { - cy.request({ - method: 'DELETE', - url: `api/detection_engine/index`, - headers: { 'kbn-xsrf': 'delete-signals' }, + cy.request({ url: '/api/detection_engine/index', failOnStatusCode: false }).then((response) => { + if (response.status === 200) { + cy.request({ + method: 'DELETE', + url: `api/detection_engine/index`, + headers: { 'kbn-xsrf': 'delete-signals' }, + }); + } }); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/common.ts b/x-pack/plugins/security_solution/cypress/tasks/common.ts index bb009f34b02d6..fbd4c4145e8ff 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/common.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/common.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { esArchiverLoadEmptyKibana } from './es_archiver'; + const primaryButton = 0; /** @@ -60,3 +62,8 @@ export const reload = (afterReload: () => void) => { cy.contains('a', 'Security'); afterReload(); }; + +export const cleanKibana = () => { + cy.exec(`curl -XDELETE "${Cypress.env('ELASTICSEARCH_URL')}/.kibana\*" -k`); + esArchiverLoadEmptyKibana(); +}; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts b/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts index ea721d0257d45..00a5e84e73448 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts @@ -3,40 +3,65 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { v4 as generateUUID } from 'uuid'; // @ts-ignore import minimist from 'minimist'; import { KbnClient, ToolingLog } from '@kbn/dev-utils'; -import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../lists/common/constants'; -import { TRUSTED_APPS_LIST_API } from '../../../common/endpoint/constants'; -import { ExceptionListItemSchema } from '../../../../lists/common/schemas/response'; +import bluebird from 'bluebird'; +import { basename } from 'path'; +import { TRUSTED_APPS_CREATE_API, TRUSTED_APPS_LIST_API } from '../../../common/endpoint/constants'; +import { NewTrustedApp, OperatingSystem, TrustedApp } from '../../../common/endpoint/types'; -interface RunOptions { - count?: number; -} - -const logger = new ToolingLog({ level: 'info', writeTo: process.stdout }); +const defaultLogger = new ToolingLog({ level: 'info', writeTo: process.stdout }); const separator = '----------------------------------------'; export const cli = async () => { - const options: RunOptions = minimist(process.argv.slice(2), { + const cliDefaults = { + string: ['kibana'], default: { count: 10, + kibana: 'http://elastic:changeme@localhost:5601', }, - }) as RunOptions; - logger.write(`${separator} + }; + const options: RunOptions = minimist(process.argv.slice(2), cliDefaults); + + if ('help' in options) { + defaultLogger.write(` +node ${basename(process.argv[1])} [options] + +Options:${Object.keys(cliDefaults.default).reduce((out, option) => { + // @ts-ignore + return `${out}\n --${option}=${cliDefaults.default[option]}`; + }, '')} +`); + return; + } + + const runLogger = createRunLogger(); + + defaultLogger.write(`${separator} Loading ${options.count} Trusted App Entries`); - await run(options); - logger.write(`Done! + await run({ + ...options, + logger: runLogger, + }); + defaultLogger.write(` +Done! ${separator}`); }; -export const run: (options?: RunOptions) => Promise = async ({ +interface RunOptions { + count?: number; + kibana?: string; + logger?: ToolingLog; +} +export const run: (options?: RunOptions) => Promise = async ({ count = 10, + kibana = 'http://elastic:changeme@localhost:5601', + logger = defaultLogger, }: RunOptions = {}) => { const kbnClient = new KbnClient({ log: logger, - url: 'http://elastic:changeme@localhost:5601', + url: kibana, }); // touch the Trusted Apps List so it can be created @@ -45,46 +70,46 @@ export const run: (options?: RunOptions) => Promise = path: TRUSTED_APPS_LIST_API, }); - return Promise.all( - Array.from({ length: count }, () => { - return kbnClient - .request({ + return bluebird.map( + Array.from({ length: count }), + () => + kbnClient + .request({ method: 'POST', - path: '/api/exception_lists/items', + path: TRUSTED_APPS_CREATE_API, body: generateTrustedAppEntry(), }) - .then((item) => (item as unknown) as ExceptionListItemSchema); - }) + .then(({ data }) => { + logger.write(data.id); + return data; + }), + { concurrency: 10 } ); }; interface GenerateTrustedAppEntryOptions { - os?: 'windows' | 'macos' | 'linux'; + os?: OperatingSystem; name?: string; } - const generateTrustedAppEntry: (options?: GenerateTrustedAppEntryOptions) => object = ({ - os = 'windows', - name = `Sample Endpoint Trusted App Entry ${Date.now()}`, -} = {}) => { + os = randomOperatingSystem(), + name = randomName(), +} = {}): NewTrustedApp => { return { - list_id: ENDPOINT_TRUSTED_APPS_LIST_ID, - item_id: `generator_endpoint_trusted_apps_${generateUUID()}`, - os_types: [os], - tags: ['user added string for a tag', 'malware'], - type: 'simple', - description: 'This is a sample agnostic endpoint trusted app entry', + description: `Generator says we trust ${name}`, name, - namespace_type: 'agnostic', + os, entries: [ { - field: 'actingProcess.file.signer', + // @ts-ignore + field: 'process.hash.*', operator: 'included', type: 'match', - value: 'Elastic, N.V.', + value: '1234234659af249ddf3e40864e9fb241', }, { - field: 'actingProcess.file.path', + // @ts-ignore + field: 'process.executable.caseless', operator: 'included', type: 'match', value: '/one/two/three', @@ -92,3 +117,72 @@ const generateTrustedAppEntry: (options?: GenerateTrustedAppEntryOptions) => obj ], }; }; + +const randomN = (max: number): number => Math.floor(Math.random() * max); + +const randomName = (() => { + const names = [ + 'Symantec Endpoint Security', + 'Bitdefender GravityZone', + 'Malwarebytes', + 'Sophos Intercept X', + 'Webroot Business Endpoint Protection', + 'ESET Endpoint Security', + 'FortiClient', + 'Kaspersky Endpoint Security', + 'Trend Micro Apex One', + 'CylancePROTECT', + 'VIPRE', + 'Norton', + 'McAfee Endpoint Security', + 'AVG AntiVirus', + 'CrowdStrike Falcon', + 'Avast Business Antivirus', + 'Avira Antivirus', + 'Cisco AMP for Endpoints', + 'Eset Endpoint Antivirus', + 'VMware Carbon Black', + 'Palo Alto Networks Traps', + 'Trend Micro', + 'SentinelOne', + 'Panda Security for Desktops', + 'Microsoft Defender ATP', + ]; + const count = names.length; + + return () => names[randomN(count)]; +})(); + +const randomOperatingSystem = (() => { + const osKeys = Object.keys(OperatingSystem) as Array; + const count = osKeys.length; + + return () => OperatingSystem[osKeys[randomN(count)]]; +})(); + +const createRunLogger = () => { + let groupCount = 1; + let itemCount = 0; + + return new ToolingLog({ + level: 'info', + writeTo: { + write: (msg: string) => { + process.stdout.write('.'); + itemCount++; + + if (itemCount === 5) { + itemCount = 0; + + if (groupCount === 5) { + process.stdout.write('\n'); + groupCount = 1; + } else { + process.stdout.write(' '); + groupCount++; + } + } + }, + }, + }); +}; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 377e38b71d455..a50c91bb3989d 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2711,22 +2711,6 @@ "inputControl.vis.listControl.selectPlaceholder": "選択してください…", "inputControl.vis.listControl.selectTextPlaceholder": "選択してください…", "inspector.closeButton": "インスペクターを閉じる", - "inspector.data.dataDescriptionTooltip": "ビジュアライゼーションの元のデータを表示", - "inspector.data.dataTitle": "データ", - "inspector.data.downloadCSVButtonLabel": "CSV をダウンロード", - "inspector.data.downloadCSVToggleButtonLabel": "CSV をダウンロード", - "inspector.data.downloadOptionsUnsavedFilename": "(未保存)", - "inspector.data.filterForValueButtonAriaLabel": "値でフィルタリング", - "inspector.data.filterForValueButtonTooltip": "値でフィルタリング", - "inspector.data.filterOutValueButtonAriaLabel": "値を除外", - "inspector.data.filterOutValueButtonTooltip": "値を除外", - "inspector.data.formattedCSVButtonLabel": "フォーマット済み CSV", - "inspector.data.formattedCSVButtonTooltip": "データを表形式でダウンロード", - "inspector.data.gatheringDataLabel": "データを収集中", - "inspector.data.noDataAvailableDescription": "エレメントがデータを提供しませんでした。", - "inspector.data.noDataAvailableTitle": "利用可能なデータがありません", - "inspector.data.rawCSVButtonLabel": "CSV", - "inspector.data.rawCSVButtonTooltip": "日付をタイムスタンプとしてなど、提供されたデータをそのままダウンロードします", "inspector.reqTimestampDescription": "リクエストの開始が記録された時刻です", "inspector.reqTimestampKey": "リクエストのタイムスタンプ", "inspector.requests.descriptionRowIconAriaLabel": "説明", @@ -12498,7 +12482,6 @@ "xpack.ml.jobsList.deletedActionStatusText": "削除されました", "xpack.ml.jobsList.deleteJobErrorMessage": "ジョブの削除に失敗しました", "xpack.ml.jobsList.deleteJobModal.cancelButtonLabel": "キャンセル", - "xpack.ml.jobsList.deleteJobModal.closeButtonLabel": "閉じる", "xpack.ml.jobsList.deleteJobModal.deleteButtonLabel": "削除", "xpack.ml.jobsList.deleteJobModal.deleteJobsTitle": "{jobsCount, plural, one {{jobId}} other {# 件のジョブ}}を削除しますか?", "xpack.ml.jobsList.deleteJobModal.deleteMultipleJobsDescription": "{jobsCount, plural, one {ジョブ} other {複数ジョブ}}の削除には時間がかかる場合があります。{jobsCount, plural, one {} other {}}バックグラウンドで削除され、ジョブリストからすぐに消えない場合があります。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 7c9fa7f0fd1f3..1cfc0cbc38eb3 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2712,22 +2712,6 @@ "inputControl.vis.listControl.selectPlaceholder": "选择......", "inputControl.vis.listControl.selectTextPlaceholder": "选择......", "inspector.closeButton": "关闭检查器", - "inspector.data.dataDescriptionTooltip": "查看可视化后面的数据", - "inspector.data.dataTitle": "数据", - "inspector.data.downloadCSVButtonLabel": "下载 CSV", - "inspector.data.downloadCSVToggleButtonLabel": "下载 CSV", - "inspector.data.downloadOptionsUnsavedFilename": "未保存", - "inspector.data.filterForValueButtonAriaLabel": "筛留值", - "inspector.data.filterForValueButtonTooltip": "筛留值", - "inspector.data.filterOutValueButtonAriaLabel": "筛除值", - "inspector.data.filterOutValueButtonTooltip": "筛除值", - "inspector.data.formattedCSVButtonLabel": "格式化 CSV", - "inspector.data.formattedCSVButtonTooltip": "以表格式下载数据", - "inspector.data.gatheringDataLabel": "正在收集数据", - "inspector.data.noDataAvailableDescription": "该元素未提供任何数据。", - "inspector.data.noDataAvailableTitle": "没有可用数据", - "inspector.data.rawCSVButtonLabel": "原始 CSV", - "inspector.data.rawCSVButtonTooltip": "按原样下载数据,例如将日期作为时间戳下载", "inspector.reqTimestampDescription": "记录请求启动的时间", "inspector.reqTimestampKey": "请求时间戳", "inspector.requests.descriptionRowIconAriaLabel": "描述", @@ -12512,7 +12496,6 @@ "xpack.ml.jobsList.deletedActionStatusText": "已删除", "xpack.ml.jobsList.deleteJobErrorMessage": "作业无法删除", "xpack.ml.jobsList.deleteJobModal.cancelButtonLabel": "取消", - "xpack.ml.jobsList.deleteJobModal.closeButtonLabel": "关闭", "xpack.ml.jobsList.deleteJobModal.deleteButtonLabel": "删除", "xpack.ml.jobsList.deleteJobModal.deleteJobsTitle": "删除 {jobsCount, plural, one {{jobId}} other {# 个作业}}?", "xpack.ml.jobsList.deleteJobModal.deleteMultipleJobsDescription": "删除{jobsCount, plural, one {一个作业} other {多个作业}}可能很费时。将在后台删除{jobsCount, plural, one {该作业} other {这些作业}},但删除的作业可能不会从作业列表中立即消失。", diff --git a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts index a8999fd065e75..e520a7c8f8e04 100644 --- a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts +++ b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts @@ -52,12 +52,14 @@ export default function ({ getService }: FtrProviderContext) { await deleteComposableIndexTemplate(name); }; - const assertDataStreamStorageSizeExists = (storageSize: string) => { - // Storage size of a document doesn't like it would be deterministic (could vary depending + const assertDataStreamStorageSizeExists = (storageSize: string, storageSizeBytes: number) => { + // Storage size of a document doesn't look like it would be deterministic (could vary depending // on how ES, Lucene, and the file system interact), so we'll just assert its presence and // type. expect(storageSize).to.be.ok(); expect(typeof storageSize).to.be('string'); + expect(storageSizeBytes).to.be.ok(); + expect(typeof storageSizeBytes).to.be('number'); }; describe('Data streams', function () { @@ -120,10 +122,9 @@ export default function ({ getService }: FtrProviderContext) { expect(testDataStream).to.be.ok(); // ES determines these values so we'll just echo them back. - const { name: indexName, uuid } = testDataStream!.indices[0]; - const { storageSize, ...dataStreamWithoutStorageSize } = testDataStream!; - assertDataStreamStorageSizeExists(storageSize); + const { storageSize, storageSizeBytes, ...dataStreamWithoutStorageSize } = testDataStream!; + assertDataStreamStorageSizeExists(storageSize, storageSizeBytes); expect(dataStreamWithoutStorageSize).to.eql({ name: testDataStreamName, @@ -153,8 +154,8 @@ export default function ({ getService }: FtrProviderContext) { // ES determines these values so we'll just echo them back. const { name: indexName, uuid } = dataStream.indices[0]; - const { storageSize, ...dataStreamWithoutStorageSize } = dataStream; - assertDataStreamStorageSizeExists(storageSize); + const { storageSize, storageSizeBytes, ...dataStreamWithoutStorageSize } = dataStream; + assertDataStreamStorageSizeExists(storageSize, storageSizeBytes); expect(dataStreamWithoutStorageSize).to.eql({ name: testDataStreamName, 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 a11f8ca3947c5..63a20cdaca234 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 @@ -235,5 +235,42 @@ export default ({ getService }: FtrProviderContext) => { expect(body.message).to.eql('Forbidden'); }); }); + + describe('GetDataFrameAnalyticsIdMap', () => { + it('should return a map of objects leading up to analytics job id', async () => { + const { body } = await supertest + .get(`/api/ml/data_frame/analytics/map/${jobId}_1`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + + expect(body).to.have.keys('elements', 'details', 'error'); + // Index node, 2 job nodes (with same source index), and 2 edge nodes to connect them + expect(body.elements.length).to.eql(5); + + for (const detailsId in body.details) { + if (detailsId.includes('analytics')) { + expect(body.details[detailsId]).to.have.keys('id', 'source', 'dest'); + } else if (detailsId.includes('index')) { + const indexId = detailsId.replace('-index', ''); + expect(body.details[detailsId][indexId]).to.have.keys('aliases', 'mappings'); + } + } + }); + + it('should return empty results and an error message if the job does not exist', async () => { + const { body } = await supertest + .get(`/api/ml/data_frame/analytics/map/${jobId}_fake`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + + expect(body.elements.length).to.eql(0); + expect(body.details).to.eql({}); + expect(body.error).to.eql(`No known job with id '${jobId}_fake'`); + + expect(body).to.have.keys('elements', 'details', 'error'); + }); + }); }); }; diff --git a/x-pack/test/functional/es_archives/dashboard/session_in_space/data.json b/x-pack/test/functional/es_archives/dashboard/session_in_space/data.json new file mode 100644 index 0000000000000..64756843245a3 --- /dev/null +++ b/x-pack/test/functional/es_archives/dashboard/session_in_space/data.json @@ -0,0 +1,328 @@ +{ + "type": "doc", + "value": { + "id": "space:default", + "index": ".kibana", + "source": { + "space": { + "description": "This is the default space!", + "name": "Default" + }, + "type": "space" + } + } +} + +{ + "type": "doc", + "value": { + "id": "space:another-space", + "index": ".kibana", + "source": { + "space": { + "description": "This is another space", + "name": "Another Space" + }, + "type": "space" + } + } +} + +{ + "type": "doc", + "value": { + "id": "visualization:6c9f3830-01e3-11eb-9b63-176d7b28a352", + "index": ".kibana", + "source": { + "type": "visualization", + "visualization": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}" + }, + "title": "Sum of Bytes by Extension (Delayed 5s)", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"Sum of Bytes by Extension (Delayed 5s)\",\"type\":\"histogram\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"sum\",\"params\":{\"field\":\"bytes\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"extension.raw\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":5,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"},{\"id\":\"3\",\"enabled\":true,\"type\":\"shard_delay\",\"params\":{\"delay\":\"5s\"},\"schema\":\"group\"}],\"params\":{\"type\":\"histogram\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"filter\":true,\"truncate\":100},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Sum of bytes\"}}],\"seriesParams\":[{\"show\":true,\"type\":\"histogram\",\"mode\":\"stacked\",\"data\":{\"label\":\"Sum of bytes\",\"id\":\"1\"},\"valueAxis\":\"ValueAxis-1\",\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false,\"labels\":{\"show\":false},\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"},\"row\":true}}" + } + } + } +} + +{ + "type": "doc", + "value": { + "id": "visualization:14501a50-01e3-11eb-9b63-176d7b28a352", + "index": ".kibana", + "source": { + "type": "visualization", + "visualization": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}" + }, + "title": "Sum of Bytes by Extension", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"Sum of Bytes by Extension\",\"type\":\"histogram\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"sum\",\"params\":{\"field\":\"bytes\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"extension.raw\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":5,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"}],\"params\":{\"type\":\"histogram\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"filter\":true,\"truncate\":100},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Sum of bytes\"}}],\"seriesParams\":[{\"show\":true,\"type\":\"histogram\",\"mode\":\"stacked\",\"data\":{\"label\":\"Sum of bytes\",\"id\":\"1\"},\"valueAxis\":\"ValueAxis-1\",\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false,\"labels\":{\"show\":false},\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"},\"row\":true}}" + } + } + } +} + +{ + "type": "doc", + "value": { + "id": "visualization:50a67010-075d-11eb-be70-0bd5e8b57d02", + "index": ".kibana", + "source": { + "type": "visualization", + "visualization": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}" + }, + "title": "Sum of Bytes by Extension (Delayed 15s)", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"Sum of Bytes by Extension (Delayed 15s)\",\"type\":\"histogram\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"sum\",\"params\":{\"field\":\"bytes\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"extension.raw\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":5,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"},{\"id\":\"3\",\"enabled\":true,\"type\":\"shard_delay\",\"params\":{\"delay\":\"15s\"},\"schema\":\"group\"}],\"params\":{\"type\":\"histogram\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"filter\":true,\"truncate\":100},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Sum of bytes\"}}],\"seriesParams\":[{\"show\":true,\"type\":\"histogram\",\"mode\":\"stacked\",\"data\":{\"label\":\"Sum of bytes\",\"id\":\"1\"},\"valueAxis\":\"ValueAxis-1\",\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false,\"labels\":{\"show\":false},\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"},\"row\":true}}" + } + } + } +} + +{ + "type": "doc", + "value": { + "id": "index-pattern:logstash-*", + "index": ".kibana", + "source": { + "index-pattern": { + "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]", + "timeFieldName": "@timestamp", + "title": "logstash-*" + }, + "type": "index-pattern" + } + } +} + +{ + "type": "doc", + "value": { + "id": "dashboard:24f3f950-69d9-11ea-a14d-e341629a29e6", + "index": ".kibana", + "source": { + "dashboard": { + "description": "", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}" + }, + "optionsJSON": "{\"useMargins\":true,\"hidePanelTitles\":false}", + "panelsJSON": "[{\"version\":\"7.10.0\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":15,\"i\":\"f8f7f5b5-b840-443c-a766-d22a8a87c493\"},\"panelIndex\":\"f8f7f5b5-b840-443c-a766-d22a8a87c493\",\"embeddableConfig\":{},\"panelRefName\":\"panel_0\"}]", + "refreshInterval": { "pause": true, "value": 0 }, + "timeFrom": "2015-09-19T17:34:10.297Z", + "timeRestore": true, + "timeTo": "2015-09-23T00:09:17.180Z", + "title" : "Delayed 5s", + "version": 1 + }, + "references": [ + { + "name" : "panel_0", + "type" : "visualization", + "id" : "6c9f3830-01e3-11eb-9b63-176d7b28a352" + } + ], + "type": "dashboard", + "updated_at": "2020-03-19T11:59:53.701Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "dashboard:41e77910-69d9-11ea-a14d-e341629a29e6", + "index": ".kibana", + "source": { + "dashboard": { + "description": "", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}" + }, + "optionsJSON": "{\"useMargins\":true,\"hidePanelTitles\":false}", + "panelsJSON": "[{\"version\":\"7.7.0\",\"gridData\":{\"w\":24,\"h\":15,\"x\":0,\"y\":0,\"i\":\"8c5df6b2-0cc9-4887-a2d9-6a9a192f3407\"},\"panelIndex\":\"8c5df6b2-0cc9-4887-a2d9-6a9a192f3407\",\"embeddableConfig\":{},\"panelRefName\":\"panel_0\"}]", + "refreshInterval": { "pause": true, "value": 0 }, + "timeFrom": "2015-09-19T17:34:10.297Z", + "timeRestore": true, + "timeTo": "2015-09-23T00:09:17.180Z", + "title": "Not Delayed", + "version": 1 + }, + "references": [ + { + "name" : "panel_0", + "type" : "visualization", + "id" : "14501a50-01e3-11eb-9b63-176d7b28a352" + } + ], + "type": "dashboard", + "updated_at": "2020-03-19T11:59:53.701Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "dashboard:a41c6790-075d-11eb-be70-0bd5e8b57d02", + "index": ".kibana", + "source": { + "dashboard": { + "description": "", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}" + }, + "optionsJSON": "{\"useMargins\":true,\"hidePanelTitles\":false}", + "panelsJSON": "[{\"version\":\"7.7.0\",\"gridData\":{\"w\":24,\"h\":15,\"x\":0,\"y\":0,\"i\":\"eedeb943-5cfc-4e2c-bc1a-10ce06345cc1\"},\"panelIndex\":\"eedeb943-5cfc-4e2c-bc1a-10ce06345cc1\",\"embeddableConfig\":{},\"panelRefName\":\"panel_0\"}]", + "refreshInterval": { "pause": true, "value": 0 }, + "timeFrom": "2015-09-19T17:34:10.297Z", + "timeRestore": true, + "timeTo": "2015-09-23T00:09:17.180Z", + "title": "Delayed 15s", + "version": 1 + }, + "references": [ + { + "name" : "panel_0", + "type" : "visualization", + "id" : "50a67010-075d-11eb-be70-0bd5e8b57d02" + } + ], + "type": "dashboard", + "updated_at": "2020-03-19T11:59:53.701Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "dashboard:a41c6790-075d-11eb-be70-0bd5e8b57d03", + "index": ".kibana", + "source": { + "dashboard": { + "description": "", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}" + }, + "optionsJSON": "{\"useMargins\":true,\"hidePanelTitles\":false}", + "panelsJSON": "[{\"version\":\"8.0.0\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":15,\"i\":\"ec585931-ce8e-43fd-aa94-a1a9612d24ba\"},\"panelIndex\":\"ec585931-ce8e-43fd-aa94-a1a9612d24ba\",\"embeddableConfig\":{},\"panelRefName\":\"panel_0\"},{\"version\":\"8.0.0\",\"gridData\":{\"x\":24,\"y\":0,\"w\":24,\"h\":15,\"i\":\"c7b18010-462b-4e55-a974-fdec2ae64b06\"},\"panelIndex\":\"c7b18010-462b-4e55-a974-fdec2ae64b06\",\"embeddableConfig\":{},\"panelRefName\":\"panel_1\"},{\"version\":\"8.0.0\",\"gridData\":{\"x\":0,\"y\":15,\"w\":24,\"h\":15,\"i\":\"e67704f7-20b7-4ade-8dee-972a9d187107\"},\"panelIndex\":\"e67704f7-20b7-4ade-8dee-972a9d187107\",\"embeddableConfig\":{},\"panelRefName\":\"panel_2\"},{\"version\":\"8.0.0\",\"gridData\":{\"x\":24,\"y\":15,\"w\":24,\"h\":15,\"i\":\"f0b03592-10f1-41cd-9929-0cb4163bcd16\"},\"panelIndex\":\"f0b03592-10f1-41cd-9929-0cb4163bcd16\",\"embeddableConfig\":{},\"panelRefName\":\"panel_3\"}]", + "refreshInterval": { "pause": true, "value": 0 }, + "timeFrom": "2015-09-19T17:34:10.297Z", + "timeRestore": true, + "timeTo": "2015-09-23T00:09:17.180Z", + "title": "Multiple delayed", + "version": 1 + }, + "references": [ + { + "id": "14501a50-01e3-11eb-9b63-176d7b28a352", + "name": "panel_0", + "type": "visualization" + }, + { + "id": "50a67010-075d-11eb-be70-0bd5e8b57d02", + "name": "panel_1", + "type": "visualization" + }, + { + "id": "6c9f3830-01e3-11eb-9b63-176d7b28a352", + "name": "panel_2", + "type": "visualization" + }, + { + "id": "50a67010-075d-11eb-be70-0bd5e8b57d02", + "name": "panel_3", + "type": "visualization" + } + ], + "type": "dashboard", + "updated_at": "2020-03-19T11:59:53.701Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "another-space:index-pattern:logstash-*", + "source": { + "index-pattern": { + "title": "logstash-*", + "timeFieldName": "@timestamp", + "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]" + }, + "type": "index-pattern", + "namespace": "another-space", + "updated_at": "2018-12-21T00:43:07.096Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "another-space:visualization:75c3e060-1e7c-11e9-8488-65449e65d0ed", + "source": { + "visualization": { + "title": "A Pie in another space", + "visState": "{\"title\":\"A Pie\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100},\"dimensions\":{\"metric\":{\"accessor\":0,\"format\":{\"id\":\"number\"},\"params\":{},\"aggType\":\"count\"}}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"geo.src\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"}}]}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[]}" + } + }, + "namespace": "another-space", + "type": "visualization", + "updated_at": "2019-01-22T19:32:31.206Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "another-space:dashboard:my-dashboard", + "source": { + "dashboard": { + "title": "A Dashboard in another space", + "hits": 0, + "description": "", + "panelsJSON": "[{\"gridData\":{\"w\":24,\"h\":15,\"x\":0,\"y\":0,\"i\":\"1\"},\"version\":\"7.0.0\",\"panelIndex\":\"1\",\"type\":\"visualization\",\"id\":\"75c3e060-1e7c-11e9-8488-65449e65d0ed\",\"embeddableConfig\":{}}]", + "optionsJSON": "{\"darkTheme\":false,\"useMargins\":true,\"hidePanelTitles\":false}", + "version": 1, + "timeRestore": false, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[]}" + } + }, + "namespace": "another-space", + "type": "dashboard", + "updated_at": "2019-01-22T19:32:47.232Z" + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/dashboard/session_in_space/mappings.json b/x-pack/test/functional/es_archives/dashboard/session_in_space/mappings.json new file mode 100644 index 0000000000000..210fade40c648 --- /dev/null +++ b/x-pack/test/functional/es_archives/dashboard/session_in_space/mappings.json @@ -0,0 +1,244 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "mappings": { + "properties": { + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "index-pattern": { + "dynamic": "strict", + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + } + } + }, + "search": { + "dynamic": "strict", + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "dynamic": "strict", + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "timelion-sheet": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "type": { + "type": "keyword" + }, + "url": { + "dynamic": "strict", + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/security_solution_cypress/runner.ts b/x-pack/test/security_solution_cypress/runner.ts index a1a1a3916ef7f..1cd32c55509ed 100644 --- a/x-pack/test/security_solution_cypress/runner.ts +++ b/x-pack/test/security_solution_cypress/runner.ts @@ -16,7 +16,6 @@ export async function SecuritySolutionCypressCliTestRunner({ getService }: FtrPr const config = getService('config'); const esArchiver = getService('esArchiver'); - await esArchiver.load('empty_kibana'); await esArchiver.load('auditbeat'); await withProcRunner(log, async (procs) => { diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts index 1e92019899b5a..4859b2474f860 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts @@ -6,14 +6,14 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../ftr_provider_context'; +import { getSearchSessionIdByPanelProvider } from './get_search_session_id_by_panel'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const es = getService('es'); const testSubjects = getService('testSubjects'); const log = getService('log'); const PageObjects = getPageObjects(['common', 'header', 'dashboard', 'visChart']); - const dashboardPanelActions = getService('dashboardPanelActions'); - const inspector = getService('inspector'); + const getSearchSessionIdByPanel = getSearchSessionIdByPanelProvider(getService); const queryBar = getService('queryBar'); const browser = getService('browser'); const sendToBackground = getService('sendToBackground'); @@ -133,15 +133,4 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); }); - - // HELPERS - async function getSearchSessionIdByPanel(panelTitle: string) { - await dashboardPanelActions.openInspectorByTitle(panelTitle); - await inspector.openInspectorRequestsView(); - const searchSessionId = await ( - await testSubjects.find('inspectorRequestSearchSessionId') - ).getAttribute('data-search-session-id'); - await inspector.close(); - return searchSessionId; - } } diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/get_search_session_id_by_panel.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/get_search_session_id_by_panel.ts new file mode 100644 index 0000000000000..6de85ca5459db --- /dev/null +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/get_search_session_id_by_panel.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. + */ + +// HELPERS +export function getSearchSessionIdByPanelProvider(getService: any) { + const dashboardPanelActions = getService('dashboardPanelActions'); + const inspector = getService('inspector'); + const testSubjects = getService('testSubjects'); + + return async function getSearchSessionIdByPanel(panelTitle: string) { + await dashboardPanelActions.openInspectorByTitle(panelTitle); + await inspector.openInspectorRequestsView(); + const searchSessionId = await ( + await testSubjects.find('inspectorRequestSearchSessionId') + ).getAttribute('data-search-session-id'); + await inspector.close(); + return searchSessionId; + }; +} diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/index.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/index.ts index 6719500d2eb3a..83085983fef05 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/index.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/index.ts @@ -24,5 +24,6 @@ export default function ({ loadTestFile, getService }: FtrProviderContext) { }); loadTestFile(require.resolve('./async_search')); + loadTestFile(require.resolve('./sessions_in_space')); }); } diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts new file mode 100644 index 0000000000000..e2e0adf447afe --- /dev/null +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts @@ -0,0 +1,90 @@ +/* + * 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'; +import { getSearchSessionIdByPanelProvider } from './get_search_session_id_by_panel'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const esArchiver = getService('esArchiver'); + const security = getService('security'); + const PageObjects = getPageObjects([ + 'common', + 'header', + 'dashboard', + 'visChart', + 'security', + 'timePicker', + ]); + const getSearchSessionIdByPanel = getSearchSessionIdByPanelProvider(getService); + const browser = getService('browser'); + const sendToBackground = getService('sendToBackground'); + + describe('dashboard in space', () => { + describe('Send to background in space', () => { + before(async () => { + await esArchiver.load('dashboard/session_in_space'); + + await security.role.create('data_analyst', { + elasticsearch: { + indices: [{ names: ['logstash-*'], privileges: ['all'] }], + }, + kibana: [ + { + base: ['all'], + spaces: ['another-space'], + }, + ], + }); + + await security.user.create('analyst', { + password: 'analyst-password', + roles: ['data_analyst'], + full_name: 'test user', + }); + + await PageObjects.security.forceLogout(); + + await PageObjects.security.login('analyst', 'analyst-password', { + expectSpaceSelector: false, + }); + }); + + after(async () => { + await esArchiver.unload('dashboard/session_in_space'); + await PageObjects.security.forceLogout(); + }); + + it('Saves and restores a session', async () => { + await PageObjects.common.navigateToApp('dashboard', { basePath: 's/another-space' }); + await PageObjects.dashboard.loadSavedDashboard('A Dashboard in another space'); + + await PageObjects.timePicker.setAbsoluteRange( + 'Sep 1, 2015 @ 00:00:00.000', + 'Oct 1, 2015 @ 00:00:00.000' + ); + + await PageObjects.dashboard.waitForRenderComplete(); + + await sendToBackground.expectState('completed'); + await sendToBackground.save(); + await sendToBackground.expectState('backgroundCompleted'); + const savedSessionId = await getSearchSessionIdByPanel('A Pie in another space'); + + // load URL to restore a saved session + const url = await browser.getCurrentUrl(); + const savedSessionURL = `${url}&searchSessionId=${savedSessionId}`; + await browser.get(savedSessionURL); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.waitForRenderComplete(); + + // Check that session is restored + await sendToBackground.expectState('restored'); + await testSubjects.missingOrFail('embeddableErrorLabel'); + }); + }); + }); +} diff --git a/yarn.lock b/yarn.lock index 032d6c4238567..4dbfa610be6c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10798,10 +10798,10 @@ cypress-promise@^1.1.0: resolved "https://registry.yarnpkg.com/cypress-promise/-/cypress-promise-1.1.0.tgz#f2d66965945fe198431aaf692d5157cea9d47b25" integrity sha512-DhIf5PJ/a0iY+Yii6n7Rbwq+9TJxU4pupXYzf9mZd8nPG0AzQrj9i+pqINv4xbI2EV1p+PKW3maCkR7oPG4GrA== -cypress@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-6.0.1.tgz#86857ca2f527c3723575737deab42fd8f2a209df" - integrity sha512-3xtQZ0YM65soLgKQUgn2wg2IbWsM6A2yBg6L4RF31mZHr5LNKdO2/9sgiwxEVMKu2C2m6+IQ75zHP41kZP5rPg== +cypress@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-6.1.0.tgz#af2596cb110aa98eaf75fef3d8ab379ca0ff2413" + integrity sha512-uQnSxRcZ6hkf9R5cr8KpRBTzN88QZwLIImbf5DWa5RIxH6o5Gpff58EcjiYhAR8/8p9SGv7O6SRygq4H+k0Qpw== dependencies: "@cypress/listr-verbose-renderer" "^0.4.1" "@cypress/request" "^2.88.5"