From 201b352d7f2c2b093091a47020816005011c31f5 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Tue, 22 Mar 2022 07:14:52 -0700 Subject: [PATCH 01/13] [DOCS] Add create case and update case APIs (#127936) --- docs/api/cases.asciidoc | 7 +- docs/api/cases/cases-api-create.asciidoc | 237 ++++++++++++++++++++ docs/api/cases/cases-api-update.asciidoc | 271 +++++++++++++++++++++++ 3 files changed, 513 insertions(+), 2 deletions(-) create mode 100644 docs/api/cases/cases-api-create.asciidoc create mode 100644 docs/api/cases/cases-api-update.asciidoc diff --git a/docs/api/cases.asciidoc b/docs/api/cases.asciidoc index 5e412c61926db..00fbedc2d1299 100644 --- a/docs/api/cases.asciidoc +++ b/docs/api/cases.asciidoc @@ -5,7 +5,7 @@ You can create, manage, configure, and send cases to external systems with these APIs: * {security-guide}/cases-api-add-comment.html[Add comment] -* {security-guide}/cases-api-create.html[Create case] +* <> * {security-guide}/cases-api-delete-case.html[Delete case] * {security-guide}/cases-api-delete-all-comments.html[Delete all comments] * {security-guide}/cases-api-delete-comment.html[Delete comment] @@ -24,5 +24,8 @@ these APIs: * {security-guide}/cases-api-push.html[Push case] * {security-guide}/assign-connector.html[Set default Elastic Security UI connector] * {security-guide}/case-api-update-connector.html[Update case configurations] -* {security-guide}/cases-api-update.html[Update case] +* <> * {security-guide}/cases-api-update-comment.html[Update comment] + +include::cases/cases-api-create.asciidoc[leveloffset=+1] +include::cases/cases-api-update.asciidoc[leveloffset=+1] \ No newline at end of file diff --git a/docs/api/cases/cases-api-create.asciidoc b/docs/api/cases/cases-api-create.asciidoc new file mode 100644 index 0000000000000..f08b69998321f --- /dev/null +++ b/docs/api/cases/cases-api-create.asciidoc @@ -0,0 +1,237 @@ +[[cases-api-create]] +== Create case API +++++ +Create case +++++ + +Creates a case. + +=== Request + +`POST :/api/cases` + +`POST :/s//api/cases` + +=== Prerequisite + +You must have `all` privileges for the *Cases* feature in the *Management*, +*{observability}*, or *Security* section of the +<>, depending on the +`owner` of the case you're creating. + +=== Path parameters + +``:: +(Optional, string) An identifier for the space. If it is not specified, the +default space is used. + +=== Request body + +`connector`:: +(Required, object) An object that contains the connector configuration. ++ +.Properties of `connector` +[%collapsible%open] +==== +`fields`:: +(Required, object) An object containing the connector fields. ++ +-- +To create a case without a connector, specify `null`. If you want to omit any +individual field, specify `null` as its value. + +For {ibm-r} connectors, specify: + +`issueTypes`::: +(Required, array of numbers) The type of the incident. + +`severityCode`::: +(Required, number) The severity code of the incident. + +For {jira} connectors, specify: + +`issueType`::: +(Required, string) The type of the issue. + +`parent`::: +(Required, string) The key of the parent issue, when the issue type is `Sub-task`. + +`priority`::: +(Required, string) The priority of the issue. + +For {sn-itsm} connectors, specify: + +`category`::: +(Required, string) The category of the incident. + +`impact`::: +(Required, string) The effect an incident had on business. + +`severity`::: +(Required, string) The severity of the incident. + +`subcategory`::: +(Required, string) The subcategory of the incident. + +`urgency`::: +(Required, string) The extent to which the incident resolution can be delayed. + +For {sn-sir} connectors, specify: + +`category`::: +(Required, string) The category of the incident. + +`destIp`::: +(Required, string) A comma separated list of destination IPs. + +`malwareHash`::: +(Required, string) A comma separated list of malware hashes. + +`malwareUrl`::: +(Required, string) A comma separated list of malware URLs. + +`priority`::: +(Required, string) The priority of the incident. + +`sourceIp`::: +(Required, string) A comma separated list of source IPs. + +`subcategory`::: +(Required, string) The subcategory of the incident. + +For {swimlane} connectors, specify: + +`caseId`::: +(Required, string) The case ID. +-- + +`id`:: +(Required, string) The identifier for the connector. To create a case without a +connector, use `none`. +//To retrieve connector IDs, use <>). + +`name`:: +(Required, string) The name of the connector. To create a case without a +connector, use `none`. + +`type`:: +(Required, string) The type of the connector. Valid values are: `.jira`, `.none`, +`.resilient`,`.servicenow`, `.servicenow-sir`, and `.swimlane`. To create a case +without a connector, use `.none`. +==== + +`description`:: +(Required, string) The description for the case. + +`owner`:: +(Required, string) The application that owns the case. Valid values are: +`cases`, `observability`, or `securitySolution`. This value affects +whether the case is visible in the {stack-manage-app}, {observability}, or +{security-app}. + +`settings`:: +(Required, object) +An object that contains the case settings. ++ +.Properties of `settings` +[%collapsible%open] +==== +`syncAlerts`:: +(Required, boolean) Turns alert syncing on or off. +==== + +`tags`:: +(Required, string array) The words and phrases that help +categorize cases. It can be an empty array. + +`title`:: +(Required, string) A title for the case. + +=== Response code + +`200`:: + Indicates a successful call. + +=== Example + +[source,sh] +-------------------------------------------------- +POST api/cases +{ + "description": "James Bond clicked on a highly suspicious email + banner advertising cheap holidays for underpaid civil servants.", + "title": "This case will self-destruct in 5 seconds", + "tags": [ + "phishing", + "social engineering" + ], + "connector": { + "id": "131d4448-abe0-4789-939d-8ef60680b498", + "name": "My connector", + "type": ".jira", + "fields": { + "issueType": "10006", + "priority": "High", + "parent": null + } + }, + "settings": { + "syncAlerts": true + }, + "owner": "securitySolution" +} +-------------------------------------------------- +// KIBANA + +The API returns a JSON object that includes the user who created the case and +the case identifier, version, and creation time. For example: + +[source,json] +-------------------------------------------------- +{ + "id": "66b9aa00-94fa-11ea-9f74-e7e108796192", <1> + "version": "WzUzMiwxXQ==", + "comments": [], + "totalComment": 0, + "totalAlerts": 0, + "title": "This case will self-destruct in 5 seconds", + "tags": [ + "phishing", + "social engineering", + "bubblegum" + ], + "settings": { + "syncAlerts": true + }, + "owner": "securitySolution", + "description": "James Bond clicked on a highly suspicious email banner advertising cheap holidays for underpaid civil servants. Operation bubblegum is active. Repeat - operation bubblegum is now active", + "closed_at": null, + "closed_by": null, + "created_at": "2022-05-13T09:16:17.416Z", + "created_by": { + "email": "ahunley@imf.usa.gov", + "full_name": "Alan Hunley", + "username": "ahunley" + }, + "status": "open", + "updated_at": null, + "updated_by": null, + "connector": { + "id": "131d4448-abe0-4789-939d-8ef60680b498", <2> + "name": "My connector", + "type": ".jira", + "fields": { + "issueType": "10006", + "parent": null, + "priority": "High" + } + }, + "external_service": null <3> +} +-------------------------------------------------- + +<1> The case identifier is also its saved object ID (`savedObjectId`), which is +used when pushing cases to external systems. +<2> The default connector used to push cases to external services. +<3> The `external_service` object stores information about the incident after it +is pushed to an external incident management system. \ No newline at end of file diff --git a/docs/api/cases/cases-api-update.asciidoc b/docs/api/cases/cases-api-update.asciidoc new file mode 100644 index 0000000000000..ed0ef069e15f4 --- /dev/null +++ b/docs/api/cases/cases-api-update.asciidoc @@ -0,0 +1,271 @@ +[[cases-api-update]] +== Update cases API +++++ +Update cases +++++ + +Updates one or more cases. + +=== Request + +`PATCH :/api/cases` + +`PATCH :/s//api/cases` + +=== Prerequisite + +You must have `all` privileges for the *Cases* feature in the *Management*, +*{observability}*, or *Security* section of the +<>, depending on the +`owner` of the cases you're updating. + +=== Path parameters + +``:: +(Optional, string) An identifier for the space. If it is not specified, the +default space is used. + +=== Request body + +`cases`:: +(Required, array of objects) Array containing one or more case objects. ++ +.Properties of `cases` objects +[%collapsible%open] +==== +`connector`:: +(Optional, object) An object that contains the connector configuration. ++ +.Properties of `connector` +[%collapsible%open] +===== +`fields`:: +(Required, object) An object containing the connector fields. ++ +-- +To remove the connector, specify `null`. If you want to omit any individual +field, specify `null` as its value. + +For {ibm-r} connectors, specify: + +`issueTypes`::: +(Required, array of numbers) The issue types of the issue. + +`severityCode`::: +(Required, number) The severity code of the issue. + +For {jira} connectors, specify: + +`issueType`::: +(Required, string) The issue type of the issue. + +`parent`::: +(Required, string) The key of the parent issue, when the issue type is +`Sub-task`. + +`priority`::: +(Required, string) The priority of the issue. + +For {sn-itsm} connectors, specify: + +`category`::: +(Required, string) The category of the incident. + +`impact`::: +(Required, string) The effect an incident had on business. + +`severity`::: +(Required, string) The severity of the incident. + +`subcategory`::: +(Required, string) The subcategory of the incident. + +`urgency`::: +(Required, string) The extent to which the incident resolution can be delayed. + +For {sn-sir} connectors, specify: + +`category`::: +(Required, string) The category of the incident. + +`destIp`::: +(Required, string) A comma separated list of destination IPs. + +`malwareHash`::: +(Required, string) A comma separated list of malware hashes. + +`malwareUrl`::: +(Required, string) A comma separated list of malware URLs. + +`priority`::: +(Required, string) The priority of the incident. + +`sourceIp`::: +(Required, string) A comma separated list of source IPs. + +`subcategory`::: +(Required, string) The subcategory of the incident. + +For {swimlane} connectors, specify: + +`caseId`::: +(Required, string) The identifier for the case. +-- + +`id`:: +(Required, string) The identifier for the connector. To remove the connector, +use `none`. +//To retrieve connector IDs, use <>). + +`name`:: +(Required, string) The name of the connector. To remove the connector, use +`none`. + +`type`:: +(Required, string) The type of the connector. Valid values are: `.jira`, `.none`, +`.resilient`,`.servicenow`, `.servicenow-sir`, and `.swimlane`. To remove the +connector, use `.none`. + +===== + +`description`:: +(Optional, string) The updated case description. + +`id`:: +(Required, string) The identifier for the case. + +`settings`:: +(Optional, object) +An object that contains the case settings. ++ +.Properties of `settings` +[%collapsible%open] +===== +`syncAlerts`:: +(Required, boolean) Turn on or off synching with alerts. +===== + +`status`:: +(Optional, string) The case status. Valid values are: `closed`, `in-progress`, +and `open`. + +`tags`:: +(Optional, string array) The words and phrases that help categorize cases. + +`title`:: +(Optional, string) A title for the case. + +`version`:: +(Required, string) The current version of the case. +//To determine this value, use <> or <> +==== + +=== Response code + +`200`:: + Indicates a successful call. + +=== Example + +Update the description, tags, and connector of case ID +`a18b38a0-71b0-11ea-a0b2-c51ea50a58e2`: + +[source,sh] +-------------------------------------------------- +PATCH api/cases +{ + "cases": [ + { + "id": "a18b38a0-71b0-11ea-a0b2-c51ea50a58e2", + "version": "WzIzLDFd", + "connector": { + "id": "131d4448-abe0-4789-939d-8ef60680b498", + "name": "My connector", + "type": ".jira", + "fields": { + "issueType": "10006", + "priority": null, + "parent": null + } + }, + "description": "James Bond clicked on a highly suspicious email + banner advertising cheap holidays for underpaid civil servants. + Operation bubblegum is active. Repeat - operation bubblegum is + now active!", + "tags": [ + "phishing", + "social engineering", + "bubblegum" + ], + "settings": { + "syncAlerts": true + } + } + ] +} +-------------------------------------------------- +// KIBANA + +The API returns the updated case with a new `version` value. For example: + +[source,json] +-------------------------------------------------- +[ + { + "id": "66b9aa00-94fa-11ea-9f74-e7e108796192", + "version": "WzU0OCwxXQ==", + "comments": [], + "totalComment": 0, + "totalAlerts": 0, + "title": "This case will self-destruct in 5 seconds", + "tags": [ + "phishing", + "social engineering", + "bubblegum" + ], + "settings": { + "syncAlerts": true + }, + "owner": "securitySolution", + "description": "James Bond clicked on a highly suspicious email banner advertising cheap holidays for underpaid civil servants. Operation bubblegum is active. Repeat - operation bubblegum is now active!", + "closed_at": null, + "closed_by": null, + "created_at": "2022-05-13T09:16:17.416Z", + "created_by": { + "email": "ahunley@imf.usa.gov", + "full_name": "Alan Hunley", + "username": "ahunley" + }, + "status": "open", + "updated_at": "2022-05-13T09:48:33.043Z", + "updated_by": { + "email": "classified@hms.oo.gov.uk", + "full_name": "Classified", + "username": "M" + }, + "connector": { + "id": "131d4448-abe0-4789-939d-8ef60680b498", + "name": "My connector", + "type": ".jira", + "fields": { + "issueType": "10006", + "parent": null, + "priority": null, + } + }, + "external_service": { + "external_title": "IS-4", + "pushed_by": { + "full_name": "Classified", + "email": "classified@hms.oo.gov.uk", + "username": "M" + }, + "external_url": "https://hms.atlassian.net/browse/IS-4", + "pushed_at": "2022-05-13T09:20:40.672Z", + "connector_id": "05da469f-1fde-4058-99a3-91e4807e2de8", + "external_id": "10003", + "connector_name": "Jira" + } + } +] +-------------------------------------------------- From 7640031a5053369140693dfd8601fc47a1cbce07 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 22 Mar 2022 15:34:48 +0100 Subject: [PATCH 02/13] [Uptime] Fix pings over time histogram when filters are defined (#127757) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../ping_histogram/ping_histogram_container.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_histogram/ping_histogram_container.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_histogram/ping_histogram_container.tsx index d8060e27f1aa2..cd60dcf725074 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_histogram/ping_histogram_container.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ping_histogram/ping_histogram_container.tsx @@ -40,9 +40,14 @@ const Container: React.FC = ({ height }) => { const { loading, pingHistogram: data } = useSelector(selectPingHistogram); useEffect(() => { - filterCheck(() => - dispatch(getPingHistogram.get({ monitorId, dateStart, dateEnd, query, filters: esKuery })) - ); + if (monitorId) { + // we don't need filter check on monitor details page, where we have monitorId defined + dispatch(getPingHistogram.get({ monitorId, dateStart, dateEnd, query, filters: esKuery })); + } else { + filterCheck(() => + dispatch(getPingHistogram.get({ monitorId, dateStart, dateEnd, query, filters: esKuery })) + ); + } }, [filterCheck, dateStart, dateEnd, monitorId, lastRefresh, esKuery, dispatch, query]); return ( Date: Tue, 22 Mar 2022 10:35:52 -0400 Subject: [PATCH 03/13] [Fleet] Add install all packages script (#128208) --- .../scripts/install_all_packages/index.js | 9 ++ .../install_all_packages.ts | 118 ++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 x-pack/plugins/fleet/scripts/install_all_packages/index.js create mode 100644 x-pack/plugins/fleet/scripts/install_all_packages/install_all_packages.ts diff --git a/x-pack/plugins/fleet/scripts/install_all_packages/index.js b/x-pack/plugins/fleet/scripts/install_all_packages/index.js new file mode 100644 index 0000000000000..aa620c4ea6a04 --- /dev/null +++ b/x-pack/plugins/fleet/scripts/install_all_packages/index.js @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +require('../../../../../src/setup_node_env'); +require('./install_all_packages').run(); diff --git a/x-pack/plugins/fleet/scripts/install_all_packages/install_all_packages.ts b/x-pack/plugins/fleet/scripts/install_all_packages/install_all_packages.ts new file mode 100644 index 0000000000000..7ff848f79185d --- /dev/null +++ b/x-pack/plugins/fleet/scripts/install_all_packages/install_all_packages.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import fetch from 'node-fetch'; +import { ToolingLog } from '@kbn/dev-utils'; +import ReadPackage from 'read-pkg'; + +const REGISTRY_URL = 'https://epr-snapshot.elastic.co'; +const KIBANA_URL = 'http://localhost:5601'; +const KIBANA_USERNAME = 'elastic'; +const KIBANA_PASSWORD = 'changeme'; + +const KIBANA_VERSION = ReadPackage.sync().version; + +const SKIP_PACKAGES: string[] = []; + +async function installPackage(name: string, version: string) { + const start = Date.now(); + const res = await fetch(`${KIBANA_URL}/api/fleet/epm/packages/${name}/${version}`, { + headers: { + accept: '*/*', + 'content-type': 'application/json', + 'kbn-xsrf': 'xyz', + Authorization: + 'Basic ' + Buffer.from(`${KIBANA_USERNAME}:${KIBANA_PASSWORD}`).toString('base64'), + }, + body: JSON.stringify({ force: true }), + method: 'POST', + }); + const end = Date.now(); + + const body = await res.json(); + + return { body, status: res.status, took: (end - start) / 1000 }; +} + +async function deletePackage(name: string, version: string) { + const res = await fetch(`${KIBANA_URL}/api/fleet/epm/packages/${name}-${version}`, { + headers: { + accept: '*/*', + 'content-type': 'application/json', + 'kbn-xsrf': 'xyz', + Authorization: + 'Basic ' + Buffer.from(`${KIBANA_USERNAME}:${KIBANA_PASSWORD}`).toString('base64'), + }, + method: 'DELETE', + }); + + const body = await res.json(); + + return { body, status: res.status }; +} + +async function getAllPackages() { + const res = await fetch( + `${REGISTRY_URL}/search?experimental=true&kibana.version=${KIBANA_VERSION}`, + { + headers: { + accept: '*/*', + }, + method: 'GET', + } + ); + const body = await res.json(); + return body; +} + +function logResult( + logger: ToolingLog, + pkg: { name: string; version: string }, + result: { took?: number; status?: number } +) { + const pre = `${pkg.name}-${pkg.version} ${result.took ? ` took ${result.took}s` : ''} : `; + if (result.status !== 200) { + logger.info('❌ ' + pre + JSON.stringify(result)); + } else { + logger.info('✅ ' + pre + 200); + } +} + +export async function run() { + const logger = new ToolingLog({ + level: 'info', + writeTo: process.stdout, + }); + const allPackages = await getAllPackages(); + + logger.info('INSTALLING packages'); + + for (const pkg of allPackages) { + if (SKIP_PACKAGES.includes(pkg.name)) { + logger.info(`Skipping ${pkg.name}`); + continue; + } + const result = await installPackage(pkg.name, pkg.version); + + logResult(logger, pkg, result); + } + + const deletePackages = process.argv.includes('--delete'); + + if (!deletePackages) return; + + logger.info('DELETING packages'); + for (const pkg of allPackages) { + if (SKIP_PACKAGES.includes(pkg.name)) { + logger.info(`Skipping ${pkg.name}`); + continue; + } + const result = await deletePackage(pkg.name, pkg.version); + + logResult(logger, pkg, result); + } +} From 4a0b376ad4ba237a1347f1d345ae2153c1e221bd Mon Sep 17 00:00:00 2001 From: srinjon <99788429+srinjon@users.noreply.github.com> Date: Tue, 22 Mar 2022 20:40:39 +0530 Subject: [PATCH 04/13] Update endpoints.mdx (#128114) Fixed a sentence formation Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- dev_docs/tutorials/endpoints.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev_docs/tutorials/endpoints.mdx b/dev_docs/tutorials/endpoints.mdx index 5f2fc7da010c7..f6367580420db 100644 --- a/dev_docs/tutorials/endpoints.mdx +++ b/dev_docs/tutorials/endpoints.mdx @@ -46,7 +46,7 @@ HTTP method. All these APIs share the same signature, and receive two parameters When invoked, the `handler` receive three parameters: `context`, `request`, and `response`, and must return a response that will be sent to serve the request. -- `context` is a request-bound context exposed for the request. It allows for example to use an elasticsearch client bound to the request's credentials. +- `context` is a request-bound context exposed for the request. For example, it allows to use an elasticsearch client bound to the request's credentials. - `request` contains information related to the request, such as the path and query parameter - `response` contains factory helpers to create the response to return from the endpoint From e02b367063c218bf6254e2093fc25d6c772ddaef Mon Sep 17 00:00:00 2001 From: Byron Hulcher Date: Tue, 22 Mar 2022 11:18:26 -0400 Subject: [PATCH 05/13] [Workplace Search] Submit `base_service_type` field when creating a pre-configured custom source (#128221) --- .../add_custom_source_logic.test.ts | 53 +++++++++++++++++++ .../add_source/add_custom_source_logic.ts | 16 ++++-- .../server/routes/workplace_search/sources.ts | 2 + 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_custom_source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_custom_source_logic.test.ts index 9360967985876..d019c66526e6c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_custom_source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_custom_source_logic.test.ts @@ -150,6 +150,31 @@ describe('AddCustomSourceLogic', () => { expect(setButtonNotLoadingSpy).toHaveBeenCalled(); }); + it('submits a base service type for pre-configured sources', () => { + mount( + { + customSourceNameValue: MOCK_NAME, + }, + { + ...MOCK_PROPS, + sourceData: { + ...CUSTOM_SOURCE_DATA_ITEM, + serviceType: 'sharepoint-server', + }, + } + ); + + AddCustomSourceLogic.actions.createContentSource(); + + expect(http.post).toHaveBeenCalledWith('/internal/workplace_search/org/create_source', { + body: JSON.stringify({ + service_type: 'custom', + name: MOCK_NAME, + base_service_type: 'sharepoint-server', + }), + }); + }); + itShowsServerErrorAsFlashMessage(http.post, () => { AddCustomSourceLogic.actions.createContentSource(); }); @@ -173,6 +198,34 @@ describe('AddCustomSourceLogic', () => { ); }); + it('submits a base service type for pre-configured sources', () => { + mount( + { + customSourceNameValue: MOCK_NAME, + }, + { + ...MOCK_PROPS, + sourceData: { + ...CUSTOM_SOURCE_DATA_ITEM, + serviceType: 'sharepoint-server', + }, + } + ); + + AddCustomSourceLogic.actions.createContentSource(); + + expect(http.post).toHaveBeenCalledWith( + '/internal/workplace_search/account/create_source', + { + body: JSON.stringify({ + service_type: 'custom', + name: MOCK_NAME, + base_service_type: 'sharepoint-server', + }), + } + ); + }); + itShowsServerErrorAsFlashMessage(http.post, () => { AddCustomSourceLogic.actions.createContentSource(); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_custom_source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_custom_source_logic.ts index 5bf86f6df41c7..c35436ccbf99a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_custom_source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_custom_source_logic.ts @@ -80,7 +80,7 @@ export const AddCustomSourceLogic = kea< ], sourceData: [props.sourceData], }), - listeners: ({ actions, values }) => ({ + listeners: ({ actions, values, props }) => ({ createContentSource: async () => { clearFlashMessages(); const { isOrganization } = AppLogic.values; @@ -90,14 +90,24 @@ export const AddCustomSourceLogic = kea< const { customSourceNameValue } = values; - const params = { + const baseParams = { service_type: 'custom', name: customSourceNameValue, }; + // pre-configured custom sources have a serviceType reflecting their target service + // we submit this as `base_service_type` to keep track of + const params = + props.sourceData.serviceType === 'custom' + ? baseParams + : { + ...baseParams, + base_service_type: props.sourceData.serviceType, + }; + try { const response = await HttpLogic.values.http.post(route, { - body: JSON.stringify({ ...params }), + body: JSON.stringify(params), }); actions.setNewCustomSource(response); } catch (e) { diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts index ba1bd8119a3e5..10fad8b39ddae 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts @@ -206,6 +206,7 @@ export function registerAccountCreateSourceRoute({ validate: { body: schema.object({ service_type: schema.string(), + base_service_type: schema.maybe(schema.string()), name: schema.maybe(schema.string()), login: schema.maybe(schema.string()), password: schema.maybe(schema.string()), @@ -566,6 +567,7 @@ export function registerOrgCreateSourceRoute({ validate: { body: schema.object({ service_type: schema.string(), + base_service_type: schema.maybe(schema.string()), name: schema.maybe(schema.string()), login: schema.maybe(schema.string()), password: schema.maybe(schema.string()), From d8a1827b44ec8a41a4297f2f081454f8d1206ef9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 22 Mar 2022 11:20:04 -0400 Subject: [PATCH 06/13] Update dependency node-forge to ^1.3.0 (#128112) --- package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 9f1cdfab2e305..a847db572fa4c 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "**/json-schema": "^0.4.0", "**/minimatch": "^3.1.2", "**/minimist": "^1.2.5", - "**/node-forge": "^1.2.1", + "**/node-forge": "^1.3.0", "**/pdfkit/crypto-js": "4.0.0", "**/react-syntax-highlighter": "^15.3.1", "**/react-syntax-highlighter/**/highlight.js": "^10.4.1", @@ -315,7 +315,7 @@ "mustache": "^2.3.2", "nock": "12.0.3", "node-fetch": "^2.6.7", - "node-forge": "^1.2.1", + "node-forge": "^1.3.0", "nodemailer": "^6.6.2", "normalize-path": "^3.0.0", "object-hash": "^1.3.1", diff --git a/yarn.lock b/yarn.lock index 6df682be18360..396cda03b1235 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20710,10 +20710,10 @@ node-fetch@2.6.1, node-fetch@^1.0.1, node-fetch@^2.3.0, node-fetch@^2.6.1, node- dependencies: whatwg-url "^5.0.0" -node-forge@^0.10.0, node-forge@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.2.1.tgz#82794919071ef2eb5c509293325cec8afd0fd53c" - integrity sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w== +node-forge@^0.10.0, node-forge@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.0.tgz#37a874ea723855f37db091e6c186e5b67a01d4b2" + integrity sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA== node-gyp-build@^4.2.3: version "4.2.3" From e9d0769a3d6c1dd586aeaba591a17c02489c2749 Mon Sep 17 00:00:00 2001 From: Ignacio Rivas Date: Tue, 22 Mar 2022 16:36:45 +0100 Subject: [PATCH 07/13] View request flyout (#127156) * wip: create code scaffold for component * Implement new flyout in ingest pipelines * Fix linter and i18n issues * Add base tests for new component * Wire everything together * Fix linter issues * Finish writing tests * fix linting issues * Refactor hook and fix small dependencies bug * commit using @elastic.co * Refactor out hook and fix linter issues * Enhance tests and fix typo * Refactor component name and fix tests * update snapshot * Address first round of CR * Update snapshot * Refactor apirequestflyout to consume applicationStart only * Fix import order --- src/plugins/es_ui_shared/kibana.json | 2 +- .../view_api_request_flyout.test.tsx.snap | 118 ++++++++++++++ .../view_api_request_flyout/index.ts | 9 ++ .../view_api_request_flyout.test.tsx | 93 +++++++++++ .../view_api_request_flyout.tsx | 147 ++++++++++++++++++ src/plugins/es_ui_shared/public/index.ts | 1 + .../helpers/pipeline_form.helpers.ts | 4 +- .../helpers/setup_environment.tsx | 16 ++ .../ingest_pipelines_create.test.tsx | 4 +- .../pipeline_request_flyout/index.ts | 2 +- .../pipeline_request_flyout.tsx | 113 ++++++-------- .../pipeline_request_flyout_provider.tsx | 46 ------ .../public/application/index.tsx | 2 + .../application/mount_management_section.ts | 2 + .../ingest_pipelines/public/shared_imports.ts | 1 + .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 18 files changed, 440 insertions(+), 123 deletions(-) create mode 100644 src/plugins/es_ui_shared/public/components/view_api_request_flyout/__snapshots__/view_api_request_flyout.test.tsx.snap create mode 100644 src/plugins/es_ui_shared/public/components/view_api_request_flyout/index.ts create mode 100644 src/plugins/es_ui_shared/public/components/view_api_request_flyout/view_api_request_flyout.test.tsx create mode 100644 src/plugins/es_ui_shared/public/components/view_api_request_flyout/view_api_request_flyout.tsx delete mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/pipeline_request_flyout_provider.tsx diff --git a/src/plugins/es_ui_shared/kibana.json b/src/plugins/es_ui_shared/kibana.json index 2735b153f738c..1a4ff33674f95 100644 --- a/src/plugins/es_ui_shared/kibana.json +++ b/src/plugins/es_ui_shared/kibana.json @@ -14,5 +14,5 @@ "static/forms/components", "static/forms/helpers/field_validators/types" ], - "requiredBundles": ["data"] + "requiredBundles": ["data", "kibanaReact"] } diff --git a/src/plugins/es_ui_shared/public/components/view_api_request_flyout/__snapshots__/view_api_request_flyout.test.tsx.snap b/src/plugins/es_ui_shared/public/components/view_api_request_flyout/__snapshots__/view_api_request_flyout.test.tsx.snap new file mode 100644 index 0000000000000..2d850ee8082f9 --- /dev/null +++ b/src/plugins/es_ui_shared/public/components/view_api_request_flyout/__snapshots__/view_api_request_flyout.test.tsx.snap @@ -0,0 +1,118 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ViewApiRequestFlyout is rendered 1`] = ` +
+ + +
+
+
+
+            
+              Hello world
+            
+          
+
+
+ + +
+ +
+ +`; diff --git a/src/plugins/es_ui_shared/public/components/view_api_request_flyout/index.ts b/src/plugins/es_ui_shared/public/components/view_api_request_flyout/index.ts new file mode 100644 index 0000000000000..deed3c5db27d6 --- /dev/null +++ b/src/plugins/es_ui_shared/public/components/view_api_request_flyout/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { ViewApiRequestFlyout } from './view_api_request_flyout'; diff --git a/src/plugins/es_ui_shared/public/components/view_api_request_flyout/view_api_request_flyout.test.tsx b/src/plugins/es_ui_shared/public/components/view_api_request_flyout/view_api_request_flyout.test.tsx new file mode 100644 index 0000000000000..4f6c954d4c37d --- /dev/null +++ b/src/plugins/es_ui_shared/public/components/view_api_request_flyout/view_api_request_flyout.test.tsx @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { mountWithI18nProvider } from '@kbn/test-jest-helpers'; +import { findTestSubject, takeMountedSnapshot } from '@elastic/eui/lib/test'; +import { compressToEncodedURIComponent } from 'lz-string'; + +import { ViewApiRequestFlyout } from './view_api_request_flyout'; +import type { UrlService } from 'src/plugins/share/common/url_service'; +import { ApplicationStart } from 'src/core/public'; +import { applicationServiceMock } from 'src/core/public/mocks'; + +const payload = { + title: 'Test title', + description: 'Test description', + request: 'Hello world', + closeFlyout: jest.fn(), +}; + +const urlServiceMock = { + locators: { + get: jest.fn().mockReturnValue({ + useUrl: jest.fn().mockImplementation((value) => { + return `devToolsUrl_${value?.loadFrom}`; + }), + }), + }, +} as any as UrlService; + +const applicationMock = { + ...applicationServiceMock.createStartContract(), + capabilities: { + dev_tools: { + show: true, + }, + }, +} as any as ApplicationStart; + +describe('ViewApiRequestFlyout', () => { + test('is rendered', () => { + const component = mountWithI18nProvider(); + expect(takeMountedSnapshot(component)).toMatchSnapshot(); + }); + + describe('props', () => { + test('on closeFlyout', async () => { + const component = mountWithI18nProvider(); + + await act(async () => { + findTestSubject(component, 'apiRequestFlyoutClose').simulate('click'); + }); + + expect(payload.closeFlyout).toBeCalled(); + }); + + test('doesnt have openInConsole when some optional props are not supplied', async () => { + const component = mountWithI18nProvider(); + + const openInConsole = findTestSubject(component, 'apiRequestFlyoutOpenInConsoleButton'); + expect(openInConsole.length).toEqual(0); + + // Flyout should *not* be wrapped with RedirectAppLinks + const redirectWrapper = findTestSubject(component, 'apiRequestFlyoutRedirectWrapper'); + expect(redirectWrapper.length).toEqual(0); + }); + + test('has openInConsole when all optional props are supplied', async () => { + const encodedRequest = compressToEncodedURIComponent(payload.request); + const component = mountWithI18nProvider( + + ); + + const openInConsole = findTestSubject(component, 'apiRequestFlyoutOpenInConsoleButton'); + expect(openInConsole.length).toEqual(1); + expect(openInConsole.props().href).toEqual(`devToolsUrl_data:text/plain,${encodedRequest}`); + + // Flyout should be wrapped with RedirectAppLinks + const redirectWrapper = findTestSubject(component, 'apiRequestFlyoutRedirectWrapper'); + expect(redirectWrapper.length).toEqual(1); + }); + }); +}); diff --git a/src/plugins/es_ui_shared/public/components/view_api_request_flyout/view_api_request_flyout.tsx b/src/plugins/es_ui_shared/public/components/view_api_request_flyout/view_api_request_flyout.tsx new file mode 100644 index 0000000000000..fa7bc6088f5c0 --- /dev/null +++ b/src/plugins/es_ui_shared/public/components/view_api_request_flyout/view_api_request_flyout.tsx @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { compressToEncodedURIComponent } from 'lz-string'; + +import { + EuiFlyout, + EuiFlyoutProps, + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiButtonEmpty, + EuiText, + EuiSpacer, + EuiCodeBlock, + EuiCopy, +} from '@elastic/eui'; +import type { UrlService } from 'src/plugins/share/common/url_service'; +import { ApplicationStart, APP_WRAPPER_CLASS } from '../../../../../core/public'; +import { RedirectAppLinks } from '../../../../kibana_react/public'; + +type FlyoutProps = Omit; +interface ViewApiRequestFlyoutProps { + title: string; + description: string; + request: string; + closeFlyout: () => void; + flyoutProps?: FlyoutProps; + application?: ApplicationStart; + urlService?: UrlService; +} + +export const ApiRequestFlyout: React.FunctionComponent = ({ + title, + description, + request, + closeFlyout, + flyoutProps, + urlService, + application, +}) => { + const getUrlParams = undefined; + const canShowDevtools = !!application?.capabilities?.dev_tools?.show; + const devToolsDataUri = compressToEncodedURIComponent(request); + + // Generate a console preview link if we have a valid locator + const consolePreviewLink = urlService?.locators.get('CONSOLE_APP_LOCATOR')?.useUrl( + { + loadFrom: `data:text/plain,${devToolsDataUri}`, + }, + getUrlParams, + [request] + ); + + // Check if both the Dev Tools UI and the Console UI are enabled. + const shouldShowDevToolsLink = canShowDevtools && consolePreviewLink !== undefined; + + return ( + + + +

{title}

+
+
+ + + +

{description}

+
+ + + +
+ + {(copy) => ( + + + + )} + + {shouldShowDevToolsLink && ( + + + + )} +
+ + + {request} + +
+ + + + + + +
+ ); +}; + +export const ViewApiRequestFlyout = (props: ViewApiRequestFlyoutProps) => { + if (props.application) { + return ( + + + + ); + } + + return ; +}; diff --git a/src/plugins/es_ui_shared/public/index.ts b/src/plugins/es_ui_shared/public/index.ts index c21587c9a6040..8a861ac993170 100644 --- a/src/plugins/es_ui_shared/public/index.ts +++ b/src/plugins/es_ui_shared/public/index.ts @@ -25,6 +25,7 @@ export type { EuiCodeEditorProps } from './components/code_editor'; export { EuiCodeEditor } from './components/code_editor'; export type { Frequency } from './components/cron_editor'; export { CronEditor } from './components/cron_editor'; +export { ViewApiRequestFlyout } from './components/view_api_request_flyout'; export type { SendRequestConfig, diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipeline_form.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipeline_form.helpers.ts index 432b9046f1071..775d05a865189 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipeline_form.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipeline_form.helpers.ts @@ -61,8 +61,8 @@ export type PipelineFormTestSubjects = | 'onFailureEditor' | 'testPipelineButton' | 'showRequestLink' - | 'requestFlyout' - | 'requestFlyout.title' + | 'apiRequestFlyout' + | 'apiRequestFlyout.apiRequestFlyoutTitle' | 'testPipelineFlyout' | 'testPipelineFlyout.title' | 'documentationLink'; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx index 8e128692c41c5..96a0f9e23348a 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx @@ -11,6 +11,8 @@ import axiosXhrAdapter from 'axios/lib/adapters/xhr'; import { LocationDescriptorObject } from 'history'; import { HttpSetup } from 'kibana/public'; +import { ApplicationStart } from 'src/core/public'; +import { MockUrlService } from 'src/plugins/share/common/mocks'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { sharePluginMock } from '../../../../../../src/plugins/share/public/mocks'; import { @@ -18,6 +20,7 @@ import { docLinksServiceMock, scopedHistoryMock, uiSettingsServiceMock, + applicationServiceMock, } from '../../../../../../src/core/public/mocks'; import { usageCollectionPluginMock } from '../../../../../../src/plugins/usage_collection/public/mocks'; @@ -38,6 +41,15 @@ history.createHref.mockImplementation((location: LocationDescriptorObject) => { return `${location.pathname}?${location.search}`; }); +const applicationMock = { + ...applicationServiceMock.createStartContract(), + capabilities: { + dev_tools: { + show: true, + }, + }, +} as any as ApplicationStart; + const appServices = { breadcrumbs: breadcrumbService, metric: uiMetricService, @@ -54,6 +66,10 @@ const appServices = { getMaxBytes: jest.fn().mockReturnValue(100), getMaxBytesFormatted: jest.fn().mockReturnValue('100'), }, + application: applicationMock, + share: { + url: new MockUrlService(), + }, }; export const setupEnvironment = () => { diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx index bb1d3f2503f9b..5be5cecd750f6 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx @@ -79,8 +79,8 @@ describe('', () => { await actions.clickShowRequestLink(); // Verify request flyout opens - expect(exists('requestFlyout')).toBe(true); - expect(find('requestFlyout.title').text()).toBe('Request'); + expect(exists('apiRequestFlyout')).toBe(true); + expect(find('apiRequestFlyout.apiRequestFlyoutTitle').text()).toBe('Request'); }); describe('form validation', () => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/index.ts index 5905d10faad85..8368dbf93b96c 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { PipelineRequestFlyoutProvider as PipelineRequestFlyout } from './pipeline_request_flyout_provider'; +export { PipelineRequestFlyout } from './pipeline_request_flyout'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/pipeline_request_flyout.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/pipeline_request_flyout.tsx index feb7d55145083..66d95c18663c0 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/pipeline_request_flyout.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/pipeline_request_flyout.tsx @@ -5,86 +5,63 @@ * 2.0. */ -import React, { useRef } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { - EuiButtonEmpty, - EuiCodeBlock, - EuiFlyout, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiFlyoutHeader, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; +import React, { useState, useEffect, FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; import { Pipeline } from '../../../../../common/types'; +import { useFormContext, ViewApiRequestFlyout, useKibana } from '../../../../shared_imports'; + +import { ReadProcessorsFunction } from '../types'; interface Props { - pipeline: Pipeline; closeFlyout: () => void; + readProcessors: ReadProcessorsFunction; } -export const PipelineRequestFlyout: React.FunctionComponent = ({ +export const PipelineRequestFlyout: FunctionComponent = ({ closeFlyout, - pipeline, + readProcessors, }) => { - const { name, ...pipelineBody } = pipeline; - const endpoint = `PUT _ingest/pipeline/${name || ''}`; - const payload = JSON.stringify(pipelineBody, null, 2); - const request = `${endpoint}\n${payload}`; - // Hack so that copied-to-clipboard value updates as content changes - // Related issue: https://github.com/elastic/eui/issues/3321 - const uuid = useRef(0); - uuid.current++; + const { services } = useKibana(); + const form = useFormContext(); + const [formData, setFormData] = useState({} as Pipeline); + const pipeline = { ...formData, ...readProcessors() }; - return ( - - - -

- {name ? ( - - ) : ( - - )} -

-
-
+ useEffect(() => { + const subscription = form.subscribe(async ({ isValid, validate, data }) => { + const isFormValid = isValid ?? (await validate()); + if (isFormValid) { + setFormData(data.format() as Pipeline); + } + }); - - -

- -

-
+ return subscription.unsubscribe; + }, [form]); - - - {request} - -
+ const { name, ...pipelineBody } = pipeline; + const endpoint = `PUT _ingest/pipeline/${name || ''}`; + const request = `${endpoint}\n${JSON.stringify(pipelineBody, null, 2)}`; - - - - - -
+ const title = name + ? i18n.translate('xpack.ingestPipelines.requestFlyout.namedTitle', { + defaultMessage: "Request for '{name}'", + values: { name }, + }) + : i18n.translate('xpack.ingestPipelines.requestFlyout.unnamedTitle', { + defaultMessage: 'Request', + }); + + return ( + ); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/pipeline_request_flyout_provider.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/pipeline_request_flyout_provider.tsx deleted file mode 100644 index 0b91b07a5a526..0000000000000 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/pipeline_request_flyout_provider.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState, useEffect, FunctionComponent } from 'react'; - -import { Pipeline } from '../../../../../common/types'; -import { useFormContext } from '../../../../shared_imports'; - -import { ReadProcessorsFunction } from '../types'; - -import { PipelineRequestFlyout } from './pipeline_request_flyout'; - -interface Props { - closeFlyout: () => void; - readProcessors: ReadProcessorsFunction; -} - -export const PipelineRequestFlyoutProvider: FunctionComponent = ({ - closeFlyout, - readProcessors, -}) => { - const form = useFormContext(); - const [formData, setFormData] = useState({} as Pipeline); - - useEffect(() => { - const subscription = form.subscribe(async ({ isValid, validate, data }) => { - const isFormValid = isValid ?? (await validate()); - if (isFormValid) { - setFormData(data.format() as Pipeline); - } - }); - - return subscription.unsubscribe; - }, [form]); - - return ( - - ); -}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/index.tsx b/x-pack/plugins/ingest_pipelines/public/application/index.tsx index bab3a1e0a074a..91c7665503a66 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/index.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/index.tsx @@ -10,6 +10,7 @@ import React, { ReactNode } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Observable } from 'rxjs'; +import { ApplicationStart } from 'src/core/public'; import { NotificationsSetup, IUiSettingsClient, CoreTheme } from 'kibana/public'; import { ManagementAppMountParams } from 'src/plugins/management/public'; import type { SharePluginStart } from 'src/plugins/share/public'; @@ -40,6 +41,7 @@ export interface AppServices { uiSettings: IUiSettingsClient; share: SharePluginStart; fileUpload: FileUploadPluginStart; + application: ApplicationStart; } export interface CoreServices { diff --git a/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts b/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts index f90b8077e6281..81f7be35074d8 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts @@ -26,6 +26,7 @@ export async function mountManagementSection( const [coreStart, depsStart] = await getStartServices(); const { docLinks, + application, i18n: { Context: I18nContext }, } = coreStart; @@ -43,6 +44,7 @@ export async function mountManagementSection( uiSettings: coreStart.uiSettings, share: depsStart.share, fileUpload: depsStart.fileUpload, + application, }; return renderApp(element, I18nContext, services, { http }, { theme$ }); diff --git a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts index f4c24f622e752..90ccf78355f1a 100644 --- a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts +++ b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts @@ -30,6 +30,7 @@ export { XJson, JsonEditor, attemptToURIDecode, + ViewApiRequestFlyout, } from '../../../../src/plugins/es_ui_shared/public/'; export type { diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index b4832c4cd24c2..05ab45fc2756f 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -13609,7 +13609,6 @@ "xpack.ingestPipelines.processors.label.urldecode": "Décodage d'URL", "xpack.ingestPipelines.processors.label.userAgent": "Agent utilisateur", "xpack.ingestPipelines.processors.uriPartsDescription": "Analyse une chaîne d'URI (Uniform Resource Identifier, identifiant uniforme de ressource) et extrait ses composants sous forme d'objet.", - "xpack.ingestPipelines.requestFlyout.closeButtonLabel": "Fermer", "xpack.ingestPipelines.requestFlyout.descriptionText": "Cette requête Elasticsearch créera ou mettra à jour le pipeline.", "xpack.ingestPipelines.requestFlyout.namedTitle": "Requête pour \"{name}\"", "xpack.ingestPipelines.requestFlyout.unnamedTitle": "Requête", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 846ef5ef2ad28..bcdafe3c8c050 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -15741,7 +15741,6 @@ "xpack.ingestPipelines.processors.label.urldecode": "URLデコード", "xpack.ingestPipelines.processors.label.userAgent": "ユーザーエージェント", "xpack.ingestPipelines.processors.uriPartsDescription": "Uniform Resource Identifier(URI)文字列を解析し、コンポーネントをオブジェクトとして抽出します。", - "xpack.ingestPipelines.requestFlyout.closeButtonLabel": "閉じる", "xpack.ingestPipelines.requestFlyout.descriptionText": "このElasticsearchリクエストは、このパイプラインを作成または更新します。", "xpack.ingestPipelines.requestFlyout.namedTitle": "「{name}」のリクエスト", "xpack.ingestPipelines.requestFlyout.unnamedTitle": "リクエスト", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3a9065a878085..d2dbc9904b9a1 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -15765,7 +15765,6 @@ "xpack.ingestPipelines.processors.label.urldecode": "URL 解码", "xpack.ingestPipelines.processors.label.userAgent": "用户代理", "xpack.ingestPipelines.processors.uriPartsDescription": "解析统一资源标识符 (URI) 字符串并提取其组件作为对象。", - "xpack.ingestPipelines.requestFlyout.closeButtonLabel": "关闭", "xpack.ingestPipelines.requestFlyout.descriptionText": "此 Elasticsearch 请求将创建或更新管道。", "xpack.ingestPipelines.requestFlyout.namedTitle": "对“{name}”的请求", "xpack.ingestPipelines.requestFlyout.unnamedTitle": "请求", From 4b474815669648ca72cbbc7e366a647558907c97 Mon Sep 17 00:00:00 2001 From: Sergi Massaneda Date: Tue, 22 Mar 2022 17:04:51 +0100 Subject: [PATCH 08/13] [Security Solution] [Timeline] Fields browser add a view all / selected option (#128049) * view selected option added * new header component * test fixed * Update x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_table_header.test.tsx use not.toBeInTheDocument Co-authored-by: Pablo Machado * pass callback down instead of state setter Co-authored-by: Pablo Machado Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../integration/hosts/events_viewer.spec.ts | 14 +- .../timelines/fields_browser.spec.ts | 15 +- .../cypress/screens/fields_browser.ts | 5 + .../cypress/tasks/fields_browser.ts | 12 ++ .../body/column_headers/default_headers.ts | 3 - .../fields_browser/field_browser.test.tsx | 7 +- .../toolbar/fields_browser/field_browser.tsx | 16 +- .../fields_browser/field_table.test.tsx | 10 +- .../toolbar/fields_browser/field_table.tsx | 22 ++- .../field_table_header.test.tsx | 119 +++++++++++ .../fields_browser/field_table_header.tsx | 112 +++++++++++ .../toolbar/fields_browser/helpers.test.tsx | 186 ++++++------------ .../t_grid/toolbar/fields_browser/helpers.tsx | 80 ++++---- .../t_grid/toolbar/fields_browser/index.tsx | 42 ++-- .../toolbar/fields_browser/translations.ts | 12 ++ 15 files changed, 443 insertions(+), 212 deletions(-) create mode 100644 x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_table_header.test.tsx create mode 100644 x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_table_header.tsx diff --git a/x-pack/plugins/security_solution/cypress/integration/hosts/events_viewer.spec.ts b/x-pack/plugins/security_solution/cypress/integration/hosts/events_viewer.spec.ts index 47e71345ff0c4..25883b5156407 100644 --- a/x-pack/plugins/security_solution/cypress/integration/hosts/events_viewer.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/hosts/events_viewer.spec.ts @@ -9,6 +9,7 @@ import { FIELDS_BROWSER_CHECKBOX, FIELDS_BROWSER_CONTAINER, FIELDS_BROWSER_SELECTED_CATEGORIES_BADGES, + FIELDS_BROWSER_VIEW_BUTTON, } from '../../screens/fields_browser'; import { HOST_GEO_CITY_NAME_HEADER, @@ -18,9 +19,10 @@ import { } from '../../screens/hosts/events'; import { + activateViewAll, + activateViewSelected, closeFieldsBrowser, filterFieldsBrowser, - toggleCategory, } from '../../tasks/fields_browser'; import { loginAndWaitForPage } from '../../tasks/login'; import { openEvents } from '../../tasks/hosts/main'; @@ -64,16 +66,20 @@ describe('Events Viewer', () => { cy.get(FIELDS_BROWSER_CONTAINER).should('not.exist'); }); + it('displays "view all" option by default', () => { + cy.get(FIELDS_BROWSER_VIEW_BUTTON).should('contain.text', 'View: all'); + }); + it('displays all categories (by default)', () => { cy.get(FIELDS_BROWSER_SELECTED_CATEGORIES_BADGES).should('be.empty'); }); - it('displays a checked checkbox for all of the default events viewer columns that are also in the default ECS category', () => { - const category = 'default ECS'; - toggleCategory(category); + it('displays only the default selected fields when "view selected" option is enabled', () => { + activateViewSelected(); defaultHeadersInDefaultEcsCategory.forEach((header) => cy.get(FIELDS_BROWSER_CHECKBOX(header.id)).should('be.checked') ); + activateViewAll(); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/fields_browser.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/fields_browser.spec.ts index 89a9fc4c0c6ba..580868fa0452c 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timelines/fields_browser.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timelines/fields_browser.spec.ts @@ -15,6 +15,7 @@ import { FIELDS_BROWSER_CATEGORIES_FILTER_CONTAINER, FIELDS_BROWSER_SELECTED_CATEGORIES_BADGES, FIELDS_BROWSER_CATEGORY_BADGE, + FIELDS_BROWSER_VIEW_BUTTON, } from '../../screens/fields_browser'; import { TIMELINE_FIELDS_BUTTON } from '../../screens/timeline'; import { cleanKibana } from '../../tasks/common'; @@ -29,6 +30,8 @@ import { removesMessageField, resetFields, toggleCategory, + activateViewSelected, + activateViewAll, } from '../../tasks/fields_browser'; import { loginAndWaitForPage } from '../../tasks/login'; import { openTimelineUsingToggle } from '../../tasks/security_main'; @@ -65,6 +68,10 @@ describe('Fields Browser', () => { cy.get(FIELDS_BROWSER_SELECTED_CATEGORIES_BADGES).should('be.empty'); }); + it('displays "view all" option by default', () => { + cy.get(FIELDS_BROWSER_VIEW_BUTTON).should('contain.text', 'View: all'); + }); + it('displays the expected count of categories that match the filter input', () => { const filterInput = 'host.mac'; @@ -80,15 +87,13 @@ describe('Fields Browser', () => { cy.get(FIELDS_BROWSER_FIELDS_COUNT).should('contain.text', '2'); }); - it('the `default ECS` category matches the default timeline header fields', () => { - const category = 'default ECS'; - toggleCategory(category); + it('displays only the selected fields when "view selected" option is enabled', () => { + activateViewSelected(); cy.get(FIELDS_BROWSER_FIELDS_COUNT).should('contain.text', `${defaultHeaders.length}`); - defaultHeaders.forEach((header) => { cy.get(`[data-test-subj="field-${header.id}-checkbox"]`).should('be.checked'); }); - toggleCategory(category); + activateViewAll(); }); it('creates the category badge when it is selected', () => { diff --git a/x-pack/plugins/security_solution/cypress/screens/fields_browser.ts b/x-pack/plugins/security_solution/cypress/screens/fields_browser.ts index 66a7ba50c8070..a9898f73207d7 100644 --- a/x-pack/plugins/security_solution/cypress/screens/fields_browser.ts +++ b/x-pack/plugins/security_solution/cypress/screens/fields_browser.ts @@ -17,6 +17,11 @@ export const FIELDS_BROWSER_FIELDS_COUNT = `${FIELDS_BROWSER_CONTAINER} [data-te export const FIELDS_BROWSER_FILTER_INPUT = `${FIELDS_BROWSER_CONTAINER} [data-test-subj="field-search"]`; +export const FIELDS_BROWSER_VIEW_BUTTON = `${FIELDS_BROWSER_CONTAINER} [data-test-subj="viewSelectorButton"]`; +export const FIELDS_BROWSER_VIEW_MENU = '[data-test-subj="viewSelectorMenu"]'; +export const FIELDS_BROWSER_VIEW_ALL = `${FIELDS_BROWSER_VIEW_MENU} [data-test-subj="viewSelectorOption-all"]`; +export const FIELDS_BROWSER_VIEW_SELECTED = `${FIELDS_BROWSER_VIEW_MENU} [data-test-subj="viewSelectorOption-selected"]`; + export const FIELDS_BROWSER_HOST_GEO_CITY_NAME_CHECKBOX = `${FIELDS_BROWSER_CONTAINER} [data-test-subj="field-host.geo.city_name-checkbox"]`; export const FIELDS_BROWSER_HOST_GEO_CITY_NAME_HEADER = diff --git a/x-pack/plugins/security_solution/cypress/tasks/fields_browser.ts b/x-pack/plugins/security_solution/cypress/tasks/fields_browser.ts index 04b59305b591a..6abc4b11aa59e 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/fields_browser.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/fields_browser.ts @@ -16,6 +16,9 @@ import { FIELDS_BROWSER_CATEGORIES_FILTER_BUTTON, FIELDS_BROWSER_CATEGORY_FILTER_OPTION, FIELDS_BROWSER_CATEGORIES_FILTER_SEARCH, + FIELDS_BROWSER_VIEW_ALL, + FIELDS_BROWSER_VIEW_BUTTON, + FIELDS_BROWSER_VIEW_SELECTED, } from '../screens/fields_browser'; export const addsFields = (fields: string[]) => { @@ -74,3 +77,12 @@ export const removesMessageField = () => { export const resetFields = () => { cy.get(FIELDS_BROWSER_RESET_FIELDS).click({ force: true }); }; + +export const activateViewSelected = () => { + cy.get(FIELDS_BROWSER_VIEW_BUTTON).click({ force: true }); + cy.get(FIELDS_BROWSER_VIEW_SELECTED).click({ force: true }); +}; +export const activateViewAll = () => { + cy.get(FIELDS_BROWSER_VIEW_BUTTON).click({ force: true }); + cy.get(FIELDS_BROWSER_VIEW_ALL).click({ force: true }); +}; diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/default_headers.ts b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/default_headers.ts index 9a32c514e7064..a5fb5f4bacd43 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/default_headers.ts +++ b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/default_headers.ts @@ -53,6 +53,3 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, ]; - -/** The default category of fields shown in the Timeline */ -export const DEFAULT_CATEGORY_NAME = 'default ECS'; diff --git a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_browser.test.tsx b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_browser.test.tsx index ed665155ddcf5..662608155d290 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_browser.test.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_browser.test.tsx @@ -12,7 +12,7 @@ import { TestProviders, mockBrowserFields, defaultHeaders } from '../../../../mo import { mockGlobalState } from '../../../../mock/global_state'; import { tGridActions } from '../../../../store/t_grid'; -import { FieldsBrowser } from './field_browser'; +import { FieldsBrowser, FieldsBrowserComponentProps } from './field_browser'; import { createStore, State } from '../../../../types'; import { createSecuritySolutionStorageMock } from '../../../../mock/mock_local_storage'; @@ -27,9 +27,8 @@ jest.mock('react-redux', () => { }); const timelineId = 'test'; const onHide = jest.fn(); -const testProps = { +const testProps: FieldsBrowserComponentProps = { columnHeaders: [], - browserFields: mockBrowserFields, filteredBrowserFields: mockBrowserFields, searchInput: '', appliedFilterInput: '', @@ -40,6 +39,8 @@ const testProps = { restoreFocusTo: React.createRef(), selectedCategoryIds: [], timelineId, + filterSelectedEnabled: false, + onFilterSelectedChange: jest.fn(), }; const { storage } = createSecuritySolutionStorageMock(); diff --git a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_browser.tsx b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_browser.tsx index c67d7cbe633b7..091f4cd79c52f 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_browser.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_browser.tsx @@ -33,7 +33,10 @@ import { CategoriesSelector } from './categories_selector'; import { FieldTable } from './field_table'; import { CategoriesBadges } from './categories_badges'; -type Props = Pick & { +export type FieldsBrowserComponentProps = Pick< + FieldBrowserProps, + 'timelineId' | 'width' | 'options' +> & { /** * The current timeline column headers */ @@ -44,6 +47,9 @@ type Props = Pick void; /** * When true, a busy spinner will be shown to indicate the field browser * is searching for fields that match the specified `searchInput` @@ -83,17 +89,19 @@ type Props = Pick = ({ +const FieldsBrowserComponent: React.FC = ({ + appliedFilterInput, columnHeaders, filteredBrowserFields, + filterSelectedEnabled, isSearching, + onFilterSelectedChange, setSelectedCategoryIds, onSearchInputChange, onHide, options, restoreFocusTo, searchInput, - appliedFilterInput, selectedCategoryIds, timelineId, width = FIELD_BROWSER_WIDTH, @@ -182,8 +190,10 @@ const FieldsBrowserComponent: React.FC = ({ timelineId={timelineId} columnHeaders={columnHeaders} filteredBrowserFields={filteredBrowserFields} + filterSelectedEnabled={filterSelectedEnabled} searchInput={appliedFilterInput} selectedCategoryIds={selectedCategoryIds} + onFilterSelectedChange={onFilterSelectedChange} getFieldTableColumns={getFieldTableColumns} onHide={onHide} /> diff --git a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_table.test.tsx b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_table.test.tsx index e6721c50f6e1c..151ed99c3621c 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_table.test.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_table.test.tsx @@ -46,16 +46,14 @@ const defaultProps: FieldTableProps = { filteredBrowserFields: {}, searchInput: '', timelineId, + filterSelectedEnabled: false, + onFilterSelectedChange: jest.fn(), onHide: jest.fn(), }; describe('FieldTable', () => { const timestampField = mockBrowserFields.base.fields![timestampFieldId]; const defaultPageSize = 10; - const totalFields = Object.values(mockBrowserFields).reduce( - (total, { fields }) => total + Object.keys(fields ?? {}).length, - 0 - ); beforeEach(() => { mockDispatch.mockClear(); @@ -69,7 +67,6 @@ describe('FieldTable', () => { ); expect(result.getByText('No items found')).toBeInTheDocument(); - expect(result.getByTestId('fields-count').textContent).toContain('0'); }); it('should render field table with fields of all categories', () => { @@ -80,7 +77,6 @@ describe('FieldTable', () => { ); expect(result.container.getElementsByClassName('euiTableRow').length).toBe(defaultPageSize); - expect(result.getByTestId('fields-count').textContent).toContain(totalFields); }); it('should render field table with fields of categories selected', () => { @@ -103,7 +99,6 @@ describe('FieldTable', () => { ); expect(result.container.getElementsByClassName('euiTableRow').length).toBe(fieldCount); - expect(result.getByTestId('fields-count').textContent).toContain(fieldCount); }); it('should render field table with custom columns', () => { @@ -125,7 +120,6 @@ describe('FieldTable', () => { ); - expect(result.getByTestId('fields-count').textContent).toContain(totalFields); expect(result.getAllByText('Custom column').length).toBeGreaterThan(0); expect(result.getAllByTestId('customColumn').length).toEqual(defaultPageSize); }); diff --git a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_table.tsx b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_table.tsx index f578d4e1b9dca..684b09d0395ab 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_table.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_table.tsx @@ -7,14 +7,14 @@ import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; -import { EuiInMemoryTable, EuiText } from '@elastic/eui'; +import { EuiInMemoryTable } from '@elastic/eui'; import { useDispatch } from 'react-redux'; import { BrowserFields, ColumnHeaderOptions } from '../../../../../common'; -import * as i18n from './translations'; import { getColumnHeader, getFieldColumns, getFieldItems, isActionsColumn } from './field_items'; import { CATEGORY_TABLE_CLASS_NAME, TABLE_HEIGHT } from './helpers'; import { tGridActions } from '../../../../store/t_grid'; import type { GetFieldTableColumns } from '../../../../../common/types/fields_browser'; +import { FieldTableHeader } from './field_table_header'; export interface FieldTableProps { timelineId: string; @@ -25,6 +25,9 @@ export interface FieldTableProps { * the filter input (as a substring). */ filteredBrowserFields: BrowserFields; + /** when true, show only the the selected field */ + filterSelectedEnabled: boolean; + onFilterSelectedChange: (enabled: boolean) => void; /** * Optional function to customize field table columns */ @@ -58,9 +61,11 @@ Count.displayName = 'Count'; const FieldTableComponent: React.FC = ({ columnHeaders, filteredBrowserFields, + filterSelectedEnabled, getFieldTableColumns, searchInput, selectedCategoryIds, + onFilterSelectedChange, timelineId, onHide, }) => { @@ -106,13 +111,13 @@ const FieldTableComponent: React.FC = ({ return ( <> - - {i18n.FIELDS_SHOWING} - {fieldItems.length} - {i18n.FIELDS_COUNT(fieldItems.length)} - + - + = ({ pagination={true} sorting={true} hasActions={hasActions} + compressed /> diff --git a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_table_header.test.tsx b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_table_header.test.tsx new file mode 100644 index 0000000000000..e7c8f5b7fe7a4 --- /dev/null +++ b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_table_header.test.tsx @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { TestProviders } from '../../../../mock'; +import { FieldTableHeader, FieldTableHeaderProps } from './field_table_header'; + +const mockOnFilterSelectedChange = jest.fn(); +const defaultProps: FieldTableHeaderProps = { + fieldCount: 0, + filterSelectedEnabled: false, + onFilterSelectedChange: mockOnFilterSelectedChange, +}; + +describe('FieldTableHeader', () => { + describe('FieldCount', () => { + it('should render empty field table', () => { + const result = render( + + + + ); + + expect(result.getByTestId('fields-showing').textContent).toBe('Showing 0 fields'); + }); + + it('should render field table with one singular field count value', () => { + const result = render( + + + + ); + + expect(result.getByTestId('fields-showing').textContent).toBe('Showing 1 field'); + }); + it('should render field table with multiple fields count value', () => { + const result = render( + + + + ); + + expect(result.getByTestId('fields-showing').textContent).toBe('Showing 4 fields'); + }); + }); + + describe('View selected', () => { + beforeEach(() => { + mockOnFilterSelectedChange.mockClear(); + }); + + it('should render "view all" option when filterSelected is not enabled', () => { + const result = render( + + + + ); + + expect(result.getByTestId('viewSelectorButton').textContent).toBe('View: all'); + }); + + it('should render "view selected" option when filterSelected is not enabled', () => { + const result = render( + + + + ); + + expect(result.getByTestId('viewSelectorButton').textContent).toBe('View: selected'); + }); + + it('should open the view selector with button click', async () => { + const result = render( + + + + ); + + expect(result.queryByTestId('viewSelectorMenu')).not.toBeInTheDocument(); + expect(result.queryByTestId('viewSelectorOption-all')).not.toBeInTheDocument(); + expect(result.queryByTestId('viewSelectorOption-selected')).not.toBeInTheDocument(); + + result.getByTestId('viewSelectorButton').click(); + + expect(result.getByTestId('viewSelectorMenu')).toBeInTheDocument(); + expect(result.getByTestId('viewSelectorOption-all')).toBeInTheDocument(); + expect(result.getByTestId('viewSelectorOption-selected')).toBeInTheDocument(); + }); + + it('should callback when "view all" option is clicked', () => { + const result = render( + + + + ); + + result.getByTestId('viewSelectorButton').click(); + result.getByTestId('viewSelectorOption-all').click(); + expect(mockOnFilterSelectedChange).toHaveBeenCalledWith(false); + }); + + it('should callback when "view selected" option is clicked', () => { + const result = render( + + + + ); + + result.getByTestId('viewSelectorButton').click(); + result.getByTestId('viewSelectorOption-selected').click(); + expect(mockOnFilterSelectedChange).toHaveBeenCalledWith(true); + }); + }); +}); diff --git a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_table_header.tsx b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_table_header.tsx new file mode 100644 index 0000000000000..ed7cc1e55b9c0 --- /dev/null +++ b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_table_header.tsx @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useState } from 'react'; +import styled from 'styled-components'; +import { + EuiText, + EuiPopover, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiHorizontalRule, +} from '@elastic/eui'; +import * as i18n from './translations'; + +export interface FieldTableHeaderProps { + fieldCount: number; + filterSelectedEnabled: boolean; + onFilterSelectedChange: (enabled: boolean) => void; +} + +const Count = styled.span` + font-weight: bold; +`; +Count.displayName = 'Count'; + +const FieldTableHeaderComponent: React.FC = ({ + fieldCount, + filterSelectedEnabled, + onFilterSelectedChange, +}) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const togglePopover = useCallback(() => { + setIsPopoverOpen((open) => !open); + }, []); + + const closePopover = useCallback(() => { + setIsPopoverOpen(false); + }, []); + + return ( + + + + {i18n.FIELDS_SHOWING} + {fieldCount} + {i18n.FIELDS_COUNT(fieldCount)} + + + + + {`${i18n.VIEW_LABEL}: ${ + filterSelectedEnabled ? i18n.VIEW_VALUE_SELECTED : i18n.VIEW_VALUE_ALL + }`} + + } + > + { + onFilterSelectedChange(false); + closePopover(); + }} + > + {`${i18n.VIEW_LABEL} ${i18n.VIEW_VALUE_ALL}`} + , + , + { + onFilterSelectedChange(true); + closePopover(); + }} + > + {`${i18n.VIEW_LABEL} ${i18n.VIEW_VALUE_SELECTED}`} + , + ]} + /> + + + + ); +}; + +export const FieldTableHeader = React.memo(FieldTableHeaderComponent); diff --git a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/helpers.test.tsx b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/helpers.test.tsx index ad90956013e41..8f4377ce020fd 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/helpers.test.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/helpers.test.tsx @@ -9,11 +9,12 @@ import { mockBrowserFields } from '../../../../mock'; import { categoryHasFields, - createVirtualCategory, getFieldCount, filterBrowserFieldsByFieldName, + filterSelectedBrowserFields, } from './helpers'; import { BrowserFields } from '../../../../../common/search_strategy'; +import { ColumnHeaderOptions } from '../../../../../common'; describe('helpers', () => { describe('categoryHasFields', () => { @@ -255,144 +256,83 @@ describe('helpers', () => { }); }); - describe('createVirtualCategory', () => { - test('it combines the specified fields into a virtual category when the input ONLY contains field names that contain dots (e.g. agent.hostname)', () => { - const expectedMatchingFields = { - fields: { - 'agent.hostname': { - aggregatable: true, - category: 'agent', - description: null, - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'agent.hostname', - searchable: true, - type: 'string', - }, - 'client.domain': { - aggregatable: true, - category: 'client', - description: 'Client domain.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.domain', - searchable: true, - type: 'string', - }, - 'client.geo.country_iso_code': { - aggregatable: true, - category: 'client', - description: 'Country ISO code.', - example: 'CA', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.geo.country_iso_code', - searchable: true, - type: 'string', - }, - }, - }; - - const fieldIds = ['agent.hostname', 'client.domain', 'client.geo.country_iso_code']; + describe('filterSelectedBrowserFields', () => { + const columnHeaders = [ + { id: 'agent.ephemeral_id' }, + { id: 'agent.id' }, + { id: 'container.id' }, + ] as ColumnHeaderOptions[]; - expect( - createVirtualCategory({ - browserFields: mockBrowserFields, - fieldIds, - }) - ).toEqual(expectedMatchingFields); + test('it returns an empty collection when browserFields is empty', () => { + expect(filterSelectedBrowserFields({ browserFields: {}, columnHeaders: [] })).toEqual({}); }); - test('it combines the specified fields into a virtual category when the input includes field names from the base category that do NOT contain dots (e.g. @timestamp)', () => { - const expectedMatchingFields = { - fields: { - 'agent.hostname': { - aggregatable: true, - category: 'agent', - description: null, - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'agent.hostname', - searchable: true, - type: 'string', - }, - '@timestamp': { - aggregatable: true, - category: 'base', - description: - 'Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.', - example: '2016-05-23T08:05:34.853Z', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: '@timestamp', - searchable: true, - type: 'date', - }, - 'client.domain': { - aggregatable: true, - category: 'client', - description: 'Client domain.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.domain', - searchable: true, - type: 'string', - }, - }, - }; - - const fieldIds = ['agent.hostname', '@timestamp', 'client.domain']; + test('it returns an empty collection when browserFields is empty and columnHeaders is non empty', () => { + expect(filterSelectedBrowserFields({ browserFields: {}, columnHeaders })).toEqual({}); + }); + test('it returns an empty collection when browserFields is NOT empty and columnHeaders is empty', () => { expect( - createVirtualCategory({ + filterSelectedBrowserFields({ browserFields: mockBrowserFields, - fieldIds, + columnHeaders: [], }) - ).toEqual(expectedMatchingFields); + ).toEqual({}); }); - test('it combines the specified fields into a virtual category omitting the fields missing in the browser fields', () => { - const expectedMatchingFields = { - fields: { - '@timestamp': { - aggregatable: true, - category: 'base', - description: - 'Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.', - example: '2016-05-23T08:05:34.853Z', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: '@timestamp', - searchable: true, - type: 'date', + test('it returns (only) non-empty categories, where each category contains only the fields matching the substring', () => { + const filtered: BrowserFields = { + agent: { + fields: { + 'agent.ephemeral_id': { + aggregatable: true, + category: 'agent', + description: + 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', + example: '8a4f500f', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'agent.ephemeral_id', + searchable: true, + type: 'string', + }, + 'agent.id': { + aggregatable: true, + category: 'agent', + description: + 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.', + example: '8a4f500d', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'agent.id', + searchable: true, + type: 'string', + }, }, - 'client.domain': { - aggregatable: true, - category: 'client', - description: 'Client domain.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.domain', - searchable: true, - type: 'string', + }, + container: { + fields: { + 'container.id': { + aggregatable: true, + category: 'container', + description: 'Unique container id.', + example: null, + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'container.id', + searchable: true, + type: 'string', + }, }, }, }; - const fieldIds = ['agent.hostname', '@timestamp', 'client.domain']; - const { agent, ...mockBrowserFieldsWithoutAgent } = mockBrowserFields; - expect( - createVirtualCategory({ - browserFields: mockBrowserFieldsWithoutAgent, - fieldIds, + filterSelectedBrowserFields({ + browserFields: mockBrowserFields, + columnHeaders, }) - ).toEqual(expectedMatchingFields); + ).toEqual(filtered); }); }); }); diff --git a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/helpers.tsx b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/helpers.tsx index 21829bda265e1..c0e1076073026 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/helpers.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/helpers.tsx @@ -6,13 +6,13 @@ */ import { EuiBadge, EuiLoadingSpinner } from '@elastic/eui'; -import { filter, get, pickBy } from 'lodash/fp'; +import { pickBy } from 'lodash/fp'; import styled from 'styled-components'; import { TimelineId } from '../../../../../public/types'; import type { BrowserField, BrowserFields } from '../../../../../common/search_strategy'; import { defaultHeaders } from '../../../../store/t_grid/defaults'; -import { DEFAULT_CATEGORY_NAME } from '../../body/column_headers/default_headers'; +import { ColumnHeaderOptions } from '../../../../../common'; export const LoadingSpinner = styled(EuiLoadingSpinner)` cursor: pointer; @@ -45,6 +45,9 @@ export const filterBrowserFieldsByFieldName = ({ substring: string; }): BrowserFields => { const trimmedSubstring = substring.trim(); + if (trimmedSubstring === '') { + return browserFields; + } // filter each category such that it only contains fields with field names // that contain the specified substring: @@ -53,11 +56,10 @@ export const filterBrowserFieldsByFieldName = ({ ...filteredCategories, [categoryId]: { ...browserFields[categoryId], - fields: filter( - (f) => f.name != null && f.name.includes(trimmedSubstring), + fields: pickBy( + ({ name }) => name != null && name.includes(trimmedSubstring), browserFields[categoryId].fields - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - ).reduce((filtered, field) => ({ ...filtered, [field.name!]: field }), {}), + ), }, }), {} @@ -73,46 +75,40 @@ export const filterBrowserFieldsByFieldName = ({ }; /** - * Returns a "virtual" category (e.g. default ECS) from the specified fieldIds + * Filters the selected `BrowserFields` to return a new collection where every + * category contains at least one field that is present in the `columnHeaders`. */ -export const createVirtualCategory = ({ +export const filterSelectedBrowserFields = ({ browserFields, - fieldIds, + columnHeaders, }: { browserFields: BrowserFields; - fieldIds: string[]; -}): Partial => ({ - fields: fieldIds.reduce>((fields, fieldId) => { - const splitId = fieldId.split('.'); // source.geo.city_name -> [source, geo, city_name] - const browserField = get( - [splitId.length > 1 ? splitId[0] : 'base', 'fields', fieldId], - browserFields - ); - - return { - ...fields, - ...(browserField - ? { - [fieldId]: { - ...browserField, - name: fieldId, - }, - } - : {}), - }; - }, {}), -}); - -/** Merges the specified browser fields with the default category (i.e. `default ECS`) */ -export const mergeBrowserFieldsWithDefaultCategory = ( - browserFields: BrowserFields -): BrowserFields => ({ - ...browserFields, - [DEFAULT_CATEGORY_NAME]: createVirtualCategory({ - browserFields, - fieldIds: defaultHeaders.map((header) => header.id), - }), -}); + columnHeaders: ColumnHeaderOptions[]; +}): BrowserFields => { + const selectedFieldIds = new Set(columnHeaders.map(({ id }) => id)); + + const filteredBrowserFields: BrowserFields = Object.keys(browserFields).reduce( + (filteredCategories, categoryId) => ({ + ...filteredCategories, + [categoryId]: { + ...browserFields[categoryId], + fields: pickBy( + ({ name }) => name != null && selectedFieldIds.has(name), + browserFields[categoryId].fields + ), + }, + }), + {} + ); + + // only pick non-empty categories from the filtered browser fields + const nonEmptyCategories: BrowserFields = pickBy( + (category) => categoryHasFields(category), + filteredBrowserFields + ); + + return nonEmptyCategories; +}; export const getAlertColumnHeader = (timelineId: string, fieldId: string) => timelineId === TimelineId.detectionsPage || timelineId === TimelineId.detectionsRulesDetailsPage diff --git a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/index.tsx index c5647c973b9d8..68bf6ca43ede9 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/index.tsx @@ -13,7 +13,7 @@ import styled from 'styled-components'; import type { BrowserFields } from '../../../../../common/search_strategy/index_fields'; import type { FieldBrowserProps } from '../../../../../common/types/fields_browser'; import { FieldsBrowser } from './field_browser'; -import { filterBrowserFieldsByFieldName, mergeBrowserFieldsWithDefaultCategory } from './helpers'; +import { filterBrowserFieldsByFieldName, filterSelectedBrowserFields } from './helpers'; import * as i18n from './translations'; const FIELDS_BUTTON_CLASS_NAME = 'fields-button'; @@ -44,6 +44,8 @@ export const StatefulFieldsBrowserComponent: React.FC = ({ const [appliedFilterInput, setAppliedFilterInput] = useState(''); /** all fields in this collection have field names that match the filterInput */ const [filteredBrowserFields, setFilteredBrowserFields] = useState(null); + /** when true, show only the the selected field */ + const [filterSelectedEnabled, setFilterSelectedEnabled] = useState(false); /** when true, show a spinner in the input to indicate the field browser is searching for matching field names */ const [isSearching, setIsSearching] = useState(false); /** this category will be displayed in the right-hand pane of the field browser */ @@ -67,14 +69,23 @@ export const StatefulFieldsBrowserComponent: React.FC = ({ }; }, [debouncedApplyFilterInput]); + const selectionFilteredBrowserFields = useMemo( + () => + filterSelectedEnabled + ? filterSelectedBrowserFields({ browserFields, columnHeaders }) + : browserFields, + [browserFields, columnHeaders, filterSelectedEnabled] + ); + useEffect(() => { - const newFilteredBrowserFields = filterBrowserFieldsByFieldName({ - browserFields: mergeBrowserFieldsWithDefaultCategory(browserFields), - substring: appliedFilterInput, - }); - setFilteredBrowserFields(newFilteredBrowserFields); + setFilteredBrowserFields( + filterBrowserFieldsByFieldName({ + browserFields: selectionFilteredBrowserFields, + substring: appliedFilterInput, + }) + ); setIsSearching(false); - }, [appliedFilterInput, browserFields]); + }, [appliedFilterInput, selectionFilteredBrowserFields]); /** Shows / hides the field browser */ const onShow = useCallback(() => { @@ -86,6 +97,7 @@ export const StatefulFieldsBrowserComponent: React.FC = ({ setFilterInput(''); setAppliedFilterInput(''); setFilteredBrowserFields(null); + setFilterSelectedEnabled(false); setIsSearching(false); setSelectedCategoryIds([]); setShow(false); @@ -101,10 +113,13 @@ export const StatefulFieldsBrowserComponent: React.FC = ({ [debouncedApplyFilterInput] ); - // only merge in the default category if the field browser is visible - const browserFieldsWithDefaultCategory = useMemo(() => { - return show ? mergeBrowserFieldsWithDefaultCategory(browserFields) : {}; - }, [show, browserFields]); + /** Invoked when the user changes the view all/selected value */ + const onFilterSelectedChange = useCallback( + (filterSelected: boolean) => { + setFilterSelectedEnabled(filterSelected); + }, + [setFilterSelectedEnabled] + ); return ( @@ -125,13 +140,14 @@ export const StatefulFieldsBrowserComponent: React.FC = ({ {show && ( values: { field }, defaultMessage: 'View {field} column', }); + +export const VIEW_LABEL = i18n.translate('xpack.timelines.fieldBrowser.viewLabel', { + defaultMessage: 'View', +}); + +export const VIEW_VALUE_SELECTED = i18n.translate('xpack.timelines.fieldBrowser.viewSelected', { + defaultMessage: 'selected', +}); + +export const VIEW_VALUE_ALL = i18n.translate('xpack.timelines.fieldBrowser.viewAll', { + defaultMessage: 'all', +}); From b0c3aab4e07f86dd2d3d9f13b2e6e1cad5116c6c Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Tue, 22 Mar 2022 11:18:19 -0500 Subject: [PATCH 09/13] [cft] Reuse elasticsearch snapshot on upgrade (#128264) Same-version snapshot upgrades have been causing deployments to become unhealthy. For now, lets reuse the original snapshot while we look for a workaround. --- .buildkite/scripts/steps/cloud/build_and_deploy.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/.buildkite/scripts/steps/cloud/build_and_deploy.sh b/.buildkite/scripts/steps/cloud/build_and_deploy.sh index 2bb9bc90e88da..91d207f8fcb31 100755 --- a/.buildkite/scripts/steps/cloud/build_and_deploy.sh +++ b/.buildkite/scripts/steps/cloud/build_and_deploy.sh @@ -75,7 +75,6 @@ if [ -z "${CLOUD_DEPLOYMENT_ID}" ]; then else ecctl deployment show "$CLOUD_DEPLOYMENT_ID" --generate-update-payload | jq ' .resources.kibana[0].plan.kibana.docker_image = "'$CLOUD_IMAGE'" | - .resources.elasticsearch[0].plan.elasticsearch.docker_image = "'$ELASTICSEARCH_CLOUD_IMAGE'" | (.. | select(.version? != null).version) = "'$VERSION'" ' > /tmp/deploy.json ecctl deployment update "$CLOUD_DEPLOYMENT_ID" --track --output json --file /tmp/deploy.json &> "$JSON_FILE" From f4f51e692e17e94d285a00cf82f8bceab0c939da Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Tue, 22 Mar 2022 17:30:21 +0100 Subject: [PATCH 10/13] User Page - KPIs and visualisations (#127617) * Create total users visualization * Organize visualizations --- .../common/experimental_features.ts | 1 + .../security_solution/index.ts | 8 ++ .../security_solution/users/index.ts | 5 + .../users/kpi/common/index.ts | 13 +++ .../users/kpi/total_users/index.ts | 19 ++++ .../integration/pagination/pagination.spec.ts | 8 +- .../cypress/screens/inspect.ts | 5 - .../security_solution/cypress/tasks/login.ts | 2 + .../navigation/tab_navigation/index.test.tsx | 8 +- .../common/components/stat_items/index.tsx | 9 +- .../users/kpi_total_users_area.ts | 84 +++++++++++++++ .../users/kpi_total_users_metric.ts | 55 ++++++++++ .../common/lib/kibana/kibana_react.mock.ts | 7 ++ .../kpi_hosts/authentications/index.tsx | 4 +- .../components/kpi_hosts/common/index.tsx | 22 ++-- .../components/kpi_hosts/hosts/index.tsx | 4 +- .../hosts/components/kpi_hosts/index.tsx | 81 +++++++------- .../kpi_hosts/risky_hosts/index.test.tsx | 2 +- .../kpi_hosts/risky_hosts/index.tsx | 4 +- .../components/kpi_hosts/unique_ips/index.tsx | 4 +- .../public/hosts/pages/details/index.tsx | 10 +- .../hosts/pages/details/nav_tabs.test.tsx | 20 +++- .../public/hosts/pages/details/nav_tabs.tsx | 14 ++- .../public/hosts/pages/hosts.tsx | 7 +- .../public/hosts/pages/index.tsx | 82 +++++++------- .../public/hosts/pages/nav_tabs.test.tsx | 49 ++++++++- .../public/hosts/pages/nav_tabs.tsx | 17 ++- .../components/kpi_network/common/index.tsx | 89 ---------------- .../components/kpi_network/dns/index.tsx | 5 +- .../kpi_network/network_events/index.tsx | 5 +- .../kpi_network/tls_handshakes/index.tsx | 4 +- .../kpi_network/unique_flows/index.tsx | 4 +- .../kpi_network/unique_private_ips/index.tsx | 4 +- .../users/components/kpi_users/index.tsx | 9 +- .../kpi_users/total_users/index.tsx | 100 ++++++++++++++++++ .../kpi_users/total_users/translations.ts | 19 ++++ .../public/users/pages/nav_tabs.test.tsx | 32 ++++++ .../public/users/pages/nav_tabs.tsx | 56 ++++++---- .../public/users/pages/navigation/types.ts | 5 +- .../public/users/pages/users.tsx | 10 +- .../security_solution/factory/users/index.ts | 2 + .../factory/users/kpi/total_users/index.ts | 50 +++++++++ .../query.build_total_users_kpi.dsl.ts | 65 ++++++++++++ .../test/security_solution_cypress/config.ts | 1 + 44 files changed, 748 insertions(+), 256 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/common/index.ts create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/total_users/index.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.ts delete mode 100644 x-pack/plugins/security_solution/public/network/components/kpi_network/common/index.tsx create mode 100644 x-pack/plugins/security_solution/public/users/components/kpi_users/total_users/index.tsx create mode 100644 x-pack/plugins/security_solution/public/users/components/kpi_users/total_users/translations.ts create mode 100644 x-pack/plugins/security_solution/public/users/pages/nav_tabs.test.tsx create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/total_users/index.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/total_users/query.build_total_users_kpi.dsl.ts diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 733d90eee2e65..3a932238f3a34 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -21,6 +21,7 @@ export const allowedExperimentalValues = Object.freeze({ detectionResponseEnabled: false, disableIsolationUIPendingStatuses: false, riskyHostsEnabled: false, + riskyUsersEnabled: false, securityRulesCancelEnabled: false, pendingActionResponsesWithAck: true, policyListEnabled: false, diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts index eb659b37a6888..a7176b3d30930 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts @@ -83,6 +83,10 @@ import { } from './risk_score'; import { UsersQueries } from './users'; import { UserDetailsRequestOptions, UserDetailsStrategyResponse } from './users/details'; +import { + TotalUsersKpiRequestOptions, + TotalUsersKpiStrategyResponse, +} from './users/kpi/total_users'; export * from './cti'; export * from './hosts'; @@ -141,6 +145,8 @@ export type StrategyResponseType = T extends HostsQ ? HostsKpiUniqueIpsStrategyResponse : T extends UsersQueries.details ? UserDetailsStrategyResponse + : T extends UsersQueries.kpiTotalUsers + ? TotalUsersKpiStrategyResponse : T extends NetworkQueries.details ? NetworkDetailsStrategyResponse : T extends NetworkQueries.dns @@ -199,6 +205,8 @@ export type StrategyRequestType = T extends HostsQu ? HostsKpiUniqueIpsRequestOptions : T extends UsersQueries.details ? UserDetailsRequestOptions + : T extends UsersQueries.kpiTotalUsers + ? TotalUsersKpiRequestOptions : T extends NetworkQueries.details ? NetworkDetailsRequestOptions : T extends NetworkQueries.dns diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts index fd5c90031b9a5..d8f6172dd80c2 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts @@ -5,6 +5,11 @@ * 2.0. */ +import { TotalUsersKpiStrategyResponse } from './kpi/total_users'; + export enum UsersQueries { details = 'userDetails', + kpiTotalUsers = 'usersKpiTotalUsers', } + +export type UserskKpiStrategyResponse = Omit; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/common/index.ts new file mode 100644 index 0000000000000..27f83e2ec623a --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/common/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Maybe } from '../../../..'; + +export interface KpiHistogramData { + x?: Maybe; + y?: Maybe; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/total_users/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/total_users/index.ts new file mode 100644 index 0000000000000..9069393102a5b --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/total_users/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { Inspect, Maybe } from '../../../../common'; +import { RequestBasicOptions } from '../../..'; +import { KpiHistogramData } from '../common'; + +export type TotalUsersKpiRequestOptions = RequestBasicOptions; + +export interface TotalUsersKpiStrategyResponse extends IEsSearchResponse { + users: Maybe; + usersHistogram: Maybe; + inspect?: Maybe; +} diff --git a/x-pack/plugins/security_solution/cypress/integration/pagination/pagination.spec.ts b/x-pack/plugins/security_solution/cypress/integration/pagination/pagination.spec.ts index 645b2916d554d..23115e2598c69 100644 --- a/x-pack/plugins/security_solution/cypress/integration/pagination/pagination.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/pagination/pagination.spec.ts @@ -12,8 +12,8 @@ import { 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'; +import { waitsForEventsToBeLoaded } from '../../tasks/hosts/events'; +import { openEvents, openUncommonProcesses } from '../../tasks/hosts/main'; import { waitForUncommonProcessesToBeLoaded } from '../../tasks/hosts/uncommon_processes'; import { loginAndWaitForPage } from '../../tasks/login'; import { goToFirstPage, goToThirdPage } from '../../tasks/pagination'; @@ -73,8 +73,8 @@ describe('Pagination', () => { .first() .invoke('text') .then((expectedThirdPageResult) => { - openAuthentications(); - waitForAuthenticationsToBeLoaded(); + openEvents(); + waitsForEventsToBeLoaded(); cy.get(FIRST_PAGE_SELECTOR).should('have.class', 'euiPaginationButton-isActive'); openUncommonProcesses(); waitForUncommonProcessesToBeLoaded(); diff --git a/x-pack/plugins/security_solution/cypress/screens/inspect.ts b/x-pack/plugins/security_solution/cypress/screens/inspect.ts index f0fbf7e6a3089..3ee675ae7ca8c 100644 --- a/x-pack/plugins/security_solution/cypress/screens/inspect.ts +++ b/x-pack/plugins/security_solution/cypress/screens/inspect.ts @@ -21,11 +21,6 @@ export const INSPECT_HOSTS_BUTTONS_IN_SECURITY: InspectButtonMetadata[] = [ title: 'All Hosts Table', tabId: '[data-test-subj="navigation-allHosts"]', }, - { - id: '[data-test-subj="table-authentications-loading-false"]', - title: 'Authentications Table', - tabId: '[data-test-subj="navigation-authentications"]', - }, { id: '[data-test-subj="table-uncommonProcesses-loading-false"]', title: 'Uncommon processes Table', diff --git a/x-pack/plugins/security_solution/cypress/tasks/login.ts b/x-pack/plugins/security_solution/cypress/tasks/login.ts index 736418325d3d2..de68a3f41d57d 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/login.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/login.ts @@ -359,6 +359,8 @@ export const loginAndWaitForTimeline = (timelineId: string, role?: ROLES) => { export const loginAndWaitForHostDetailsPage = (hostName = 'suricata-iowa') => { loginAndWaitForPage(hostDetailsUrl(hostName)); + + cy.get('[data-test-subj="hostDetailsPage"]', { timeout: 12000 }).should('exist'); cy.get('[data-test-subj="loading-spinner"]', { timeout: 12000 }).should('not.exist'); }; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx index d2a17e87cffcf..d90709f69ee03 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx @@ -52,13 +52,19 @@ const hostName = 'siem-window'; describe('Table Navigation', () => { const mockHasMlUserPermissions = true; const mockRiskyHostEnabled = true; + const mockProps: TabNavigationProps & RouteSpyState = { pageName: 'hosts', pathName: '/hosts', detailName: undefined, search: '', tabName: HostsTableType.authentications, - navTabs: navTabsHostDetails(hostName, mockHasMlUserPermissions, mockRiskyHostEnabled), + navTabs: navTabsHostDetails({ + hostName, + hasMlUserPermissions: mockHasMlUserPermissions, + isRiskyHostsEnabled: mockRiskyHostEnabled, + }), + [CONSTANTS.timerange]: { global: { [CONSTANTS.timerange]: { diff --git a/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx b/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx index 93a13dd5dee8b..424920d34e2e8 100644 --- a/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx @@ -34,6 +34,7 @@ import { InspectButton } from '../inspect'; import { VisualizationActions, HISTOGRAM_ACTIONS_BUTTON_CLASS } from '../visualization_actions'; import { HoverVisibilityContainer } from '../hover_visibility_container'; import { LensAttributes } from '../visualization_actions/types'; +import { UserskKpiStrategyResponse } from '../../../../common/search_strategy/security_solution/users'; const FlexItem = styled(EuiFlexItem)` min-width: 0; @@ -125,12 +126,12 @@ export const barchartConfigs = (config?: { onElementClick?: ElementClickListener export const addValueToFields = ( fields: StatItem[], - data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UserskKpiStrategyResponse ): StatItem[] => fields.map((field) => ({ ...field, value: get(field.key, data) })); export const addValueToAreaChart = ( fields: StatItem[], - data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UserskKpiStrategyResponse ): ChartSeriesData[] => fields .filter((field) => get(`${field.key}Histogram`, data) != null) @@ -142,7 +143,7 @@ export const addValueToAreaChart = ( export const addValueToBarChart = ( fields: StatItem[], - data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UserskKpiStrategyResponse ): ChartSeriesData[] => { if (fields.length === 0) return []; return fields.reduce((acc: ChartSeriesData[], field: StatItem, idx: number) => { @@ -171,7 +172,7 @@ export const addValueToBarChart = ( export const useKpiMatrixStatus = ( mappings: Readonly, - data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse, + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UserskKpiStrategyResponse, id: string, from: string, to: string, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.ts new file mode 100644 index 0000000000000..482086289e14d --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LensAttributes } from '../../types'; + +export const kpiTotalUsersAreaLensAttributes: LensAttributes = { + description: '', + state: { + datasourceStates: { + indexpattern: { + layers: { + '416b6fad-1923-4f6a-a2df-b223bb287e30': { + columnOrder: [ + '5eea817b-67b7-4268-8ecb-7688d1094721', + 'b00c65ea-32be-4163-bfc8-f795b1ef9d06', + ], + columns: { + '5eea817b-67b7-4268-8ecb-7688d1094721': { + dataType: 'date', + isBucketed: true, + label: '@timestamp', + operationType: 'date_histogram', + params: { interval: 'auto' }, + scale: 'interval', + sourceField: '@timestamp', + }, + 'b00c65ea-32be-4163-bfc8-f795b1ef9d06': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: ' ', + operationType: 'unique_count', + scale: 'ratio', + sourceField: 'user.name', + }, + }, + incompleteColumns: {}, + }, + }, + }, + }, + filters: [], + query: { language: 'kuery', query: '' }, + visualization: { + axisTitlesVisibilitySettings: { x: false, yLeft: false, yRight: false }, + fittingFunction: 'None', + gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + labelsOrientation: { x: 0, yLeft: 0, yRight: 0 }, + layers: [ + { + accessors: ['b00c65ea-32be-4163-bfc8-f795b1ef9d06'], + layerId: '416b6fad-1923-4f6a-a2df-b223bb287e30', + layerType: 'data', + seriesType: 'area', + xAccessor: '5eea817b-67b7-4268-8ecb-7688d1094721', + }, + ], + legend: { isVisible: true, position: 'right' }, + preferredSeriesType: 'area', + tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, + valueLabels: 'hide', + yLeftExtent: { mode: 'full' }, + yRightExtent: { mode: 'full' }, + }, + }, + title: '[User] Users - area', + visualizationType: 'lnsXY', + references: [ + { + id: '{dataViewId}', + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: '{dataViewId}', + name: 'indexpattern-datasource-layer-416b6fad-1923-4f6a-a2df-b223bb287e30', + type: 'index-pattern', + }, + ], +} as LensAttributes; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.ts new file mode 100644 index 0000000000000..7f1d2253eb3be --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LensAttributes } from '../../types'; + +export const kpiTotalUsersMetricLensAttributes: LensAttributes = { + description: '', + state: { + datasourceStates: { + indexpattern: { + layers: { + '416b6fad-1923-4f6a-a2df-b223bb287e30': { + columnOrder: ['3e51b035-872c-4b44-824b-fe069c222e91'], + columns: { + '3e51b035-872c-4b44-824b-fe069c222e91': { + dataType: 'number', + isBucketed: false, + label: 'Unique count of user.name', + operationType: 'unique_count', + scale: 'ratio', + sourceField: 'user.name', + }, + }, + incompleteColumns: {}, + }, + }, + }, + }, + filters: [], + query: { language: 'kuery', query: '' }, + visualization: { + accessor: '3e51b035-872c-4b44-824b-fe069c222e91', + layerId: '416b6fad-1923-4f6a-a2df-b223bb287e30', + layerType: 'data', + }, + }, + title: '[User] Users - metric', + visualizationType: 'lnsMetric', + references: [ + { + id: '{dataViewId}', + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: '{dataViewId}', + name: 'indexpattern-datasource-layer-416b6fad-1923-4f6a-a2df-b223bb287e30', + type: 'index-pattern', + }, + ], +} as LensAttributes; diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts index 366cd271fb57d..b683f4bd1375a 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts @@ -139,6 +139,13 @@ export const createStartServicesMock = ( next: jest.fn(), unsubscribe: jest.fn(), })), + pipe: jest.fn().mockImplementation(() => ({ + subscribe: jest.fn().mockImplementation(() => ({ + error: jest.fn(), + next: jest.fn(), + unsubscribe: jest.fn(), + })), + })), })), }, }, diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx index ce73b1cd07f61..1158c842e04cb 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx @@ -13,7 +13,7 @@ import { kpiUserAuthenticationsBarLensAttributes } from '../../../../common/comp import { kpiUserAuthenticationsMetricSuccessLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentications_metric_success'; import { kpiUserAuthenticationsMetricFailureLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentication_metric_failure'; import { useHostsKpiAuthentications } from '../../../containers/kpi_hosts/authentications'; -import { HostsKpiBaseComponentManage } from '../common'; +import { KpiBaseComponentManage } from '../common'; import { HostsKpiProps, HostsKpiChartColors } from '../types'; import * as i18n from './translations'; @@ -66,7 +66,7 @@ const HostsKpiAuthenticationsComponent: React.FC = ({ }); return ( - ; - data: HostsKpiStrategyResponse; + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse | UserskKpiStrategyResponse; loading?: boolean; id: string; from: string; @@ -40,7 +44,7 @@ interface HostsKpiBaseComponentProps { narrowDateRange: UpdateDateRange; } -export const HostsKpiBaseComponent = React.memo( +export const KpiBaseComponent = React.memo( ({ fieldsMapping, data, id, loading = false, from, to, narrowDateRange }) => { const { cases } = useKibana().services; const CasesContext = cases.ui.getCasesContext(); @@ -57,7 +61,7 @@ export const HostsKpiBaseComponent = React.memo( ); if (loading) { - return ; + return ; } return ( @@ -80,12 +84,12 @@ export const HostsKpiBaseComponent = React.memo( deepEqual(prevProps.data, nextProps.data) ); -HostsKpiBaseComponent.displayName = 'HostsKpiBaseComponent'; +KpiBaseComponent.displayName = 'KpiBaseComponent'; -export const HostsKpiBaseComponentManage = manageQuery(HostsKpiBaseComponent); +export const KpiBaseComponentManage = manageQuery(KpiBaseComponent); -export const HostsKpiBaseComponentLoader: React.FC = () => ( - +export const KpiBaseComponentLoader: React.FC = () => ( + diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx index 4e73a429fbc1d..79118b66a3f71 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx @@ -11,7 +11,7 @@ import { StatItems } from '../../../../common/components/stat_items'; import { kpiHostAreaLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_host_area'; import { kpiHostMetricLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_host_metric'; import { useHostsKpiHosts } from '../../../containers/kpi_hosts/hosts'; -import { HostsKpiBaseComponentManage } from '../common'; +import { KpiBaseComponentManage } from '../common'; import { HostsKpiProps, HostsKpiChartColors } from '../types'; import * as i18n from './translations'; @@ -51,7 +51,7 @@ const HostsKpiHostsComponent: React.FC = ({ }); return ( - ( ({ filterQuery, from, indexNames, to, setQuery, skip, narrowDateRange }) => { const [_, { isModuleEnabled }] = useHostRiskScore({}); + const usersEnabled = useIsExperimentalFeatureEnabled('usersEnabled'); return ( <> @@ -59,8 +61,21 @@ export const HostsKpiComponent = React.memo( skip={skip} /> + {!usersEnabled && ( + + + + )} - ( skip={skip} /> - - + + ); + } +); + +HostsKpiComponent.displayName = 'HostsKpiComponent'; + +export const HostsDetailsKpiComponent = React.memo( + ({ filterQuery, from, indexNames, to, setQuery, skip, narrowDateRange }) => { + const usersEnabled = useIsExperimentalFeatureEnabled('usersEnabled'); + return ( + + {!usersEnabled && ( + + ( skip={skip} /> - - + )} + + + + ); } ); -HostsKpiComponent.displayName = 'HostsKpiComponent'; - -export const HostsDetailsKpiComponent = React.memo( - ({ filterQuery, from, indexNames, to, setQuery, skip, narrowDateRange }) => ( - - - - - - - - - ) -); - HostsDetailsKpiComponent.displayName = 'HostsDetailsKpiComponent'; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/index.test.tsx index c4fa134bd88e2..b000b2f22dc95 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/index.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/index.test.tsx @@ -36,7 +36,7 @@ describe('RiskyHosts', () => { ); - expect(getByTestId('hostsKpiLoader')).toBeInTheDocument(); + expect(getByTestId('KpiLoader')).toBeInTheDocument(); }); test('it displays 0 risky hosts when initializing', () => { diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/index.tsx index d4897702f9407..f515490252d40 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/index.tsx @@ -22,7 +22,7 @@ import { BUTTON_CLASS as INPECT_BUTTON_CLASS, } from '../../../../common/components/inspect'; -import { HostsKpiBaseComponentLoader } from '../common'; +import { KpiBaseComponentLoader } from '../common'; import * as i18n from './translations'; import { useInspectQuery } from '../../../../common/hooks/use_inspect_query'; @@ -66,7 +66,7 @@ const RiskyHostsComponent: React.FC<{ useErrorToast(i18n.ERROR_TITLE, error); if (loading) { - return ; + return ; } const criticalRiskCount = data?.kpiRiskScore.Critical ?? 0; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/index.tsx index 2d95e3c98f4ae..ef7bdfa1dc031 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/index.tsx @@ -13,7 +13,7 @@ import { kpiUniqueIpsBarLensAttributes } from '../../../../common/components/vis import { kpiUniqueIpsDestinationMetricLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_destination_metric'; import { kpiUniqueIpsSourceMetricLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_source_metric'; import { useHostsKpiUniqueIps } from '../../../containers/kpi_hosts/unique_ips'; -import { HostsKpiBaseComponentManage } from '../common'; +import { KpiBaseComponentManage } from '../common'; import { HostsKpiProps, HostsKpiChartColors } from '../types'; import * as i18n from './translations'; @@ -66,7 +66,7 @@ const HostsKpiUniqueIpsComponent: React.FC = ({ }); return ( - = ({ detailName, hostDeta diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.test.tsx index 90f3c223c5501..8b951722439a6 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.test.tsx @@ -11,7 +11,11 @@ import { navTabsHostDetails } from './nav_tabs'; describe('navTabsHostDetails', () => { const mockHostName = 'mockHostName'; test('it should skip anomalies tab if without mlUserPermission', () => { - const tabs = navTabsHostDetails(mockHostName, false, false); + const tabs = navTabsHostDetails({ + hasMlUserPermissions: false, + isRiskyHostsEnabled: false, + hostName: mockHostName, + }); expect(tabs).toHaveProperty(HostsTableType.authentications); expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses); expect(tabs).not.toHaveProperty(HostsTableType.anomalies); @@ -20,7 +24,12 @@ describe('navTabsHostDetails', () => { }); test('it should display anomalies tab if with mlUserPermission', () => { - const tabs = navTabsHostDetails(mockHostName, true, false); + const tabs = navTabsHostDetails({ + hasMlUserPermissions: true, + isRiskyHostsEnabled: false, + hostName: mockHostName, + }); + expect(tabs).toHaveProperty(HostsTableType.authentications); expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses); expect(tabs).toHaveProperty(HostsTableType.anomalies); @@ -29,7 +38,12 @@ describe('navTabsHostDetails', () => { }); test('it should display risky hosts tab if when risky hosts is enabled', () => { - const tabs = navTabsHostDetails(mockHostName, false, true); + const tabs = navTabsHostDetails({ + hasMlUserPermissions: false, + isRiskyHostsEnabled: true, + hostName: mockHostName, + }); + expect(tabs).toHaveProperty(HostsTableType.authentications); expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses); expect(tabs).not.toHaveProperty(HostsTableType.anomalies); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx index c58fbde09aef1..33cafd8ef2114 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx @@ -14,11 +14,15 @@ import { HOSTS_PATH } from '../../../../common/constants'; const getTabsOnHostDetailsUrl = (hostName: string, tabName: HostsTableType) => `${HOSTS_PATH}/${hostName}/${tabName}`; -export const navTabsHostDetails = ( - hostName: string, - hasMlUserPermissions: boolean, - isRiskyHostsEnabled: boolean -): HostDetailsNavTab => { +export const navTabsHostDetails = ({ + hasMlUserPermissions, + isRiskyHostsEnabled, + hostName, +}: { + hostName: string; + hasMlUserPermissions: boolean; + isRiskyHostsEnabled: boolean; +}): HostDetailsNavTab => { const hiddenTabs = []; const hostDetailsNavTabs = { diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx index 5ca7aa1f1dd49..3b57a22d15a6a 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx @@ -151,6 +151,7 @@ const HostsComponent = () => { ); const riskyHostsFeatureEnabled = useIsExperimentalFeatureEnabled('riskyHostsEnabled'); + const usersEnabled = useIsExperimentalFeatureEnabled('usersEnabled'); useInvalidFilterQuery({ id: ID, filterQuery, kqlError, query, startDate: from, endDate: to }); @@ -214,7 +215,11 @@ const HostsComponent = () => { diff --git a/x-pack/plugins/security_solution/public/hosts/pages/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/index.tsx index 4eb8175aea4cb..453d6182984c1 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/index.tsx @@ -34,48 +34,46 @@ const getHostDetailsTabPath = () => `${HostsTableType.risk}|` + `${HostsTableType.alerts})`; -export const HostsContainer = React.memo(() => { - return ( - - - - - - - - } - /> - ( - - )} - /> +export const HostsContainer = React.memo(() => ( + + + + + + + + } + /> + ( + + )} + /> - ( - - )} - /> - - ); -}); + ( + + )} + /> + +)); HostsContainer.displayName = 'HostsContainer'; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.test.tsx index 50e301d4b4f57..b882dca3faaf1 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.test.tsx @@ -10,7 +10,11 @@ import { navTabsHosts } from './nav_tabs'; describe('navTabsHosts', () => { test('it should skip anomalies tab if without mlUserPermission', () => { - const tabs = navTabsHosts(false, false); + const tabs = navTabsHosts({ + hasMlUserPermissions: false, + isRiskyHostsEnabled: false, + isUsersEnabled: false, + }); expect(tabs).toHaveProperty(HostsTableType.hosts); expect(tabs).toHaveProperty(HostsTableType.authentications); expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses); @@ -19,15 +23,24 @@ describe('navTabsHosts', () => { }); test('it should display anomalies tab if with mlUserPermission', () => { - const tabs = navTabsHosts(true, false); + const tabs = navTabsHosts({ + hasMlUserPermissions: true, + isRiskyHostsEnabled: false, + isUsersEnabled: false, + }); expect(tabs).toHaveProperty(HostsTableType.hosts); expect(tabs).toHaveProperty(HostsTableType.authentications); expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses); expect(tabs).toHaveProperty(HostsTableType.anomalies); expect(tabs).toHaveProperty(HostsTableType.events); }); + test('it should skip risk tab if without hostRisk', () => { - const tabs = navTabsHosts(false, false); + const tabs = navTabsHosts({ + hasMlUserPermissions: false, + isRiskyHostsEnabled: false, + isUsersEnabled: false, + }); expect(tabs).toHaveProperty(HostsTableType.hosts); expect(tabs).toHaveProperty(HostsTableType.authentications); expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses); @@ -36,11 +49,39 @@ describe('navTabsHosts', () => { }); test('it should display risk tab if with hostRisk', () => { - const tabs = navTabsHosts(false, true); + const tabs = navTabsHosts({ + hasMlUserPermissions: false, + isRiskyHostsEnabled: true, + isUsersEnabled: false, + }); expect(tabs).toHaveProperty(HostsTableType.hosts); expect(tabs).toHaveProperty(HostsTableType.authentications); expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses); expect(tabs).toHaveProperty(HostsTableType.risk); expect(tabs).toHaveProperty(HostsTableType.events); }); + + test('it should skip authentications tab if isUsersEnabled is true', () => { + const tabs = navTabsHosts({ + hasMlUserPermissions: false, + isRiskyHostsEnabled: false, + isUsersEnabled: true, + }); + expect(tabs).toHaveProperty(HostsTableType.hosts); + expect(tabs).not.toHaveProperty(HostsTableType.authentications); + expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses); + expect(tabs).toHaveProperty(HostsTableType.events); + }); + + test('it should display authentications tab if isUsersEnabled is false', () => { + const tabs = navTabsHosts({ + hasMlUserPermissions: false, + isRiskyHostsEnabled: false, + isUsersEnabled: false, + }); + expect(tabs).toHaveProperty(HostsTableType.hosts); + expect(tabs).toHaveProperty(HostsTableType.authentications); + expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses); + expect(tabs).toHaveProperty(HostsTableType.events); + }); }); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx index 0d8a5e252bfbb..789273da073e9 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx @@ -13,10 +13,15 @@ import { HOSTS_PATH } from '../../../common/constants'; const getTabsOnHostsUrl = (tabName: HostsTableType) => `${HOSTS_PATH}/${tabName}`; -export const navTabsHosts = ( - hasMlUserPermissions: boolean, - isRiskyHostsEnabled: boolean -): HostsNavTab => { +export const navTabsHosts = ({ + hasMlUserPermissions, + isRiskyHostsEnabled, + isUsersEnabled, +}: { + hasMlUserPermissions: boolean; + isRiskyHostsEnabled: boolean; + isUsersEnabled: boolean; +}): HostsNavTab => { const hiddenTabs = []; const hostsNavTabs = { [HostsTableType.hosts]: { @@ -71,5 +76,9 @@ export const navTabsHosts = ( hiddenTabs.push(HostsTableType.risk); } + if (isUsersEnabled) { + hiddenTabs.push(HostsTableType.authentications); + } + return omit(hiddenTabs, hostsNavTabs); }; diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/common/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/common/index.tsx deleted file mode 100644 index 8fbc75aff4e19..0000000000000 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/common/index.tsx +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiFlexItem, EuiLoadingSpinner, EuiFlexGroup } from '@elastic/eui'; -import styled from 'styled-components'; -import deepEqual from 'fast-deep-equal'; - -import { manageQuery } from '../../../../common/components/page/manage_query'; -import { NetworkKpiStrategyResponse } from '../../../../../common/search_strategy'; -import { - StatItemsComponent, - StatItemsProps, - useKpiMatrixStatus, - StatItems, -} from '../../../../common/components/stat_items'; -import { UpdateDateRange } from '../../../../common/components/charts/common'; -import { useKibana, useGetUserCasesPermissions } from '../../../../common/lib/kibana'; -import { APP_ID } from '../../../../../common/constants'; - -const kpiWidgetHeight = 228; - -export const FlexGroup = styled(EuiFlexGroup)` - min-height: ${kpiWidgetHeight}px; -`; - -FlexGroup.displayName = 'FlexGroup'; - -export const NetworkKpiBaseComponent = React.memo<{ - fieldsMapping: Readonly; - data: NetworkKpiStrategyResponse; - loading?: boolean; - id: string; - from: string; - to: string; - narrowDateRange: UpdateDateRange; -}>( - ({ fieldsMapping, data, id, loading = false, from, to, narrowDateRange }) => { - const { cases } = useKibana().services; - const CasesContext = cases.ui.getCasesContext(); - const userPermissions = useGetUserCasesPermissions(); - const userCanCrud = userPermissions?.crud ?? false; - - const statItemsProps: StatItemsProps[] = useKpiMatrixStatus( - fieldsMapping, - data, - id, - from, - to, - narrowDateRange - ); - - if (loading) { - return ( - - - - - - ); - } - - return ( - - - {statItemsProps.map((mappedStatItemProps) => ( - - ))} - - - ); - }, - (prevProps, nextProps) => - prevProps.fieldsMapping === nextProps.fieldsMapping && - prevProps.loading === nextProps.loading && - prevProps.id === nextProps.id && - prevProps.from === nextProps.from && - prevProps.to === nextProps.to && - prevProps.narrowDateRange === nextProps.narrowDateRange && - deepEqual(prevProps.data, nextProps.data) -); - -NetworkKpiBaseComponent.displayName = 'NetworkKpiBaseComponent'; - -export const NetworkKpiBaseComponentManage = manageQuery(NetworkKpiBaseComponent); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx index 2c9db1cde6daf..6291e7fd4dc12 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx @@ -9,8 +9,9 @@ import React from 'react'; import { StatItems } from '../../../../common/components/stat_items'; import { kpiDnsQueriesLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/network/kpi_dns_queries'; +import { KpiBaseComponentManage } from '../../../../hosts/components/kpi_hosts/common'; import { useNetworkKpiDns } from '../../../containers/kpi_network/dns'; -import { NetworkKpiBaseComponentManage } from '../common'; + import { NetworkKpiProps } from '../types'; import * as i18n from './translations'; @@ -46,7 +47,7 @@ const NetworkKpiDnsComponent: React.FC = ({ }); return ( - = ({ }); return ( - = ({ }); return ( - = ({ }); return ( - = ({ }); return ( - ( ({ filterQuery, from, indexNames, to, setQuery, skip, narrowDateRange }) => { @@ -19,7 +19,7 @@ export const UsersKpiComponent = React.memo( <> - ( skip={skip} /> - - + = [ + { + key: 'users', + fields: [ + { + key: 'users', + value: null, + color: euiColorVis1, + icon: 'storage', + lensAttributes: kpiTotalUsersMetricLensAttributes, + }, + ], + enableAreaChart: true, + description: i18n.USERS, + areaChartLensAttributes: kpiTotalUsersAreaLensAttributes, + }, +]; + +export interface UsersKpiProps { + filterQuery?: string; + from: string; + to: string; + indexNames: string[]; + narrowDateRange: UpdateDateRange; + setQuery: GlobalTimeArgs['setQuery']; + skip: boolean; +} + +const QUERY_ID = 'TotalUsersKpiQuery'; + +const TotalUsersKpiComponent: React.FC = ({ + filterQuery, + from, + indexNames, + to, + narrowDateRange, + setQuery, + skip, +}) => { + const { loading, result, search, refetch, inspect } = + useSearchStrategy({ + factoryQueryType: UsersQueries.kpiTotalUsers, + initialResult: { users: 0, usersHistogram: [] }, + errorMessage: i18n.ERROR_USERS_KPI, + }); + + useEffect(() => { + if (!skip) { + search({ + filterQuery, + defaultIndex: indexNames, + timerange: { + interval: '12h', + from, + to, + }, + }); + } + }, [search, from, to, filterQuery, indexNames, skip]); + + return ( + + ); +}; + +export const TotalUsersKpi = React.memo(TotalUsersKpiComponent); diff --git a/x-pack/plugins/security_solution/public/users/components/kpi_users/total_users/translations.ts b/x-pack/plugins/security_solution/public/users/components/kpi_users/total_users/translations.ts new file mode 100644 index 0000000000000..3bbcf3f08c119 --- /dev/null +++ b/x-pack/plugins/security_solution/public/users/components/kpi_users/total_users/translations.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const USERS = i18n.translate('xpack.securitySolution.kpiUsers.totalUsers.title', { + defaultMessage: 'Users', +}); + +export const ERROR_USERS_KPI = i18n.translate( + 'xpack.securitySolution.kpiUsers.totalUsers.errorSearchDescription', + { + defaultMessage: `An error has occurred on total users kpi search`, + } +); diff --git a/x-pack/plugins/security_solution/public/users/pages/nav_tabs.test.tsx b/x-pack/plugins/security_solution/public/users/pages/nav_tabs.test.tsx new file mode 100644 index 0000000000000..492f85ec7ec02 --- /dev/null +++ b/x-pack/plugins/security_solution/public/users/pages/nav_tabs.test.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { UsersTableType } from '../store/model'; +import { navTabsUsers } from './nav_tabs'; + +describe('navTabsUsers', () => { + test('it should display all tabs', () => { + const tabs = navTabsUsers(true, true); + expect(tabs).toHaveProperty(UsersTableType.allUsers); + expect(tabs).toHaveProperty(UsersTableType.anomalies); + expect(tabs).toHaveProperty(UsersTableType.risk); + }); + + test('it should not display anomalies tab if user has no ml permission', () => { + const tabs = navTabsUsers(false, true); + expect(tabs).toHaveProperty(UsersTableType.allUsers); + expect(tabs).not.toHaveProperty(UsersTableType.anomalies); + expect(tabs).toHaveProperty(UsersTableType.risk); + }); + + test('it should not display risk tab if isRiskyUserEnabled disabled', () => { + const tabs = navTabsUsers(true, false); + expect(tabs).toHaveProperty(UsersTableType.allUsers); + expect(tabs).toHaveProperty(UsersTableType.anomalies); + expect(tabs).not.toHaveProperty(UsersTableType.risk); + }); +}); diff --git a/x-pack/plugins/security_solution/public/users/pages/nav_tabs.tsx b/x-pack/plugins/security_solution/public/users/pages/nav_tabs.tsx index f991316983f49..35124d1deddb1 100644 --- a/x-pack/plugins/security_solution/public/users/pages/nav_tabs.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/nav_tabs.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { omit } from 'lodash/fp'; import * as i18n from './translations'; import { UsersTableType } from '../store/model'; import { UsersNavTab } from './navigation/types'; @@ -12,23 +13,40 @@ import { USERS_PATH } from '../../../common/constants'; const getTabsOnUsersUrl = (tabName: UsersTableType) => `${USERS_PATH}/${tabName}`; -export const navTabsUsers: UsersNavTab = { - [UsersTableType.allUsers]: { - id: UsersTableType.allUsers, - name: i18n.NAVIGATION_ALL_USERS_TITLE, - href: getTabsOnUsersUrl(UsersTableType.allUsers), - disabled: false, - }, - [UsersTableType.anomalies]: { - id: UsersTableType.anomalies, - name: i18n.NAVIGATION_ANOMALIES_TITLE, - href: getTabsOnUsersUrl(UsersTableType.anomalies), - disabled: false, - }, - [UsersTableType.risk]: { - id: UsersTableType.risk, - name: i18n.NAVIGATION_RISK_TITLE, - href: getTabsOnUsersUrl(UsersTableType.risk), - disabled: false, - }, +export const navTabsUsers = ( + hasMlUserPermissions: boolean, + isRiskyUserEnabled: boolean +): UsersNavTab => { + const hiddenTabs = []; + + const userNavTabs = { + [UsersTableType.allUsers]: { + id: UsersTableType.allUsers, + name: i18n.NAVIGATION_ALL_USERS_TITLE, + href: getTabsOnUsersUrl(UsersTableType.allUsers), + disabled: false, + }, + [UsersTableType.anomalies]: { + id: UsersTableType.anomalies, + name: i18n.NAVIGATION_ANOMALIES_TITLE, + href: getTabsOnUsersUrl(UsersTableType.anomalies), + disabled: false, + }, + [UsersTableType.risk]: { + id: UsersTableType.risk, + name: i18n.NAVIGATION_RISK_TITLE, + href: getTabsOnUsersUrl(UsersTableType.risk), + disabled: false, + }, + }; + + if (!hasMlUserPermissions) { + hiddenTabs.push(UsersTableType.anomalies); + } + + if (!isRiskyUserEnabled) { + hiddenTabs.push(UsersTableType.risk); + } + + return omit(hiddenTabs, userNavTabs); }; diff --git a/x-pack/plugins/security_solution/public/users/pages/navigation/types.ts b/x-pack/plugins/security_solution/public/users/pages/navigation/types.ts index 1e4c28f38450e..f3fd099d78548 100644 --- a/x-pack/plugins/security_solution/public/users/pages/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/users/pages/navigation/types.ts @@ -10,7 +10,10 @@ import { ESTermQuery } from '../../../../common/typed_json'; import { DocValueFields } from '../../../../../timelines/common'; import { NavTab } from '../../../common/components/navigation/types'; -type KeyUsersNavTab = UsersTableType.allUsers | UsersTableType.anomalies | UsersTableType.risk; +type KeyUsersNavTabWithoutMlPermission = UsersTableType.allUsers & UsersTableType.risk; +type KeyUsersNavTabWithMlPermission = KeyUsersNavTabWithoutMlPermission & UsersTableType.anomalies; + +type KeyUsersNavTab = KeyUsersNavTabWithoutMlPermission | KeyUsersNavTabWithMlPermission; export type UsersNavTab = Record; export interface QueryTabBodyProps { diff --git a/x-pack/plugins/security_solution/public/users/pages/users.tsx b/x-pack/plugins/security_solution/public/users/pages/users.tsx index 91cdb5cc1e430..bd6cc2d097c46 100644 --- a/x-pack/plugins/security_solution/public/users/pages/users.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/users.tsx @@ -46,6 +46,9 @@ import { UpdateDateRange } from '../../common/components/charts/common'; import { LastEventIndexKey } from '../../../common/search_strategy'; import { generateSeverityFilter } from '../../hosts/store/helpers'; import { UsersTableType } from '../store/model'; +import { hasMlUserPermissions } from '../../../common/machine_learning/has_ml_user_permissions'; +import { useMlCapabilities } from '../../common/components/ml/hooks/use_ml_capabilities'; +import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; const ID = 'UsersQueryId'; @@ -157,6 +160,9 @@ const UsersComponent = () => { [dispatch] ); + const capabilities = useMlCapabilities(); + const riskyUsersFeatureEnabled = useIsExperimentalFeatureEnabled('riskyUsersEnabled'); + return ( <> {indicesExist ? ( @@ -191,7 +197,9 @@ const UsersComponent = () => { - + diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/index.ts index 211d9a71f8a55..2fe2f44c94e8d 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/index.ts @@ -10,7 +10,9 @@ import { UsersQueries } from '../../../../../common/search_strategy/security_sol import { SecuritySolutionFactory } from '../types'; import { userDetails } from './details'; +import { totalUsersKpi } from './kpi/total_users'; export const usersFactory: Record> = { [UsersQueries.details]: userDetails, + [UsersQueries.kpiTotalUsers]: totalUsersKpi, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/total_users/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/total_users/index.ts new file mode 100644 index 0000000000000..50e4cfe50bca2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/total_users/index.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getOr } from 'lodash/fp'; + +import type { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; +import { UsersQueries } from '../../../../../../../common/search_strategy/security_solution/users'; +import { + TotalUsersKpiRequestOptions, + TotalUsersKpiStrategyResponse, +} from '../../../../../../../common/search_strategy/security_solution/users/kpi/total_users'; + +import { inspectStringifyObject } from '../../../../../../utils/build_query'; +import { formatGeneralHistogramData } from '../../../hosts/kpi'; +import { SecuritySolutionFactory } from '../../../types'; +import { buildTotalUsersKpiQuery } from './query.build_total_users_kpi.dsl'; + +export const totalUsersKpi: SecuritySolutionFactory = { + buildDsl: (options: TotalUsersKpiRequestOptions) => buildTotalUsersKpiQuery(options), + parse: async ( + options: TotalUsersKpiRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildTotalUsersKpiQuery(options))], + }; + + const usersHistogram = getOr( + null, + 'aggregations.users_histogram.buckets', + response.rawResponse + ); + return { + ...response, + inspect, + users: getOr(null, 'aggregations.users.value', response.rawResponse), + usersHistogram: formatGeneralHistogramData(usersHistogram), + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/total_users/query.build_total_users_kpi.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/total_users/query.build_total_users_kpi.dsl.ts new file mode 100644 index 0000000000000..d86763e4cd3f6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/total_users/query.build_total_users_kpi.dsl.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { HostsKpiHostsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { createQueryFilterClauses } from '../../../../../../utils/build_query'; + +export const buildTotalUsersKpiQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: HostsKpiHostsRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, + }, + }, + ]; + + const dslQuery = { + index: defaultIndex, + allow_no_indices: true, + ignore_unavailable: true, + track_total_hits: false, + body: { + aggregations: { + users: { + cardinality: { + field: 'user.name', + }, + }, + users_histogram: { + auto_date_histogram: { + field: '@timestamp', + buckets: 6, + }, + aggs: { + count: { + cardinality: { + field: 'user.name', + }, + }, + }, + }, + }, + query: { + bool: { + filter, + }, + }, + size: 0, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/test/security_solution_cypress/config.ts b/x-pack/test/security_solution_cypress/config.ts index 47be0c9b2c8ce..e32283685f0b2 100644 --- a/x-pack/test/security_solution_cypress/config.ts +++ b/x-pack/test/security_solution_cypress/config.ts @@ -51,6 +51,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--xpack.securitySolution.enableExperimental=${JSON.stringify([ 'riskyHostsEnabled', 'usersEnabled', + 'riskyUsersEnabled', 'ruleRegistryEnabled', ])}`, `--home.disableWelcomeScreen=true`, From ef570f6f0c48581423fbcf4b04e37215a2c7a2a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ece=20=C3=96zalp?= Date: Tue, 22 Mar 2022 12:58:26 -0400 Subject: [PATCH 11/13] [CTI] fixes rule preview incorrect interval and from values (#128003) Co-authored-by: Ece Ozalp Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/detection_engine/constants.ts | 14 +++++++++++ .../rules/use_preview_rule.ts | 25 ++++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/common/detection_engine/constants.ts b/x-pack/plugins/security_solution/common/detection_engine/constants.ts index 7f3c822800673..b61cd34dc4790 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/constants.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/constants.ts @@ -11,3 +11,17 @@ export enum RULE_PREVIEW_INVOCATION_COUNT { WEEK = 168, MONTH = 30, } + +export enum RULE_PREVIEW_INTERVAL { + HOUR = '5m', + DAY = '1h', + WEEK = '1h', + MONTH = '1d', +} + +export enum RULE_PREVIEW_FROM { + HOUR = 'now-6m', + DAY = 'now-65m', + WEEK = 'now-65m', + MONTH = 'now-25h', +} diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_preview_rule.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_preview_rule.ts index 43572ddaf4d37..b610e96273ebd 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_preview_rule.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_preview_rule.ts @@ -8,7 +8,11 @@ import { useEffect, useState } from 'react'; import { Unit } from '@elastic/datemath'; -import { RULE_PREVIEW_INVOCATION_COUNT } from '../../../../../common/detection_engine/constants'; +import { + RULE_PREVIEW_FROM, + RULE_PREVIEW_INTERVAL, + RULE_PREVIEW_INVOCATION_COUNT, +} from '../../../../../common/detection_engine/constants'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { PreviewResponse, @@ -31,16 +35,24 @@ export const usePreviewRule = (timeframe: Unit = 'h') => { const [isLoading, setIsLoading] = useState(false); const { addError } = useAppToasts(); let invocationCount = RULE_PREVIEW_INVOCATION_COUNT.HOUR; + let interval = RULE_PREVIEW_INTERVAL.HOUR; + let from = RULE_PREVIEW_FROM.HOUR; switch (timeframe) { case 'd': invocationCount = RULE_PREVIEW_INVOCATION_COUNT.DAY; + interval = RULE_PREVIEW_INTERVAL.DAY; + from = RULE_PREVIEW_FROM.DAY; break; case 'w': invocationCount = RULE_PREVIEW_INVOCATION_COUNT.WEEK; + interval = RULE_PREVIEW_INTERVAL.WEEK; + from = RULE_PREVIEW_FROM.WEEK; break; case 'M': invocationCount = RULE_PREVIEW_INVOCATION_COUNT.MONTH; + interval = RULE_PREVIEW_INTERVAL.MONTH; + from = RULE_PREVIEW_FROM.MONTH; break; } @@ -60,7 +72,14 @@ export const usePreviewRule = (timeframe: Unit = 'h') => { try { setIsLoading(true); const previewRuleResponse = await previewRule({ - rule: { ...transformOutput(rule), invocationCount }, + rule: { + ...transformOutput({ + ...rule, + interval, + from, + }), + invocationCount, + }, signal: abortCtrl.signal, }); if (isSubscribed) { @@ -82,7 +101,7 @@ export const usePreviewRule = (timeframe: Unit = 'h') => { isSubscribed = false; abortCtrl.abort(); }; - }, [rule, addError, invocationCount]); + }, [rule, addError, invocationCount, from, interval]); return { isLoading, response, rule, setRule }; }; From 725000d679348fe103aeba0c77c5a9e4d9a8486d Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 22 Mar 2022 18:51:26 +0100 Subject: [PATCH 12/13] [Lens] Implement null instead of zero switch (#127731) * implement null instead of zero switch * make default * fix tests * fix test * move into advanced options * show switch Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../search/aggs/buckets/multi_terms.test.ts | 3 + .../common/search/aggs/buckets/terms.test.ts | 3 + .../common/search/aggs/metrics/cardinality.ts | 2 + .../aggs/metrics/cardinality_fn.test.ts | 1 + .../search/aggs/metrics/cardinality_fn.ts | 7 ++ .../data/common/search/aggs/metrics/count.ts | 15 +++- .../search/aggs/metrics/count_fn.test.ts | 1 + .../common/search/aggs/metrics/count_fn.ts | 7 ++ .../search/aggs/metrics/metric_agg_type.ts | 20 ++++- .../data/common/search/aggs/metrics/sum.ts | 2 + .../common/search/aggs/metrics/sum_fn.test.ts | 1 + .../data/common/search/aggs/metrics/sum_fn.ts | 7 ++ src/plugins/data/common/search/aggs/types.ts | 3 +- .../search/tabify/response_writer.test.ts | 2 + .../main/utils/fetch_chart.test.ts | 4 +- .../main/utils/get_chart_agg_config.test.ts | 4 +- .../snapshots/baseline/combined_test2.json | 2 +- .../snapshots/baseline/combined_test3.json | 2 +- .../snapshots/baseline/final_output_test.json | 2 +- .../snapshots/baseline/metric_all_data.json | 2 +- .../snapshots/baseline/metric_empty_data.json | 2 +- .../baseline/metric_multi_metric_data.json | 2 +- .../baseline/metric_percentage_mode.json | 2 +- .../baseline/metric_single_metric_data.json | 2 +- .../snapshots/baseline/partial_test_1.json | 2 +- .../snapshots/baseline/partial_test_2.json | 2 +- .../snapshots/baseline/step_output_test2.json | 2 +- .../snapshots/baseline/step_output_test3.json | 2 +- .../snapshots/baseline/tagcloud_all_data.json | 2 +- .../baseline/tagcloud_empty_data.json | 2 +- .../snapshots/baseline/tagcloud_fontsize.json | 2 +- .../baseline/tagcloud_metric_data.json | 2 +- .../snapshots/baseline/tagcloud_options.json | 2 +- .../dimension_panel/advanced_options.tsx | 40 +++++----- .../dimension_panel/dimension_editor.tsx | 50 ++++++------ .../dimension_panel/dimension_panel.test.tsx | 2 +- .../droppable/droppable.test.ts | 4 +- .../operations/definitions/cardinality.tsx | 69 +++++++++++++++-- .../operations/definitions/column_types.ts | 14 ++-- .../operations/definitions/count.tsx | 76 ++++++++++++++++--- .../operations/definitions/formula/parse.ts | 13 ++-- .../operations/definitions/index.ts | 12 +++ .../operations/definitions/metrics.tsx | 72 +++++++++++++++--- .../operations/layer_helpers.test.ts | 1 + 44 files changed, 359 insertions(+), 110 deletions(-) diff --git a/src/plugins/data/common/search/aggs/buckets/multi_terms.test.ts b/src/plugins/data/common/search/aggs/buckets/multi_terms.test.ts index 7751c47575f42..f390bd860a219 100644 --- a/src/plugins/data/common/search/aggs/buckets/multi_terms.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/multi_terms.test.ts @@ -100,6 +100,9 @@ describe('Multi Terms Agg', () => { "chain": Array [ Object { "arguments": Object { + "emptyAsNull": Array [ + false, + ], "enabled": Array [ true, ], diff --git a/src/plugins/data/common/search/aggs/buckets/terms.test.ts b/src/plugins/data/common/search/aggs/buckets/terms.test.ts index 524606f7c562f..a2d9b94283e8b 100644 --- a/src/plugins/data/common/search/aggs/buckets/terms.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/terms.test.ts @@ -114,6 +114,9 @@ describe('Terms Agg', () => { "chain": Array [ Object { "arguments": Object { + "emptyAsNull": Array [ + false, + ], "enabled": Array [ true, ], diff --git a/src/plugins/data/common/search/aggs/metrics/cardinality.ts b/src/plugins/data/common/search/aggs/metrics/cardinality.ts index 5a18924902fc3..f965deb6b0fe8 100644 --- a/src/plugins/data/common/search/aggs/metrics/cardinality.ts +++ b/src/plugins/data/common/search/aggs/metrics/cardinality.ts @@ -19,6 +19,7 @@ const uniqueCountTitle = i18n.translate('data.search.aggs.metrics.uniqueCountTit export interface AggParamsCardinality extends BaseAggParams { field: string; + emptyAsNull?: boolean; } export const getCardinalityMetricAgg = () => @@ -27,6 +28,7 @@ export const getCardinalityMetricAgg = () => valueType: 'number', expressionName: aggCardinalityFnName, title: uniqueCountTitle, + enableEmptyAsNull: true, makeLabel(aggConfig: IMetricAggConfig) { return i18n.translate('data.search.aggs.metrics.uniqueCountLabel', { defaultMessage: 'Unique count of {field}', diff --git a/src/plugins/data/common/search/aggs/metrics/cardinality_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/cardinality_fn.test.ts index 08d64e599d8a9..2d1f8a7baa23b 100644 --- a/src/plugins/data/common/search/aggs/metrics/cardinality_fn.test.ts +++ b/src/plugins/data/common/search/aggs/metrics/cardinality_fn.test.ts @@ -25,6 +25,7 @@ describe('agg_expression_functions', () => { "id": undefined, "params": Object { "customLabel": undefined, + "emptyAsNull": undefined, "field": "machine.os.keyword", "json": undefined, "timeShift": undefined, diff --git a/src/plugins/data/common/search/aggs/metrics/cardinality_fn.ts b/src/plugins/data/common/search/aggs/metrics/cardinality_fn.ts index 89006761407f7..cdff364f7c45e 100644 --- a/src/plugins/data/common/search/aggs/metrics/cardinality_fn.ts +++ b/src/plugins/data/common/search/aggs/metrics/cardinality_fn.ts @@ -74,6 +74,13 @@ export const aggCardinality = (): FunctionDefinition => ({ 'Shift the time range for the metric by a set time, for example 1h or 7d. "previous" will use the closest time range from the date histogram or time range filter.', }), }, + emptyAsNull: { + types: ['boolean'], + help: i18n.translate('data.search.aggs.metrics.emptyAsNull.help', { + defaultMessage: + 'If set to true, a missing value is treated as null in the resulting data table. If set to false, a "zero" is filled in', + }), + }, }, fn: (input, args) => { const { id, enabled, schema, ...rest } = args; diff --git a/src/plugins/data/common/search/aggs/metrics/count.ts b/src/plugins/data/common/search/aggs/metrics/count.ts index fac1751290f70..be3c6b7cdfb53 100644 --- a/src/plugins/data/common/search/aggs/metrics/count.ts +++ b/src/plugins/data/common/search/aggs/metrics/count.ts @@ -7,10 +7,15 @@ */ import { i18n } from '@kbn/i18n'; +import { BaseAggParams } from '../types'; import { aggCountFnName } from './count_fn'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; +export interface AggParamsCount extends BaseAggParams { + emptyAsNull?: boolean; +} + export const getCountMetricAgg = () => new MetricAggType({ name: METRIC_TYPES.COUNT, @@ -20,6 +25,7 @@ export const getCountMetricAgg = () => }), hasNoDsl: true, json: false, + enableEmptyAsNull: true, makeLabel() { return i18n.translate('data.search.aggs.metrics.countLabel', { defaultMessage: 'Count', @@ -32,11 +38,16 @@ export const getCountMetricAgg = () => }, getValue(agg, bucket) { const timeShift = agg.getTimeShift(); + let value: unknown; if (!timeShift) { - return bucket.doc_count; + value = bucket.doc_count; } else { - return bucket[`doc_count_${timeShift.asMilliseconds()}`]; + value = bucket[`doc_count_${timeShift.asMilliseconds()}`]; + } + if (value === 0 && agg.params.emptyAsNull) { + return null; } + return value; }, isScalable() { return true; diff --git a/src/plugins/data/common/search/aggs/metrics/count_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/count_fn.test.ts index c6736c5b69f7d..7a68b7a962373 100644 --- a/src/plugins/data/common/search/aggs/metrics/count_fn.test.ts +++ b/src/plugins/data/common/search/aggs/metrics/count_fn.test.ts @@ -23,6 +23,7 @@ describe('agg_expression_functions', () => { "id": undefined, "params": Object { "customLabel": undefined, + "emptyAsNull": undefined, "timeShift": undefined, }, "schema": undefined, diff --git a/src/plugins/data/common/search/aggs/metrics/count_fn.ts b/src/plugins/data/common/search/aggs/metrics/count_fn.ts index a3a4bcc16a391..c302e0abf7c5d 100644 --- a/src/plugins/data/common/search/aggs/metrics/count_fn.ts +++ b/src/plugins/data/common/search/aggs/metrics/count_fn.ts @@ -61,6 +61,13 @@ export const aggCount = (): FunctionDefinition => ({ 'Shift the time range for the metric by a set time, for example 1h or 7d. "previous" will use the closest time range from the date histogram or time range filter.', }), }, + emptyAsNull: { + types: ['boolean'], + help: i18n.translate('data.search.aggs.metrics.emptyAsNull.help', { + defaultMessage: + 'If set to true, a missing value is treated as null in the resulting data table. If set to false, a "zero" is filled in', + }), + }, }, fn: (input, args) => { const { id, enabled, schema, ...rest } = args; diff --git a/src/plugins/data/common/search/aggs/metrics/metric_agg_type.ts b/src/plugins/data/common/search/aggs/metrics/metric_agg_type.ts index 5237c1ecffe58..c96ba217779a6 100644 --- a/src/plugins/data/common/search/aggs/metrics/metric_agg_type.ts +++ b/src/plugins/data/common/search/aggs/metrics/metric_agg_type.ts @@ -31,6 +31,7 @@ interface MetricAggTypeConfig extends AggTypeConfig> { isScalable?: () => boolean; subtype?: string; + enableEmptyAsNull?: boolean; } // TODO need to make a more explicit interface for this @@ -57,6 +58,17 @@ export class MetricAggType ); + if (config.enableEmptyAsNull) { + this.params.push( + new BaseParamType({ + name: 'emptyAsNull', + type: 'boolean', + default: false, + write: () => {}, + }) as MetricAggParam + ); + } + this.getValue = config.getValue || ((agg, bucket) => { @@ -67,9 +79,13 @@ export class MetricAggType { @@ -27,6 +28,7 @@ export const getSumMetricAgg = () => { expressionName: aggSumFnName, title: sumTitle, valueType: 'number', + enableEmptyAsNull: true, makeLabel(aggConfig) { return i18n.translate('data.search.aggs.metrics.sumLabel', { defaultMessage: 'Sum of {field}', diff --git a/src/plugins/data/common/search/aggs/metrics/sum_fn.test.ts b/src/plugins/data/common/search/aggs/metrics/sum_fn.test.ts index f4d4fb5451dcd..3f36f98f40eac 100644 --- a/src/plugins/data/common/search/aggs/metrics/sum_fn.test.ts +++ b/src/plugins/data/common/search/aggs/metrics/sum_fn.test.ts @@ -25,6 +25,7 @@ describe('agg_expression_functions', () => { "id": undefined, "params": Object { "customLabel": undefined, + "emptyAsNull": undefined, "field": "machine.os.keyword", "json": undefined, "timeShift": undefined, diff --git a/src/plugins/data/common/search/aggs/metrics/sum_fn.ts b/src/plugins/data/common/search/aggs/metrics/sum_fn.ts index d8e03d28bb12a..063cc10839813 100644 --- a/src/plugins/data/common/search/aggs/metrics/sum_fn.ts +++ b/src/plugins/data/common/search/aggs/metrics/sum_fn.ts @@ -69,6 +69,13 @@ export const aggSum = (): FunctionDefinition => ({ 'Shift the time range for the metric by a set time, for example 1h or 7d. "previous" will use the closest time range from the date histogram or time range filter.', }), }, + emptyAsNull: { + types: ['boolean'], + help: i18n.translate('data.search.aggs.metrics.emptyAsNull.help', { + defaultMessage: + 'If set to true, a missing value is treated as null in the resulting data table. If set to false, a "zero" is filled in', + }), + }, }, fn: (input, args) => { const { id, enabled, schema, ...rest } = args; diff --git a/src/plugins/data/common/search/aggs/types.ts b/src/plugins/data/common/search/aggs/types.ts index edc328bcb5099..3781b35e5b767 100644 --- a/src/plugins/data/common/search/aggs/types.ts +++ b/src/plugins/data/common/search/aggs/types.ts @@ -95,6 +95,7 @@ import { AggParamsDiversifiedSampler } from './buckets/diversified_sampler'; import { AggParamsSignificantText } from './buckets/significant_text'; import { AggParamsTopMetrics } from './metrics/top_metrics'; import { aggTopMetrics } from './metrics/top_metrics_fn'; +import { AggParamsCount } from './metrics'; export type { IAggConfig, AggConfigSerialized } from './agg_config'; export type { CreateAggConfigParams, IAggConfigs } from './agg_configs'; @@ -168,7 +169,7 @@ export interface AggParamsMapping { [BUCKET_TYPES.DIVERSIFIED_SAMPLER]: AggParamsDiversifiedSampler; [METRIC_TYPES.AVG]: AggParamsAvg; [METRIC_TYPES.CARDINALITY]: AggParamsCardinality; - [METRIC_TYPES.COUNT]: BaseAggParams; + [METRIC_TYPES.COUNT]: AggParamsCount; [METRIC_TYPES.GEO_BOUNDS]: AggParamsGeoBounds; [METRIC_TYPES.GEO_CENTROID]: AggParamsGeoCentroid; [METRIC_TYPES.MAX]: AggParamsMax; diff --git a/src/plugins/data/common/search/tabify/response_writer.test.ts b/src/plugins/data/common/search/tabify/response_writer.test.ts index ec131458b8510..85f815447619a 100644 --- a/src/plugins/data/common/search/tabify/response_writer.test.ts +++ b/src/plugins/data/common/search/tabify/response_writer.test.ts @@ -201,6 +201,7 @@ describe('TabbedAggResponseWriter class', () => { indexPatternId: '1234', params: { field: 'machine.os.raw', + emptyAsNull: false, }, type: 'cardinality', }, @@ -264,6 +265,7 @@ describe('TabbedAggResponseWriter class', () => { indexPatternId: '1234', params: { field: 'machine.os.raw', + emptyAsNull: false, }, type: 'cardinality', }, diff --git a/src/plugins/discover/public/application/main/utils/fetch_chart.test.ts b/src/plugins/discover/public/application/main/utils/fetch_chart.test.ts index 9f3a25e15d741..17423fad1ae9e 100644 --- a/src/plugins/discover/public/application/main/utils/fetch_chart.test.ts +++ b/src/plugins/discover/public/application/main/utils/fetch_chart.test.ts @@ -76,7 +76,9 @@ describe('test fetchCharts', () => { Object { "enabled": true, "id": "1", - "params": Object {}, + "params": Object { + "emptyAsNull": false, + }, "schema": "metric", "type": "count", }, diff --git a/src/plugins/discover/public/application/main/utils/get_chart_agg_config.test.ts b/src/plugins/discover/public/application/main/utils/get_chart_agg_config.test.ts index ccd7584d4bff3..ded1cf1d858af 100644 --- a/src/plugins/discover/public/application/main/utils/get_chart_agg_config.test.ts +++ b/src/plugins/discover/public/application/main/utils/get_chart_agg_config.test.ts @@ -33,7 +33,9 @@ describe('getChartAggConfigs', () => { Object { "enabled": true, "id": "1", - "params": Object {}, + "params": Object { + "emptyAsNull": false, + }, "schema": "metric", "type": "count", }, diff --git a/test/interpreter_functional/snapshots/baseline/combined_test2.json b/test/interpreter_functional/snapshots/baseline/combined_test2.json index 3b030ec8fb597..b4129ac898eed 100644 --- a/test/interpreter_functional/snapshots/baseline/combined_test2.json +++ b/test/interpreter_functional/snapshots/baseline/combined_test2.json @@ -1 +1 @@ -{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file +{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/combined_test3.json b/test/interpreter_functional/snapshots/baseline/combined_test3.json index dc39ecfc53594..dc1c037f45e95 100644 --- a/test/interpreter_functional/snapshots/baseline/combined_test3.json +++ b/test/interpreter_functional/snapshots/baseline/combined_test3.json @@ -1 +1 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/final_output_test.json b/test/interpreter_functional/snapshots/baseline/final_output_test.json index dc39ecfc53594..dc1c037f45e95 100644 --- a/test/interpreter_functional/snapshots/baseline/final_output_test.json +++ b/test/interpreter_functional/snapshots/baseline/final_output_test.json @@ -1 +1 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_all_data.json b/test/interpreter_functional/snapshots/baseline/metric_all_data.json index a26b85daee932..939e51b619928 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_all_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_all_data.json @@ -1 +1 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"bytes","params":null},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"bytes","params":null},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_empty_data.json b/test/interpreter_functional/snapshots/baseline/metric_empty_data.json index 0917ecc8f9b2e..6adb4e117d2c7 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_empty_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_empty_data.json @@ -1 +1 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json b/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json index 44bc7717db04f..4a324a133c057 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json @@ -1 +1 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json b/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json index 99604aa377475..944820d0ed16d 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json +++ b/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json @@ -1 +1 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":{"colors":["rgb(0,0,0,0)","rgb(100, 100, 100)"],"continuity":"none","gradient":false,"range":"number","rangeMax":10000,"rangeMin":0,"stops":[0,10000]},"percentageMode":true,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":{"colors":["rgb(0,0,0,0)","rgb(100, 100, 100)"],"continuity":"none","gradient":false,"range":"number","rangeMax":10000,"rangeMin":0,"stops":[0,10000]},"percentageMode":true,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json b/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json index 63c91a3cc749d..392649d410e15 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json @@ -1 +1 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/partial_test_1.json b/test/interpreter_functional/snapshots/baseline/partial_test_1.json index e8a847b43de3b..8ce0ee16a0b3b 100644 --- a/test/interpreter_functional/snapshots/baseline/partial_test_1.json +++ b/test/interpreter_functional/snapshots/baseline/partial_test_1.json @@ -1 +1 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/partial_test_2.json b/test/interpreter_functional/snapshots/baseline/partial_test_2.json index dc39ecfc53594..dc1c037f45e95 100644 --- a/test/interpreter_functional/snapshots/baseline/partial_test_2.json +++ b/test/interpreter_functional/snapshots/baseline/partial_test_2.json @@ -1 +1 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/step_output_test2.json b/test/interpreter_functional/snapshots/baseline/step_output_test2.json index 3b030ec8fb597..b4129ac898eed 100644 --- a/test/interpreter_functional/snapshots/baseline/step_output_test2.json +++ b/test/interpreter_functional/snapshots/baseline/step_output_test2.json @@ -1 +1 @@ -{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file +{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/step_output_test3.json b/test/interpreter_functional/snapshots/baseline/step_output_test3.json index dc39ecfc53594..dc1c037f45e95 100644 --- a/test/interpreter_functional/snapshots/baseline/step_output_test3.json +++ b/test/interpreter_functional/snapshots/baseline/step_output_test3.json @@ -1 +1 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json index 518eb529e70f4..837251a438911 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json @@ -1 +1 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_empty_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_empty_data.json index 7417545550cd8..5c3ca14f4eab7 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_empty_data.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_empty_data.json @@ -1 +1 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[],"type":"datatable"},"visParams":{"ariaLabel":null,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[],"type":"datatable"},"visParams":{"ariaLabel":null,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json b/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json index 986e6d19e91f3..5e99024d6e52b 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json @@ -1 +1 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json index cf0aa1162c23f..e00233197bda3 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json @@ -1 +1 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_options.json b/test/interpreter_functional/snapshots/baseline/tagcloud_options.json index 357ac0fc76784..759b2752f9328 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_options.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_options.json @@ -1 +1 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"log","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"log","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/advanced_options.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/advanced_options.tsx index db3dfe8901fb9..3d1928edf27dc 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/advanced_options.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/advanced_options.tsx @@ -8,15 +8,7 @@ import { EuiLink, EuiText, EuiPopover, EuiButtonEmpty, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; - -interface AdvancedOption { - title: string; - dataTestSubj: string; - onClick: () => void; - showInPopover: boolean; - inlineElement: React.ReactElement | null; - helpPopup?: string | null; -} +import { AdvancedOption } from '../operations/definitions'; export function AdvancedOptions(props: { options: AdvancedOption[] }) { const [popoverOpen, setPopoverOpen] = useState(false); @@ -49,20 +41,24 @@ export function AdvancedOptions(props: { options: AdvancedOption[] }) { setPopoverOpen(false); }} > - {popoverOptions.map(({ dataTestSubj, onClick, title }, index) => ( + {popoverOptions.map(({ dataTestSubj, onClick, title, optionElement }, index) => ( - - { - setPopoverOpen(false); - onClick(); - }} - > - {title} - - + {optionElement ? ( + optionElement + ) : ( + + { + setPopoverOpen(false); + onClick(); + }} + > + {title} + + + )} {popoverOptions.length - 1 !== index && } ))} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index 24f3a5a65c8d9..a9e37e2d53d70 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -57,6 +57,7 @@ import { import type { TemporaryState } from './dimensions_editor_helpers'; import { FieldInput } from './field_input'; import { NameInput } from '../../shared_components'; +import { ParamEditorProps } from '../operations/definitions'; const operationPanels = getOperationDisplay(); @@ -422,6 +423,28 @@ export function DimensionEditor(props: DimensionEditorProps) { const FieldInputComponent = selectedOperationDefinition?.renderFieldInput || FieldInput; + const paramEditorProps: ParamEditorProps = { + layer: state.layers[layerId], + layerId, + activeData: props.activeData, + updateLayer: (setter) => { + if (temporaryQuickFunction) { + setTemporaryState('none'); + } + setStateWrapper(setter, { forceRender: temporaryQuickFunction }); + }, + columnId, + currentColumn: state.layers[layerId].columns[columnId], + dateRange, + indexPattern: currentIndexPattern, + operationDefinitionMap, + toggleFullscreen, + isFullscreen, + setIsCloseable, + paramEditorCustomProps, + ...services, + }; + const quickFunctions = ( <>
@@ -517,29 +540,7 @@ export function DimensionEditor(props: DimensionEditorProps) { /> ) : null} - {shouldDisplayExtraOptions && ( - { - if (temporaryQuickFunction) { - setTemporaryState('none'); - } - setStateWrapper(setter, { forceRender: temporaryQuickFunction }); - }} - columnId={columnId} - currentColumn={state.layers[layerId].columns[columnId]} - dateRange={dateRange} - indexPattern={currentIndexPattern} - operationDefinitionMap={operationDefinitionMap} - toggleFullscreen={toggleFullscreen} - isFullscreen={isFullscreen} - setIsCloseable={setIsCloseable} - paramEditorCustomProps={paramEditorCustomProps} - {...services} - /> - )} + {shouldDisplayExtraOptions && }
); @@ -767,6 +768,9 @@ export function DimensionEditor(props: DimensionEditorProps) { /> ) : null, }, + ...(operationDefinitionMap[selectedColumn.operationType].getAdvancedOptions?.( + paramEditorProps + ) || []), ]} /> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index 70baa1d772e1b..356ad5ac9543e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -602,7 +602,7 @@ describe('IndexPatternDimensionEditorPanel', () => { col1: expect.objectContaining({ operationType: 'min', sourceField: 'bytes', - params: { format: { id: 'bytes' } }, + params: { format: { id: 'bytes' }, emptyAsNull: true }, // Other parts of this don't matter for this test }), }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/droppable.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/droppable.test.ts index 2b193eb01c9d6..ea3978ce8ca94 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/droppable.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/droppable.test.ts @@ -2306,7 +2306,9 @@ describe('IndexPatternDimensionEditorPanel', () => { sourceField: 'src', timeShift: undefined, dataType: 'number', - params: undefined, + params: { + emptyAsNull: true, + }, scale: 'ratio', }, }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx index c97447803524d..8490b48ad320e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx @@ -6,10 +6,13 @@ */ import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { EuiSwitch } from '@elastic/eui'; +import { euiThemeVars } from '@kbn/ui-theme'; import { AggFunctionsMapping } from '../../../../../../../src/plugins/data/public'; import { buildExpressionFunction } from '../../../../../../../src/plugins/expressions/public'; -import { OperationDefinition } from './index'; -import { FormattedIndexPatternColumn, FieldBasedIndexPatternColumn } from './column_types'; +import { OperationDefinition, ParamEditorProps } from './index'; +import { FieldBasedIndexPatternColumn, FormatParams } from './column_types'; import { getFormatFromPreviousColumn, @@ -17,9 +20,11 @@ import { getSafeName, getFilter, combineErrorMessages, + isColumnOfType, } from './helpers'; import { adjustTimeScaleLabelSuffix } from '../time_scale_utils'; import { getDisallowedPreviousShiftMessage } from '../../time_shift_utils'; +import { updateColumnParam } from '../layer_helpers'; const supportedTypes = new Set([ 'string', @@ -51,10 +56,12 @@ function ofName(name: string, timeShift: string | undefined) { ); } -export interface CardinalityIndexPatternColumn - extends FormattedIndexPatternColumn, - FieldBasedIndexPatternColumn { +export interface CardinalityIndexPatternColumn extends FieldBasedIndexPatternColumn { operationType: typeof OPERATION_TYPE; + params?: { + emptyAsNull?: boolean; + format?: FormatParams; + }; } export const cardinalityOperation: OperationDefinition = { @@ -101,9 +108,58 @@ export const cardinalityOperation: OperationDefinition('unique_count', previousColumn) + ? previousColumn.params?.emptyAsNull + : !columnParams?.usedInMath, + }, }; }, + getAdvancedOptions: ({ + layer, + columnId, + currentColumn, + updateLayer, + }: ParamEditorProps) => { + return [ + { + dataTestSubj: 'hide-zero-values', + optionElement: ( + <> + { + updateLayer( + updateColumnParam({ + layer, + columnId, + paramName: 'emptyAsNull', + value: !currentColumn.params?.emptyAsNull, + }) + ); + }} + compressed + /> + + ), + title: '', + showInPopover: true, + inlineElement: null, + onClick: () => {}, + }, + ]; + }, toEsAggsFn: (column, columnId) => { return buildExpressionFunction('aggCardinality', { id: columnId, @@ -112,6 +168,7 @@ export const cardinalityOperation: OperationDefinition { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts index 2b11d182eeed0..333312116949f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts @@ -19,15 +19,17 @@ export interface BaseIndexPatternColumn extends Operation { timeShift?: string; } +export interface FormatParams { + id: string; + params?: { + decimals: number; + }; +} + // Formatting can optionally be added to any column export interface FormattedIndexPatternColumn extends BaseIndexPatternColumn { params?: { - format?: { - id: string; - params?: { - decimals: number; - }; - }; + format?: FormatParams; }; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx index a35f8fbc08acf..7ecd5a4970c95 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx @@ -6,31 +6,39 @@ */ import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { EuiSwitch } from '@elastic/eui'; import { AggFunctionsMapping } from '../../../../../../../src/plugins/data/public'; import { buildExpressionFunction } from '../../../../../../../src/plugins/expressions/public'; -import { OperationDefinition } from './index'; -import { FormattedIndexPatternColumn, FieldBasedIndexPatternColumn } from './column_types'; +import { OperationDefinition, ParamEditorProps } from './index'; +import { FieldBasedIndexPatternColumn, FormatParams } from './column_types'; import { IndexPatternField } from '../../types'; import { getInvalidFieldMessage, getFilter, - isColumnFormatted, combineErrorMessages, + getFormatFromPreviousColumn, + isColumnOfType, } from './helpers'; import { adjustTimeScaleLabelSuffix, adjustTimeScaleOnOtherColumnChange, } from '../time_scale_utils'; import { getDisallowedPreviousShiftMessage } from '../../time_shift_utils'; +import { updateColumnParam } from '../layer_helpers'; const countLabel = i18n.translate('xpack.lens.indexPattern.countOf', { defaultMessage: 'Count of records', }); -export type CountIndexPatternColumn = FormattedIndexPatternColumn & - FieldBasedIndexPatternColumn & { - operationType: 'count'; +export type CountIndexPatternColumn = FieldBasedIndexPatternColumn & { + operationType: 'count'; + params?: { + emptyAsNull?: boolean; + format?: FormatParams; }; +}; export const countOperation: OperationDefinition = { type: 'count', @@ -91,14 +99,57 @@ export const countOperation: OperationDefinition('count', previousColumn) + ? previousColumn.params?.emptyAsNull + : !columnParams?.usedInMath, + }, }; }, + getAdvancedOptions: ({ + layer, + columnId, + currentColumn, + updateLayer, + }: ParamEditorProps) => { + return [ + { + dataTestSubj: 'hide-zero-values', + optionElement: ( + <> + { + updateLayer( + updateColumnParam({ + layer, + columnId, + paramName: 'emptyAsNull', + value: !currentColumn.params?.emptyAsNull, + }) + ); + }} + compressed + /> + + ), + title: '', + showInPopover: true, + inlineElement: null, + onClick: () => {}, + }, + ]; + }, onOtherColumnChanged: (layer, thisColumnId, changedColumnId) => adjustTimeScaleOnOtherColumnChange( layer, @@ -112,6 +163,7 @@ export const countOperation: OperationDefinition { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/parse.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/parse.ts index a3b61429fb0bf..81a6943e600d0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/parse.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/parse.ts @@ -107,11 +107,14 @@ function extractColumns( ? indexPattern.getFieldByName(fieldName.value)! : documentField; - const mappedParams = mergeWithGlobalFilter( - nodeOperation, - getOperationParams(nodeOperation, namedArguments || []), - globalFilter - ); + const mappedParams = { + ...mergeWithGlobalFilter( + nodeOperation, + getOperationParams(nodeOperation, namedArguments || []), + globalFilter + ), + usedInMath: true, + }; const newCol = ( nodeOperation as OperationDefinition diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts index dec70130d1282..87c7ab5913a20 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts @@ -191,6 +191,16 @@ export interface HelpProps { export type TimeScalingMode = 'disabled' | 'mandatory' | 'optional'; +export interface AdvancedOption { + title: string; + optionElement?: React.ReactElement; + dataTestSubj: string; + onClick: () => void; + showInPopover: boolean; + inlineElement: React.ReactElement | null; + helpPopup?: string | null; +} + interface BaseOperationDefinitionProps { type: C['operationType']; /** @@ -227,6 +237,7 @@ interface BaseOperationDefinitionProps * React component for operation specific settings shown in the flyout editor */ paramEditor?: React.ComponentType>; + getAdvancedOptions?: (params: ParamEditorProps) => AdvancedOption[] | undefined; /** * Returns true if the `column` can also be used on `newIndexPattern`. * If this function returns false, the column is removed when switching index pattern @@ -416,6 +427,7 @@ interface FieldBasedOperationDefinition C; /** diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx index f2ab811427ac5..2b46e52defdba 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx @@ -6,30 +6,34 @@ */ import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { EuiSwitch } from '@elastic/eui'; +import { euiThemeVars } from '@kbn/ui-theme'; import { buildExpressionFunction } from '../../../../../../../src/plugins/expressions/public'; -import { OperationDefinition } from './index'; +import { OperationDefinition, ParamEditorProps } from './index'; import { getFormatFromPreviousColumn, getInvalidFieldMessage, getSafeName, getFilter, combineErrorMessages, + isColumnOfType, } from './helpers'; -import { - FormattedIndexPatternColumn, - FieldBasedIndexPatternColumn, - BaseIndexPatternColumn, -} from './column_types'; +import { FieldBasedIndexPatternColumn, BaseIndexPatternColumn, FormatParams } from './column_types'; import { adjustTimeScaleLabelSuffix, adjustTimeScaleOnOtherColumnChange, } from '../time_scale_utils'; import { getDisallowedPreviousShiftMessage } from '../../time_shift_utils'; +import { updateColumnParam } from '../layer_helpers'; -type MetricColumn = FormattedIndexPatternColumn & - FieldBasedIndexPatternColumn & { - operationType: T; +type MetricColumn = FieldBasedIndexPatternColumn & { + operationType: T; + params?: { + emptyAsNull?: boolean; + format?: FormatParams; }; +}; const typeToFn: Record = { min: 'aggMin', @@ -49,6 +53,7 @@ function buildMetricOperation>({ priority, optionalTimeScaling, supportsDate, + hideZeroOption, }: { type: T['operationType']; displayName: string; @@ -57,6 +62,7 @@ function buildMetricOperation>({ optionalTimeScaling?: boolean; description?: string; supportsDate?: boolean; + hideZeroOption?: boolean; }) { const labelLookup = (name: string, column?: BaseIndexPatternColumn) => { const label = ofName(name); @@ -115,7 +121,13 @@ function buildMetricOperation>({ timeScale: optionalTimeScaling ? previousColumn?.timeScale : undefined, filter: getFilter(previousColumn, columnParams), timeShift: columnParams?.shift || previousColumn?.timeShift, - params: getFormatFromPreviousColumn(previousColumn), + params: { + ...getFormatFromPreviousColumn(previousColumn), + emptyAsNull: + hideZeroOption && previousColumn && isColumnOfType(type, previousColumn) + ? previousColumn.params?.emptyAsNull + : !columnParams?.usedInMath, + }, } as T; }, onFieldChange: (oldColumn, field) => { @@ -126,6 +138,44 @@ function buildMetricOperation>({ sourceField: field.name, }; }, + getAdvancedOptions: ({ layer, columnId, currentColumn, updateLayer }: ParamEditorProps) => { + if (!hideZeroOption) return []; + return [ + { + dataTestSubj: 'hide-zero-values', + optionElement: ( + <> + { + updateLayer( + updateColumnParam({ + layer, + columnId, + paramName: 'emptyAsNull', + value: !currentColumn.params?.emptyAsNull, + }) + ); + }} + compressed + /> + + ), + title: '', + showInPopover: true, + inlineElement: null, + onClick: () => {}, + }, + ]; + }, toEsAggsFn: (column, columnId, _indexPattern) => { return buildExpressionFunction(typeToFn[type], { id: columnId, @@ -134,6 +184,7 @@ function buildMetricOperation>({ field: column.sourceField, // time shift is added to wrapping aggFilteredMetric if filter is set timeShift: column.filter ? undefined : column.timeShift, + emptyAsNull: hideZeroOption ? column.params?.emptyAsNull : undefined, }).toAst(); }, getErrorMessage: (layer, columnId, indexPattern) => @@ -242,6 +293,7 @@ export const sumOperation = buildMetricOperation({ defaultMessage: 'A single-value metrics aggregation that sums up numeric values that are extracted from the aggregated documents.', }), + hideZeroOption: true, }); export const medianOperation = buildMetricOperation({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts index f7a8df3d5ef1f..b6398970056e2 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts @@ -2162,6 +2162,7 @@ describe('state_helpers', () => { id: 'number', params: { decimals: 2 }, }, + emptyAsNull: true, }, }) ); From 5d519f3e72af628abdfa1d3d5d51712f1943d871 Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Tue, 22 Mar 2022 19:13:33 +0100 Subject: [PATCH 13/13] [Workplace Search] Add feedback link to pages with external source (#128290) --- .../components/add_source/add_source.test.tsx | 16 +++++++++ .../components/add_source/add_source.tsx | 1 + .../add_source/config_completed.test.tsx | 7 ++++ .../add_source/config_completed.tsx | 29 +++++++++++++++ .../components/overview.test.tsx | 18 +++++++++- .../content_sources/components/overview.tsx | 30 ++++++++++++++++ .../components/source_config.test.tsx | 10 +++++- .../settings/components/source_config.tsx | 35 ++++++++++++++++++- 8 files changed, 143 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.test.tsx index 8e3171dc71bec..d4b5a1dbd9829 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.test.tsx @@ -134,12 +134,28 @@ describe('AddSourceList', () => { addSourceCurrentStep: AddSourceSteps.ConfigCompletedStep, }); const wrapper = shallow(); + expect(wrapper.find(ConfigCompleted).prop('showFeedbackLink')).toEqual(false); wrapper.find(ConfigCompleted).prop('advanceStep')(); expect(navigateToUrl).toHaveBeenCalledWith('/sources/add/confluence_cloud/connect'); expect(setAddSourceStep).toHaveBeenCalledWith(AddSourceSteps.ConnectInstanceStep); }); + it('renders Config Completed step with feedback for external connectors', () => { + setMockValues({ + ...mockValues, + sourceConfigData: { ...sourceConfigData, serviceType: 'external' }, + addSourceCurrentStep: AddSourceSteps.ConfigCompletedStep, + }); + const wrapper = shallow( + + ); + expect(wrapper.find(ConfigCompleted).prop('showFeedbackLink')).toEqual(true); + + expect(navigateToUrl).toHaveBeenCalledWith('/sources/add/confluence_cloud/connect'); + expect(setAddSourceStep).toHaveBeenCalledWith(AddSourceSteps.ConnectInstanceStep); + }); + it('renders Save Config step', () => { setMockValues({ ...mockValues, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx index d2c665a4acd74..4bdf8db217a7b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx @@ -109,6 +109,7 @@ export const AddSource: React.FC = (props) => { advanceStep={goToConnectInstance} privateSourcesEnabled={privateSourcesEnabled} header={header} + showFeedbackLink={serviceType === 'external'} /> )} {addSourceCurrentStep === AddSourceSteps.ConnectInstanceStep && ( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.test.tsx index 163da5297e370..0980bd8a61cd6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.test.tsx @@ -9,6 +9,8 @@ import React from 'react'; import { shallow } from 'enzyme'; +import { EuiCallOut } from '@elastic/eui'; + import { ConfigCompleted } from './config_completed'; describe('ConfigCompleted', () => { @@ -26,6 +28,7 @@ describe('ConfigCompleted', () => { expect(wrapper.find('[data-test-subj="OrgCanConnectMessage"]')).toHaveLength(1); expect(wrapper.find('[data-test-subj="PersonalConnectLinkMessage"]')).toHaveLength(0); + expect(wrapper.find(EuiCallOut)).toHaveLength(0); }); it('renders account context', () => { @@ -45,4 +48,8 @@ describe('ConfigCompleted', () => { expect(wrapper.find('[data-test-subj="PrivateDisabledMessage"]')).toHaveLength(1); }); + it('renders feedback callout when set', () => { + const wrapper = shallow(); + expect(wrapper.find(EuiCallOut)).toHaveLength(1); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx index 9b34053bfe524..edd39409893a6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { EuiButton, + EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiIcon, @@ -37,6 +38,7 @@ interface ConfigCompletedProps { accountContextOnly?: boolean; privateSourcesEnabled: boolean; advanceStep(): void; + showFeedbackLink?: boolean; } export const ConfigCompleted: React.FC = ({ @@ -45,6 +47,7 @@ export const ConfigCompleted: React.FC = ({ accountContextOnly, header, privateSourcesEnabled, + showFeedbackLink, }) => ( <> {header} @@ -166,5 +169,31 @@ export const ConfigCompleted: React.FC = ({ )}
+ {showFeedbackLink && ( + <> + + + + + {i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.configCompleted.feedbackCallOutText', + { + defaultMessage: + 'Have feedback about deploying a {name} Connector Package? Let us know.', + values: { name }, + } + )} + + } + /> + + + + )} ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.test.tsx index c9eb2e0afdf5e..21a71308a1832 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.test.tsx @@ -12,7 +12,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { EuiConfirmModal, EuiEmptyPrompt, EuiPanel, EuiTable } from '@elastic/eui'; +import { EuiCallOut, EuiConfirmModal, EuiEmptyPrompt, EuiPanel, EuiTable } from '@elastic/eui'; import { ComponentLoader } from '../../../components/shared/component_loader'; @@ -121,6 +121,22 @@ describe('Overview', () => { expect(wrapper.find('[data-test-subj="DocumentPermissionsDisabled"]')).toHaveLength(1); }); + it('renders feedback callout for external sources', () => { + setMockValues({ + ...mockValues, + contentSource: { + ...fullContentSources[1], + serviceTypeSupportsPermissions: true, + custom: false, + serviceType: 'external', + }, + }); + + const wrapper = shallow(); + + expect(wrapper.find(EuiCallOut)).toHaveLength(1); + }); + it('handles confirmModal submission', () => { const wrapper = shallow(); const button = wrapper.find('[data-test-subj="SyncButton"]'); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx index e5c4b3a09f93f..8f287537e4109 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx @@ -29,7 +29,9 @@ import { EuiText, EuiTextColor, EuiTitle, + EuiCallOut, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { CANCEL_BUTTON_LABEL, START_BUTTON_LABEL } from '../../../../shared/constants'; @@ -107,6 +109,8 @@ export const Overview: React.FC = () => { hasPermissions, isFederatedSource, isIndexedSource, + serviceType, + name, } = contentSource; const [isSyncing, setIsSyncing] = useState(false); @@ -582,6 +586,32 @@ export const Overview: React.FC = () => {
+ {serviceType === 'external' && ( + <> + + + + + {i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.feedbackCallOutText', + { + defaultMessage: + 'Have feedback about deploying a {name} Connector Package? Let us know.', + values: { name }, + } + )} + + } + /> + + + + )} ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/source_config.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/source_config.test.tsx index af8b8fe461f16..8399df946ea83 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/source_config.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/source_config.test.tsx @@ -14,7 +14,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { EuiConfirmModal } from '@elastic/eui'; +import { EuiCallOut, EuiConfirmModal } from '@elastic/eui'; import { SaveConfig } from '../../content_sources/components/add_source/save_config'; @@ -40,6 +40,7 @@ describe('SourceConfig', () => { saveConfig.prop('onDeleteConfig')!(); expect(wrapper.find(EuiConfirmModal)).toHaveLength(1); + expect(wrapper.find(EuiCallOut)).toHaveLength(0); }); it('renders a breadcrumb fallback while data is loading', () => { @@ -84,4 +85,11 @@ describe('SourceConfig', () => { expect(wrapper.find(EuiConfirmModal)).toHaveLength(0); }); + + it('shows feedback link for external sources', () => { + const wrapper = shallow( + + ); + expect(wrapper.find(EuiCallOut)).toHaveLength(1); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/source_config.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/source_config.tsx index ea63f3bab77d9..6973732fa6727 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/source_config.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/source_config.tsx @@ -9,7 +9,14 @@ import React, { useEffect, useState } from 'react'; import { useActions, useValues } from 'kea'; -import { EuiConfirmModal } from '@elastic/eui'; +import { + EuiCallOut, + EuiConfirmModal, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiSpacer, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { WorkplaceSearchPageTemplate } from '../../../components/layout'; @@ -77,6 +84,32 @@ export const SourceConfig: React.FC = ({ sourceData }) => { )} )} + {serviceType === 'external' && ( + <> + + + + + {i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.settings.feedbackCallOutText', + { + defaultMessage: + 'Have feedback about deploying a {name} Connector Package? Let us know.', + values: { name }, + } + )} + + } + /> + + + + )} ); };