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`] = `
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"