From d4ed28f15e87e379c57f3e211506eece74de5501 Mon Sep 17 00:00:00 2001 From: Cristina Amico Date: Mon, 1 Jul 2024 11:15:37 +0200 Subject: [PATCH 001/128] [Fleet] Add doc links to unprivileged agents documentation (#186832) ## Summary Add links to unprivileged agents documentation in Fleet & integration UI ### Screenshots ![Screenshot 2024-06-24 at 16 45 15](https://github.com/elastic/kibana/assets/16084106/7b32fcb4-54cb-4d8b-9ac3-1d9003097a26) ![Screenshot 2024-06-24 at 16 52 27](https://github.com/elastic/kibana/assets/16084106/8d040ce1-79bf-424a-a0d5-d0083c5d2a14) ![Screenshot 2024-06-24 at 17 59 05](https://github.com/elastic/kibana/assets/16084106/8f403a9b-2f00-4a03-8f7c-a25a2159bf37) ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-doc-links/src/get_doc_links.ts | 1 + packages/kbn-doc-links/src/types.ts | 1 + .../single_page_layout/root_callout.tsx | 18 +++++++++-- .../screens/detail/overview/requirements.tsx | 31 +++++++++++++++++-- .../root_privileges_callout.tsx | 17 ++++++++-- .../unprivileged_info.tsx | 15 +++++++-- 6 files changed, 74 insertions(+), 9 deletions(-) diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 6d8b18bb5d714..a135d8a58ae96 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -865,6 +865,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D scalingKubernetesResourcesAndLimits: `${FLEET_DOCS}scaling-on-kubernetes.html#_specifying_resources_and_limits_in_agent_manifests`, roleAndPrivileges: `${FLEET_DOCS}fleet-roles-and-privileges.html`, proxiesSettings: `${FLEET_DOCS}fleet-agent-proxy-support.html`, + unprivilegedMode: `${FLEET_DOCS}elastic-agent-unprivileged.html#unprivileged-change-mode`, }, integrationDeveloper: { upload: `${INTEGRATIONS_DEV_DOCS}upload-a-new-integration.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index 4784ff45789d8..7970d7dadb4b9 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -554,6 +554,7 @@ export interface DocLinks { scalingKubernetesResourcesAndLimits: string; roleAndPrivileges: string; proxiesSettings: string; + unprivilegedMode: string; }>; readonly integrationDeveloper: { upload: string; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/root_callout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/root_callout.tsx index 3a4c01528f4de..46f940e5c6551 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/root_callout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/root_callout.tsx @@ -5,15 +5,19 @@ * 2.0. */ -import { EuiCallOut } from '@elastic/eui'; +import { EuiCallOut, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import React from 'react'; +import { useStartServices } from '../../../../hooks'; + interface Props { dataStreams: Array<{ name: string; title: string }>; } export const RootPrivilegesCallout: React.FC = ({ dataStreams }) => { + const { docLinks } = useStartServices(); + return ( = ({ dataStreams }) => { <> + + + ), + }} />
    {dataStreams.map((item) => ( diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/requirements.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/requirements.tsx index e028f45aaf7c8..fb106f38f628e 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/requirements.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/requirements.tsx @@ -7,9 +7,20 @@ import React, { memo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiFlexGroup, EuiFlexItem, EuiText, EuiDescriptionList, EuiToolTip } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiDescriptionList, + EuiToolTip, + EuiLink, +} from '@elastic/eui'; + +import { useStartServices } from '../../../../../hooks'; export const Requirements = memo(() => { + const { docLinks } = useStartServices(); + return ( @@ -48,13 +59,27 @@ export const Requirements = memo(() => { 'xpack.fleet.epm.requirements.permissionRequireRootTooltip', { defaultMessage: - 'Elastic agent needs to be run with root or administor privileges', + 'Elastic agent needs to be run with root or administrator privileges', } )} > + + + ), + }} /> diff --git a/x-pack/plugins/fleet/public/components/enrollment_instructions/root_privileges_callout.tsx b/x-pack/plugins/fleet/public/components/enrollment_instructions/root_privileges_callout.tsx index 3c115a78567af..50177698099de 100644 --- a/x-pack/plugins/fleet/public/components/enrollment_instructions/root_privileges_callout.tsx +++ b/x-pack/plugins/fleet/public/components/enrollment_instructions/root_privileges_callout.tsx @@ -6,13 +6,16 @@ */ import React from 'react'; -import { EuiCallOut, EuiSpacer } from '@elastic/eui'; +import { EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { useStartServices } from '../../hooks'; + export const RootPrivilegesCallout: React.FC<{ rootIntegrations?: Array<{ name: string; title: string }>; }> = ({ rootIntegrations = [] }) => { + const { docLinks } = useStartServices(); return rootIntegrations.length > 0 ? ( <> + + + ), + }} />
      {rootIntegrations.map((item) => ( diff --git a/x-pack/plugins/fleet/public/components/enrollment_instructions/unprivileged_info.tsx b/x-pack/plugins/fleet/public/components/enrollment_instructions/unprivileged_info.tsx index d6f1fd368538f..e8d36591998cb 100644 --- a/x-pack/plugins/fleet/public/components/enrollment_instructions/unprivileged_info.tsx +++ b/x-pack/plugins/fleet/public/components/enrollment_instructions/unprivileged_info.tsx @@ -6,20 +6,31 @@ */ import React from 'react'; -import { EuiCode, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiCode, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { useStartServices } from '../../hooks'; + export const UnprivilegedInfo: React.FC = () => { + const { docLinks } = useStartServices(); return ( <>

      --unprivileged, command: sudo ./elastic-agent, + guideLink: ( + + + + ), }} />

      From 79f71b7e06b9b807d8ddaff8f005f14d4507c097 Mon Sep 17 00:00:00 2001 From: Bharat Pasupula <123897612+bhapas@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:30:40 +0200 Subject: [PATCH 002/128] [Integration Assistant] [ Security GenAI] Update readme template with the new integration guidelines (#187216) ## Summary Update current README.md.njk structure to match the official integration doc structure according to the [integrations doc guidelines](https://www.elastic.co/guide/en/integrations-developer/current/documentation-guidelines.html). --- .../server/templates/package_readme.md.njk | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/integration_assistant/server/templates/package_readme.md.njk b/x-pack/plugins/integration_assistant/server/templates/package_readme.md.njk index 74520da051b8b..02bf606ab386a 100644 --- a/x-pack/plugins/integration_assistant/server/templates/package_readme.md.njk +++ b/x-pack/plugins/integration_assistant/server/templates/package_readme.md.njk @@ -1,24 +1,38 @@ # {{ package_name }} Integration -This integration is for ingesting data from [{{ package_name }}](https://example.com/). -{% for data_stream in data_streams %} -- `{{ data_stream.name }}`: {{ data_stream.description }} -{% endfor %} -See [Link to docs](https://example.com/docs) for more information. +## Overview + +Explain what the integration is, define the third-party product that is providing data, establish its relationship to the larger ecosystem of Elastic products, and help the reader understand how it can be used to solve a tangible problem. +Check the [overview guidelines](https://www.elastic.co/guide/en/integrations-developer/current/documentation-guidelines.html#idg-docs-guidelines-overview) for more information. + +## Datastreams + +Provide a high-level overview of the kind of data that is collected by the integration. +Check the [datastreams guidelines](https://www.elastic.co/guide/en/integrations-developer/current/documentation-guidelines.html#idg-docs-guidelines-datastreams) for more information. -## Compatibility +## Requirements -Insert compatibility information here. This could for example be which versions of the product it was tested with. +The requirements section helps readers to confirm that the integration will work with their systems. +Check the [requirements guidelines](https://www.elastic.co/guide/en/integrations-developer/current/documentation-guidelines.html#idg-docs-guidelines-requirements) for more information. ## Setup -Insert how to configure the vendor side of the integration here, for example how to configure the API, create a syslog remote destination etc. +Point the reader to the [Observability Getting started guide](https://www.elastic.co/guide/en/observability/master/observability-get-started.html) for generic, step-by-step instructions. Include any additional setup instructions beyond what’s included in the guide, which may include instructions to update the configuration of a third-party service. +Check the [setup guidelines](https://www.elastic.co/guide/en/integrations-developer/current/documentation-guidelines.html#idg-docs-guidelines-setup) for more information. + +## Troubleshooting (optional) + +Provide information about special cases and exceptions that aren’t necessary for getting started or won’t be applicable to all users. Check the [troubleshooting guidelines](https://www.elastic.co/guide/en/integrations-developer/current/documentation-guidelines.html#idg-docs-guidelines-troubleshooting) for more information. + +## Reference + +Provide detailed information about the log or metric types we support within the integration. Check the [reference guidelines](https://www.elastic.co/guide/en/integrations-developer/current/documentation-guidelines.html#idg-docs-guidelines-reference) for more information. ## Logs {% for data_stream in data_streams %} ### {{ data_stream.name }} -Insert a description of the data stream here. +Insert a description of the datastream here. {% raw %}{{fields {% endraw %}"{{ data_stream.name }}"{% raw %}}}{% endraw %} {% endfor %} From cbe6b7b9b38f5a62b2a3763ff5873f76cc9d0a7a Mon Sep 17 00:00:00 2001 From: Ash <1849116+ashokaditya@users.noreply.github.com> Date: Mon, 1 Jul 2024 12:00:54 +0200 Subject: [PATCH 003/128] [Security Solution][Endpoint] Use "valid" agents for creating action requests and updating cases (#187145) ## Summary This PR corrects an API we were able to create multi-agent action for duplicate/non-existing agent ids. The changes in the PR fix that 1. by not including invalid agent ids (that do not have Elastic Defend installed) and 2. adds a warning info in the action request comment so that the user is aware of discarded agents This action when errored shows multi agent action Host/Error grouping. **Before** (notice the single `Hostname` above outputs) You could do this: ```json5 POST /api/endpoint/action/suspend_process { "endpoint_ids": [ "1d793b46-7b6a-4dd4-a6b7-4c75718e04be", "1d793b46-7b6a-4dd4-a6b7-4c75718e04be" ], "parameters": { "pid": "42424242" } } ``` and get a response that looked like (notice the duplicate agent ids in `agents`) ```json5 { "data": { "id": "ff66dc36-5be9-4b12-a94f-09421baed8da", "agentType": "endpoint", "agents": [ "1d793b46-7b6a-4dd4-a6b7-4c75718e04be", 1d793b46-7b6a-4dd4-a6b7-4c75718e04be ], "hosts": { "1d793b46-7b6a-4dd4-a6b7-4c75718e04be": { "name": "ashokaditya-dev-7131" } }, "command": "suspend-process", "startedAt": "2024-06-28T13:56:40.524Z", "isCompleted": false, "wasSuccessful": false, "isExpired": false, "status": "pending", "outputs": {}, "agentState": { "1d793b46-7b6a-4dd4-a6b7-4c75718e04be": { "isCompleted": false, "wasSuccessful": false } }, "createdBy": "elastic", "parameters": { "pid": 4242424242 } } } ``` **after** With this change when you try and send a request such as ```json5 POST /api/endpoint/action/suspend_process { "endpoint_ids": [ "1d793b46-7b6a-4dd4-a6b7-4c75718e04be", "1d793b46-7b6a-4dd4-a6b7-4c75718e04be", "no-dice" ] , "parameters": { "pid": 4242424242 } } ``` you get a response that looks like (notice neither the duplicate agent id nor the invalid agent id in `agents`, also additional WARNING in the `comment` field) ```json5 { "data": { "id": "9bf67816-4d58-4843-bc7a-776f6017b06b", "agentType": "endpoint", "agents": [ "1d793b46-7b6a-4dd4-a6b7-4c75718e04be" ], "hosts": { "1d793b46-7b6a-4dd4-a6b7-4c75718e04be": { "name": "ashokaditya-dev-7131" } }, "command": "suspend-process", "startedAt": "2024-06-28T15:13:20.138Z", "isCompleted": false, "wasSuccessful": false, "isExpired": false, "status": "pending", "outputs": {}, "agentState": { "1d793b46-7b6a-4dd4-a6b7-4c75718e04be": { "isCompleted": false, "wasSuccessful": false } }, "createdBy": "elastic", "comment": "undefined; WARNING: The following agent ids are not valid: [\"no-dice\"] and would not be included in action request.", "parameters": { "pid": 4242424242 } } } ``` follow up of elastic/kibana/pull/186284 ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../endpoint/endpoint_actions_client.test.ts | 126 ++++++++++++++++-- .../endpoint/endpoint_actions_client.ts | 35 +++-- 2 files changed, 144 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.test.ts index 858a1f53a10d6..1fa046496f231 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.test.ts @@ -27,7 +27,7 @@ describe('EndpointActionsClient', () => { 'endpoint_ids' | 'case_ids' > => { return { - endpoint_ids: ['1-2-3', 'invalid-id'], + endpoint_ids: ['1-2-3', 'invalid-id', '1-2-3'], case_ids: ['case-a'], }; }; @@ -42,8 +42,8 @@ describe('EndpointActionsClient', () => { responseActionsClientMock.createIsolateOptions(getCommonResponseActionOptions()) ); - expect(classConstructorOptions.endpointService.createLogger().debug).toHaveBeenCalledWith( - 'The following agent ids are not valid: ["invalid-id"]' + expect(classConstructorOptions.endpointService.createLogger().warn).toHaveBeenCalledWith( + 'The following agent ids are not valid: ["invalid-id"] and will not be included in action request' ); }); @@ -85,11 +85,87 @@ describe('EndpointActionsClient', () => { }); }); - it('should write action request document', async () => { + it('should write action request document to endpoint action request index with given set of valid/invalid agent ids', async () => { await endpointActionsClient.isolate( responseActionsClientMock.createIsolateOptions(getCommonResponseActionOptions()) ); + expect(classConstructorOptions.esClient.index).toHaveBeenCalledWith( + { + index: ENDPOINT_ACTIONS_INDEX, + document: { + '@timestamp': expect.any(String), + EndpointActions: { + action_id: expect.any(String), + data: { + command: 'isolate', + comment: + 'test comment. (WARNING: The following agent ids are not valid: ["invalid-id"] and will not be included in action request)', + parameters: undefined, + }, + expiration: expect.any(String), + input_type: 'endpoint', + type: 'INPUT_ACTION', + }, + agent: { + id: ['1-2-3'], + }, + user: { + id: 'foo', + }, + }, + refresh: 'wait_for', + }, + expect.anything() + ); + }); + + it('should write correct comment when invalid agent ids', async () => { + await endpointActionsClient.isolate( + responseActionsClientMock.createIsolateOptions({ + ...getCommonResponseActionOptions(), + comment: '', + }) + ); + + expect(classConstructorOptions.esClient.index).toHaveBeenCalledWith( + { + index: ENDPOINT_ACTIONS_INDEX, + document: { + '@timestamp': expect.any(String), + EndpointActions: { + action_id: expect.any(String), + data: { + command: 'isolate', + comment: + '(WARNING: The following agent ids are not valid: ["invalid-id"] and will not be included in action request)', + parameters: undefined, + }, + expiration: expect.any(String), + input_type: 'endpoint', + type: 'INPUT_ACTION', + }, + agent: { + id: ['1-2-3'], + }, + user: { + id: 'foo', + }, + }, + refresh: 'wait_for', + }, + expect.anything() + ); + }); + + it('should write action request document to endpoint action request index with given valid agent ids', async () => { + await endpointActionsClient.isolate( + responseActionsClientMock.createIsolateOptions({ + endpoint_ids: ['1-2-3'], + case_ids: ['case-a'], + }) + ); + expect(classConstructorOptions.esClient.index).toHaveBeenCalledWith( { index: ENDPOINT_ACTIONS_INDEX, @@ -107,7 +183,7 @@ describe('EndpointActionsClient', () => { type: 'INPUT_ACTION', }, agent: { - id: ['1-2-3', 'invalid-id'], + id: ['1-2-3'], }, user: { id: 'foo', @@ -119,9 +195,12 @@ describe('EndpointActionsClient', () => { ); }); - it('should update cases', async () => { + it('should update cases for valid agent ids', async () => { await endpointActionsClient.isolate( - responseActionsClientMock.createIsolateOptions(getCommonResponseActionOptions()) + responseActionsClientMock.createIsolateOptions({ + endpoint_ids: ['1-2-3'], + case_ids: ['case-a'], + }) ); expect(classConstructorOptions.casesClient?.attachments.bulkCreate).toHaveBeenCalledWith({ @@ -138,10 +217,38 @@ describe('EndpointActionsClient', () => { endpointId: '1-2-3', hostname: 'Host-ku5jy6j0pw', }, + ], + }, + externalReferenceStorage: { + type: 'elasticSearchDoc', + }, + owner: 'securitySolution', + type: 'externalReference', + }, + ], + caseId: 'case-a', + }); + }); + + it('should update cases for valid/invalid agent ids', async () => { + await endpointActionsClient.isolate( + responseActionsClientMock.createIsolateOptions(getCommonResponseActionOptions()) + ); + + expect(classConstructorOptions.casesClient?.attachments.bulkCreate).toHaveBeenCalledWith({ + attachments: [ + { + externalReferenceAttachmentTypeId: 'endpoint', + externalReferenceId: expect.any(String), + externalReferenceMetadata: { + command: 'isolate', + comment: + 'test comment. (WARNING: The following agent ids are not valid: ["invalid-id"] and will not be included in action request)', + targets: [ { agentType: 'endpoint', - endpointId: 'invalid-id', - hostname: '', + endpointId: '1-2-3', + hostname: 'Host-ku5jy6j0pw', }, ], }, @@ -175,6 +282,7 @@ describe('EndpointActionsClient', () => { { meta: true } ); }); + it('should create an action with error when agents are invalid', async () => { // @ts-expect-error mocking this for testing purposes endpointActionsClient.checkAgentIds = jest.fn().mockResolvedValueOnce({ diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts index eb7921e4ca420..2c908bd1a3f30 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts @@ -52,6 +52,13 @@ import type { } from '../lib/types'; import { DEFAULT_EXECUTE_ACTION_TIMEOUT } from '../../../../../../common/endpoint/service/response_actions/constants'; +const getInvalidAgentsWarning = (invalidAgents: string[]) => + invalidAgents.length + ? `The following agent ids are not valid: ${JSON.stringify( + invalidAgents + )} and will not be included in action request` + : ''; + export class EndpointActionsClient extends ResponseActionsClientImpl { protected readonly agentType: ResponseActionAgentType = 'endpoint'; @@ -61,14 +68,15 @@ export class EndpointActionsClient extends ResponseActionsClientImpl { allValid: boolean; hosts: HostMetadata[]; }> { + const uniqueIds = [...new Set(ids)]; const foundEndpointHosts = await this.options.endpointService .getEndpointMetadataService() - .getMetadataForEndpoints(this.options.esClient, [...new Set(ids)]); + .getMetadataForEndpoints(this.options.esClient, uniqueIds); const validIds = foundEndpointHosts.map((endpoint: HostMetadata) => endpoint.elastic.agent.id); const invalidIds = ids.filter((id) => !validIds.includes(id)); if (invalidIds.length) { - this.log.debug(`The following agent ids are not valid: ${JSON.stringify(invalidIds)}`); + this.log.warn(getInvalidAgentsWarning(invalidIds)); } return { @@ -88,12 +96,12 @@ export class EndpointActionsClient extends ResponseActionsClientImpl { actionReq: TOptions, options?: TMethodOptions ): Promise { - const agentIds = await this.checkAgentIds(actionReq.endpoint_ids); + const validatedAgents = await this.checkAgentIds(actionReq.endpoint_ids); const actionId = uuidv4(); const { error: validationError } = await this.validateRequest({ ...actionReq, command, - endpoint_ids: agentIds.valid || [], + endpoint_ids: validatedAgents.valid || [], }); const { hosts, ruleName, ruleId, error } = this.getMethodOptions(options); @@ -104,7 +112,7 @@ export class EndpointActionsClient extends ResponseActionsClientImpl { try { await this.dispatchActionViaFleet({ actionId, - agents: agentIds.valid, + agents: validatedAgents.valid, data: { command, comment: actionReq.comment, @@ -122,29 +130,40 @@ export class EndpointActionsClient extends ResponseActionsClientImpl { } } + // Append warning message to comment if there are invalid agents + const commentMessage = actionReq.comment ? actionReq.comment : ''; + const warningMessage = `(WARNING: ${getInvalidAgentsWarning(validatedAgents.invalid)})`; + const comment = validatedAgents.invalid.length + ? commentMessage + ? `${commentMessage}. ${warningMessage}` + : warningMessage + : actionReq.comment; + // Write action to endpoint index await this.writeActionRequestToEndpointIndex({ ...actionReq, + endpoint_ids: validatedAgents.valid, error: actionError, ruleId, ruleName, hosts, actionId, command, + comment, }); // Update cases await this.updateCases({ command, actionId, - comment: actionReq.comment, + comment, caseIds: actionReq.case_ids, alertIds: actionReq.alert_ids, - hosts: actionReq.endpoint_ids.map((hostId) => { + hosts: validatedAgents.valid.map((hostId) => { return { hostId, hostname: - agentIds.hosts.find((host) => host.agent.id === hostId)?.host.hostname ?? + validatedAgents.hosts.find((host) => host.agent.id === hostId)?.host.hostname ?? hosts?.[hostId].name ?? '', }; From 5e002d54fc05d27e4728348f0806c2af90b18c25 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:06:36 +0100 Subject: [PATCH 004/128] [Security Solution][Detection Engine] fixes flaky Detections > Callouts cypress (missing_privileges_callout.cy) tests (#186482) ## Summary - addresses https://github.com/elastic/kibana/issues/178176 - I ran test 400 times on flaky test runner, no failures. So this PR is to unskip test. If it happen to fail again, we would need to look further - also refactored tests a bit, to remove duplicate one command tests --- .../callouts/missing_privileges_callout.cy.ts | 149 ++++++++---------- 1 file changed, 67 insertions(+), 82 deletions(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/callouts/missing_privileges_callout.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/callouts/missing_privileges_callout.cy.ts index bcb29a456bdd2..69e6bb8b35253 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/callouts/missing_privileges_callout.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/callouts/missing_privileges_callout.cy.ts @@ -44,106 +44,91 @@ const waitForPageTitleToBeShown = () => { cy.get(PAGE_TITLE).should('be.visible'); }; -// FLAKY: https://github.com/elastic/kibana/issues/178176 -describe.skip( - 'Detections > Callouts', - { tags: ['@ess', '@serverless', '@skipInServerless'] }, - () => { - before(() => { - // First, we have to open the app on behalf of a privileged user in order to initialize it. - // Otherwise the app will be disabled and show a "welcome"-like page. - login(); - visit(ALERTS_URL); - waitForPageTitleToBeShown(); - }); - - context('indicating read-only access to resources', () => { - context('On Detections home page', () => { - beforeEach(() => { - loadPageAsReadOnlyUser(ALERTS_URL); - }); - - it('We show one primary callout', () => { - waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary'); - }); - - context('When a user clicks Dismiss on the callout', () => { - it('We hide it and persist the dismissal', () => { - waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary'); - dismissCallOut(MISSING_PRIVILEGES_CALLOUT); - reloadPage(); - getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); - }); - }); +describe('Detections > Callouts', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { + before(() => { + // First, we have to open the app on behalf of a privileged user in order to initialize it. + // Otherwise the app will be disabled and show a "welcome"-like page. + login(); + visit(ALERTS_URL); + waitForPageTitleToBeShown(); + }); + + context('indicating read-only access to resources', () => { + context('On Detections home page', () => { + beforeEach(() => { + loadPageAsReadOnlyUser(ALERTS_URL); }); - // FYI: Rules Management check moved to ../detection_rules/all_rules_read_only.spec.ts + it('dismisses callout and persists its state', () => { + waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary'); - context('On Rule Details page', () => { - beforeEach(() => { - createRule(getNewRule()).then((rule) => - loadPageAsReadOnlyUser(ruleDetailsUrl(rule.body.id)) - ); - }); + dismissCallOut(MISSING_PRIVILEGES_CALLOUT); + reloadPage(); - afterEach(() => { - deleteCustomRule(); - }); + getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + }); + }); + + // FYI: Rules Management check moved to ../detection_rules/all_rules_read_only.spec.ts + + context('On Rule Details page', () => { + beforeEach(() => { + createRule(getNewRule()).then((rule) => + loadPageAsReadOnlyUser(ruleDetailsUrl(rule.body.id)) + ); + }); - it('We show one primary callout', () => { - waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary'); - }); + afterEach(() => { + deleteCustomRule(); + }); - context('When a user clicks Dismiss on the callouts', () => { - it('We hide them and persist the dismissal', () => { - waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary'); + it('dismisses callout and persists its state', () => { + waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary'); - dismissCallOut(MISSING_PRIVILEGES_CALLOUT); - reloadPage(); + dismissCallOut(MISSING_PRIVILEGES_CALLOUT); + reloadPage(); - getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); - }); - }); + getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); }); }); + }); - context('indicating read-write access to resources', () => { - context('On Detections home page', () => { - beforeEach(() => { - loadPageAsPlatformEngineer(ALERTS_URL); - }); + context('indicating read-write access to resources', () => { + context('On Detections home page', () => { + beforeEach(() => { + loadPageAsPlatformEngineer(ALERTS_URL); + }); - it('We show no callout', () => { - getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); - }); + it('We show no callout', () => { + getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); }); + }); - context('On Rules Management page', () => { - beforeEach(() => { - login(ROLES.platform_engineer); - loadPageAsPlatformEngineer(RULES_MANAGEMENT_URL); - }); + context('On Rules Management page', () => { + beforeEach(() => { + login(ROLES.platform_engineer); + loadPageAsPlatformEngineer(RULES_MANAGEMENT_URL); + }); - it('We show no callout', () => { - getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); - }); + it('We show no callout', () => { + getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); }); + }); - context('On Rule Details page', () => { - beforeEach(() => { - createRule(getNewRule()).then((rule) => - loadPageAsPlatformEngineer(ruleDetailsUrl(rule.body.id)) - ); - }); + context('On Rule Details page', () => { + beforeEach(() => { + createRule(getNewRule()).then((rule) => + loadPageAsPlatformEngineer(ruleDetailsUrl(rule.body.id)) + ); + }); - afterEach(() => { - deleteCustomRule(); - }); + afterEach(() => { + deleteCustomRule(); + }); - it('We show no callouts', () => { - getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); - }); + it('We show no callouts', () => { + getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); }); }); - } -); + }); +}); From 6394e46e17f43bae68c9b42beed10863571498d6 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Mon, 1 Jul 2024 13:23:01 +0300 Subject: [PATCH 005/128] feat: enable TLS and SAML realm in local Serverless ES by default (#186886) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Currently, when you start both Serverless Elasticsearch (`yarn es serverless --projectType=oblt` and Serverless Kibana (`yarn start --serverless=oblt`) locally, by default the stack will be configured in a way that developers are encouraged to use native users via the Kibana native login form. The problem is that this doesn't correspond to what our users use in production, where we solely rely on SAML-mapped users that are different from the native users in a number of notable ways. The problem is exacerbated by the fact that all native users you can use during Serverless development locally **are operator users**. As a result, this has led to a number of missed bugs in functionalities that worked well locally but were broken for our users in production. The only way to test Serverless Kibana locally with SAML-mapped users is to include `--ssl` flags in both Elasticsearch and Kibana run commands, but since it's not a default go-to command, many developers don't use it. Also, with `--ssl` flag, both Elasticsearch and Kibana are set up with TLS using self-signed certificates, even though TLS is only required for Elasticsearch to configure the SAML realm. For Kibana, this is rather annoying since browsers complain about non-trusted certificates. Moreover, even with the `--ssl` argument, developers are still presented with the option to pick an operator user, which has proven to be very tempting due to a habit developed over many years. The goal of this PR is to synchronize the local developer experience with the experience we provide to our users in production and includes the following changes: * The `yarn es serverless --projectType=oblt|security|es` command will automatically configure TLS and test-only SAML security realm for Serverless Elasticsearch by default, but will still assume that Serverless Kibana is NOT configured with TLS. * The `yarn start --serverless=oblt|security|es` command will NOT automatically configure TLS for Kibana to supress browser and HTTP client warnings, but will still assume that Serverless Elasticsearch is configured with TLS. * The `yarn start --serverless=oblt|security|es` command will also try to configure SAML authentication provider, and will hide the option to use basic credentials to log in as an operator. As a result, whenever a developer starts Serverless Elasticsearch and Serverless Kibana locally with the default commands and navigates to `http://localhost:5601` (notice `http://` and not `https://`), they will be greeted with the following screen (only SAML project-specific roles available by default): ![image](https://github.com/elastic/kibana/assets/1713708/db793bf0-88ed-4be6-86d6-a34363223a73) __NOTE:__ If they click on `More login options` or navigate to `/login` route directly, they will still be able to log in as an operator user, if needed. ## Breaking changes Unfortunately, due to the nature of Elasticsearch SAML configuration, which requires the Kibana URL to be known at the time Elasticsearch is started, and the fact that `kbn-es` cannot distinguish between cases when `--ssl` is not provided and when TLS is explicitly disabled with `--no-ssl`, the previously recommended configuration with the `--ssl` argument for both Elasticsearch and Kibana will no longer work by default. It will require an additional `--kibanaUrl` argument for the `kbn es` command. For example, the following commands will lead to an unusable experience since Elasticsearch will expect Kibana to be configured WITHOUT TLS: ```bash yarn es serverless --projectType=oblt|security|es --ssl yarn start --serverless=oblt|security|es --ssl ``` This command will need to change to this: ```bash yarn es serverless --projectType=oblt|security|es --ssl --kibanaUrl=https://localhost:5601 yarn start --serverless=oblt|security|es --ssl ``` We can potentially fix this if we somehow teach `kbn-es` CLI arguments parser to distinguish between `--no-ssl` and missing `--ssl`, but I didn't manage to find a way to do that. ## Supported configurations 1. Default or Elasticsearch TLS only, Elasticsearch is configured with TLS, Kibana is configured WITHOUT TLS, SAML-test realm is configured and native login form is hidden: ```bash yarn es serverless --projectType=oblt|security|es [--ssl] yarn start --serverless=oblt|security|es ``` 2. Full TLS, both Elasticsearch and Kibana are configured with TLS, SAML-test realm is configured and native login form is hidden: ```bash yarn es serverless --projectType=oblt|security|es --ssl --kibanaUrl=https://localhost:5601 yarn start --serverless=oblt|security|es --ssl ``` 3. No TLS, both Elasticsearch and Kibana are configured WITHOUT TLS, SAML-test realm is NOT configured and only native login form for operator users is available. We need `--no-ssl` for Kibana since by default it assumes Elasticsearch is running with TLS enabled in Serverless: ```bash yarn es serverless --projectType=oblt|security|es --no-ssl yarn start --serverless=oblt|security|es --no-ssl ``` ## Notes Forcing developers to use TLS for Elasticsearch with self-signed certificates by default might be a little annoying for them as they might need to adjust their tooling. If this change is merged, I'm going to send an email with the relevant information to help developers overcome these annoyances. For example, if they query Elasticsearch with `curl` or `wget`, they need to also specify `-k`/`--insecure` or `--no-check-certificate` arguments respectively to ignore certificate errors. TLS also makes it a bit difficult to intercept traffic coming from Kibana to Elasticsearch with tools like Wireshark, but it's still possible with a few more hoops. One just needs to run Kibana with a few special command line arguments: ```bash NODE_OPTIONS="--no-warnings --tls-cipher-list=AES128-SHA --tls-keylog=/some-path/keylog.txt" yarn start --serverless=oblt|security|es ``` And then use the created `keylog.txt` file to decrypt traffic automatically in Wireshark: right-click an encrypted TLS packet and click on `Protocol Preferences -> (Pre)-Master-Secret log filename…` and choose the `keylog.txt` file in the dialog (more details can be found [here](https://tutorialtech.net/how-to-decrypt-nodejs-tls-traffic-with-wireshark/)): ![image](https://github.com/elastic/kibana/assets/1713708/869fec6f-5069-4f99-a785-39d71d499219) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../kbn-es/src/cli_commands/serverless.ts | 24 +++- packages/kbn-es/src/utils/docker.ts | 7 +- .../kbn-mock-idp-plugin/public/login_page.tsx | 2 +- src/cli/serve/serve.js | 114 +++++++++++++----- 4 files changed, 103 insertions(+), 44 deletions(-) diff --git a/packages/kbn-es/src/cli_commands/serverless.ts b/packages/kbn-es/src/cli_commands/serverless.ts index 6b0fe4acd5be7..83e2ade9186b9 100644 --- a/packages/kbn-es/src/cli_commands/serverless.ts +++ b/packages/kbn-es/src/cli_commands/serverless.ts @@ -6,10 +6,12 @@ * Side Public License, v 1. */ +import chalk from 'chalk'; import dedent from 'dedent'; import getopts from 'getopts'; import { ToolingLog } from '@kbn/tooling-log'; import { getTimeReporter } from '@kbn/ci-stats-reporter'; +import { MOCK_IDP_REALM_NAME } from '@kbn/mock-idp-utils'; import { basename } from 'path'; import { SERVERLESS_RESOURCES_PATHS } from '../paths'; @@ -44,8 +46,8 @@ export const serverless: Command = { --kill Kill running ES serverless nodes if detected on startup --host Publish ES docker container on additional host IP --port The port to bind to on 127.0.0.1 [default: ${DEFAULT_PORT}] - --ssl Enable HTTP SSL on the ES cluster - --kibanaUrl Fully qualified URL where Kibana is hosted (including base path). [default: https://localhost:5601/] + --ssl Enable HTTP SSL on the ES cluster [default: true] + --kibanaUrl Fully qualified URL where Kibana is hosted (including base path). [default: http://localhost:5601/] --skipTeardown If this process exits, leave the ES cluster running in the background --waitForReady Wait for the ES cluster to be ready to serve requests --resources Overrides resources under ES 'config/' directory, which are by default @@ -103,7 +105,12 @@ export const serverless: Command = { ], boolean: ['clean', 'ssl', 'kill', 'background', 'skipTeardown', 'waitForReady'], - default: { ...defaults, kibanaUrl: 'https://localhost:5601/', dataPath: 'stateless' }, + default: { + ...defaults, + kibanaUrl: 'http://localhost:5601/', + dataPath: 'stateless', + ssl: true, + }, }) as unknown as ServerlessOptions; if (!options.projectType) { @@ -114,7 +121,16 @@ export const serverless: Command = { if (!isServerlessProjectType(options.projectType)) { throw createCliError( - `Invalid projectPype '${options.projectType}', supported values: ${supportedProjectTypesStr}` + `Invalid projectType '${options.projectType}', supported values: ${supportedProjectTypesStr}` + ); + } + + // In case `--no-ssl` CLI argument is provided. + if (!options.ssl) { + log.warning( + `Serverless ES cluster cannot configure ${chalk.bold.cyan( + MOCK_IDP_REALM_NAME + )} realm since TLS is disabled.` ); } diff --git a/packages/kbn-es/src/utils/docker.ts b/packages/kbn-es/src/utils/docker.ts index 0e8182920feba..5e462a74ccfb7 100644 --- a/packages/kbn-es/src/utils/docker.ts +++ b/packages/kbn-es/src/utils/docker.ts @@ -767,14 +767,9 @@ export async function runServerlessCluster(log: ToolingLog, options: ServerlessO Login with username ${chalk.bold.cyan(ELASTIC_SERVERLESS_SUPERUSER)} or ${chalk.bold.cyan( SYSTEM_INDICES_SUPERUSER )} and password ${chalk.bold.magenta(ELASTIC_SERVERLESS_SUPERUSER_PASSWORD)} - Stop the cluster: ${chalk.bold(`docker container stop ${nodeNames.join(' ')}`)} - `); - - if (options.ssl) { - log.warning(`SSL has been enabled for ES. Kibana should be started with the SSL flag so that it can authenticate with ES. See packages/kbn-es/src/serverless_resources/README.md for additional information on authentication. + Stop the cluster: ${chalk.bold(`docker container stop ${nodeNames.join(' ')}`)} `); - } if (!options.skipTeardown) { // SIGINT will not trigger in FTR (see cluster.runServerless for FTR signal) diff --git a/packages/kbn-mock-idp-plugin/public/login_page.tsx b/packages/kbn-mock-idp-plugin/public/login_page.tsx index e5329fd652f1e..83fff68408000 100644 --- a/packages/kbn-mock-idp-plugin/public/login_page.tsx +++ b/packages/kbn-mock-idp-plugin/public/login_page.tsx @@ -145,7 +145,7 @@ export const LoginPage = () => { > Log in , - + More login options , ]} diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js index 48b94a4fa8c61..b29e054b7ed95 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -114,38 +114,11 @@ export function applyConfigOverrides(rawConfig, opts, extraCliOptions, keystoreC // only used to set cliArgs.envName, we don't want to inject that into the config delete extraCliOptions.env; + let isServerlessSamlSupported = false; if (opts.dev) { if (opts.serverless) { setServerlessKibanaDevServiceAccountIfPossible(get, set, opts); - - // Configure realm if supported (ES only supports SAML when run with SSL) - if (opts.ssl && MOCK_IDP_PLUGIN_SUPPORTED) { - // Ensure the plugin is loaded in dynamically to exclude from production build - // eslint-disable-next-line import/no-dynamic-require - const { MOCK_IDP_REALM_NAME } = require(MOCK_IDP_PLUGIN_PATH); - - if (has('server.basePath')) { - console.log( - `Custom base path is not supported when running in Serverless, it will be removed.` - ); - _.unset(rawConfig, 'server.basePath'); - } - - set(`xpack.security.authc.providers.saml.${MOCK_IDP_REALM_NAME}`, { - order: Number.MAX_SAFE_INTEGER, - realm: MOCK_IDP_REALM_NAME, - icon: 'user', - description: 'Continue as Test User', - hint: 'Allows testing serverless user roles', - }); - // Add basic realm since defaults won't be applied when a provider has been configured - if (!has('xpack.security.authc.providers.basic')) { - set('xpack.security.authc.providers.basic.basic', { - order: 0, - enabled: true, - }); - } - } + isServerlessSamlSupported = tryConfigureServerlessSamlProvider(rawConfig, opts); } if (!has('elasticsearch.serviceAccountToken') && opts.devCredentials !== false) { @@ -182,7 +155,7 @@ export function applyConfigOverrides(rawConfig, opts, extraCliOptions, keystoreC } // Kib/ES encryption - if (opts.ssl) { + if (opts.ssl || isServerlessSamlSupported) { // @kbn/dev-utils is part of devDependencies // eslint-disable-next-line import/no-extraneous-dependencies const { CA_CERT_PATH } = require('@kbn/dev-utils'); @@ -278,6 +251,7 @@ export default function (program) { command .option('--dev', 'Run the server with development mode defaults') .option('--ssl', 'Run the dev server using HTTPS') + .option('--no-ssl', 'Run the server without HTTPS') .option('--http2', 'Run the dev server using HTTP2 with TLS') .option('--dist', 'Use production assets from kbn/optimizer') .option( @@ -303,13 +277,14 @@ export default function (program) { serverless: opts.serverless || unknownOptions.serverless, }); - const configsEvaluted = getConfigFromFiles(configs); + const configsEvaluated = getConfigFromFiles(configs); const isServerlessMode = !!( - configsEvaluted.serverless || + configsEvaluated.serverless || opts.serverless || unknownOptions.serverless ); + const isServerlessSamlSupported = isServerlessMode && opts.ssl !== false; const cliArgs = { dev: !!opts.dev, envName: unknownOptions.env ? unknownOptions.env.name : undefined, @@ -324,7 +299,7 @@ export default function (program) { // elastic.co links. // We also want to run without base path when running in serverless mode so that Elasticsearch can // connect to Kibana's mock identity provider. - basePath: opts.runExamples || isServerlessMode ? false : !!opts.basePath, + basePath: opts.runExamples || isServerlessSamlSupported ? false : !!opts.basePath, optimize: !!opts.optimize, disableOptimizer: !opts.optimizer, oss: !!opts.oss, @@ -362,3 +337,76 @@ function mergeAndReplaceArrays(objValue, srcValue) { return undefined; } } + +/** + * Tries to configure SAML provider in serverless mode and applies the necessary configuration. + * @param rawConfig Full configuration object. + * @param opts CLI options. + * @returns {boolean} True if SAML provider was successfully configured. + */ +function tryConfigureServerlessSamlProvider(rawConfig, opts) { + if (!MOCK_IDP_PLUGIN_SUPPORTED || opts.ssl === false) { + return false; + } + + // Ensure the plugin is loaded in dynamically to exclude from production build + // eslint-disable-next-line import/no-dynamic-require + const { MOCK_IDP_REALM_NAME } = require(MOCK_IDP_PLUGIN_PATH); + + // Check if there are any custom authentication providers already configure with the order `0` reserved for the + // Serverless SAML provider. + let hasBasicOrTokenProviderConfigured = false; + const providersConfig = _.get(rawConfig, 'xpack.security.authc.providers', {}); + for (const [providerType, providers] of Object.entries(providersConfig)) { + if (providerType === 'basic' || providerType === 'token') { + hasBasicOrTokenProviderConfigured = true; + } + + for (const [providerName, provider] of Object.entries(providers)) { + if (provider.order === 0) { + console.warn( + `The serverless SAML authentication provider won't be configured because the order "0" is already used by the custom authentication provider "${providerType}/${providerName}".` + + `Please update the custom provider to use a different order or remove it to allow the serverless SAML provider to be configured.` + ); + return false; + } + } + } + + if (_.has(rawConfig, 'server.basePath')) { + console.warn( + `Custom base path is not supported when running in Serverless, it will be removed.` + ); + _.unset(rawConfig, 'server.basePath'); + } + + if (opts.ssl) { + console.info( + 'Kibana is being served over HTTPS. Make sure to adjust the `--kibanaUrl` parameter while running the local Serverless ES cluster.' + ); + } + + // Make SAML provider the first in the provider chain + lodashSet(rawConfig, `xpack.security.authc.providers.saml.${MOCK_IDP_REALM_NAME}`, { + order: 0, + realm: MOCK_IDP_REALM_NAME, + icon: 'user', + description: 'Continue as Test User', + hint: 'Allows testing serverless user roles', + }); + + // Disable login selector to automatically trigger SAML authentication, unless it's explicitly enabled. + if (!_.has(rawConfig, 'xpack.security.authc.selector.enabled')) { + lodashSet(rawConfig, 'xpack.security.authc.selector.enabled', false); + } + + // Since we explicitly configured SAML authentication provider, default Basic provider won't be automatically + // configured, and we have to do it manually instead unless other Basic or Token provider was already configured. + if (!hasBasicOrTokenProviderConfigured) { + lodashSet(rawConfig, 'xpack.security.authc.providers.basic.basic', { + order: Number.MAX_SAFE_INTEGER, + }); + } + + return true; +} From 3ab56f5e4eb60ee0898be1964c815a5327016287 Mon Sep 17 00:00:00 2001 From: Asuka Nakajima Date: Mon, 1 Jul 2024 19:57:23 +0900 Subject: [PATCH 006/128] [Security Solution] Filterlist Update for Endpoint Events (#187053) ## Summary Updates endpoint alerts telemetry filter list to add the following new fields for the events. * origin_referrer_url * origin_url ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../server/lib/telemetry/filterlists/endpoint_alerts.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/endpoint_alerts.ts b/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/endpoint_alerts.ts index 848335f26e58a..27c2db7d21d9b 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/endpoint_alerts.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/endpoint_alerts.ts @@ -80,6 +80,8 @@ const allowlistBaseEventFields: AllowlistFields = { mtime: true, directory: true, hash: true, + origin_referrer_url: true, + origin_url: true, pe: true, Ext: { bytes_compressed: true, From 1eb7a5ee0be1e079dc0ff4729058c176c4c76c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Mon, 1 Jul 2024 13:57:21 +0200 Subject: [PATCH 007/128] [Obs AI Assistant] Capture error thrown in context (#186826) Closes https://github.com/elastic/kibana/issues/180190 ![image](https://github.com/elastic/kibana/assets/209966/68b6888d-1613-4eaa-ac73-d82f88b8f549) --- .../server/functions/context.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/context.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/context.ts index c7df07dc18d2f..fd57968617187 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/context.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/context.ts @@ -98,7 +98,21 @@ export function registerContextFunction({ subscriber.complete(); }) .catch((error) => { - subscriber.error(error); + resources.logger.error('Error in context function'); + resources.logger.error(error); + + subscriber.next( + createFunctionResponseMessage({ + name: CONTEXT_FUNCTION_NAME, + content: `Error in context function: ${error.message}`, + data: { + error: { + message: error.message, + }, + }, + }) + ); + subscriber.complete(); }); }); } From fc02b21daf52cbe6c8d9abdea85a91a02d26a77a Mon Sep 17 00:00:00 2001 From: Sergi Massaneda Date: Mon, 1 Jul 2024 13:58:27 +0200 Subject: [PATCH 008/128] [SecuritySolution] Create integration link added to Security onboarding page (#187201) ## Summary Link panel to go to create integrations added. It is a compressed version of the link card displayed on the integrations page. The integration creation feature is not enabled by default, to enable the feature: ``` xpack.integration_assistant.enabled: true ``` ![link](https://github.com/elastic/kibana/assets/17747913/2532819e-da6e-4d2f-ab07-45042d6a069f) When the integrationAssistant plugin is not enabled (default) the link does not appear. ![no_link](https://github.com/elastic/kibana/assets/17747913/82cc67b1-ce16-4fd8-ad59-f8a4d4cd31fb) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../integrations/layouts/default.tsx | 2 +- .../create_integration_card_button.tsx | 62 +++++++++++------ x-pack/plugins/security_solution/kibana.jsonc | 3 +- .../step_links/add_integration_buttons.tsx | 66 +++++++++++-------- .../plugins/security_solution/public/types.ts | 2 + .../plugins/security_solution/tsconfig.json | 1 + 6 files changed, 85 insertions(+), 51 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx b/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx index 5b1105762dd5a..7f49cae5979c9 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx @@ -81,7 +81,7 @@ export const DefaultLayout: React.FC = memo( rightColumn={ CreateIntegrationCardButton ? ( - + ) : undefined } diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration_card_button/create_integration_card_button.tsx b/x-pack/plugins/integration_assistant/public/components/create_integration_card_button/create_integration_card_button.tsx index cb6e64a1aa554..55baee561bb4a 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration_card_button/create_integration_card_button.tsx +++ b/x-pack/plugins/integration_assistant/public/components/create_integration_card_button/create_integration_card_button.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useCallback, useMemo } from 'react'; import { EuiLink, EuiPanel, @@ -15,41 +15,53 @@ import { EuiIcon, EuiText, EuiTitle, - useEuiTheme, + useEuiPaddingCSS, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { css } from '@emotion/react'; import integrationsImage from '../../common/images/integrations_light.svg'; +import { useKibana } from '../../common/hooks/use_kibana'; -const useStyles = () => { - const { euiTheme } = useEuiTheme(); +const useStyles = (compressed: boolean) => { + const paddings = useEuiPaddingCSS(); return { image: css` - width: 160px; - height: 155px; + width: ${compressed ? '140px' : '160px'}; + height: ${compressed ? '90px' : '155px'}; object-fit: cover; object-position: left center; `, container: css` - height: 135px; + height: ${compressed ? '80px' : '135px'}; `, textContainer: css` height: 100%; - padding: ${euiTheme.size.l} 0 ${euiTheme.size.l} ${euiTheme.size.l}; + ${compressed ? `${paddings.m.styles}` : `${paddings.l.styles} padding-right: 0;`} `, }; }; export interface CreateIntegrationCardButtonProps { - href: string; + compressed?: boolean; } export const CreateIntegrationCardButton = React.memo( - ({ href }) => { - const styles = useStyles(); + ({ compressed = false }) => { + const { getUrlForApp, navigateToUrl } = useKibana().services.application; + const styles = useStyles(compressed); + + const href = useMemo(() => getUrlForApp('integrations', { path: '/create' }), [getUrlForApp]); + const navigate = useCallback( + (ev) => { + ev.preventDefault(); + navigateToUrl(href); + }, + [href, navigateToUrl] + ); + return ( - - - + {!compressed && ( + + + + )} - - + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + + - + ( - - - - - - - - - - - -)); +export const AddIntegrationButtons: React.FC = React.memo(() => { + const { integrationAssistant } = useKibana().services; + const CreateIntegrationCardButton = integrationAssistant?.CreateIntegrationCardButton; + return ( + + + + + + + + + + + {CreateIntegrationCardButton && ( + + + + )} + + ); +}); AddIntegrationButtons.displayName = 'AddIntegrationButtons'; diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index 1eb944143c0fe..129f3e7728f9d 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -60,6 +60,7 @@ import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public'; import type { PluginStartContract } from '@kbn/alerting-plugin/public/plugin'; import type { MapsStartApi } from '@kbn/maps-plugin/public'; +import type { IntegrationAssistantPluginStart } from '@kbn/integration-assistant-plugin/public'; import type { ResolverPluginSetup } from './resolver/types'; import type { Inspect } from '../common/search_strategy'; import type { Detections } from './detections'; @@ -152,6 +153,7 @@ export interface StartPlugins { savedSearch: SavedSearchPublicPluginStart; alerting: PluginStartContract; core: CoreStart; + integrationAssistant?: IntegrationAssistantPluginStart; } export interface StartPluginsDependencies extends StartPlugins { diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index f1320dc3205be..400efb61ae0aa 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -209,5 +209,6 @@ "@kbn/core-analytics-browser", "@kbn/core-i18n-browser", "@kbn/core-theme-browser", + "@kbn/integration-assistant-plugin", ] } From 9682968003079ac66dbbd902e079d057f28a21ad Mon Sep 17 00:00:00 2001 From: Antonio Date: Mon, 1 Jul 2024 14:10:44 +0200 Subject: [PATCH 009/128] [ResponseOps][Connectors] Unhide cases webhook SSL UI. (#186461) ## Summary Reverts [this commit](https://github.com/elastic/kibana/pull/185925/commits/fd9f70d887caa6a71e54b85e63d36ecbd4016245) to display the Cases Webhook SSL UI. --- .../public/common/auth/auth_config.tsx | 200 ++++++++---------- .../cases_webhook/steps/auth.tsx | 2 +- .../cases_webhook/webhook_connectors.test.tsx | 8 +- 3 files changed, 99 insertions(+), 111 deletions(-) diff --git a/x-pack/plugins/stack_connectors/public/common/auth/auth_config.tsx b/x-pack/plugins/stack_connectors/public/common/auth/auth_config.tsx index e4477be874ecb..8a4bfe6520885 100644 --- a/x-pack/plugins/stack_connectors/public/common/auth/auth_config.tsx +++ b/x-pack/plugins/stack_connectors/public/common/auth/auth_config.tsx @@ -39,14 +39,13 @@ import * as i18n from './translations'; interface Props { readOnly: boolean; - hideSSL?: boolean; } const { emptyField } = fieldValidators; const VERIFICATION_MODE_DEFAULT = 'full'; -export const AuthConfig: FunctionComponent = ({ readOnly, hideSSL }) => { +export const AuthConfig: FunctionComponent = ({ readOnly }) => { const { setFieldValue, getFieldDefaultValue } = useFormContext(); const [{ config, __internal__ }] = useFormData({ watch: [ @@ -77,37 +76,6 @@ export const AuthConfig: FunctionComponent = ({ readOnly, hideSSL }) => { useEffect(() => setFieldValue('config.hasAuth', Boolean(authType)), [authType, setFieldValue]); - const hideSSLFields = hideSSL && authType !== AuthType.SSL; - - const authOptions = [ - { - value: null, - label: i18n.AUTHENTICATION_NONE, - 'data-test-subj': 'authNone', - }, - { - value: AuthType.Basic, - label: i18n.AUTHENTICATION_BASIC, - children: authType === AuthType.Basic && , - 'data-test-subj': 'authBasic', - }, - ]; - - if (!hideSSLFields) { - authOptions.push({ - value: AuthType.SSL, - label: i18n.AUTHENTICATION_SSL, - children: authType === AuthType.SSL && ( - - ), - 'data-test-subj': 'authSSL', - }); - } - return ( <> @@ -124,7 +92,31 @@ export const AuthConfig: FunctionComponent = ({ readOnly, hideSSL }) => { defaultValue={authTypeDefaultValue} component={CardRadioGroupField} componentProps={{ - options: authOptions, + options: [ + { + value: null, + label: i18n.AUTHENTICATION_NONE, + 'data-test-subj': 'authNone', + }, + { + value: AuthType.Basic, + label: i18n.AUTHENTICATION_BASIC, + children: authType === AuthType.Basic && , + 'data-test-subj': 'authBasic', + }, + { + value: AuthType.SSL, + label: i18n.AUTHENTICATION_SSL, + children: authType === AuthType.SSL && ( + + ), + 'data-test-subj': 'authSSL', + }, + ], }} /> @@ -208,81 +200,77 @@ export const AuthConfig: FunctionComponent = ({ readOnly, hideSSL }) => { )} - {!hideSSLFields && ( + + {hasCA && ( <> - - {hasCA && ( + + + + {}, + }, + ], + }} + component={FilePickerField} + componentProps={{ + euiFieldProps: { + display: 'default', + 'data-test-subj': 'webhookCAInput', + accept: '.ca,.pem', + }, + }} + /> + + + + + + {hasInitialCA && ( <> - - - {}, - }, - ], - }} - component={FilePickerField} - componentProps={{ - euiFieldProps: { - display: 'default', - 'data-test-subj': 'webhookCAInput', - accept: '.ca,.pem', - }, - }} - /> - - - - - - {hasInitialCA && ( - <> - - - - )} + )} diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/steps/auth.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/steps/auth.tsx index 3d7b5bf27e173..cbe250ea823e5 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/steps/auth.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/steps/auth.tsx @@ -19,7 +19,7 @@ interface Props { export const AuthStep: FunctionComponent = ({ display, readOnly }) => { return ( - + ); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.test.tsx index 66d15550b7b75..92ea2fd6a1b30 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.test.tsx @@ -74,7 +74,7 @@ describe('CasesWebhookActionConnectorFields renders', () => { expect(await screen.findByTestId('authNone')).toBeInTheDocument(); expect(await screen.findByTestId('authBasic')).toBeInTheDocument(); - // expect(await screen.findByTestId('authSSL')).toBeInTheDocument(); + expect(await screen.findByTestId('authSSL')).toBeInTheDocument(); expect(await screen.findByTestId('webhookUserInput')).toBeInTheDocument(); expect(await screen.findByTestId('webhookPasswordInput')).toBeInTheDocument(); expect(await screen.findByTestId('webhookHeadersKeyInput')).toBeInTheDocument(); @@ -363,7 +363,7 @@ describe('CasesWebhookActionConnectorFields renders', () => { data: { ...rest, __internal__: { - // hasCA: false, + hasCA: false, hasHeaders: true, }, }, @@ -404,7 +404,7 @@ describe('CasesWebhookActionConnectorFields renders', () => { authType: null, }, __internal__: { - // hasCA: false, + hasCA: false, hasHeaders: true, }, }, @@ -442,7 +442,7 @@ describe('CasesWebhookActionConnectorFields renders', () => { ...rest, config: rest2, __internal__: { - // hasCA: false, + hasCA: false, hasHeaders: false, }, }, From a7ef2f4d1bdf14e568149321e3e16415a1361c82 Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski Date: Mon, 1 Jul 2024 14:44:43 +0200 Subject: [PATCH 010/128] [EDR Workflows] remove checking the step tour (#187223) --- .../osquery/cypress/e2e/all/alerts_cases.cy.ts | 11 ++--------- .../osquery/cypress/e2e/all/alerts_linked_apps.cy.ts | 10 ++-------- .../cypress/e2e/all/alerts_multiple_agents.cy.ts | 5 +---- .../osquery/cypress/e2e/roles/alert_test.cy.ts | 5 +---- x-pack/plugins/osquery/cypress/tasks/integrations.ts | 11 ----------- 5 files changed, 6 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts index 6d41d0a50e250..d9cbe5c69ed6c 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts @@ -23,14 +23,9 @@ import { submitQuery, viewRecentCaseAndCheckResults, } from '../../tasks/live_query'; -import { - closeAlertsStepTourIfVisible, - generateRandomStringName, - interceptCaseId, -} from '../../tasks/integrations'; +import { generateRandomStringName, interceptCaseId } from '../../tasks/integrations'; -// Failing: See https://github.com/elastic/kibana/issues/187182 -describe.skip('Alert Event Details - Cases', { tags: ['@ess', '@serverless'] }, () => { +describe('Alert Event Details - Cases', { tags: ['@ess', '@serverless'] }, () => { let ruleId: string; let packId: string; let packName: string; @@ -71,7 +66,6 @@ describe.skip('Alert Event Details - Cases', { tags: ['@ess', '@serverless'] }, it('runs osquery against alert and creates a new case', () => { const [caseName, caseDescription] = generateRandomStringName(2); cy.getBySel('expand-event').first().click(); - closeAlertsStepTourIfVisible(); cy.getBySel('take-action-dropdown-btn').click(); cy.getBySel('osquery-action-item').click(); cy.contains(/^\d+ agen(t|ts) selected/); @@ -107,7 +101,6 @@ describe.skip('Alert Event Details - Cases', { tags: ['@ess', '@serverless'] }, it('sees osquery results from last action and add to a case', () => { cy.getBySel('expand-event').first().click(); - closeAlertsStepTourIfVisible(); cy.getBySel('securitySolutionFlyoutResponseSectionHeader').click(); cy.getBySel('securitySolutionFlyoutResponseButton').click(); cy.getBySel('responseActionsViewWrapper').should('exist'); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/alerts_linked_apps.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/alerts_linked_apps.cy.ts index 3949ef8986124..f1284bf8b528f 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/alerts_linked_apps.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/alerts_linked_apps.cy.ts @@ -15,15 +15,10 @@ import { selectAllAgents, submitQuery, } from '../../tasks/live_query'; -import { - closeAlertsStepTourIfVisible, - closeModalIfVisible, - closeToastIfVisible, -} from '../../tasks/integrations'; +import { closeModalIfVisible, closeToastIfVisible } from '../../tasks/integrations'; import { RESULTS_TABLE, RESULTS_TABLE_BUTTON } from '../../screens/live_query'; -// Failing: See https://github.com/elastic/kibana/issues/180852 -describe.skip( +describe( 'Alert Event Details', { tags: ['@ess', '@serverless'], @@ -74,7 +69,6 @@ describe.skip( it('should be able to run live query and add to timeline', () => { const TIMELINE_NAME = 'Untitled timeline'; cy.getBySel('expand-event').first().click(); - closeAlertsStepTourIfVisible(); cy.getBySel('take-action-dropdown-btn').click(); cy.getBySel('osquery-action-item').click(); cy.contains('1 agent selected.'); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/alerts_multiple_agents.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/alerts_multiple_agents.cy.ts index 1e3e17e61b245..ca10dc80fe6b6 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/alerts_multiple_agents.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/alerts_multiple_agents.cy.ts @@ -14,10 +14,8 @@ import { takeOsqueryActionWithParams, } from '../../tasks/live_query'; import { OSQUERY_FLYOUT_BODY_EDITOR } from '../../screens/live_query'; -import { closeAlertsStepTourIfVisible } from '../../tasks/integrations'; -// Failing: See https://github.com/elastic/kibana/issues/180851 -describe.skip( +describe( 'Alert Event Details - dynamic params', { tags: ['@ess', '@serverless'], @@ -44,7 +42,6 @@ describe.skip( it('should substitute parameters in investigation guide', () => { cy.getBySel('expand-event').first().click(); - closeAlertsStepTourIfVisible(); cy.getBySel('securitySolutionFlyoutInvestigationGuideButton').click(); // Flakes at times if the button is only clicked once cy.contains('Get processes').should('be.visible').dblclick({ force: true }); diff --git a/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts b/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts index 1a63c4dde955d..718c2f32fd581 100644 --- a/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts @@ -9,10 +9,8 @@ import { initializeDataViews } from '../../tasks/login'; import { checkResults, clickRuleName, submitQuery } from '../../tasks/live_query'; import { loadRule, cleanupRule } from '../../tasks/api_fixtures'; import { ServerlessRoleName } from '../../support/roles'; -import { closeAlertsStepTourIfVisible } from '../../tasks/integrations'; -// Failing: See https://github.com/elastic/kibana/issues/180853 -describe.skip('Alert Test', { tags: ['@ess'] }, () => { +describe('Alert Test', { tags: ['@ess'] }, () => { let ruleName: string; let ruleId: string; @@ -31,7 +29,6 @@ describe.skip('Alert Test', { tags: ['@ess'] }, () => { cy.visit('/app/security/rules'); clickRuleName(ruleName); cy.getBySel('expand-event').first().click({ force: true }); - closeAlertsStepTourIfVisible(); cy.wait(500); cy.getBySel('securitySolutionFlyoutInvestigationGuideButton').click(); diff --git a/x-pack/plugins/osquery/cypress/tasks/integrations.ts b/x-pack/plugins/osquery/cypress/tasks/integrations.ts index e87b9228ad7ef..e4c38854a5faf 100644 --- a/x-pack/plugins/osquery/cypress/tasks/integrations.ts +++ b/x-pack/plugins/osquery/cypress/tasks/integrations.ts @@ -16,7 +16,6 @@ import { DATA_COLLECTION_SETUP_STEP, DATE_PICKER_ABSOLUTE_TAB, DATE_PICKER_ABSOLUTE_TAB_SEL, - SECURITY_SOLUTION_FLYOUT_TOUR_SEL, TOAST_CLOSE_BTN, TOAST_CLOSE_BTN_SEL, } from '../screens/integrations'; @@ -136,16 +135,6 @@ export function closeToastIfVisible() { }); } -export function closeAlertsStepTourIfVisible() { - cy.get(SECURITY_SOLUTION_FLYOUT_TOUR_SEL).then(($el) => { - if ($el.length > 0) { - cy.wrap($el).within(() => { - cy.contains('Exit').click(); - }); - } - }); -} - export const deleteIntegrations = async (integrationName: string) => { const ids: string[] = []; cy.contains(integrationName) From 8cbab32f6a0f4fb30535e66c47febe508386b2e9 Mon Sep 17 00:00:00 2001 From: Joe McElroy Date: Mon, 1 Jul 2024 13:53:57 +0100 Subject: [PATCH 011/128] [Search] [Playground] update semantic_text example (#187240) ## Summary Update the view code example so that it takes into account when there is a semantic_text inner_hit match, use the matched passages for the context. ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../__snapshots__/py_lang_client.test.tsx.snap | 12 +++++++++--- .../components/view_code/examples/py_lang_client.tsx | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/search_playground/public/components/view_code/examples/__snapshots__/py_lang_client.test.tsx.snap b/x-pack/plugins/search_playground/public/components/view_code/examples/__snapshots__/py_lang_client.test.tsx.snap index 1b930bbc80a74..792a7bf37bb62 100644 --- a/x-pack/plugins/search_playground/public/components/view_code/examples/__snapshots__/py_lang_client.test.tsx.snap +++ b/x-pack/plugins/search_playground/public/components/view_code/examples/__snapshots__/py_lang_client.test.tsx.snap @@ -40,9 +40,15 @@ def get_elasticsearch_results(query): def create_openai_prompt(question, results): context = \\"\\" for hit in results: - source_field = index_source_fields.get(hit[\\"_index\\"])[0] - hit_context = hit[\\"_source\\"][source_field] - context += f\\"{hit_context}\\\\n\\" + inner_hit_path = f\\"{hit['_index']}.{index_source_fields.get(hit['_index'])[0]}\\" + + ## For semantic_text matches, we need to extract the text from the inner_hits + if 'inner_hits' in hit and inner_hit_path in hit['inner_hits']: + context += '\\\\n --- \\\\n'.join(inner_hit['_source']['text'] for inner_hit in hit['inner_hits'][inner_hit_path]['hits']['hits']) + else: + source_field = index_source_fields.get(hit[\\"_index\\"])[0] + hit_context = hit[\\"_source\\"][source_field] + context += f\\"{hit_context}\\\\n\\" prompt = f\\"\\"\\" Instructions: diff --git a/x-pack/plugins/search_playground/public/components/view_code/examples/py_lang_client.tsx b/x-pack/plugins/search_playground/public/components/view_code/examples/py_lang_client.tsx index e7532706627d3..b920d2f4a6d28 100644 --- a/x-pack/plugins/search_playground/public/components/view_code/examples/py_lang_client.tsx +++ b/x-pack/plugins/search_playground/public/components/view_code/examples/py_lang_client.tsx @@ -40,9 +40,15 @@ def get_elasticsearch_results(query): def create_openai_prompt(question, results): context = "" for hit in results: - source_field = index_source_fields.get(hit["_index"])[0] - hit_context = hit["_source"][source_field] - context += f"{hit_context}\\n" + inner_hit_path = f"{hit['_index']}.{index_source_fields.get(hit['_index'])[0]}" + + ## For semantic_text matches, we need to extract the text from the inner_hits + if 'inner_hits' in hit and inner_hit_path in hit['inner_hits']: + context += '\\n --- \\n'.join(inner_hit['_source']['text'] for inner_hit in hit['inner_hits'][inner_hit_path]['hits']['hits']) + else: + source_field = index_source_fields.get(hit["_index"])[0] + hit_context = hit["_source"][source_field] + context += f"{hit_context}\\n" prompt = f"""${Prompt(formValues.prompt, { context: true, From 1767aa461577a39bf33bd4c859c28713b3bcfae8 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 1 Jul 2024 16:01:01 +0300 Subject: [PATCH 012/128] fix: [Obs Applications > Service Error Detail][SCREEN READER]: Table rows need TH[scope="row"] for SR usability: 0010 (#186973) Closes: https://github.com/elastic/observability-dev/issues/3547 ## Summary `rowHeader` attribute was added for the following table image --- .../app/error_group_details/top_erroneous_transactions/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/top_erroneous_transactions/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/top_erroneous_transactions/index.tsx index 50d63b305a644..f81d6b8d7abf3 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/top_erroneous_transactions/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/top_erroneous_transactions/index.tsx @@ -154,6 +154,7 @@ export function TopErroneousTransactions({ serviceName }: Props) { Date: Mon, 1 Jul 2024 15:37:32 +0200 Subject: [PATCH 013/128] [Logs UI] Add constraint check (#186872) Add constraint check to limit max number of buckets that can be requested at once. --- .../log_entries/kibana_log_entries_adapter.ts | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/observability_solution/logs_shared/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts b/x-pack/plugins/observability_solution/logs_shared/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts index a6ce92ad25126..f8102e8f0dffa 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts @@ -28,6 +28,21 @@ import { TIMESTAMP_FIELD, TIEBREAKER_FIELD } from '../../../../common/constants' const TIMESTAMP_FORMAT = 'epoch_millis'; +const MAX_BUCKETS = 1000; + +function getBucketIntervalStarts( + startTimestamp: number, + endTimestamp: number, + bucketSize: number +): Date[] { + // estimated number of buckets + const bucketCount = Math.ceil((endTimestamp - startTimestamp) / bucketSize); + if (bucketCount > MAX_BUCKETS) { + throw new Error(`Requested too many buckets: ${bucketCount} > ${MAX_BUCKETS}`); + } + return timeMilliseconds(new Date(startTimestamp), new Date(endTimestamp), bucketSize); +} + export class LogsSharedKibanaLogEntriesAdapter implements LogEntriesAdapter { constructor(private readonly framework: KibanaFramework) {} @@ -134,11 +149,7 @@ export class LogsSharedKibanaLogEntriesAdapter implements LogEntriesAdapter { bucketSize: number, filterQuery?: LogEntryQuery ): Promise { - const bucketIntervalStarts = timeMilliseconds( - new Date(startTimestamp), - new Date(endTimestamp), - bucketSize - ); + const bucketIntervalStarts = getBucketIntervalStarts(startTimestamp, endTimestamp, bucketSize); const query = { allow_no_indices: true, From 51c7afd70132fa2f647ad4fd28b00bc0c4522408 Mon Sep 17 00:00:00 2001 From: Sergi Massaneda Date: Mon, 1 Jul 2024 15:43:35 +0200 Subject: [PATCH 014/128] [GenAI] Add License/productType controls to the integration assistant (#187105) ## Summary Adds the licence and product type controls to the Integration assistant. ### UI - ESS Licence: The `Enterprise` license must be active to use the feature. It is checked inside the _integration_assistant_ plugin itself by default and the license paywall is displayed in case the license requirement is not met, no need to configure anything from outside for the license check. - Serverless Product type: The `Security complete` tier must be enabled to use the feature in security projects. The _integration_assistant_ plugin exposes a contract API to set up a custom upselling. The product type check is performed in the _security_solution_serverless_ plugin and the upselling component is passed to the _integration_assistant_ plugin to be rendered. ### API The `withAvailability` wrapper has been introduced to encapsulate this availability check on all the routes. The `isAvailable` flag is defined at a plugin level and passed to the router context. The flag is defaulted to `true` and can be set to `false` by not having the `Enterprise` license (ESS), or by calling the `setIsActive(false)` contract exposed (serverless). All API requests done while the license / product type requirements are not met will be responded with: ``` 404 Not Found: This API route is not available using your current license/tier. ``` ### Screenshots ESS: ![ess](https://github.com/elastic/kibana/assets/17747913/e3f4947e-4f86-4536-bdc0-a14715c01d2f) Serverless: ![serverless](https://github.com/elastic/kibana/assets/17747913/d7ba23d9-cb4e-429a-9dc8-034840afdde7) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../features/src/product_features_keys.ts | 5 + .../src/security/product_feature_config.ts | 1 + .../upselling/service/types.ts | 3 +- .../integrations/layouts/default.tsx | 2 +- .../sections/epm/screens/create/index.tsx | 7 +- .../integration_assistant/kibana.jsonc | 1 + .../availability_wrapper.tsx | 22 +++ .../components/availability_wrapper/index.ts | 7 + .../license_paywall_card.tsx | 68 +++++++ .../availability_wrapper/translations.ts | 41 ++++ .../public/common/hooks/use_availability.ts | 33 ++++ .../create_integration/create_integration.tsx | 19 +- .../create_integration_landing.tsx | 178 +++++------------- .../integration_assistant_card.tsx | 90 +++++++++ .../integration_assistant/public/plugin.ts | 14 +- .../public/services/index.ts | 9 +- .../integration_assistant/public/types.ts | 22 ++- .../server/__mocks__/mock_server.ts | 1 - .../server/__mocks__/request_context.ts | 1 + .../server/__mocks__/test_adapters.ts | 5 + .../integration_assistant/server/plugin.ts | 34 +++- .../routes/build_integration_routes.test.ts | 11 ++ .../server/routes/build_integration_routes.ts | 5 +- .../routes/categorization_routes.test.ts | 11 ++ .../server/routes/categorization_routes.ts | 81 ++++---- .../server/routes/ecs_routes.test.ts | 11 ++ .../server/routes/ecs_routes.ts | 6 +- .../server/routes/pipeline_routes.test.ts | 11 ++ .../server/routes/pipeline_routes.ts | 31 +-- .../server/routes/related_routes.test.ts | 11 ++ .../server/routes/related_routes.ts | 5 +- .../server/routes/with_availability.ts | 33 ++++ .../integration_assistant/server/types.ts | 14 +- .../integration_assistant/tsconfig.json | 1 + .../step_links/add_integration_buttons.tsx | 2 +- .../common/pli/pli_config.ts | 1 + .../security_solution_serverless/kibana.jsonc | 3 +- .../public/plugin.ts | 3 +- .../public/types.ts | 2 + .../hooks/use_product_type_by_pli.ts | 5 + .../upselling/register_upsellings.test.tsx | 47 ++--- .../public/upselling/register_upsellings.tsx | 168 +++-------------- .../sections/integration_assistant/index.ts | 14 ++ .../integration_assistant.tsx | 87 +++++++++ .../public/upselling/upsellings.tsx | 155 +++++++++++++++ .../server/plugin.ts | 32 +--- .../server/product_features/index.ts | 42 ++++- .../server/types.ts | 2 + .../tsconfig.json | 1 + 49 files changed, 938 insertions(+), 420 deletions(-) create mode 100644 x-pack/plugins/integration_assistant/public/common/components/availability_wrapper/availability_wrapper.tsx create mode 100644 x-pack/plugins/integration_assistant/public/common/components/availability_wrapper/index.ts create mode 100644 x-pack/plugins/integration_assistant/public/common/components/availability_wrapper/license_paywall_card.tsx create mode 100644 x-pack/plugins/integration_assistant/public/common/components/availability_wrapper/translations.ts create mode 100644 x-pack/plugins/integration_assistant/public/common/hooks/use_availability.ts create mode 100644 x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_landing/integration_assistant_card.tsx create mode 100644 x-pack/plugins/integration_assistant/server/routes/with_availability.ts create mode 100644 x-pack/plugins/security_solution_serverless/public/upselling/sections/integration_assistant/index.ts create mode 100644 x-pack/plugins/security_solution_serverless/public/upselling/sections/integration_assistant/integration_assistant.tsx create mode 100644 x-pack/plugins/security_solution_serverless/public/upselling/upsellings.tsx diff --git a/x-pack/packages/security-solution/features/src/product_features_keys.ts b/x-pack/packages/security-solution/features/src/product_features_keys.ts index be60a0edfdb3d..9f1a268b6f674 100644 --- a/x-pack/packages/security-solution/features/src/product_features_keys.ts +++ b/x-pack/packages/security-solution/features/src/product_features_keys.ts @@ -72,6 +72,11 @@ export enum ProductFeatureSecurityKey { * enables Cloud Security Posture - CSPM, KSPM, CNVM */ cloudSecurityPosture = 'cloud_security_posture', + + /** + * enables the integration assistant + */ + integrationAssistant = 'integration_assistant', } export enum ProductFeatureCasesKey { diff --git a/x-pack/packages/security-solution/features/src/security/product_feature_config.ts b/x-pack/packages/security-solution/features/src/security/product_feature_config.ts index 46ca114943795..8445a00fecc6c 100644 --- a/x-pack/packages/security-solution/features/src/security/product_feature_config.ts +++ b/x-pack/packages/security-solution/features/src/security/product_feature_config.ts @@ -122,4 +122,5 @@ export const securityDefaultProductFeaturesConfig: DefaultSecurityProductFeature [ProductFeatureSecurityKey.endpointAgentTamperProtection]: {}, [ProductFeatureSecurityKey.externalRuleActions]: {}, [ProductFeatureSecurityKey.cloudSecurityPosture]: {}, + [ProductFeatureSecurityKey.integrationAssistant]: {}, }; diff --git a/x-pack/packages/security-solution/upselling/service/types.ts b/x-pack/packages/security-solution/upselling/service/types.ts index fdbd840429fec..83a6e2f6bb12a 100644 --- a/x-pack/packages/security-solution/upselling/service/types.ts +++ b/x-pack/packages/security-solution/upselling/service/types.ts @@ -18,7 +18,8 @@ export type UpsellingSectionId = | 'endpoint_protection_updates' | 'endpoint_agent_tamper_protection' | 'cloud_security_posture_integration_installation' - | 'ruleDetailsEndpointExceptions'; + | 'ruleDetailsEndpointExceptions' + | 'integration_assistant'; export type UpsellingMessageId = | 'investigation_guide' diff --git a/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx b/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx index 7f49cae5979c9..440637512490f 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx @@ -46,7 +46,7 @@ export const DefaultLayout: React.FC = memo( }, ]; - const CreateIntegrationCardButton = integrationAssistant?.CreateIntegrationCardButton; + const { CreateIntegrationCardButton } = integrationAssistant?.components ?? {}; return ( { const { integrationAssistant } = useStartServices(); useBreadcrumbs('integration_create'); - const CreateIntegrationAssistant = useMemo( - () => integrationAssistant?.CreateIntegration, - [integrationAssistant] - ); + const CreateIntegrationAssistant = integrationAssistant?.components.CreateIntegration; return CreateIntegrationAssistant ? : null; }); diff --git a/x-pack/plugins/integration_assistant/kibana.jsonc b/x-pack/plugins/integration_assistant/kibana.jsonc index a70120d9cefba..b2ef3045e12be 100644 --- a/x-pack/plugins/integration_assistant/kibana.jsonc +++ b/x-pack/plugins/integration_assistant/kibana.jsonc @@ -13,6 +13,7 @@ ], "requiredPlugins": [ "kibanaReact", + "licensing", "triggersActionsUi", "actions", "stackConnectors", diff --git a/x-pack/plugins/integration_assistant/public/common/components/availability_wrapper/availability_wrapper.tsx b/x-pack/plugins/integration_assistant/public/common/components/availability_wrapper/availability_wrapper.tsx new file mode 100644 index 0000000000000..d9f4a8b2e6c9f --- /dev/null +++ b/x-pack/plugins/integration_assistant/public/common/components/availability_wrapper/availability_wrapper.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { type PropsWithChildren } from 'react'; +import { LicensePaywallCard } from './license_paywall_card'; +import { useAvailability } from '../../hooks/use_availability'; + +type AvailabilityWrapperProps = PropsWithChildren<{}>; +export const AvailabilityWrapper = React.memo(({ children }) => { + const { hasLicense, renderUpselling } = useAvailability(); + if (renderUpselling) { + return <>{renderUpselling}; + } + if (!hasLicense) { + return ; + } + return <>{children}; +}); +AvailabilityWrapper.displayName = 'AvailabilityWrapper'; diff --git a/x-pack/plugins/integration_assistant/public/common/components/availability_wrapper/index.ts b/x-pack/plugins/integration_assistant/public/common/components/availability_wrapper/index.ts new file mode 100644 index 0000000000000..d8f08f5a132d8 --- /dev/null +++ b/x-pack/plugins/integration_assistant/public/common/components/availability_wrapper/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ +export { AvailabilityWrapper } from './availability_wrapper'; diff --git a/x-pack/plugins/integration_assistant/public/common/components/availability_wrapper/license_paywall_card.tsx b/x-pack/plugins/integration_assistant/public/common/components/availability_wrapper/license_paywall_card.tsx new file mode 100644 index 0000000000000..937f9c2411eb1 --- /dev/null +++ b/x-pack/plugins/integration_assistant/public/common/components/availability_wrapper/license_paywall_card.tsx @@ -0,0 +1,68 @@ +/* + * 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 { + EuiCard, + EuiIcon, + EuiTextColor, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiSpacer, +} from '@elastic/eui'; +import { useKibana } from '../../hooks/use_kibana'; +import * as i18n from './translations'; + +export const LicensePaywallCard = React.memo(() => { + const { getUrlForApp } = useKibana().services.application; + return ( + <> + + } + title={ +

      + {i18n.ENTERPRISE_LICENSE_TITLE} +

      + } + description={false} + > + + + + +

      + {i18n.ENTERPRISE_LICENSE_UPGRADE_TITLE} +

      +
      +
      + + {i18n.ENTERPRISE_LICENSE_UPGRADE_DESCRIPTION} + + +
      + + {i18n.ENTERPRISE_LICENSE_UPGRADE_BUTTON} + +
      +
      +
      +
      + + ); +}); +LicensePaywallCard.displayName = 'LicensePaywallCard'; diff --git a/x-pack/plugins/integration_assistant/public/common/components/availability_wrapper/translations.ts b/x-pack/plugins/integration_assistant/public/common/components/availability_wrapper/translations.ts new file mode 100644 index 0000000000000..e7774f9ecb460 --- /dev/null +++ b/x-pack/plugins/integration_assistant/public/common/components/availability_wrapper/translations.ts @@ -0,0 +1,41 @@ +/* + * 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 ENTERPRISE_LICENSE_LABEL = i18n.translate( + 'xpack.integrationAssistant.license.enterprise.label', + { + defaultMessage: 'Enterprise', + } +); + +export const ENTERPRISE_LICENSE_TITLE = i18n.translate( + 'xpack.integrationAssistant.license.enterprise.title', + { + defaultMessage: 'Enterprise License Required', + } +); +export const ENTERPRISE_LICENSE_UPGRADE_TITLE = i18n.translate( + 'xpack.integrationAssistant.license.enterprise.upgradeTitle', + { + defaultMessage: 'Upgrade to Elastic Enterprise', + } +); +export const ENTERPRISE_LICENSE_UPGRADE_DESCRIPTION = i18n.translate( + 'xpack.integrationAssistant.license.enterprise.upgradeDescription', + { + defaultMessage: + 'To turn on this feature, you must upgrade your license to Enterprise or start a free 30-day trial', + } +); +export const ENTERPRISE_LICENSE_UPGRADE_BUTTON = i18n.translate( + 'xpack.integrationAssistant.license.enterprise.upgradeButton', + { + defaultMessage: 'Upgrade license', + } +); diff --git a/x-pack/plugins/integration_assistant/public/common/hooks/use_availability.ts b/x-pack/plugins/integration_assistant/public/common/hooks/use_availability.ts new file mode 100644 index 0000000000000..547c6dd2842c5 --- /dev/null +++ b/x-pack/plugins/integration_assistant/public/common/hooks/use_availability.ts @@ -0,0 +1,33 @@ +/* + * 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 { useMemo } from 'react'; +import { useObservable } from 'react-use'; +import type { LicenseType } from '@kbn/licensing-plugin/public'; +import { useKibana } from './use_kibana'; +import type { RenderUpselling } from '../../services'; + +const MinimumLicenseRequired: LicenseType = 'enterprise'; + +export const useAvailability = (): { + hasLicense: boolean; + renderUpselling: RenderUpselling | undefined; +} => { + const { licensing, renderUpselling$ } = useKibana().services; + const licenseService = useObservable(licensing.license$); + const renderUpselling = useObservable(renderUpselling$); + const hasLicense = useMemo( + () => licenseService?.hasAtLeast(MinimumLicenseRequired) ?? true, + [licenseService] + ); + return { hasLicense, renderUpselling }; +}; + +export const useIsAvailable = (): boolean => { + const { hasLicense, renderUpselling } = useAvailability(); + return hasLicense && !renderUpselling; +}; diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration.tsx b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration.tsx index e915ac920d7df..494bc94d8c58c 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration.tsx +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration.tsx @@ -5,7 +5,7 @@ * 2.0. */ import React from 'react'; -import { Switch } from 'react-router-dom'; +import { Redirect, Switch } from 'react-router-dom'; import { Route } from '@kbn/shared-ux-router'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import type { Services } from '../../services'; @@ -15,6 +15,7 @@ import { CreateIntegrationUpload } from './create_integration_upload'; import { CreateIntegrationAssistant } from './create_integration_assistant'; import { Page, PagePath } from '../../common/constants'; import { useRoutesAuthorization } from '../../common/hooks/use_authorization'; +import { useIsAvailable } from '../../common/hooks/use_availability'; interface CreateIntegrationProps { services: Services; @@ -26,21 +27,23 @@ export const CreateIntegration = React.memo(({ services )); - CreateIntegration.displayName = 'CreateIntegration'; const CreateIntegrationRouter = React.memo(() => { const { canUseIntegrationAssistant, canUseIntegrationUpload } = useRoutesAuthorization(); - + const isAvailable = useIsAvailable(); return ( - {canUseIntegrationAssistant && ( - + {isAvailable && canUseIntegrationAssistant && ( + )} - {canUseIntegrationUpload && ( - + {isAvailable && canUseIntegrationUpload && ( + )} - + + + + } /> ); }); diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_landing/create_integration_landing.tsx b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_landing/create_integration_landing.tsx index b1e29dc25db93..39cbd2cea1026 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_landing/create_integration_landing.tsx +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_landing/create_integration_landing.tsx @@ -6,100 +6,18 @@ */ import React from 'react'; -import { - EuiButton, - EuiCard, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiLink, - EuiSpacer, - EuiText, - EuiTitle, - useEuiTheme, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; -import { AssistantAvatar } from '@kbn/elastic-assistant'; -import { css } from '@emotion/react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { useAuthorization } from '../../../common/hooks/use_authorization'; -import { - AuthorizationWrapper, - MissingPrivilegesTooltip, -} from '../../../common/components/authorization'; +import { AuthorizationWrapper } from '../../../common/components/authorization'; +import { AvailabilityWrapper } from '../../../common/components/availability_wrapper'; import { IntegrationImageHeader } from '../../../common/components/integration_image_header'; import { ButtonsFooter } from '../../../common/components/buttons_footer'; import { SectionWrapper } from '../../../common/components/section_wrapper'; import { useNavigate, Page } from '../../../common/hooks/use_navigate'; +import { IntegrationAssistantCard } from './integration_assistant_card'; import * as i18n from './translations'; -const useAssistantCardCss = () => { - const { euiTheme } = useEuiTheme(); - return css` - /* compensate for EuiCard children margin-block-start */ - margin-block-start: calc(${euiTheme.size.s} * -2); - `; -}; - -const IntegrationAssistantCard = React.memo(() => { - const { canExecuteConnectors } = useAuthorization(); - const navigate = useNavigate(); - const assistantCardCss = useAssistantCardCss(); - return ( - - - - - - - - - -

      {i18n.ASSISTANT_TITLE}

      -
      -
      - - - {i18n.ASSISTANT_DESCRIPTION} - - -
      -
      - - {canExecuteConnectors ? ( - navigate(Page.assistant)}>{i18n.ASSISTANT_BUTTON} - ) : ( - - {i18n.ASSISTANT_BUTTON} - - )} - -
      -
      - ); -}); -IntegrationAssistantCard.displayName = 'IntegrationAssistantCard'; - export const CreateIntegrationLanding = React.memo(() => { const navigate = useNavigate(); return ( @@ -107,49 +25,51 @@ export const CreateIntegrationLanding = React.memo(() => { - - - - - - - - - - - - - - navigate(Page.upload)}> - -
      - ), - }} - /> -
      - - - - - + + + + + + + + + + + + + + + navigate(Page.upload)}> + + + ), + }} + /> + + + + + + + diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_landing/integration_assistant_card.tsx b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_landing/integration_assistant_card.tsx new file mode 100644 index 0000000000000..c616cf5233279 --- /dev/null +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_landing/integration_assistant_card.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { + EuiButton, + EuiCard, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiTitle, + useEuiTheme, +} from '@elastic/eui'; +import { AssistantAvatar } from '@kbn/elastic-assistant'; +import { css } from '@emotion/react'; +import { useAuthorization } from '../../../common/hooks/use_authorization'; +import { MissingPrivilegesTooltip } from '../../../common/components/authorization'; +import { useNavigate, Page } from '../../../common/hooks/use_navigate'; +import * as i18n from './translations'; + +const useAssistantCardCss = () => { + const { euiTheme } = useEuiTheme(); + return css` + /* compensate for EuiCard children margin-block-start */ + margin-block-start: calc(${euiTheme.size.s} * -2); + `; +}; + +export const IntegrationAssistantCard = React.memo(() => { + const { canExecuteConnectors } = useAuthorization(); + const navigate = useNavigate(); + const assistantCardCss = useAssistantCardCss(); + return ( + + + + + + + + + +

      {i18n.ASSISTANT_TITLE}

      +
      +
      + + + {i18n.ASSISTANT_DESCRIPTION} + + +
      +
      + + {canExecuteConnectors ? ( + navigate(Page.assistant)}>{i18n.ASSISTANT_BUTTON} + ) : ( + + {i18n.ASSISTANT_BUTTON} + + )} + +
      +
      + ); +}); +IntegrationAssistantCard.displayName = 'IntegrationAssistantCard'; diff --git a/x-pack/plugins/integration_assistant/public/plugin.ts b/x-pack/plugins/integration_assistant/public/plugin.ts index 9eb03950062c6..ecc55565a472b 100644 --- a/x-pack/plugins/integration_assistant/public/plugin.ts +++ b/x-pack/plugins/integration_assistant/public/plugin.ts @@ -6,6 +6,7 @@ */ import type { CoreStart, Plugin, CoreSetup } from '@kbn/core/public'; +import { BehaviorSubject } from 'rxjs'; import type { IntegrationAssistantPluginSetup, IntegrationAssistantPluginStart, @@ -13,12 +14,13 @@ import type { } from './types'; import { getCreateIntegrationLazy } from './components/create_integration'; import { getCreateIntegrationCardButtonLazy } from './components/create_integration_card_button'; -import { Telemetry, type Services } from './services'; +import { Telemetry, type Services, type RenderUpselling } from './services'; export class IntegrationAssistantPlugin implements Plugin { private telemetry = new Telemetry(); + private renderUpselling$ = new BehaviorSubject(undefined); public setup(core: CoreSetup): IntegrationAssistantPluginSetup { this.telemetry.setup(core.analytics); @@ -33,11 +35,17 @@ export class IntegrationAssistantPlugin ...core, ...dependencies, telemetry: this.telemetry.start(), + renderUpselling$: this.renderUpselling$.asObservable(), }; return { - CreateIntegration: getCreateIntegrationLazy(services), - CreateIntegrationCardButton: getCreateIntegrationCardButtonLazy(), + components: { + CreateIntegration: getCreateIntegrationLazy(services), + CreateIntegrationCardButton: getCreateIntegrationCardButtonLazy(), + }, + renderUpselling: (renderUpselling) => { + this.renderUpselling$.next(renderUpselling); + }, }; } diff --git a/x-pack/plugins/integration_assistant/public/services/index.ts b/x-pack/plugins/integration_assistant/public/services/index.ts index 346c3e2d04a37..d3a5e7e0f6885 100644 --- a/x-pack/plugins/integration_assistant/public/services/index.ts +++ b/x-pack/plugins/integration_assistant/public/services/index.ts @@ -4,12 +4,17 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { Observable } from 'rxjs'; import type { CoreStart } from '@kbn/core/public'; import type { IntegrationAssistantPluginStartDependencies } from '../types'; import type { TelemetryService } from './telemetry/service'; export { Telemetry } from './telemetry/service'; +export type RenderUpselling = React.ReactNode; + export type Services = CoreStart & - IntegrationAssistantPluginStartDependencies & { telemetry: TelemetryService }; + IntegrationAssistantPluginStartDependencies & { + telemetry: TelemetryService; + renderUpselling$: Observable; + }; diff --git a/x-pack/plugins/integration_assistant/public/types.ts b/x-pack/plugins/integration_assistant/public/types.ts index 35f108f84883c..e3b2954cccd6d 100644 --- a/x-pack/plugins/integration_assistant/public/types.ts +++ b/x-pack/plugins/integration_assistant/public/types.ts @@ -4,25 +4,43 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import type { LicensingPluginSetup, LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { TriggersAndActionsUIPublicPluginSetup, TriggersAndActionsUIPublicPluginStart, } from '@kbn/triggers-actions-ui-plugin/public'; import type { CreateIntegrationComponent } from './components/create_integration/types'; import type { CreateIntegrationCardButtonComponent } from './components/create_integration_card_button/types'; +import type { RenderUpselling } from './services'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface IntegrationAssistantPluginSetup {} export interface IntegrationAssistantPluginStart { - CreateIntegration: CreateIntegrationComponent; - CreateIntegrationCardButton: CreateIntegrationCardButtonComponent; + components: { + /** + * Component that allows the user to create an integration. + */ + CreateIntegration: CreateIntegrationComponent; + /** + * Component that links the user to the create integration component. + */ + CreateIntegrationCardButton: CreateIntegrationCardButtonComponent; + }; + /** + * Sets the upselling to be rendered in the UI. + * If defined, the section will be displayed and it will prevent + * the user from interacting with the rest of the UI. + */ + renderUpselling: (upselling: RenderUpselling | undefined) => void; } export interface IntegrationAssistantPluginSetupDependencies { triggersActionsUi: TriggersAndActionsUIPublicPluginSetup; + licensing: LicensingPluginSetup; } export interface IntegrationAssistantPluginStartDependencies { triggersActionsUi: TriggersAndActionsUIPublicPluginStart; + licensing: LicensingPluginStart; } diff --git a/x-pack/plugins/integration_assistant/server/__mocks__/mock_server.ts b/x-pack/plugins/integration_assistant/server/__mocks__/mock_server.ts index 35eb7fd28103a..d2ece6e91ead6 100644 --- a/x-pack/plugins/integration_assistant/server/__mocks__/mock_server.ts +++ b/x-pack/plugins/integration_assistant/server/__mocks__/mock_server.ts @@ -91,7 +91,6 @@ class MockServer { public async inject(request: KibanaRequest, context: RequestHandlerContext = this.contextMock) { const validatedRequest = this.validateRequest(request); - const [rejection] = this.resultMock.badRequest.mock.calls; if (rejection) { throw new Error(`Request was rejected with message: '${rejection}'`); diff --git a/x-pack/plugins/integration_assistant/server/__mocks__/request_context.ts b/x-pack/plugins/integration_assistant/server/__mocks__/request_context.ts index f7b27c353e2af..95292e491cc73 100644 --- a/x-pack/plugins/integration_assistant/server/__mocks__/request_context.ts +++ b/x-pack/plugins/integration_assistant/server/__mocks__/request_context.ts @@ -72,6 +72,7 @@ const createRequestContextMock = (clients: MockClients = createMockClients()) => ]; } ), + isAvailable: jest.fn((): boolean => true), logger: loggerMock.create(), }, }; diff --git a/x-pack/plugins/integration_assistant/server/__mocks__/test_adapters.ts b/x-pack/plugins/integration_assistant/server/__mocks__/test_adapters.ts index 8972c5f102bdb..61fbf03e4c14d 100644 --- a/x-pack/plugins/integration_assistant/server/__mocks__/test_adapters.ts +++ b/x-pack/plugins/integration_assistant/server/__mocks__/test_adapters.ts @@ -40,6 +40,11 @@ const buildResponses = (method: Method, calls: MockCall[]): ResponseCall[] => { status: 400, body: call.body, })); + case 'notFound': + return calls.map(([call]) => ({ + status: 404, + body: call.body, + })); default: throw new Error(`Encountered unexpected call to response.${method}`); } diff --git a/x-pack/plugins/integration_assistant/server/plugin.ts b/x-pack/plugins/integration_assistant/server/plugin.ts index 4f7dfa87291cc..247ebee04739e 100644 --- a/x-pack/plugins/integration_assistant/server/plugin.ts +++ b/x-pack/plugins/integration_assistant/server/plugin.ts @@ -15,13 +15,18 @@ import type { } from '@kbn/core/server'; import type { PluginStartContract as ActionsPluginsStart } from '@kbn/actions-plugin/server/plugin'; import { registerRoutes } from './routes'; -import type { IntegrationAssistantPluginSetup, IntegrationAssistantPluginStart } from './types'; +import type { + IntegrationAssistantPluginSetup, + IntegrationAssistantPluginStart, + IntegrationAssistantPluginStartDependencies, +} from './types'; export type IntegrationAssistantRouteHandlerContext = CustomRequestHandlerContext<{ integrationAssistant: { getStartServices: CoreSetup<{ actions: ActionsPluginsStart; }>['getStartServices']; + isAvailable: () => boolean; logger: Logger; }; }>; @@ -30,20 +35,24 @@ export class IntegrationAssistantPlugin implements Plugin { private readonly logger: Logger; + private isAvailable: boolean; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); + this.isAvailable = true; } + public setup( core: CoreSetup<{ actions: ActionsPluginsStart; }> - ) { + ): IntegrationAssistantPluginSetup { core.http.registerRouteHandlerContext< IntegrationAssistantRouteHandlerContext, 'integrationAssistant' >('integrationAssistant', () => ({ getStartServices: core.getStartServices, + isAvailable: () => this.isAvailable, logger: this.logger, })); const router = core.http.createRouter(); @@ -51,11 +60,28 @@ export class IntegrationAssistantPlugin registerRoutes(router); - return {}; + return { + setIsAvailable: (isAvailable: boolean) => { + if (!isAvailable) { + this.isAvailable = false; + } + }, + }; } - public start(core: CoreStart) { + public start( + _: CoreStart, + dependencies: IntegrationAssistantPluginStartDependencies + ): IntegrationAssistantPluginStart { this.logger.debug('integrationAssistant api: Started'); + const { licensing } = dependencies; + + licensing.license$.subscribe((license) => { + if (!license.hasAtLeast('enterprise')) { + this.isAvailable = false; + } + }); + return {}; } diff --git a/x-pack/plugins/integration_assistant/server/routes/build_integration_routes.test.ts b/x-pack/plugins/integration_assistant/server/routes/build_integration_routes.test.ts index 6809d826cb81f..3b73e4afb1c94 100644 --- a/x-pack/plugins/integration_assistant/server/routes/build_integration_routes.test.ts +++ b/x-pack/plugins/integration_assistant/server/routes/build_integration_routes.test.ts @@ -58,4 +58,15 @@ describe('registerIntegrationBuilderRoutes', () => { expect(response.body).toEqual({ test: 'test' }); expect(response.status).toEqual(200); }); + + describe('when the integration assistant is not available', () => { + beforeEach(() => { + context.integrationAssistant.isAvailable.mockReturnValue(false); + }); + + it('returns a 404', async () => { + const response = await server.inject(req, requestContextMock.convertContext(context)); + expect(response.status).toEqual(404); + }); + }); }); diff --git a/x-pack/plugins/integration_assistant/server/routes/build_integration_routes.ts b/x-pack/plugins/integration_assistant/server/routes/build_integration_routes.ts index e9f0d000e040a..d4b4424f5c844 100644 --- a/x-pack/plugins/integration_assistant/server/routes/build_integration_routes.ts +++ b/x-pack/plugins/integration_assistant/server/routes/build_integration_routes.ts @@ -10,6 +10,7 @@ import { BuildIntegrationRequestBody, INTEGRATION_BUILDER_PATH } from '../../com import { buildPackage } from '../integration_builder'; import type { IntegrationAssistantRouteHandlerContext } from '../plugin'; import { buildRouteValidationWithZod } from '../util/route_validation'; +import { withAvailability } from './with_availability'; export function registerIntegrationBuilderRoutes( router: IRouter @@ -28,7 +29,7 @@ export function registerIntegrationBuilderRoutes( }, }, }, - async (_, request, response) => { + withAvailability(async (_, request, response) => { const { integration } = request.body; try { const zippedIntegration = await buildPackage(integration); @@ -40,6 +41,6 @@ export function registerIntegrationBuilderRoutes( } catch (e) { return response.customError({ statusCode: 500, body: e }); } - } + }) ); } diff --git a/x-pack/plugins/integration_assistant/server/routes/categorization_routes.test.ts b/x-pack/plugins/integration_assistant/server/routes/categorization_routes.test.ts index 5a62162eae543..1f8038d3e75cc 100644 --- a/x-pack/plugins/integration_assistant/server/routes/categorization_routes.test.ts +++ b/x-pack/plugins/integration_assistant/server/routes/categorization_routes.test.ts @@ -64,4 +64,15 @@ describe('registerCategorizationRoute', () => { const response = await server.inject(req, requestContextMock.convertContext(context)); expect(response.status).toEqual(400); }); + + describe('when the integration assistant is not available', () => { + beforeEach(() => { + context.integrationAssistant.isAvailable.mockReturnValue(false); + }); + + it('returns a 404', async () => { + const response = await server.inject(req, requestContextMock.convertContext(context)); + expect(response.status).toEqual(404); + }); + }); }); diff --git a/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts b/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts index 6654898bd0232..772ee8f2527b8 100644 --- a/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts +++ b/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts @@ -20,6 +20,7 @@ import { ROUTE_HANDLER_TIMEOUT } from '../constants'; import { getCategorizationGraph } from '../graphs/categorization'; import type { IntegrationAssistantRouteHandlerContext } from '../plugin'; import { buildRouteValidationWithZod } from '../util/route_validation'; +import { withAvailability } from './with_availability'; export function registerCategorizationRoutes( router: IRouter @@ -43,49 +44,51 @@ export function registerCategorizationRoutes( }, }, }, - async (context, req, res): Promise> => { - const { packageName, dataStreamName, rawSamples, currentPipeline } = req.body; - const services = await context.resolve(['core']); - const { client } = services.core.elasticsearch; - const { getStartServices, logger } = await context.integrationAssistant; - const [, { actions: actionsPlugin }] = await getStartServices(); + withAvailability( + async (context, req, res): Promise> => { + const { packageName, dataStreamName, rawSamples, currentPipeline } = req.body; + const services = await context.resolve(['core']); + const { client } = services.core.elasticsearch; + const { getStartServices, logger } = await context.integrationAssistant; + const [, { actions: actionsPlugin }] = await getStartServices(); - try { - const actionsClient = await actionsPlugin.getActionsClientWithRequest(req); - const connector = req.body.connectorId - ? await actionsClient.get({ id: req.body.connectorId }) - : (await actionsClient.getAll()).filter( - (connectorItem) => connectorItem.actionTypeId === '.bedrock' - )[0]; + try { + const actionsClient = await actionsPlugin.getActionsClientWithRequest(req); + const connector = req.body.connectorId + ? await actionsClient.get({ id: req.body.connectorId }) + : (await actionsClient.getAll()).filter( + (connectorItem) => connectorItem.actionTypeId === '.bedrock' + )[0]; - const abortSignal = getRequestAbortedSignal(req.events.aborted$); - const isOpenAI = connector.actionTypeId === '.gen-ai'; - const llmClass = isOpenAI ? ActionsClientChatOpenAI : ActionsClientSimpleChatModel; + const abortSignal = getRequestAbortedSignal(req.events.aborted$); + const isOpenAI = connector.actionTypeId === '.gen-ai'; + const llmClass = isOpenAI ? ActionsClientChatOpenAI : ActionsClientSimpleChatModel; - const model = new llmClass({ - actions: actionsPlugin, - connectorId: connector.id, - request: req, - logger, - llmType: isOpenAI ? 'openai' : 'bedrock', - model: connector.config?.defaultModel, - temperature: 0.05, - maxTokens: 4096, - signal: abortSignal, - streaming: false, - }); + const model = new llmClass({ + actions: actionsPlugin, + connectorId: connector.id, + request: req, + logger, + llmType: isOpenAI ? 'openai' : 'bedrock', + model: connector.config?.defaultModel, + temperature: 0.05, + maxTokens: 4096, + signal: abortSignal, + streaming: false, + }); - const graph = await getCategorizationGraph(client, model); - const results = await graph.invoke({ - packageName, - dataStreamName, - rawSamples, - currentPipeline, - }); - return res.ok({ body: CategorizationResponse.parse(results) }); - } catch (e) { - return res.badRequest({ body: e }); + const graph = await getCategorizationGraph(client, model); + const results = await graph.invoke({ + packageName, + dataStreamName, + rawSamples, + currentPipeline, + }); + return res.ok({ body: CategorizationResponse.parse(results) }); + } catch (e) { + return res.badRequest({ body: e }); + } } - } + ) ); } diff --git a/x-pack/plugins/integration_assistant/server/routes/ecs_routes.test.ts b/x-pack/plugins/integration_assistant/server/routes/ecs_routes.test.ts index 70d0068eb963c..b68da3c2d56e2 100644 --- a/x-pack/plugins/integration_assistant/server/routes/ecs_routes.test.ts +++ b/x-pack/plugins/integration_assistant/server/routes/ecs_routes.test.ts @@ -69,4 +69,15 @@ describe('registerEcsRoute', () => { const response = await server.inject(req, requestContextMock.convertContext(context)); expect(response.status).toEqual(400); }); + + describe('when the integration assistant is not available', () => { + beforeEach(() => { + context.integrationAssistant.isAvailable.mockReturnValue(false); + }); + + it('returns a 404', async () => { + const response = await server.inject(req, requestContextMock.convertContext(context)); + expect(response.status).toEqual(404); + }); + }); }); diff --git a/x-pack/plugins/integration_assistant/server/routes/ecs_routes.ts b/x-pack/plugins/integration_assistant/server/routes/ecs_routes.ts index ee461b94feba4..923e0a9de4c5d 100644 --- a/x-pack/plugins/integration_assistant/server/routes/ecs_routes.ts +++ b/x-pack/plugins/integration_assistant/server/routes/ecs_routes.ts @@ -16,6 +16,7 @@ import { ROUTE_HANDLER_TIMEOUT } from '../constants'; import { getEcsGraph } from '../graphs/ecs'; import type { IntegrationAssistantRouteHandlerContext } from '../plugin'; import { buildRouteValidationWithZod } from '../util/route_validation'; +import { withAvailability } from './with_availability'; export function registerEcsRoutes(router: IRouter) { router.versioned @@ -37,9 +38,10 @@ export function registerEcsRoutes(router: IRouter> => { + withAvailability(async (context, req, res): Promise> => { const { packageName, dataStreamName, rawSamples, mapping } = req.body; const { getStartServices, logger } = await context.integrationAssistant; + const [, { actions: actionsPlugin }] = await getStartServices(); try { const actionsClient = await actionsPlugin.getActionsClientWithRequest(req); @@ -86,6 +88,6 @@ export function registerEcsRoutes(router: IRouter { }); expect(response.status).toEqual(200); }); + + describe('when the integration assistant is not available', () => { + beforeEach(() => { + context.integrationAssistant.isAvailable.mockReturnValue(false); + }); + + it('returns a 404', async () => { + const response = await server.inject(req, requestContextMock.convertContext(context)); + expect(response.status).toEqual(404); + }); + }); }); diff --git a/x-pack/plugins/integration_assistant/server/routes/pipeline_routes.ts b/x-pack/plugins/integration_assistant/server/routes/pipeline_routes.ts index 0bf04e566c649..37d3692923068 100644 --- a/x-pack/plugins/integration_assistant/server/routes/pipeline_routes.ts +++ b/x-pack/plugins/integration_assistant/server/routes/pipeline_routes.ts @@ -11,6 +11,7 @@ import { ROUTE_HANDLER_TIMEOUT } from '../constants'; import type { IntegrationAssistantRouteHandlerContext } from '../plugin'; import { testPipeline } from '../util/pipeline'; import { buildRouteValidationWithZod } from '../util/route_validation'; +import { withAvailability } from './with_availability'; export function registerPipelineRoutes(router: IRouter) { router.versioned @@ -32,21 +33,23 @@ export function registerPipelineRoutes(router: IRouter> => { - const { rawSamples, pipeline } = req.body; - const services = await context.resolve(['core']); - const { client } = services.core.elasticsearch; - try { - const { errors, pipelineResults } = await testPipeline(rawSamples, pipeline, client); - if (errors?.length) { - return res.badRequest({ body: JSON.stringify(errors) }); + withAvailability( + async (context, req, res): Promise> => { + const { rawSamples, pipeline } = req.body; + const services = await context.resolve(['core']); + const { client } = services.core.elasticsearch; + try { + const { errors, pipelineResults } = await testPipeline(rawSamples, pipeline, client); + if (errors?.length) { + return res.badRequest({ body: JSON.stringify(errors) }); + } + return res.ok({ + body: CheckPipelineResponse.parse({ results: { docs: pipelineResults } }), + }); + } catch (e) { + return res.badRequest({ body: e }); } - return res.ok({ - body: CheckPipelineResponse.parse({ results: { docs: pipelineResults } }), - }); - } catch (e) { - return res.badRequest({ body: e }); } - } + ) ); } diff --git a/x-pack/plugins/integration_assistant/server/routes/related_routes.test.ts b/x-pack/plugins/integration_assistant/server/routes/related_routes.test.ts index f393b36a30599..aeefa48c2e4d3 100644 --- a/x-pack/plugins/integration_assistant/server/routes/related_routes.test.ts +++ b/x-pack/plugins/integration_assistant/server/routes/related_routes.test.ts @@ -64,4 +64,15 @@ describe('registerRelatedRoutes', () => { const response = await server.inject(req, requestContextMock.convertContext(context)); expect(response.status).toEqual(400); }); + + describe('when the integration assistant is not available', () => { + beforeEach(() => { + context.integrationAssistant.isAvailable.mockReturnValue(false); + }); + + it('returns a 404', async () => { + const response = await server.inject(req, requestContextMock.convertContext(context)); + expect(response.status).toEqual(404); + }); + }); }); diff --git a/x-pack/plugins/integration_assistant/server/routes/related_routes.ts b/x-pack/plugins/integration_assistant/server/routes/related_routes.ts index e612b4d3a8d58..844c011ba7f73 100644 --- a/x-pack/plugins/integration_assistant/server/routes/related_routes.ts +++ b/x-pack/plugins/integration_assistant/server/routes/related_routes.ts @@ -16,6 +16,7 @@ import { ROUTE_HANDLER_TIMEOUT } from '../constants'; import { getRelatedGraph } from '../graphs/related'; import type { IntegrationAssistantRouteHandlerContext } from '../plugin'; import { buildRouteValidationWithZod } from '../util/route_validation'; +import { withAvailability } from './with_availability'; export function registerRelatedRoutes(router: IRouter) { router.versioned @@ -37,7 +38,7 @@ export function registerRelatedRoutes(router: IRouter> => { + withAvailability(async (context, req, res): Promise> => { const { packageName, dataStreamName, rawSamples, currentPipeline } = req.body; const services = await context.resolve(['core']); const { client } = services.core.elasticsearch; @@ -79,6 +80,6 @@ export function registerRelatedRoutes(router: IRouter( + handler: RequestHandler +): RequestHandler => { + return async (context, req, res) => { + const { isAvailable } = await context.integrationAssistant; + if (!isAvailable()) { + return res.notFound({ + body: { message: 'This API route is not available using your current license/tier.' }, + }); + } + return handler(context, req, res); + }; +}; diff --git a/x-pack/plugins/integration_assistant/server/types.ts b/x-pack/plugins/integration_assistant/server/types.ts index 54713df18b0d8..503c318648bad 100644 --- a/x-pack/plugins/integration_assistant/server/types.ts +++ b/x-pack/plugins/integration_assistant/server/types.ts @@ -5,11 +5,21 @@ * 2.0. */ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface IntegrationAssistantPluginSetup {} +import type { LicensingPluginSetup, LicensingPluginStart } from '@kbn/licensing-plugin/server'; + +export interface IntegrationAssistantPluginSetup { + setIsAvailable: (isAvailable: boolean) => void; +} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface IntegrationAssistantPluginStart {} +export interface IntegrationAssistantPluginSetupDependencies { + licensing: LicensingPluginSetup; +} +export interface IntegrationAssistantPluginStartDependencies { + licensing: LicensingPluginStart; +} + export interface CategorizationState { rawSamples: string[]; samples: string[]; diff --git a/x-pack/plugins/integration_assistant/tsconfig.json b/x-pack/plugins/integration_assistant/tsconfig.json index ec7a7e094997d..47087c83731a0 100644 --- a/x-pack/plugins/integration_assistant/tsconfig.json +++ b/x-pack/plugins/integration_assistant/tsconfig.json @@ -33,6 +33,7 @@ "@kbn/stack-connectors-plugin", "@kbn/core-analytics-browser", "@kbn/logging-mocks", + "@kbn/licensing-plugin", "@kbn/core-http-request-handler-context-server", "@kbn/core-http-router-server-mocks", "@kbn/core-http-server" diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/step_links/add_integration_buttons.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/step_links/add_integration_buttons.tsx index 4bbf13d617b8c..2ee5928a4c786 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/step_links/add_integration_buttons.tsx +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/step_links/add_integration_buttons.tsx @@ -156,7 +156,7 @@ AddIntegrationPanel.displayName = 'AddIntegrationPanel'; export const AddIntegrationButtons: React.FC = React.memo(() => { const { integrationAssistant } = useKibana().services; - const CreateIntegrationCardButton = integrationAssistant?.CreateIntegrationCardButton; + const { CreateIntegrationCardButton } = integrationAssistant?.components ?? {}; return ( diff --git a/x-pack/plugins/security_solution_serverless/common/pli/pli_config.ts b/x-pack/plugins/security_solution_serverless/common/pli/pli_config.ts index f4365787b9189..9a6eb9ab743ca 100644 --- a/x-pack/plugins/security_solution_serverless/common/pli/pli_config.ts +++ b/x-pack/plugins/security_solution_serverless/common/pli/pli_config.ts @@ -27,6 +27,7 @@ export const PLI_PRODUCT_FEATURES: PliProductFeatures = { ProductFeatureKey.threatIntelligence, ProductFeatureKey.casesConnectors, ProductFeatureKey.externalRuleActions, + ProductFeatureKey.integrationAssistant, ], }, endpoint: { diff --git a/x-pack/plugins/security_solution_serverless/kibana.jsonc b/x-pack/plugins/security_solution_serverless/kibana.jsonc index 76fb90ec236c6..1829503bfe988 100644 --- a/x-pack/plugins/security_solution_serverless/kibana.jsonc +++ b/x-pack/plugins/security_solution_serverless/kibana.jsonc @@ -24,7 +24,8 @@ "discover" ], "optionalPlugins": [ - "securitySolutionEss" + "securitySolutionEss", + "integrationAssistant" ], } } diff --git a/x-pack/plugins/security_solution_serverless/public/plugin.ts b/x-pack/plugins/security_solution_serverless/public/plugin.ts index 679884a0c1406..8ea73d406cb3e 100644 --- a/x-pack/plugins/security_solution_serverless/public/plugin.ts +++ b/x-pack/plugins/security_solution_serverless/public/plugin.ts @@ -64,10 +64,9 @@ export class SecuritySolutionServerlessPlugin ): SecuritySolutionServerlessPluginStart { const { securitySolution } = startDeps; const { productTypes } = this.config; - const services = createServices(core, startDeps, this.experimentalFeatures); - registerUpsellings(securitySolution.getUpselling(), productTypes, services); + registerUpsellings(productTypes, services); securitySolution.setComponents({ DashboardsLandingCallout: getDashboardsLandingCallout(services), diff --git a/x-pack/plugins/security_solution_serverless/public/types.ts b/x-pack/plugins/security_solution_serverless/public/types.ts index 11ce38c77642b..0e47917f8122c 100644 --- a/x-pack/plugins/security_solution_serverless/public/types.ts +++ b/x-pack/plugins/security_solution_serverless/public/types.ts @@ -14,6 +14,7 @@ import type { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverle import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public'; import type { CloudStart } from '@kbn/cloud-plugin/public'; import type { DiscoverSetup } from '@kbn/discover-plugin/public'; +import type { IntegrationAssistantPluginStart } from '@kbn/integration-assistant-plugin/public'; import type { ServerlessSecurityConfigSchema } from '../common/config'; // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -36,6 +37,7 @@ export interface SecuritySolutionServerlessPluginStartDeps { serverless: ServerlessPluginStart; management: ManagementStart; cloud: CloudStart; + integrationAssistant?: IntegrationAssistantPluginStart; } export type ServerlessSecurityPublicConfig = Pick< diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/hooks/use_product_type_by_pli.ts b/x-pack/plugins/security_solution_serverless/public/upselling/hooks/use_product_type_by_pli.ts index 46d09cbae6c26..da98c3003c2a2 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/hooks/use_product_type_by_pli.ts +++ b/x-pack/plugins/security_solution_serverless/public/upselling/hooks/use_product_type_by_pli.ts @@ -6,6 +6,7 @@ */ import type { ProductFeatureKeyType } from '@kbn/security-solution-features'; +import { useMemo } from 'react'; import { PLI_PRODUCT_FEATURES } from '../../../common/pli/pli_config'; export const getProductTypeByPLI = (requiredPLI: ProductFeatureKeyType): string | null => { @@ -29,3 +30,7 @@ export const getProductTypeByPLI = (requiredPLI: ProductFeatureKeyType): string } return null; }; + +export const useProductTypeByPLI = (requiredPLI: ProductFeatureKeyType): string => { + return useMemo(() => getProductTypeByPLI(requiredPLI) ?? '', [requiredPLI]); +}; diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.test.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.test.tsx index 542ab4cd11fb0..9eeafe816f09b 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.test.tsx +++ b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.test.tsx @@ -5,23 +5,31 @@ * 2.0. */ -import { - registerUpsellings, - upsellingMessages, - upsellingPages, - upsellingSections, -} from './register_upsellings'; +import { registerUpsellings } from './register_upsellings'; +import { upsellingMessages, upsellingPages, upsellingSections } from './upsellings'; import { ProductLine, ProductTier } from '../../common/product'; import type { SecurityProductTypes } from '../../common/config'; import { ALL_PRODUCT_FEATURE_KEYS } from '@kbn/security-solution-features/keys'; import type { UpsellingService } from '@kbn/security-solution-upselling/service'; import { mockServices } from '../common/services/__mocks__/services.mock'; +import { of } from 'rxjs'; const mockGetProductProductFeatures = jest.fn(); jest.mock('../../common/pli/pli_features', () => ({ getProductProductFeatures: () => mockGetProductProductFeatures(), })); +const setPages = jest.fn(); +const setSections = jest.fn(); +const setMessages = jest.fn(); +const upselling = { + setPages, + setSections, + setMessages, + sections$: of([]), +} as unknown as UpsellingService; +mockServices.securitySolution.getUpselling = jest.fn(() => upselling); + const allProductTypes: SecurityProductTypes = [ { product_line: ProductLine.security, product_tier: ProductTier.complete }, { product_line: ProductLine.endpoint, product_tier: ProductTier.complete }, @@ -29,19 +37,14 @@ const allProductTypes: SecurityProductTypes = [ ]; describe('registerUpsellings', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + it('should not register anything when all PLIs features are enabled', () => { mockGetProductProductFeatures.mockReturnValue(ALL_PRODUCT_FEATURE_KEYS); - const setPages = jest.fn(); - const setSections = jest.fn(); - const setMessages = jest.fn(); - const upselling = { - setPages, - setSections, - setMessages, - } as unknown as UpsellingService; - - registerUpsellings(upselling, allProductTypes, mockServices); + registerUpsellings(allProductTypes, mockServices); expect(setPages).toHaveBeenCalledTimes(1); expect(setPages).toHaveBeenCalledWith({}); @@ -56,17 +59,7 @@ describe('registerUpsellings', () => { it('should register all upsellings pages, sections and messages when PLIs features are disabled', () => { mockGetProductProductFeatures.mockReturnValue([]); - const setPages = jest.fn(); - const setSections = jest.fn(); - const setMessages = jest.fn(); - - const upselling = { - setPages, - setSections, - setMessages, - } as unknown as UpsellingService; - - registerUpsellings(upselling, allProductTypes, mockServices); + registerUpsellings(allProductTypes, mockServices); const expectedPagesObject = Object.fromEntries( upsellingPages.map(({ pageName }) => [pageName, expect.anything()]) diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx index ef4574424b42c..851bf6010cb44 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx +++ b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx @@ -4,63 +4,32 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { ProductFeatureKeyType } from '@kbn/security-solution-features'; -import { ProductFeatureKey } from '@kbn/security-solution-features/keys'; -import { SecurityPageName } from '@kbn/security-solution-plugin/common'; -import { - UPGRADE_INVESTIGATION_GUIDE, - UPGRADE_INVESTIGATION_GUIDE_INTERACTIONS, -} from '@kbn/security-solution-upselling/messages'; import type { UpsellingService } from '@kbn/security-solution-upselling/service'; import type { MessageUpsellings, PageUpsellings, SectionUpsellings, - UpsellingMessageId, - UpsellingSectionId, } from '@kbn/security-solution-upselling/service/types'; -import React from 'react'; -import { CloudSecurityPostureIntegrationPliBlockLazy } from './sections/cloud_security_posture'; -import { - EndpointAgentTamperProtectionLazy, - EndpointPolicyProtectionsLazy, - EndpointProtectionUpdatesLazy, - RuleDetailsEndpointExceptionsLazy, -} from './sections/endpoint_management'; import type { SecurityProductTypes } from '../../common/config'; import { getProductProductFeatures } from '../../common/pli/pli_features'; import type { Services } from '../common/services'; import { withServicesProvider } from '../common/services'; -import { getProductTypeByPLI } from './hooks/use_product_type_by_pli'; -import { - EndpointExceptionsDetailsUpsellingLazy, - EntityAnalyticsUpsellingPageLazy, - EntityAnalyticsUpsellingSectionLazy, - OsqueryResponseActionsUpsellingSectionLazy, - ThreatIntelligencePaywallLazy, -} from './lazy_upselling'; -import * as i18n from './translations'; +import { upsellingPages, upsellingSections, upsellingMessages } from './upsellings'; -interface UpsellingsConfig { - pli: ProductFeatureKeyType; - component: React.ComponentType; -} - -interface UpsellingsMessageConfig { - pli: ProductFeatureKeyType; - message: string; - id: UpsellingMessageId; -} - -type UpsellingPages = Array; -type UpsellingSections = Array; -type UpsellingMessages = UpsellingsMessageConfig[]; +export const registerUpsellings = (productTypes: SecurityProductTypes, services: Services) => { + const upsellingService = registerSecuritySolutionUpsellings(productTypes, services); + configurePluginsUpsellings(upsellingService, services); +}; -export const registerUpsellings = ( - upselling: UpsellingService, +/** + * Registers the upsellings for the security solution. + */ +const registerSecuritySolutionUpsellings = ( productTypes: SecurityProductTypes, services: Services -) => { +): UpsellingService => { + const upsellingService = services.securitySolution.getUpselling(); + const enabledPLIsSet = new Set(getProductProductFeatures(productTypes)); const upsellingPagesToRegister = upsellingPages.reduce( @@ -93,105 +62,20 @@ export const registerUpsellings = ( {} ); - upselling.setPages(upsellingPagesToRegister); - upselling.setSections(upsellingSectionsToRegister); - upselling.setMessages(upsellingMessagesToRegister); -}; - -// Upselling for entire pages, linked to a SecurityPageName -export const upsellingPages: UpsellingPages = [ - // It is highly advisable to make use of lazy loaded components to minimize bundle size. - { - pageName: SecurityPageName.entityAnalytics, - pli: ProductFeatureKey.advancedInsights, - component: () => ( - - ), - }, - { - pageName: SecurityPageName.threatIntelligence, - pli: ProductFeatureKey.threatIntelligence, - component: () => ( - - ), - }, - { - pageName: SecurityPageName.exceptions, - pli: ProductFeatureKey.endpointExceptions, - component: () => ( - - ), - }, -]; + upsellingService.setPages(upsellingPagesToRegister); + upsellingService.setSections(upsellingSectionsToRegister); + upsellingService.setMessages(upsellingMessagesToRegister); -const entityAnalyticsProductType = getProductTypeByPLI(ProductFeatureKey.advancedInsights) ?? ''; + return upsellingService; +}; -// Upselling for sections, linked by arbitrary ids -export const upsellingSections: UpsellingSections = [ - // It is highly advisable to make use of lazy loaded components to minimize bundle size. - { - id: 'osquery_automated_response_actions', - pli: ProductFeatureKey.osqueryAutomatedResponseActions, - component: () => ( - - ), - }, - { - id: 'endpoint_agent_tamper_protection', - pli: ProductFeatureKey.endpointAgentTamperProtection, - component: EndpointAgentTamperProtectionLazy, - }, - { - id: 'endpointPolicyProtections', - pli: ProductFeatureKey.endpointPolicyProtections, - component: EndpointPolicyProtectionsLazy, - }, - { - id: 'ruleDetailsEndpointExceptions', - pli: ProductFeatureKey.endpointExceptions, - component: RuleDetailsEndpointExceptionsLazy, - }, - { - id: 'endpoint_protection_updates', - pli: ProductFeatureKey.endpointProtectionUpdates, - component: EndpointProtectionUpdatesLazy, - }, - { - id: 'cloud_security_posture_integration_installation', - pli: ProductFeatureKey.cloudSecurityPosture, - component: CloudSecurityPostureIntegrationPliBlockLazy, - }, - { - id: 'entity_analytics_panel', - pli: ProductFeatureKey.advancedInsights, - component: () => ( - - ), - }, -]; +/** + * Configures the upsellings for other plugins. + */ +const configurePluginsUpsellings = (upsellingService: UpsellingService, services: Services) => { + const { integrationAssistant } = services; -// Upselling for sections, linked by arbitrary ids -export const upsellingMessages: UpsellingMessages = [ - { - id: 'investigation_guide', - pli: ProductFeatureKey.investigationGuide, - message: UPGRADE_INVESTIGATION_GUIDE( - getProductTypeByPLI(ProductFeatureKey.investigationGuide) ?? '' - ), - }, - { - id: 'investigation_guide_interactions', - pli: ProductFeatureKey.investigationGuideInteractions, - message: UPGRADE_INVESTIGATION_GUIDE_INTERACTIONS( - getProductTypeByPLI(ProductFeatureKey.investigationGuideInteractions) ?? '' - ), - }, -]; + upsellingService.sections$.subscribe((sections) => { + integrationAssistant?.renderUpselling(sections.get('integration_assistant')); + }); +}; diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/sections/integration_assistant/index.ts b/x-pack/plugins/security_solution_serverless/public/upselling/sections/integration_assistant/index.ts new file mode 100644 index 0000000000000..320ed8be7ffc1 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/upselling/sections/integration_assistant/index.ts @@ -0,0 +1,14 @@ +/* + * 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 { lazy } from 'react'; + +export const IntegrationsAssistantLazy = lazy(() => + import('./integration_assistant').then(({ IntegrationsAssistant }) => ({ + default: IntegrationsAssistant, + })) +); diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/sections/integration_assistant/integration_assistant.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/sections/integration_assistant/integration_assistant.tsx new file mode 100644 index 0000000000000..7d1d797c1ecad --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/upselling/sections/integration_assistant/integration_assistant.tsx @@ -0,0 +1,87 @@ +/* + * 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 { + EuiCard, + EuiIcon, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiTextColor, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import type { ProductFeatureKeyType } from '@kbn/security-solution-features'; +import { useProductTypeByPLI } from '../../hooks/use_product_type_by_pli'; + +export const UPGRADE_PRODUCT_MESSAGE = (requiredProductType: string) => + i18n.translate( + 'xpack.securitySolutionServerless.upselling.integrationAssistant.upgradeProductMessage', + { + defaultMessage: + 'To turn on the Integration Assistant feature, you must upgrade the product tier to {requiredProductType}', + values: { + requiredProductType, + }, + } + ); +export const TIER_REQUIRED = (requiredProductType: string) => + i18n.translate('xpack.securitySolutionServerless.upselling.integrationAssistant.tierRequired', { + defaultMessage: '{requiredProductType} tier required', + values: { + requiredProductType, + }, + }); +export const CONTACT_ADMINISTRATOR = i18n.translate( + 'xpack.securitySolutionServerless.upselling.integrationAssistant.contactAdministrator', + { + defaultMessage: 'Contact your administrator for assistance.', + } +); + +export interface IntegrationsAssistantProps { + requiredPLI: ProductFeatureKeyType; +} +export const IntegrationsAssistant = React.memo(({ requiredPLI }) => { + const requiredProductType = useProductTypeByPLI(requiredPLI); + return ( + <> + + } + title={ +

      + {TIER_REQUIRED(requiredProductType)} +

      + } + description={false} + > + + + + +

      + + {UPGRADE_PRODUCT_MESSAGE(requiredProductType)} + +

      +
      +
      + + {CONTACT_ADMINISTRATOR} + +
      +
      + + ); +}); +IntegrationsAssistant.displayName = 'IntegrationsAssistant'; diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/upsellings.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/upsellings.tsx new file mode 100644 index 0000000000000..cb0e1514b1df5 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/upselling/upsellings.tsx @@ -0,0 +1,155 @@ +/* + * 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 { ProductFeatureKeyType } from '@kbn/security-solution-features'; +import { ProductFeatureKey } from '@kbn/security-solution-features/keys'; +import { SecurityPageName } from '@kbn/security-solution-plugin/common'; +import { + UPGRADE_INVESTIGATION_GUIDE, + UPGRADE_INVESTIGATION_GUIDE_INTERACTIONS, +} from '@kbn/security-solution-upselling/messages'; +import type { + UpsellingMessageId, + UpsellingSectionId, +} from '@kbn/security-solution-upselling/service/types'; +import React from 'react'; +import { CloudSecurityPostureIntegrationPliBlockLazy } from './sections/cloud_security_posture'; +import { + EndpointAgentTamperProtectionLazy, + EndpointPolicyProtectionsLazy, + EndpointProtectionUpdatesLazy, + RuleDetailsEndpointExceptionsLazy, +} from './sections/endpoint_management'; +import { getProductTypeByPLI } from './hooks/use_product_type_by_pli'; +import { + EndpointExceptionsDetailsUpsellingLazy, + EntityAnalyticsUpsellingPageLazy, + EntityAnalyticsUpsellingSectionLazy, + OsqueryResponseActionsUpsellingSectionLazy, + ThreatIntelligencePaywallLazy, +} from './lazy_upselling'; +import * as i18n from './translations'; +import { IntegrationsAssistantLazy } from './sections/integration_assistant'; + +interface UpsellingsConfig { + pli: ProductFeatureKeyType; + component: React.ComponentType; +} + +interface UpsellingsMessageConfig { + pli: ProductFeatureKeyType; + message: string; + id: UpsellingMessageId; +} + +type UpsellingPages = Array; +type UpsellingSections = Array; +type UpsellingMessages = UpsellingsMessageConfig[]; + +// Upselling for entire pages, linked to a SecurityPageName +export const upsellingPages: UpsellingPages = [ + // It is highly advisable to make use of lazy loaded components to minimize bundle size. + { + pageName: SecurityPageName.entityAnalytics, + pli: ProductFeatureKey.advancedInsights, + component: () => ( + + ), + }, + { + pageName: SecurityPageName.threatIntelligence, + pli: ProductFeatureKey.threatIntelligence, + component: () => ( + + ), + }, + { + pageName: SecurityPageName.exceptions, + pli: ProductFeatureKey.endpointExceptions, + component: () => ( + + ), + }, +]; + +const entityAnalyticsProductType = getProductTypeByPLI(ProductFeatureKey.advancedInsights) ?? ''; + +// Upselling for sections, linked by arbitrary ids +export const upsellingSections: UpsellingSections = [ + // It is highly advisable to make use of lazy loaded components to minimize bundle size. + { + id: 'osquery_automated_response_actions', + pli: ProductFeatureKey.osqueryAutomatedResponseActions, + component: () => ( + + ), + }, + { + id: 'endpoint_agent_tamper_protection', + pli: ProductFeatureKey.endpointAgentTamperProtection, + component: EndpointAgentTamperProtectionLazy, + }, + { + id: 'endpointPolicyProtections', + pli: ProductFeatureKey.endpointPolicyProtections, + component: EndpointPolicyProtectionsLazy, + }, + { + id: 'ruleDetailsEndpointExceptions', + pli: ProductFeatureKey.endpointExceptions, + component: RuleDetailsEndpointExceptionsLazy, + }, + { + id: 'endpoint_protection_updates', + pli: ProductFeatureKey.endpointProtectionUpdates, + component: EndpointProtectionUpdatesLazy, + }, + { + id: 'cloud_security_posture_integration_installation', + pli: ProductFeatureKey.cloudSecurityPosture, + component: CloudSecurityPostureIntegrationPliBlockLazy, + }, + { + id: 'entity_analytics_panel', + pli: ProductFeatureKey.advancedInsights, + component: () => ( + + ), + }, + { + id: 'integration_assistant', + pli: ProductFeatureKey.integrationAssistant, + component: () => ( + + ), + }, +]; + +// Upselling for sections, linked by arbitrary ids +export const upsellingMessages: UpsellingMessages = [ + { + id: 'investigation_guide', + pli: ProductFeatureKey.investigationGuide, + message: UPGRADE_INVESTIGATION_GUIDE( + getProductTypeByPLI(ProductFeatureKey.investigationGuide) ?? '' + ), + }, + { + id: 'investigation_guide_interactions', + pli: ProductFeatureKey.investigationGuideInteractions, + message: UPGRADE_INVESTIGATION_GUIDE_INTERACTIONS( + getProductTypeByPLI(ProductFeatureKey.investigationGuideInteractions) ?? '' + ), + }, +]; diff --git a/x-pack/plugins/security_solution_serverless/server/plugin.ts b/x-pack/plugins/security_solution_serverless/server/plugin.ts index a7783ad37d53c..d63152f169490 100644 --- a/x-pack/plugins/security_solution_serverless/server/plugin.ts +++ b/x-pack/plugins/security_solution_serverless/server/plugin.ts @@ -26,13 +26,12 @@ import type { } from './types'; import { SecurityUsageReportingTask } from './task_manager/usage_reporting_task'; import { cloudSecurityMetringTaskProperties } from './cloud_security/cloud_security_metering_task_config'; -import { getProductProductFeaturesConfigurator, getSecurityProductTier } from './product_features'; +import { registerProductFeatures, getSecurityProductTier } from './product_features'; import { METERING_TASK as ENDPOINT_METERING_TASK } from './endpoint/constants/metering'; import { endpointMeteringService, setEndpointPackagePolicyServerlessBillingFlags, } from './endpoint/services'; -import { enableRuleActions } from './rules/enable_rule_actions'; import { NLPCleanupTask } from './task_manager/nlp_cleanup_task/nlp_cleanup_task'; import { telemetryEvents } from './telemetry/event_based_telemetry'; @@ -54,34 +53,25 @@ export class SecuritySolutionServerlessPlugin constructor(private readonly initializerContext: PluginInitializerContext) { this.config = this.initializerContext.config.get(); this.logger = this.initializerContext.logger.get(); + + const productTypesStr = JSON.stringify(this.config.productTypes, null, 2); + this.logger.info(`Security Solution running with product types:\n${productTypesStr}`); } public setup(coreSetup: CoreSetup, pluginsSetup: SecuritySolutionServerlessPluginSetupDeps) { this.config = createConfig(this.initializerContext, pluginsSetup.securitySolution); - const enabledProductFeatures = getProductProductFeatures(this.config.productTypes); - // securitySolutionEss plugin should always be disabled when securitySolutionServerless is enabled. - // This check is an additional layer of security to prevent double registrations when - // `plugins.forceEnableAllPlugins` flag is enabled. Should never happen in real scenarios. - const shouldRegister = pluginsSetup.securitySolutionEss == null; - if (shouldRegister) { - const productTypesStr = JSON.stringify(this.config.productTypes, null, 2); - this.logger.info(`Security Solution running with product types:\n${productTypesStr}`); - const productFeaturesConfigurator = getProductProductFeaturesConfigurator( - enabledProductFeatures, - this.config - ); - pluginsSetup.securitySolution.setProductFeaturesConfigurator(productFeaturesConfigurator); - } + // Register product features + const enabledProductFeatures = getProductProductFeatures(this.config.productTypes); + registerProductFeatures(pluginsSetup, enabledProductFeatures, this.config); // Register telemetry events telemetryEvents.forEach((eventConfig) => coreSetup.analytics.registerEventType(eventConfig)); - enableRuleActions({ - actions: pluginsSetup.actions, - productFeatureKeys: enabledProductFeatures, - }); + // Setup project uiSettings whitelisting + pluginsSetup.serverless.setupProjectSettings(SECURITY_PROJECT_SETTINGS); + // Tasks this.cloudSecurityUsageReportingTask = new SecurityUsageReportingTask({ core: coreSetup, logFactory: this.initializerContext.logger, @@ -113,8 +103,6 @@ export class SecuritySolutionServerlessPlugin taskManager: pluginsSetup.taskManager, }); - pluginsSetup.serverless.setupProjectSettings(SECURITY_PROJECT_SETTINGS); - return {}; } diff --git a/x-pack/plugins/security_solution_serverless/server/product_features/index.ts b/x-pack/plugins/security_solution_serverless/server/product_features/index.ts index 40ae2a4253cdd..6c0b2b9091c66 100644 --- a/x-pack/plugins/security_solution_serverless/server/product_features/index.ts +++ b/x-pack/plugins/security_solution_serverless/server/product_features/index.ts @@ -5,30 +5,56 @@ * 2.0. */ -import type { ProductFeatureKeys } from '@kbn/security-solution-features'; -import type { ProductFeaturesConfigurator } from '@kbn/security-solution-plugin/server/lib/product_features_service/types'; import type { Logger } from '@kbn/logging'; -import type { ServerlessSecurityConfig } from '../config'; + +import { ProductFeatureKey } from '@kbn/security-solution-features/keys'; +import type { ProductFeatureKeys } from '@kbn/security-solution-features'; import { getCasesProductFeaturesConfigurator } from './cases_product_features_config'; import { getSecurityProductFeaturesConfigurator } from './security_product_features_config'; import { getSecurityAssistantProductFeaturesConfigurator } from './assistant_product_features_config'; -import type { Tier } from '../types'; +import { enableRuleActions } from '../rules/enable_rule_actions'; +import type { ServerlessSecurityConfig } from '../config'; +import type { Tier, SecuritySolutionServerlessPluginSetupDeps } from '../types'; import { ProductLine } from '../../common/product'; -export const getProductProductFeaturesConfigurator = ( +export const registerProductFeatures = ( + pluginsSetup: SecuritySolutionServerlessPluginSetupDeps, enabledProductFeatureKeys: ProductFeatureKeys, config: ServerlessSecurityConfig -): ProductFeaturesConfigurator => { - return { +): void => { + // securitySolutionEss plugin should always be disabled when securitySolutionServerless is enabled. + // This check is an additional layer of security to prevent double registrations when + // `plugins.forceEnableAllPlugins` flag is enabled. Should never happen in real scenarios. + const shouldRegister = pluginsSetup.securitySolutionEss == null; + if (!shouldRegister) { + return; + } + + // register product features for the main security solution product features service + pluginsSetup.securitySolution.setProductFeaturesConfigurator({ security: getSecurityProductFeaturesConfigurator( enabledProductFeatureKeys, config.experimentalFeatures ), cases: getCasesProductFeaturesConfigurator(enabledProductFeatureKeys), securityAssistant: getSecurityAssistantProductFeaturesConfigurator(enabledProductFeatureKeys), - }; + }); + + // enable rule actions based on the enabled product features + enableRuleActions({ + actions: pluginsSetup.actions, + productFeatureKeys: enabledProductFeatureKeys, + }); + + // set availability for the integration assistant plugin based on the product features + pluginsSetup.integrationAssistant?.setIsAvailable( + enabledProductFeatureKeys.includes(ProductFeatureKey.integrationAssistant) + ); }; +/** + * Get the security product tier from the security product type in the config + */ export const getSecurityProductTier = (config: ServerlessSecurityConfig, logger: Logger): Tier => { const securityProductType = config.productTypes.find( (productType) => productType.product_line === ProductLine.security diff --git a/x-pack/plugins/security_solution_serverless/server/types.ts b/x-pack/plugins/security_solution_serverless/server/types.ts index 7e84b418cf4cf..eec8185a28199 100644 --- a/x-pack/plugins/security_solution_serverless/server/types.ts +++ b/x-pack/plugins/security_solution_serverless/server/types.ts @@ -21,6 +21,7 @@ import type { FleetStartContract } from '@kbn/fleet-plugin/server'; import type { PluginSetupContract as ActionsPluginSetupContract } from '@kbn/actions-plugin/server'; import type { ServerlessPluginSetup } from '@kbn/serverless/server'; +import type { IntegrationAssistantPluginSetup } from '@kbn/integration-assistant-plugin/server'; import type { ProductTier } from '../common/product'; import type { ServerlessSecurityConfig } from './config'; @@ -39,6 +40,7 @@ export interface SecuritySolutionServerlessPluginSetupDeps { taskManager: TaskManagerSetupContract; cloud: CloudSetup; actions: ActionsPluginSetupContract; + integrationAssistant?: IntegrationAssistantPluginSetup; } export interface SecuritySolutionServerlessPluginStartDeps { diff --git a/x-pack/plugins/security_solution_serverless/tsconfig.json b/x-pack/plugins/security_solution_serverless/tsconfig.json index a1c6cefd396ca..b6bfbea1cc7be 100644 --- a/x-pack/plugins/security_solution_serverless/tsconfig.json +++ b/x-pack/plugins/security_solution_serverless/tsconfig.json @@ -44,5 +44,6 @@ "@kbn/management-cards-navigation", "@kbn/discover-plugin", "@kbn/logging", + "@kbn/integration-assistant-plugin", ] } From cf11c5fb3fb635e0a260a67fb3c7159d50232eaf Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski Date: Mon, 1 Jul 2024 15:57:27 +0200 Subject: [PATCH 015/128] [EDR Workflows] change osquery pipeline (#187222) --- .buildkite/scripts/pipelines/pull_request/pipeline.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.buildkite/scripts/pipelines/pull_request/pipeline.ts b/.buildkite/scripts/pipelines/pull_request/pipeline.ts index c6b28bc20c6f3..cd5d9aa470b3c 100644 --- a/.buildkite/scripts/pipelines/pull_request/pipeline.ts +++ b/.buildkite/scripts/pipelines/pull_request/pipeline.ts @@ -307,7 +307,11 @@ const getPipeline = (filename: string, removeSteps = true) => { } if ( - ((await doAnyChangesMatch([/^x-pack\/plugins\/osquery/, /^x-pack\/test\/osquery_cypress/])) || + ((await doAnyChangesMatch([ + /^x-pack\/plugins\/osquery/, + /^x-pack\/test\/osquery_cypress/, + /^x-pack\/plugins\/security_solution/, + ])) || GITHUB_PR_LABELS.includes('ci:all-cypress-suites')) && !GITHUB_PR_LABELS.includes('ci:skip-cypress-osquery') ) { From c7789a4fc12967b6870c8dfe5b03ecbbcbaa89ba Mon Sep 17 00:00:00 2001 From: Miriam <31922082+MiriamAparicio@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:27:28 +0100 Subject: [PATCH 016/128] [ObsUX][Infra] Fix container image name not being displayed (#187220) ## Summary Small fix, container.image.name wasn't display on the metadata summary fields image image --- .../infra/common/http_api/metadata_api.ts | 1 - .../tabs/overview/metadata_summary/metadata_summary_list.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/observability_solution/infra/common/http_api/metadata_api.ts b/x-pack/plugins/observability_solution/infra/common/http_api/metadata_api.ts index f9db4df10ed1c..f2f5bc2c07dbe 100644 --- a/x-pack/plugins/observability_solution/infra/common/http_api/metadata_api.ts +++ b/x-pack/plugins/observability_solution/infra/common/http_api/metadata_api.ts @@ -48,7 +48,6 @@ export const InfraMetadataContainerRT = rt.partial({ name: rt.string, id: rt.string, runtime: rt.string, - imageName: rt.string, image: rt.partial({ name: rt.string }), }); diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/metadata_summary/metadata_summary_list.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/metadata_summary/metadata_summary_list.tsx index fdd5b0b8aeee5..0b369c90f1390 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/metadata_summary/metadata_summary_list.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/metadata_summary/metadata_summary_list.tsx @@ -104,7 +104,7 @@ const containerMetadataData = (metadataInfo: InfraMetadata['info']): MetadataDat }, { field: 'containerImageName', - value: metadataInfo?.container?.imageName, + value: metadataInfo?.container?.image?.name, tooltipFieldLabel: 'container.image.name', }, { From 2b9d7fd607bb4c30e2bf6acb2ff89878cf8e24e2 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Mon, 1 Jul 2024 16:29:27 +0200 Subject: [PATCH 017/128] [Discover] Unskip ES|QL view flaky tests (#187218) - Closes https://github.com/elastic/kibana/issues/183493 - Closes https://github.com/elastic/kibana/issues/183479 - Closes https://github.com/elastic/kibana/issues/183193 25x https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6422 --- .../common/discover/esql/_esql_view.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts b/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts index 6ce8021024366..8e7b921d9655d 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts @@ -35,8 +35,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { defaultIndex: 'logstash-*', }; - // Failing: See https://github.com/elastic/kibana/issues/183493 - describe.skip('discover esql view', async function () { + describe('discover esql view', async function () { before(async () => { await kibanaServer.savedObjects.cleanStandardList(); log.debug('load kibana index with default index pattern'); @@ -50,8 +49,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRange(); }); - // FLAKY: https://github.com/elastic/kibana/issues/183193 - describe.skip('test', () => { + describe('test', () => { it('should render esql view correctly', async function () { await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); @@ -133,11 +131,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should query an index pattern that doesnt translate to a dataview correctly', async function () { await PageObjects.discover.selectTextBaseLang(); - const testQuery = `from logstash* | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`; + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const testQuery = `from logstash* | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`; await monacoEditor.setCodeEditorValue(testQuery); await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); const cell = await dataGrid.getCellElement(0, 2); expect(await cell.getVisibleText()).to.be('1'); @@ -175,10 +176,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/183479 - describe.skip('errors', () => { + describe('errors', () => { it('should show error messages for syntax errors in query', async function () { await PageObjects.discover.selectTextBaseLang(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const brokenQueries = [ 'from logstash-* | limit 10*', 'from logstash-* | limit A', From 9785ce0e59b6a9c6fa1241df273afe2de880883d Mon Sep 17 00:00:00 2001 From: Agustina Nahir Ruidiaz <61565784+agusruidiazgd@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:45:18 +0200 Subject: [PATCH 018/128] [Security Solution] Adding accordion to dashboard security views (#186465) ## Summary In this feature the following UI changes are required: Add an accordion layout for wrap all the image link cards. Change Layout of image cards. Remove "Default" Header Change second header label Screenshot 2024-06-19 at 15 18 37 ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Angela Chuang <6295984+angorayc@users.noreply.github.com> --- .../landing_links_image_card.stories.tsx | 117 ++++++++++++++++ .../landing_links_image_card.test.tsx | 104 ++++++++++++++ .../landing_links_image_card.tsx | 119 ++++++++++++++++ .../landing_links_images_cards.test.tsx | 71 ++-------- .../landing_links_images_cards.tsx | 127 ++++++++---------- .../src/landing_links/translations.ts | 14 ++ .../dashboards/pages/landing_page/index.tsx | 6 +- .../pages/landing_page/translations.ts | 9 +- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../ftr/cases/attachment_framework.ts | 1 + 12 files changed, 424 insertions(+), 147 deletions(-) create mode 100644 x-pack/packages/security-solution/navigation/src/landing_links/landing_links_image_card.stories.tsx create mode 100644 x-pack/packages/security-solution/navigation/src/landing_links/landing_links_image_card.test.tsx create mode 100644 x-pack/packages/security-solution/navigation/src/landing_links/landing_links_image_card.tsx create mode 100644 x-pack/packages/security-solution/navigation/src/landing_links/translations.ts diff --git a/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_image_card.stories.tsx b/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_image_card.stories.tsx new file mode 100644 index 0000000000000..ba54ff1ebd2cf --- /dev/null +++ b/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_image_card.stories.tsx @@ -0,0 +1,117 @@ +/* + * 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 type { CoreStart } from '@kbn/core/public'; +import type { NavigationLink } from '../types'; +import type { LandingLinksImagesProps } from './landing_links_images_cards'; +import { LandingLinksImageCard as LandingLinksImageCardComponent } from './landing_links_image_card'; +import { NavigationProvider } from '../context'; + +const items: NavigationLink[] = [ + { + id: 'link1', + title: 'link #1', + description: 'This is the description of the link #1', + landingImage: 'https://dummyimage.com/360x200/efefef/000', + }, + { + id: 'link2', + title: 'link #2', + description: 'This is the description of the link #2', + isBeta: true, + landingImage: 'https://dummyimage.com/360x200/efefef/000', + }, + { + id: 'link3', + title: 'link #3', + description: 'This is the description of the link #3', + landingImage: 'https://dummyimage.com/360x200/efefef/000', + }, + { + id: 'link4', + title: 'link #4', + description: 'This is the description of the link #4', + landingImage: 'https://dummyimage.com/360x200/efefef/000', + }, + { + id: 'link5', + title: 'link #5', + description: 'This is the description of the link #5', + landingImage: 'https://dummyimage.com/360x200/efefef/000', + }, + { + id: 'link6', + title: 'link #6', + description: 'This is the description of the link #6', + landingImage: 'https://dummyimage.com/360x200/efefef/000', + }, + { + id: 'link7', + title: 'link #7', + description: 'This is the description of the link #7', + landingImage: 'https://dummyimage.com/360x200/efefef/000', + }, +]; + +export default { + title: 'Landing Links/Landing Links Image Card', + description: 'Renders the links with images in a horizontal layout', + decorators: [ + (storyFn: Function) => ( +
      + {storyFn()} +
      + ), + ], +}; + +const mockCore = { + application: { + navigateToApp: () => {}, + getUrlForApp: () => '#', + }, +} as unknown as CoreStart; + +export const LandingLinksImageCards = (params: LandingLinksImagesProps) => ( +
      + +
      + {items.map((item) => { + const { id } = item; + return ; + })} +
      +
      +
      +); + +LandingLinksImageCards.argTypes = { + items: { + control: 'object', + defaultValue: items, + }, +}; + +LandingLinksImageCards.parameters = { + layout: 'fullscreen', +}; diff --git a/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_image_card.test.tsx b/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_image_card.test.tsx new file mode 100644 index 0000000000000..b55527146bbad --- /dev/null +++ b/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_image_card.test.tsx @@ -0,0 +1,104 @@ +/* + * 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 { SecurityPageName } from '../constants'; +import { mockNavigateTo, mockGetAppUrl } from '../../mocks/navigation'; +import { LandingLinksImageCard } from './landing_links_image_card'; +import { BETA } from './beta_badge'; + +jest.mock('../navigation'); + +mockGetAppUrl.mockImplementation(({ deepLinkId }: { deepLinkId: string }) => `/${deepLinkId}`); +const mockOnLinkClick = jest.fn(); + +const DEFAULT_NAV_ITEM = { + id: SecurityPageName.overview, + title: 'TEST LABEL', + description: 'TEST DESCRIPTION', + landingImage: 'TEST_IMAGE.png', +}; + +describe('LandingLinksImageCard', () => { + it('should render', () => { + const title = 'test label'; + + const { queryByText } = render( + + ); + + expect(queryByText(title)).toBeInTheDocument(); + }); + + it('should render landingImage', () => { + const landingImage = 'test_image.jpeg'; + const title = 'TEST_LABEL'; + + const { getByTestId } = render( + + ); + + expect(getByTestId('LandingImageCard-image')).toHaveStyle({ + backgroundImage: `url(${landingImage})`, + }); + }); + + it('should render beta tag when isBeta is true', () => { + const { queryByText } = render( + + ); + expect(queryByText(BETA)).toBeInTheDocument(); + }); + + it('should not render beta tag when isBeta is false', () => { + const { queryByText } = render(); + expect(queryByText(BETA)).not.toBeInTheDocument(); + }); + + it('should navigate link', () => { + const id = SecurityPageName.administration; + const title = 'test label 2'; + + const { getByText } = render( + + ); + + getByText(title).click(); + + expect(mockGetAppUrl).toHaveBeenCalledWith({ + deepLinkId: SecurityPageName.administration, + absolute: false, + path: '', + }); + expect(mockNavigateTo).toHaveBeenCalledWith({ url: '/administration' }); + }); + + it('should call onLinkClick', () => { + const id = SecurityPageName.administration; + const title = 'myTestLabel'; + + const { getByText } = render( + + ); + + getByText(title).click(); + + expect(mockOnLinkClick).toHaveBeenCalledWith(id); + }); +}); diff --git a/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_image_card.tsx b/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_image_card.tsx new file mode 100644 index 0000000000000..fb03cf5386115 --- /dev/null +++ b/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_image_card.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 { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText, EuiTitle, useEuiTheme } from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { css } from '@emotion/react'; +import { withLink } from '../links'; +import type { NavigationLink } from '../types'; +import { BetaBadge } from './beta_badge'; +import { getKibanaLinkProps } from './utils'; + +export interface LandingLinksImageCardProps { + item: NavigationLink; + urlState?: string; + onLinkClick?: (id: string) => void; +} + +const CARD_HEIGHT = 116; +const CARD_WIDTH = 370; +const CARD_HEIGHT_IMAGE = 98; + +const useStyles = () => { + const { euiTheme } = useEuiTheme(); + return { + card: css` + height: ${CARD_HEIGHT}px; + max-width: ${CARD_WIDTH}px; + `, + cardWrapper: css` + height: 100%; + `, + titleContainer: css` + height: ${euiTheme.size.l}; + `, + title: css` + color: ${euiTheme.colors.primaryText}; + font-weight: ${euiTheme.font.weight.semiBold}; + `, + getImageContainer: (imageUrl: string | undefined) => css` + height: ${CARD_HEIGHT_IMAGE}px; + width: ${CARD_HEIGHT_IMAGE}px; + background-position: center center; + background-repeat: no-repeat; + background-image: url(${imageUrl ?? ''}); + background-size: auto 98px; + `, + }; +}; + +const EuiPanelWithLink = withLink(EuiPanel); + +export const LandingLinksImageCard: React.FC = React.memo( + function LandingLinksImageCard({ item, urlState, onLinkClick }) { + const styles = useStyles(); + + const linkProps = getKibanaLinkProps({ item, urlState, onLinkClick }); + const { landingImage, title, description, isBeta, betaOptions } = item; + + const imageBackground = useMemo( + () => styles.getImageContainer(landingImage), + [landingImage, styles] + ); + + return ( + + + + + {landingImage && ( + + )} + + + + + + + +

      {title}

      +
      +
      + {isBeta && } +
      +
      + + {description} + +
      +
      +
      +
      +
      + ); + } +); + +// eslint-disable-next-line import/no-default-export +export default LandingLinksImageCard; diff --git a/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_images_cards.test.tsx b/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_images_cards.test.tsx index e614c99a500d3..690739576c926 100644 --- a/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_images_cards.test.tsx +++ b/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_images_cards.test.tsx @@ -8,14 +8,12 @@ import React from 'react'; import { render } from '@testing-library/react'; import { SecurityPageName } from '../constants'; -import { mockNavigateTo, mockGetAppUrl } from '../../mocks/navigation'; +import { mockGetAppUrl } from '../../mocks/navigation'; import { LandingLinksImageCards } from './landing_links_images_cards'; -import { BETA } from './beta_badge'; jest.mock('../navigation'); mockGetAppUrl.mockImplementation(({ deepLinkId }: { deepLinkId: string }) => `/${deepLinkId}`); -const mockOnLinkClick = jest.fn(); const DEFAULT_NAV_ITEM = { id: SecurityPageName.overview, @@ -25,70 +23,19 @@ const DEFAULT_NAV_ITEM = { }; describe('LandingLinksImageCards', () => { - it('should render', () => { - const title = 'test label'; + it('should render accordion', () => { + const landingLinksCardsAccordionTestId = 'LandingImageCards-accordion'; - const { queryByText } = render( - - ); + const { queryByTestId } = render(); - expect(queryByText(title)).toBeInTheDocument(); + expect(queryByTestId(landingLinksCardsAccordionTestId)).toBeInTheDocument(); }); - it('should render landingImage', () => { - const landingImage = 'test_image.jpeg'; - const title = 'TEST_LABEL'; + it('should render LandingLinksImageCard item', () => { + const landingLinksCardTestId = 'LandingImageCard-item'; - const { getByTestId } = render( - - ); + const { queryByTestId } = render(); - expect(getByTestId('LandingImageCard-image')).toHaveAttribute('src', landingImage); - }); - - it('should render beta tag when isBeta is true', () => { - const { queryByText } = render( - - ); - expect(queryByText(BETA)).toBeInTheDocument(); - }); - - it('should not render beta tag when isBeta is false', () => { - const { queryByText } = render(); - expect(queryByText(BETA)).not.toBeInTheDocument(); - }); - - it('should navigate link', () => { - const id = SecurityPageName.administration; - const title = 'test label 2'; - - const { getByText } = render( - - ); - - getByText(title).click(); - - expect(mockGetAppUrl).toHaveBeenCalledWith({ - deepLinkId: SecurityPageName.administration, - absolute: false, - path: '', - }); - expect(mockNavigateTo).toHaveBeenCalledWith({ url: '/administration' }); - }); - - it('should call onLinkClick', () => { - const id = SecurityPageName.administration; - const title = 'myTestLabel'; - - const { getByText } = render( - - ); - - getByText(title).click(); - - expect(mockOnLinkClick).toHaveBeenCalledWith(id); + expect(queryByTestId(landingLinksCardTestId)).toBeInTheDocument(); }); }); diff --git a/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_images_cards.tsx b/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_images_cards.tsx index ac8598c427026..6ba9b63dc7ca3 100644 --- a/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_images_cards.tsx +++ b/x-pack/packages/security-solution/navigation/src/landing_links/landing_links_images_cards.tsx @@ -4,13 +4,21 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiCard, EuiFlexGroup, EuiFlexItem, EuiImage, EuiTitle, useEuiTheme } from '@elastic/eui'; +import { + EuiAccordion, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiPanel, + EuiSpacer, + EuiTitle, + useGeneratedHtmlId, +} from '@elastic/eui'; import React from 'react'; import { css } from '@emotion/react'; -import { withLink } from '../links'; +import * as i18n from './translations'; import type { NavigationLink } from '../types'; -import { BetaBadge } from './beta_badge'; -import { getKibanaLinkProps } from './utils'; +import LandingLinksImageCard from './landing_links_image_card'; export interface LandingLinksImagesProps { items: NavigationLink[]; @@ -18,83 +26,64 @@ export interface LandingLinksImagesProps { onLinkClick?: (id: string) => void; } -const CARD_WIDTH = 320; - const useStyles = () => { - const { euiTheme } = useEuiTheme(); return { - container: css` - max-width: ${CARD_WIDTH}px; - `, - card: css` - // Needed to use the primary color in the title underlining on hover - .euiCard__title { - color: ${euiTheme.colors.primaryText}; + accordion: css` + .euiAccordion__childWrapper { + overflow: visible; } `, - titleContainer: css` - display: flex; - align-items: center; - `, - title: css` - color: ${euiTheme.colors.primaryText}; - `, - description: css` - padding-top: ${euiTheme.size.xs}; - max-width: 550px; - `, }; }; -const EuiCardWithLink = withLink(EuiCard); - export const LandingLinksImageCards: React.FC = React.memo( function LandingLinksImageCards({ items, urlState, onLinkClick }) { + const landingLinksAccordionId = useGeneratedHtmlId({ prefix: 'landingLinksAccordion' }); const styles = useStyles(); + return ( - - {items.map((item) => { - const linkProps = getKibanaLinkProps({ item, urlState, onLinkClick }); - const { id, landingImage, title, description, isBeta, betaOptions } = item; - return ( - + - - ) - } - title={ -
      - -

      {title}

      -
      - {isBeta && } -
      - } - titleElement="span" - description={{description}} - /> -
      - ); - })} -
      + + + + + + +

      {i18n.LANDING_LINKS_ACCORDION_HEADER}

      +
      +
      +
      + } + > + + + {items.map((item) => { + const { id } = item; + return ( + + ); + })} + + + ); } ); diff --git a/x-pack/packages/security-solution/navigation/src/landing_links/translations.ts b/x-pack/packages/security-solution/navigation/src/landing_links/translations.ts new file mode 100644 index 0000000000000..d9409b46534db --- /dev/null +++ b/x-pack/packages/security-solution/navigation/src/landing_links/translations.ts @@ -0,0 +1,14 @@ +/* + * 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 LANDING_LINKS_ACCORDION_HEADER = i18n.translate( + 'securitySolutionPackages.navigation.landingLinks', + { + defaultMessage: 'Security views', + } +); diff --git a/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.tsx b/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.tsx index affe83b0682ca..b25b235213e60 100644 --- a/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.tsx +++ b/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.tsx @@ -128,10 +128,6 @@ export const DashboardsLandingPage = () => { )} - -

      {i18n.DASHBOARDS_PAGE_SECTION_DEFAULT}

      -
      - { {canReadDashboard && securityTagsExist && initialFilter && ( <> - +

      {i18n.DASHBOARDS_PAGE_SECTION_CUSTOM}

      diff --git a/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/translations.ts b/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/translations.ts index 71bafc6a80990..256c3a71a33e1 100644 --- a/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/translations.ts +++ b/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/translations.ts @@ -13,16 +13,9 @@ export const DASHBOARDS_PAGE_CREATE_BUTTON = i18n.translate( } ); -export const DASHBOARDS_PAGE_SECTION_DEFAULT = i18n.translate( - 'xpack.securitySolution.dashboards.landing.section.default', - { - defaultMessage: 'DEFAULT', - } -); - export const DASHBOARDS_PAGE_SECTION_CUSTOM = i18n.translate( 'xpack.securitySolution.dashboards.landing.section.custom', { - defaultMessage: 'CUSTOM', + defaultMessage: 'Custom Dashboards', } ); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index a1871e039fbb3..3619b6096c0f0 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -33700,7 +33700,6 @@ "xpack.securitySolution.dashboards.description": "Description", "xpack.securitySolution.dashboards.landing.createButton": "Créer un tableau de bord", "xpack.securitySolution.dashboards.landing.section.custom": "PERSONNALISÉ", - "xpack.securitySolution.dashboards.landing.section.default": "PAR DÉFAUT", "xpack.securitySolution.dashboards.pageTitle": "Tableaux de bord", "xpack.securitySolution.dashboards.queryError": "Erreur lors de la récupération des tableaux de bord de sécurité", "xpack.securitySolution.dashboards.title": "Titre", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 7b1fb5c238f52..86790c7959135 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -33675,7 +33675,6 @@ "xpack.securitySolution.dashboards.description": "説明", "xpack.securitySolution.dashboards.landing.createButton": "ダッシュボードを作成", "xpack.securitySolution.dashboards.landing.section.custom": "カスタム", - "xpack.securitySolution.dashboards.landing.section.default": "デフォルト", "xpack.securitySolution.dashboards.pageTitle": "ダッシュボード", "xpack.securitySolution.dashboards.queryError": "セキュリティダッシュボードの取得エラー", "xpack.securitySolution.dashboards.title": "タイトル", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c783af0ea5060..22ff7f4be1b70 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -33718,7 +33718,6 @@ "xpack.securitySolution.dashboards.description": "描述", "xpack.securitySolution.dashboards.landing.createButton": "创建仪表板", "xpack.securitySolution.dashboards.landing.section.custom": "定制", - "xpack.securitySolution.dashboards.landing.section.default": "默认", "xpack.securitySolution.dashboards.pageTitle": "仪表板", "xpack.securitySolution.dashboards.queryError": "检索安全仪表板时出错", "xpack.securitySolution.dashboards.title": "标题", diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/attachment_framework.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/attachment_framework.ts index 99edcd7926905..4e3283c6600d6 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/attachment_framework.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/attachment_framework.ts @@ -101,6 +101,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { await common.navigateToApp('security', { path: 'dashboards' }); await header.waitUntilLoadingHasFinished(); + await testSubjects.click('LandingImageCards-accordionButton'); if (await testSubjects.exists('edit-unsaved-New-Dashboard')) { await testSubjects.click('edit-unsaved-New-Dashboard'); From bd92b18e407af4106e9308d87f5b85f421a03479 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 1 Jul 2024 16:19:30 +0100 Subject: [PATCH 019/128] skip flaky suite (#169894) --- .../management/cypress/e2e/response_actions/responder.cy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/responder.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/responder.cy.ts index e72b5a42eaf1f..3a763fc0e8e23 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/responder.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/responder.cy.ts @@ -43,7 +43,8 @@ describe( login(); }); - describe('from Cases', () => { + // FLAKY: https://github.com/elastic/kibana/issues/169894 + describe.skip('from Cases', () => { let endpointData: ReturnTypeFromChainable; let caseData: ReturnTypeFromChainable; let alertData: ReturnTypeFromChainable; From 3309c4f723e4ff2550ffa3771d1ca6b7025a3e19 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 1 Jul 2024 18:28:36 +0300 Subject: [PATCH 020/128] fix: [Obs Applications > Services][SCREEN READER]: Tooltips must be able to take keyboard focus: 0001 (#186884) closes: https://github.com/elastic/observability-dev/issues/3595 ## What was done?: 1. Replace `EuiToolTip` with `EuiIconTip` to improve accessibility (a11y). image --- .../service_list/apm_services_table.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/apm_signal_inventory/service_list/apm_services_table.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/apm_signal_inventory/service_list/apm_services_table.tsx index 50f8a873b6c8c..de2c45862d30f 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/apm_signal_inventory/service_list/apm_services_table.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/apm_signal_inventory/service_list/apm_services_table.tsx @@ -9,7 +9,6 @@ import { EuiBadge, EuiFlexGroup, EuiFlexItem, - EuiIcon, EuiIconTip, EuiLink, EuiSpacer, @@ -436,15 +435,15 @@ export function ApmServicesTable({ )} {maxCountExceeded && ( - - - + /> )} From 06a77a73b4a9963e94b0b47611f46594e6ccc4ce Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 1 Jul 2024 18:29:25 +0300 Subject: [PATCH 021/128] fix: Add `rowHeader` attribute for `observability_solution -> managed_table` (#186625) Closes: https://github.com/elastic/observability-dev/issues/3542 Closes: https://github.com/elastic/observability-dev/issues/3543 Closes: https://github.com/elastic/observability-dev/issues/3544 Closes: https://github.com/elastic/observability-dev/issues/3545 Closes: https://github.com/elastic/observability-dev/issues/3546 Closes: https://github.com/elastic/observability-dev/issues/3550 Closes: https://github.com/elastic/observability-dev/issues/3551 Closes: https://github.com/elastic/observability-dev/issues/3552 Closes: https://github.com/elastic/observability-dev/issues/3549 ## Summary This PR introduces support for the rowHeader attribute in `ManagedTable`. By default, the first column is marked as `TH[scope="row"]`. However, users now have the flexibility to either override this value or set it to 'false' to disable this feature. --- .../apm/public/components/shared/managed_table/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/managed_table/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/managed_table/index.tsx index e1d22482a30f3..dcc5a5ee9c677 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/managed_table/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/managed_table/index.tsx @@ -66,6 +66,7 @@ export const shouldfetchServer = ({ function UnoptimizedManagedTable(props: { items: T[]; columns: Array>; + rowHeader?: string | false; noItemsMessage?: React.ReactNode; isLoading?: boolean; error?: boolean; @@ -96,6 +97,7 @@ function UnoptimizedManagedTable(props: { const { items, columns, + rowHeader, noItemsMessage, isLoading = false, error = false, @@ -285,6 +287,7 @@ function UnoptimizedManagedTable(props: { } items={renderedItems} columns={columns as unknown as Array>} // EuiBasicTableColumn is stricter than ITableColumn + rowHeader={rowHeader === false ? undefined : rowHeader ?? columns[0]?.field} sorting={sorting} onChange={onTableChange} {...(paginationProps ? { pagination: paginationProps } : {})} From 3dddc52056820d88435ddc40c71cfd5192c1d1a3 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 1 Jul 2024 18:30:12 +0300 Subject: [PATCH 022/128] fix: [Obs Synthetics > Monitor Test Run][SCREEN READER]: Table rows need TH[scope="row"] for SR usability: 0017 (#186957) Closes: https://github.com/elastic/observability-dev/issues/3570 ## Summary `rowHeader` attribute was added for the following table Screenshot 2024-06-26 at 11 37 56 --- .../apps/synthetics/components/common/components/stderr_logs.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/stderr_logs.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/stderr_logs.tsx index 2b332ec78671f..ac9a313aaf1b1 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/stderr_logs.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/stderr_logs.tsx @@ -129,6 +129,7 @@ export const StdErrorLogs = ({ Date: Mon, 1 Jul 2024 18:30:49 +0300 Subject: [PATCH 023/128] fix: [Obs Infrastructure > Hosts, Kubernetes, Docker][SCREEN READER]: Table rows need TH[scope="row"] for SR usability: 0016 (#186962) Closes: https://github.com/elastic/observability-dev/issues/3569 ## Summary `rowHeader` attribute was added for the following table Screenshot 2024-06-26 at 12 29 57 --- .../metrics/inventory_view/components/table_view.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/table_view.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/table_view.tsx index 4791875958c67..f90176edddaf9 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/table_view.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/table_view.tsx @@ -162,6 +162,12 @@ export const TableView = (props: Props) => { ); return ( - + ); }; From ccfc5d3bb9283c3316ecd7df34e457ed4e3a31fc Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Mon, 1 Jul 2024 17:40:56 +0200 Subject: [PATCH 024/128] [ftr] remove svlCommonPage.forceLogout(); for tests using SAML auth (#187140) ## Summary **tl; dr;** PR removes `svlCommonPage.forceLogout();` for test suites, that use _SAML auth_ to Kibana with `svlCommonPage.loginWithRole()` as this call is not needed. More details: `svlCommonPage.forceLogout();` was introduced together with `svlCommonPage.login()` when FTR allowed only basic authentication with `/login` route and operator user. Mixing basic auth with Cloud SAML auth caused flakiness, probably some of you noticed inconsistent redirects to Cloud UI in before/after hooks. Calling `/logout` route and handling what happens next was important to reset Kibana state. However with `svlCommonPage.loginWithRole()` it is no longer needed, it works this way: 1. use Cloud SAML auth (mock IDP to replicate SAML locally) to generate session based on provided project role 2. clear browser state: delete all cookies, clear session & local storage, verify it succeeded 3. set cookie in browser, validate role/user is applied properly by validating user profile in browser Since we generate only 1 session per unique role within each FTR config run, it is not possible to have 2 different sessions for the same user. When your next test is calling `svlCommonPage.loginWithRole()`, the cookie in browser and browser state overall will be reset completely. --- .../common/platform_security/user_profiles/user_profiles.ts | 4 ---- .../test_suites/observability/advanced_settings.ts | 4 ---- .../test_suites/observability/cases/attachment_framework.ts | 1 - .../functional/test_suites/observability/cases/configure.ts | 1 - .../test_suites/observability/cases/create_case_form.ts | 1 - .../functional/test_suites/observability/cases/list_view.ts | 1 - .../functional/test_suites/observability/cases/view_case.ts | 1 - .../test_suites/observability/infra/header_menu.ts | 1 - .../test_suites/observability/infra/hosts_page.ts | 1 - .../functional/test_suites/observability/infra/infra.ts | 4 ---- .../test_suites/observability/infra/navigation.ts | 4 ---- .../test_suites/observability/infra/node_details.ts | 1 - .../observability/ml/anomaly_detection_jobs_list.ts | 1 - .../test_suites/observability/ml/search_bar_features.ts | 4 ---- .../observability_logs_explorer/columns_selection.ts | 1 - .../observability_logs_explorer/custom_control_columns.ts | 1 - .../data_source_selection_state.ts | 4 ---- .../observability_logs_explorer/data_source_selector.ts | 4 ---- .../observability/observability_logs_explorer/field_list.ts | 1 - .../observability_logs_explorer/filter_controls.ts | 1 - .../observability/observability_logs_explorer/flyout.ts | 1 - .../observability_logs_explorer/header_menu.ts | 1 - .../test_suites/observability/rules/rules_list.ts | 1 - .../response_ops_docs/cases/list_view.ts | 6 +----- .../response_ops_docs/connectors/server_log_connector.ts | 4 ---- .../response_ops_docs/maintenance_windows/create_window.ts | 4 ---- .../test_suites/security/ftr/advanced_settings.ts | 4 ---- .../ftr/cloud_security_posture/compliance_dashboard.ts | 1 - 28 files changed, 1 insertion(+), 62 deletions(-) diff --git a/x-pack/test_serverless/functional/test_suites/common/platform_security/user_profiles/user_profiles.ts b/x-pack/test_serverless/functional/test_suites/common/platform_security/user_profiles/user_profiles.ts index 50313bc4da7cf..4909f4a99a8f8 100644 --- a/x-pack/test_serverless/functional/test_suites/common/platform_security/user_profiles/user_profiles.ts +++ b/x-pack/test_serverless/functional/test_suites/common/platform_security/user_profiles/user_profiles.ts @@ -20,10 +20,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await pageObjects.svlCommonPage.loginWithRole(VIEWER_ROLE); }); - after(async () => { - await pageObjects.svlCommonPage.forceLogout(); - }); - describe('User details', async () => { it('should display correct user details', async () => { await pageObjects.common.navigateToApp('security_account'); diff --git a/x-pack/test_serverless/functional/test_suites/observability/advanced_settings.ts b/x-pack/test_serverless/functional/test_suites/observability/advanced_settings.ts index 0e3c4d65349c9..6a9f06974a5cc 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/advanced_settings.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/advanced_settings.ts @@ -22,10 +22,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.common.navigateToApp('settings'); }); - after(async () => { - await pageObjects.svlCommonPage.forceLogout(); - }); - it('renders the page', async () => { await retry.waitFor('title to be visible', async () => { return await testSubjects.exists('managementSettingsTitle'); diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts index 7719ba01ce8ea..f7374c5f03e7e 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts @@ -50,7 +50,6 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { ); await kibanaServer.savedObjects.cleanStandardList(); - await svlCommonPage.forceLogout(); }); it('adds lens visualization to a new case', async () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts index 8a966c6b7d135..44cce6cfb520d 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts @@ -42,7 +42,6 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { after(async () => { await svlCases.api.deleteAllCaseItems(); - await svlCommonPage.forceLogout(); }); describe('Closure options', function () { diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts index 88f2663a10749..67b7cd1f3dfb3 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts @@ -35,7 +35,6 @@ export default ({ getService, getPageObject }: FtrProviderContext) => { after(async () => { await svlCases.api.deleteAllCaseItems(); - await svlCommonPage.forceLogout(); }); it('creates a case', async () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/list_view.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/list_view.ts index e98cc99bdec5b..71ce65ba59eb1 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/list_view.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/list_view.ts @@ -29,7 +29,6 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { after(async () => { await svlCases.api.deleteAllCaseItems(); await cases.casesTable.waitForCasesToBeDeleted(); - await svlCommonPage.forceLogout(); }); describe('empty state', () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts index 5962a5f7c4195..033af9d21848b 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts @@ -41,7 +41,6 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { after(async () => { await svlCases.api.deleteAllCaseItems(); - await svlCommonPage.forceLogout(); }); describe('page', () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/infra/header_menu.ts b/x-pack/test_serverless/functional/test_suites/observability/infra/header_menu.ts index e59021dbd7e1a..0abb121cd2d87 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/infra/header_menu.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/infra/header_menu.ts @@ -21,7 +21,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { after(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - await pageObjects.svlCommonPage.forceLogout(); }); describe('Alerts dropdown', () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/infra/hosts_page.ts b/x-pack/test_serverless/functional/test_suites/observability/infra/hosts_page.ts index 45aa622281312..9a2b6377e7369 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/infra/hosts_page.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/infra/hosts_page.ts @@ -53,7 +53,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await Promise.all([ esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'), ]); - await pageObjects.svlCommonPage.forceLogout(); }); describe('#Single Host Flyout', () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/infra/infra.ts b/x-pack/test_serverless/functional/test_suites/observability/infra/infra.ts index 0f9ddcef05db6..af663d98e0bc4 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/infra/infra.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/infra/infra.ts @@ -34,10 +34,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.svlCommonPage.loginWithRole('viewer'); }); - after(async () => { - await pageObjects.svlCommonPage.forceLogout(); - }); - describe('Inventory page', function () { this.tags('includeFirefox'); before(async () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/infra/navigation.ts b/x-pack/test_serverless/functional/test_suites/observability/infra/navigation.ts index 6bbb746eb65a6..9b30cb0d7d39c 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/infra/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/infra/navigation.ts @@ -30,10 +30,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await svlObltNavigation.navigateToLandingPage(); }); - after(async () => { - await pageObjects.svlCommonPage.forceLogout(); - }); - describe('when Hosts settings is on', () => { before(async () => { await setHostsSetting(true); diff --git a/x-pack/test_serverless/functional/test_suites/observability/infra/node_details.ts b/x-pack/test_serverless/functional/test_suites/observability/infra/node_details.ts index bd56244eb12ba..80111c3833f5d 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/infra/node_details.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/infra/node_details.ts @@ -44,7 +44,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { after(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - await pageObjects.svlCommonPage.forceLogout(); }); describe('Osquery Tab', () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/ml/anomaly_detection_jobs_list.ts b/x-pack/test_serverless/functional/test_suites/observability/ml/anomaly_detection_jobs_list.ts index e8dddef3eb35a..a7bf63f95ba88 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/ml/anomaly_detection_jobs_list.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/ml/anomaly_detection_jobs_list.ts @@ -31,7 +31,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async () => { - await PageObjects.svlCommonPage.forceLogout(); await ml.api.cleanMlIndices(); await ml.testResources.cleanMLSavedObjects(); await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); diff --git a/x-pack/test_serverless/functional/test_suites/observability/ml/search_bar_features.ts b/x-pack/test_serverless/functional/test_suites/observability/ml/search_bar_features.ts index 15b7d3d76c61a..dbafa9d420221 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/ml/search_bar_features.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/ml/search_bar_features.ts @@ -49,10 +49,6 @@ export default function ({ getPageObjects }: FtrProviderContext) { await PageObjects.svlCommonPage.loginWithRole(role); }); - after(async () => { - await PageObjects.svlCommonPage.forceLogout(); - }); - describe('list features', () => { it('has the correct features enabled', async () => { await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts index 14c5de7185310..0f2b7a8c12e0b 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts @@ -41,7 +41,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { after(async () => { await synthtrace.clean(); - await PageObjects.svlCommonPage.forceLogout(); }); describe('columns selection initialization and update', () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts index 6e836f00cc3f2..3b6495e79ab0a 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts @@ -42,7 +42,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { after(async () => { await synthtrace.clean(); - await PageObjects.svlCommonPage.forceLogout(); }); describe('should render custom control columns properly', async () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selection_state.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selection_state.ts index 871c0151432a2..6f44f68308b4d 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selection_state.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selection_state.ts @@ -37,10 +37,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.svlCommonPage.loginWithRole('viewer'); }); - after(async () => { - await PageObjects.svlCommonPage.forceLogout(); - }); - describe('when no dataSourceSelection is given', () => { it('should initialize the "All logs" selection', async () => { await PageObjects.observabilityLogsExplorer.navigateTo(); diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selector.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selector.ts index 3bb012c81311f..4491df8d7c890 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selector.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selector.ts @@ -44,10 +44,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.observabilityLogsExplorer.removeInstalledPackages(); }); - after(async () => { - await PageObjects.svlCommonPage.forceLogout(); - }); - describe('as consistent behavior', () => { before(async () => { await PageObjects.observabilityLogsExplorer.navigateTo(); diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/field_list.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/field_list.ts index 5c5cd85f624a4..688a6844f6066 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/field_list.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/field_list.ts @@ -41,7 +41,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { after(async () => { await synthtrace.clean(); - await PageObjects.svlCommonPage.forceLogout(); }); describe('field list initialisation', () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/filter_controls.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/filter_controls.ts index 0dc479b391800..c63d41da70f79 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/filter_controls.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/filter_controls.ts @@ -18,7 +18,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after('clean up archives', async () => { - await PageObjects.svlCommonPage.forceLogout(); await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/flyout.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/flyout.ts index 2e181f1a7d9b4..1019f56fd43fe 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/flyout.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/flyout.ts @@ -61,7 +61,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after('clean up archives', async () => { - await PageObjects.svlCommonPage.forceLogout(); if (cleanupDataStreamSetup) { cleanupDataStreamSetup(); } diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/header_menu.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/header_menu.ts index a61c1afbfb618..82b58e5c4f928 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/header_menu.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/header_menu.ts @@ -37,7 +37,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async () => { - await PageObjects.svlCommonPage.forceLogout(); await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); await esArchiver.unload( 'x-pack/test/functional/es_archives/observability_logs_explorer/data_streams' diff --git a/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts b/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts index 4696907ed8ee4..8208c73ad0b6f 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts @@ -103,7 +103,6 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); after(async () => { - await svlCommonPage.forceLogout(); await svlUserManager.invalidateApiKeyForRole(roleAuthc); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/cases/list_view.ts b/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/cases/list_view.ts index b4666e7807342..4d453a06daa7f 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/cases/list_view.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/cases/list_view.ts @@ -55,15 +55,11 @@ export default function ({ getPageObject, getPageObjects, getService }: FtrProvi roleAuthc ); caseIdMonitoring = caseMonitoring.id; + await pageObjects.svlCommonPage.loginWithRole('admin'); }); after(async () => { await svlCases.api.deleteAllCaseItems(); - await pageObjects.svlCommonPage.forceLogout(); - }); - - beforeEach(async () => { - await pageObjects.svlCommonPage.loginWithRole('admin'); }); it('cases list screenshot', async () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/connectors/server_log_connector.ts b/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/connectors/server_log_connector.ts index a38807f4537ca..cef28cd33ebfb 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/connectors/server_log_connector.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/connectors/server_log_connector.ts @@ -18,10 +18,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await pageObjects.svlCommonPage.loginWithRole('admin'); }); - after(async () => { - await pageObjects.svlCommonPage.forceLogout(); - }); - it('server log connector screenshots', async () => { await pageObjects.common.navigateToApp('connectors'); await pageObjects.header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/maintenance_windows/create_window.ts b/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/maintenance_windows/create_window.ts index 73f431eeb6ccb..1346abe722a79 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/maintenance_windows/create_window.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/maintenance_windows/create_window.ts @@ -19,10 +19,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await pageObjects.svlCommonPage.loginWithRole('admin'); }); - after(async () => { - await pageObjects.svlCommonPage.forceLogout(); - }); - it('create maintenance window screenshot', async () => { await pageObjects.common.navigateToApp('maintenanceWindows'); await pageObjects.header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/advanced_settings.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/advanced_settings.ts index c6448aac27ec8..9571ec689f7a6 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/advanced_settings.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/advanced_settings.ts @@ -22,10 +22,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.common.navigateToApp('settings'); }); - after(async () => { - await pageObjects.svlCommonPage.forceLogout(); - }); - it('renders the page', async () => { await retry.waitFor('title to be visible', async () => { return await testSubjects.exists('managementSettingsTitle'); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/compliance_dashboard.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/compliance_dashboard.ts index a5b99f0dd31bf..c7ea84a2abe21 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/compliance_dashboard.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/compliance_dashboard.ts @@ -58,7 +58,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { after(async () => { await cspDashboard.index.remove(); - await pageObjects.svlCommonPage.forceLogout(); }); describe('Kubernetes Dashboard', () => { From 581e88ba81972370fd8d6da774a1238a941d8e61 Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 1 Jul 2024 11:23:06 -0500 Subject: [PATCH 025/128] [ci] Use github action to approve API docs (#187246) Cleans up our auto-approve scripts a bit to be consistent. This matches the auto-approve backport workflow. --- .buildkite/scripts/common/setup_job_env.sh | 3 --- .../scripts/steps/api_docs/publish_api_docs.sh | 2 -- .github/workflows/auto-approve-api-docs.yml | 18 ++++++++++++++++++ .github/workflows/auto-approve-backports.yml | 6 ++++-- 4 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/auto-approve-api-docs.yml diff --git a/.buildkite/scripts/common/setup_job_env.sh b/.buildkite/scripts/common/setup_job_env.sh index 6066e24708f76..bc1d902af781b 100644 --- a/.buildkite/scripts/common/setup_job_env.sh +++ b/.buildkite/scripts/common/setup_job_env.sh @@ -16,9 +16,6 @@ fi GITHUB_TOKEN=$(vault_get kibanamachine github_token) export GITHUB_TOKEN - KIBANA_CI_GITHUB_TOKEN=$(vault_get kibana-ci-github github_token) - export KIBANA_CI_GITHUB_TOKEN - KIBANA_DOCKER_USERNAME="$(vault_get container-registry username)" KIBANA_DOCKER_PASSWORD="$(vault_get container-registry password)" if (command -v docker && docker version) &> /dev/null; then diff --git a/.buildkite/scripts/steps/api_docs/publish_api_docs.sh b/.buildkite/scripts/steps/api_docs/publish_api_docs.sh index 77f6fb0888fb3..7ab4ae6b3497c 100755 --- a/.buildkite/scripts/steps/api_docs/publish_api_docs.sh +++ b/.buildkite/scripts/steps/api_docs/publish_api_docs.sh @@ -29,5 +29,3 @@ git push origin "$branch" prUrl=$(gh pr create --repo elastic/kibana --base main --head "$branch" --title "[api-docs] $(date +%F) Daily api_docs build" --body "Generated by $BUILDKITE_BUILD_URL" --label "release_note:skip" --label "docs") echo "Opened PR: $prUrl" gh pr merge --repo elastic/kibana --auto --squash "$prUrl" - -GH_TOKEN="$KIBANA_CI_GITHUB_TOKEN" gh pr review --repo elastic/kibana --approve -b "Automated review from $BUILDKITE_BUILD_URL" "$prUrl" diff --git a/.github/workflows/auto-approve-api-docs.yml b/.github/workflows/auto-approve-api-docs.yml new file mode 100644 index 0000000000000..503ea9634d00e --- /dev/null +++ b/.github/workflows/auto-approve-api-docs.yml @@ -0,0 +1,18 @@ +on: + pull_request: + branches: + - main + types: + - opened + +jobs: + approve: + name: Auto-approve API docs + runs-on: ubuntu-latest + if: | + startsWith(github.event.pull_request.head.ref, 'api_docs') && + github.event.pull_request.user.login == 'kibanamachine' + permissions: + pull-requests: write + steps: + - uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363 #4.0.0 diff --git a/.github/workflows/auto-approve-backports.yml b/.github/workflows/auto-approve-backports.yml index 4b205f6971792..2f73696406d92 100644 --- a/.github/workflows/auto-approve-backports.yml +++ b/.github/workflows/auto-approve-backports.yml @@ -1,7 +1,9 @@ on: - pull_request_target: + pull_request: branches-ignore: - main + types: + - opened jobs: approve: @@ -13,4 +15,4 @@ jobs: permissions: pull-requests: write steps: - - uses: hmarr/auto-approve-action@v3 + - uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363 #4.0.0 From 71a47c0d5c8bfee58bf95e3c04fc6b0739cbc4d8 Mon Sep 17 00:00:00 2001 From: Sergi Massaneda Date: Mon, 1 Jul 2024 18:23:49 +0200 Subject: [PATCH 026/128] [GenAi] Improve Integration assistant generation errors (#187204) ## Summary Follow-up of https://github.com/elastic/kibana/pull/186304#pullrequestreview-2132968362 Improved the error messages and `Retry` button added. ### Screenshots Before ![before](https://github.com/elastic/kibana/assets/17747913/b412449f-1f3b-4d98-b069-916f881c1cad) After ![after](https://github.com/elastic/kibana/assets/17747913/07847ad5-8d15-485f-82d0-3c9ac9ac7671) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../data_stream_step/generation_modal.tsx | 91 +++++++++++++------ .../steps/data_stream_step/translations.ts | 9 ++ 2 files changed, 70 insertions(+), 30 deletions(-) diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/generation_modal.tsx b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/generation_modal.tsx index 08df5d3d2d74b..fe5a1dabb1374 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/generation_modal.tsx +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/generation_modal.tsx @@ -6,6 +6,8 @@ */ import { + EuiButtonEmpty, + EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, @@ -20,7 +22,7 @@ import { useEuiTheme, } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { css } from '@emotion/react'; import type { CategorizationRequestBody, @@ -62,9 +64,11 @@ export const useGeneration = ({ const { http, notifications } = useKibana().services; const [progress, setProgress] = useState(); const [error, setError] = useState(null); + const [isRequesting, setIsRequesting] = useState(true); useEffect(() => { if ( + !isRequesting || http == null || connector == null || integrationSettings == null || @@ -122,7 +126,9 @@ export const useGeneration = ({ onComplete(relatedGraphResult.results); } catch (e) { if (abortController.signal.aborted) return; - const errorMessage = e.body?.message ?? e.message; + const errorMessage = `${e.message}${ + e.body ? ` (${e.body.statusCode}): ${e.body.message}` : '' + }`; reportGenerationComplete({ connector, @@ -131,13 +137,16 @@ export const useGeneration = ({ error: errorMessage, }); - setError(`Error: ${errorMessage}`); + setError(errorMessage); + } finally { + setIsRequesting(false); } })(); return () => { abortController.abort(); }; }, [ + isRequesting, onComplete, setProgress, connector, @@ -147,9 +156,15 @@ export const useGeneration = ({ notifications?.toasts, ]); + const retry = useCallback(() => { + setError(null); + setIsRequesting(true); + }, []); + return { progress, error, + retry, }; }; @@ -176,7 +191,7 @@ interface GenerationModalProps { export const GenerationModal = React.memo( ({ integrationSettings, connector, onComplete, onClose }) => { const { headerCss, bodyCss } = useModalCss(); - const { progress, error } = useGeneration({ + const { progress, error, retry } = useGeneration({ integrationSettings, connector, onComplete, @@ -196,41 +211,57 @@ export const GenerationModal = React.memo( {progress && ( <> - - - {!error && ( - - - - )} - - - {progressText[progress]} - - - - - - - - {error && ( + {error ? ( - + {error} - + + ) : ( + <> + + + + + + + + {progressText[progress]} + + + + + + + + + )} )} - + {error ? ( + + + + {i18n.RETRY} + + + + ) : ( + + )} ); diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/translations.ts b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/translations.ts index 396e6a7990157..e4cc004e2673b 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/translations.ts +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/translations.ts @@ -166,3 +166,12 @@ export const PROGRESS_RELATED_GRAPH = i18n.translate( defaultMessage: 'Generating related fields', } ); +export const GENERATION_ERROR = (progressStep: string) => + i18n.translate('xpack.integrationAssistant.step.dataStream.generationError', { + values: { progressStep }, + defaultMessage: 'An error occurred during: {progressStep}', + }); + +export const RETRY = i18n.translate('xpack.integrationAssistant.step.dataStream.retryButtonLabel', { + defaultMessage: 'Retry', +}); From 003ff3e601d548d54f91af963dc8b102485f68af Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:36:07 +0200 Subject: [PATCH 027/128] skip failing test suite (#187275) --- .../spaces_only/tests/alerting/group2/monitoring_collection.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/monitoring_collection.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/monitoring_collection.ts index e4e52d6af314a..f87c1c095dd8f 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/monitoring_collection.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/monitoring_collection.ts @@ -38,7 +38,8 @@ export default function alertingMonitoringCollectionTests({ getService }: FtrPro ? dedicatedTaskRunner.getSupertest() : supertest; - describe('monitoring_collection', () => { + // Failing: See https://github.com/elastic/kibana/issues/187275 + describe.skip('monitoring_collection', () => { let endDate: string; const objectRemover = new ObjectRemover(supertest); From 2b611d88350f9eb281cc463f6c8ee17954697ca5 Mon Sep 17 00:00:00 2001 From: Cristina Amico Date: Mon, 1 Jul 2024 19:00:12 +0200 Subject: [PATCH 028/128] [Fleet] Enable rollover in custom integrations install when getting mapper_exception error (#186991) Fixes https://github.com/elastic/kibana/issues/183496 Fixes https://github.com/elastic/kibana/issues/180062 ## Summary Custom integration creation / integration update currently fails on changing subobjects property on mapping with `mapper_exception` error. This PR handles this case by triggering a rollover when the error is received. ## Testing - In dev tools, enter the following ``` POST logs-testintegration.test-default/_doc { "message": "abc" } ``` - Navigate to `/app/observabilityOnboarding/customLogs/?category=logs` and fill it with the following: ![Screenshot 2024-06-26 at 15 19 19](https://github.com/elastic/kibana/assets/16084106/a8a0016b-79aa-41b9-9b9b-8a4198a43feb) In the screenshot the error is visible but in the current branch the request should succeed and install `testintegration` correctly. ### Checklist - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../elasticsearch/template/template.test.ts | 53 ++++++++++++++++ .../epm/elasticsearch/template/template.ts | 15 +++-- .../apis/epm/install_custom.ts | 60 +++++++++++++++++-- 3 files changed, 116 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts index 7ea6fa0b692e8..68664e6bc0f0b 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts @@ -1805,6 +1805,59 @@ describe('EPM template', () => { }) ); }); + + it('should rollover on expected error when field subobjects in mappings changed', async () => { + const esClient = elasticsearchServiceMock.createElasticsearchClient(); + esClient.indices.getDataStream.mockResponse({ + data_streams: [{ name: 'test.prefix1-default' }], + } as any); + esClient.indices.get.mockResponse({ + 'test.prefix1-default': { + mappings: {}, + }, + } as any); + esClient.indices.simulateTemplate.mockResponse({ + template: { + settings: { index: {} }, + mappings: { subobjects: false }, + }, + } as any); + esClient.indices.putMapping.mockImplementation(() => { + throw new errors.ResponseError({ + body: { + error: { + message: + 'mapper_exception\n' + + '\tRoot causes:\n' + + "\t\tmapper_exception: the [subobjects] parameter can't be updated for the object mapping [_doc]", + }, + }, + } as any); + }); + + const logger = loggerMock.create(); + await updateCurrentWriteIndices(esClient, logger, [ + { + templateName: 'test', + indexTemplate: { + index_patterns: ['test.*-*'], + template: { + settings: { index: {} }, + mappings: {}, + }, + } as any, + }, + ]); + + expect(esClient.transport.request).toHaveBeenCalledWith( + expect.objectContaining({ + path: '/test.prefix1-default/_rollover', + querystring: { + lazy: true, + }, + }) + ); + }); it('should skip rollover on expected error when flag is on', async () => { const esClient = elasticsearchServiceMock.createElasticsearchClient(); esClient.indices.getDataStream.mockResponse({ diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts index c4984b1195475..654ce7ea8ed81 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts @@ -1012,7 +1012,6 @@ const updateExistingDataStream = async ({ const existingDsConfig = Object.values(existingDs); const currentBackingIndexConfig = existingDsConfig.at(-1); - const currentIndexMode = currentBackingIndexConfig?.settings?.index?.mode; // @ts-expect-error Property 'mode' does not exist on type 'MappingSourceField' const currentSourceType = currentBackingIndexConfig.mappings?._source?.mode; @@ -1020,7 +1019,7 @@ const updateExistingDataStream = async ({ let settings: IndicesIndexSettings; let mappings: MappingTypeMapping; let lifecycle: any; - + let subobjectsFieldChanged: boolean = false; try { const simulateResult = await retryTransientEsErrors(async () => esClient.indices.simulateTemplate({ @@ -1040,7 +1039,9 @@ const updateExistingDataStream = async ({ delete mappings.properties.stream; delete mappings.properties.data_stream; } - + if (currentBackingIndexConfig?.mappings?.subobjects !== mappings.subobjects) { + subobjectsFieldChanged = true; + } logger.info(`Attempt to update the mappings for the ${dataStreamName} (write_index_only)`); await retryTransientEsErrors( () => @@ -1055,9 +1056,11 @@ const updateExistingDataStream = async ({ // if update fails, rollover data stream and bail out } catch (err) { if ( - isResponseError(err) && - err.statusCode === 400 && - err.body?.error?.type === 'illegal_argument_exception' + (isResponseError(err) && + err.statusCode === 400 && + err.body?.error?.type === 'illegal_argument_exception') || + // handling the case when subobjects field changed, it should also trigger a rollover + subobjectsFieldChanged ) { logger.info(`Mappings update for ${dataStreamName} failed due to ${err}`); if (options?.skipDataStreamRollover === true) { diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_custom.ts b/x-pack/test/fleet_api_integration/apis/epm/install_custom.ts index c1dec4e1eb300..bcd4421f493d7 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_custom.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_custom.ts @@ -26,13 +26,12 @@ export default function (providerContext: FtrProviderContext) { .send({ force: true }); }; - // FLAKY: https://github.com/elastic/kibana/issues/180062 - describe.skip('Installing custom integrations', async () => { + describe('TESTME Installing custom integrations', async () => { afterEach(async () => { await uninstallPackage(); }); - it("Correcty installs a custom integration and all of it's assets", async () => { + it('Correcty installs a custom integration and all of its assets', async () => { const response = await supertest .post(`/api/fleet/epm/custom_integrations`) .set('kbn-xsrf', 'xxxx') @@ -170,20 +169,40 @@ export default function (providerContext: FtrProviderContext) { dynamic_templates: [ { ecs_timestamp: { - match: '@timestamp', mapping: { ignore_malformed: false, type: 'date', }, + match: '@timestamp', }, }, { ecs_message_match_only_text: { + mapping: { + type: 'match_only_text', + }, path_match: ['message', '*.message'], unmatch_mapping_type: 'object', + }, + }, + { + ecs_non_indexed_keyword: { mapping: { - type: 'match_only_text', + doc_values: false, + index: false, + type: 'keyword', + }, + path_match: 'event.original', + }, + }, + { + ecs_non_indexed_long: { + mapping: { + doc_values: false, + index: false, + type: 'long', }, + path_match: '*.x509.public_key_exponent', }, }, { @@ -305,7 +324,7 @@ export default function (providerContext: FtrProviderContext) { }, { ecs_geo_point: { - path_match: ['location', '*.location'], + path_match: '*.geo.location', mapping: { type: 'geo_point', }, @@ -426,6 +445,7 @@ export default function (providerContext: FtrProviderContext) { 'event.ingested': { type: 'date', format: 'strict_date_time_no_millis||strict_date_optional_time||epoch_millis', + ignore_malformed: false, }, 'host.architecture': { type: 'keyword', @@ -508,6 +528,34 @@ export default function (providerContext: FtrProviderContext) { }); }); + it('Works correctly when there is an existing datastream with the same name', async () => { + const INTEGRATION_NAME_1 = 'myintegration'; + const DATASET_NAME = 'test'; + await esClient.transport.request({ + method: 'POST', + path: `logs-${INTEGRATION_NAME_1}.${DATASET_NAME}-default/_doc`, + body: { + '@timestamp': '2015-01-01', + logs_test_name: `${DATASET_NAME}`, + data_stream: { + dataset: `${INTEGRATION_NAME_1}.${DATASET_NAME}_logs`, + namespace: 'default', + type: 'logs', + }, + }, + }); + await supertest + .post(`/api/fleet/epm/custom_integrations`) + .set('kbn-xsrf', 'xxxx') + .type('application/json') + .send({ + force: true, + integrationName: INTEGRATION_NAME_1, + datasets: [{ name: `${INTEGRATION_NAME_1}.${DATASET_NAME}`, type: 'logs' }], + }) + .expect(200); + }); + it('Throws an error when there is a naming collision with a current package installation', async () => { await supertest .post(`/api/fleet/epm/custom_integrations`) From 1a8a905835bbc8da4f4fc74a80dc62e18fd1f79d Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 1 Jul 2024 19:28:33 +0200 Subject: [PATCH 029/128] [api-docs] 2024-07-01 Daily api_docs build (#187289) Generated by https://buildkite.com/elastic/kibana-api-docs-daily/builds/755 --- api_docs/integration_assistant.devdocs.json | 83 +++++++++++++++++---- api_docs/integration_assistant.mdx | 2 +- api_docs/kbn_doc_links.devdocs.json | 2 +- api_docs/plugin_directory.mdx | 4 +- 4 files changed, 72 insertions(+), 19 deletions(-) diff --git a/api_docs/integration_assistant.devdocs.json b/api_docs/integration_assistant.devdocs.json index 6611c8b640018..7bc21089a09fd 100644 --- a/api_docs/integration_assistant.devdocs.json +++ b/api_docs/integration_assistant.devdocs.json @@ -34,13 +34,17 @@ "children": [ { "parentPluginId": "integrationAssistant", - "id": "def-public.IntegrationAssistantPluginStart.CreateIntegration", - "type": "CompoundType", + "id": "def-public.IntegrationAssistantPluginStart.components", + "type": "Object", "tags": [], - "label": "CreateIntegration", + "label": "components", "description": [], "signature": [ - "React.ComponentClass<{}, any> | React.FunctionComponent<{}>" + "{ CreateIntegration: ", + "CreateIntegrationComponent", + "; CreateIntegrationCardButton: ", + "CreateIntegrationCardButtonComponent", + "; }" ], "path": "x-pack/plugins/integration_assistant/public/types.ts", "deprecated": false, @@ -48,21 +52,37 @@ }, { "parentPluginId": "integrationAssistant", - "id": "def-public.IntegrationAssistantPluginStart.CreateIntegrationCardButton", - "type": "CompoundType", + "id": "def-public.IntegrationAssistantPluginStart.renderUpselling", + "type": "Function", "tags": [], - "label": "CreateIntegrationCardButton", - "description": [], + "label": "renderUpselling", + "description": [ + "\nSets the upselling to be rendered in the UI.\nIf defined, the section will be displayed and it will prevent\nthe user from interacting with the rest of the UI." + ], "signature": [ - "React.ComponentClass<", - "CreateIntegrationCardButtonProps", - ", any> | React.FunctionComponent<", - "CreateIntegrationCardButtonProps", - ">" + "(upselling: React.ReactNode) => void" ], "path": "x-pack/plugins/integration_assistant/public/types.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "children": [ + { + "parentPluginId": "integrationAssistant", + "id": "def-public.IntegrationAssistantPluginStart.renderUpselling.$1", + "type": "CompoundType", + "tags": [], + "label": "upselling", + "description": [], + "signature": [ + "React.ReactNode" + ], + "path": "x-pack/plugins/integration_assistant/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] } ], "lifecycle": "start", @@ -86,7 +106,40 @@ "path": "x-pack/plugins/integration_assistant/server/types.ts", "deprecated": false, "trackAdoption": false, - "children": [], + "children": [ + { + "parentPluginId": "integrationAssistant", + "id": "def-server.IntegrationAssistantPluginSetup.setIsAvailable", + "type": "Function", + "tags": [], + "label": "setIsAvailable", + "description": [], + "signature": [ + "(isAvailable: boolean) => void" + ], + "path": "x-pack/plugins/integration_assistant/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "integrationAssistant", + "id": "def-server.IntegrationAssistantPluginSetup.setIsAvailable.$1", + "type": "boolean", + "tags": [], + "label": "isAvailable", + "description": [], + "signature": [ + "boolean" + ], + "path": "x-pack/plugins/integration_assistant/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], "lifecycle": "setup", "initialIsOpen": true }, diff --git a/api_docs/integration_assistant.mdx b/api_docs/integration_assistant.mdx index 48d553e09fa9f..171ca4c1eeaa8 100644 --- a/api_docs/integration_assistant.mdx +++ b/api_docs/integration_assistant.mdx @@ -21,7 +21,7 @@ Contact [@elastic/security-solution](https://github.com/orgs/elastic/teams/secur | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 44 | 0 | 38 | 2 | +| 47 | 0 | 40 | 3 | ## Client diff --git a/api_docs/kbn_doc_links.devdocs.json b/api_docs/kbn_doc_links.devdocs.json index f34c6c85b7fe3..c2893cf2b4973 100644 --- a/api_docs/kbn_doc_links.devdocs.json +++ b/api_docs/kbn_doc_links.devdocs.json @@ -840,7 +840,7 @@ "label": "fleet", "description": [], "signature": [ - "{ readonly beatsAgentComparison: string; readonly guide: string; readonly fleetServer: string; readonly fleetServerAddFleetServer: string; readonly esSettings: string; readonly settings: string; readonly logstashSettings: string; readonly kafkaSettings: string; readonly settingsFleetServerHostSettings: string; readonly settingsFleetServerProxySettings: string; readonly troubleshooting: string; readonly elasticAgent: string; readonly datastreams: string; readonly datastreamsILM: string; readonly datastreamsNamingScheme: string; readonly datastreamsManualRollover: string; readonly datastreamsTSDS: string; readonly datastreamsTSDSMetrics: string; readonly datastreamsDownsampling: string; readonly installElasticAgent: string; readonly installElasticAgentStandalone: string; readonly packageSignatures: string; readonly upgradeElasticAgent: string; readonly learnMoreBlog: string; readonly apiKeysLearnMore: string; readonly onPremRegistry: string; readonly secureLogstash: string; readonly agentPolicy: string; readonly api: string; readonly uninstallAgent: string; readonly installAndUninstallIntegrationAssets: string; readonly elasticAgentInputConfiguration: string; readonly policySecrets: string; readonly remoteESOoutput: string; readonly performancePresets: string; readonly scalingKubernetesResourcesAndLimits: string; readonly roleAndPrivileges: string; readonly proxiesSettings: string; }" + "{ readonly beatsAgentComparison: string; readonly guide: string; readonly fleetServer: string; readonly fleetServerAddFleetServer: string; readonly esSettings: string; readonly settings: string; readonly logstashSettings: string; readonly kafkaSettings: string; readonly settingsFleetServerHostSettings: string; readonly settingsFleetServerProxySettings: string; readonly troubleshooting: string; readonly elasticAgent: string; readonly datastreams: string; readonly datastreamsILM: string; readonly datastreamsNamingScheme: string; readonly datastreamsManualRollover: string; readonly datastreamsTSDS: string; readonly datastreamsTSDSMetrics: string; readonly datastreamsDownsampling: string; readonly installElasticAgent: string; readonly installElasticAgentStandalone: string; readonly packageSignatures: string; readonly upgradeElasticAgent: string; readonly learnMoreBlog: string; readonly apiKeysLearnMore: string; readonly onPremRegistry: string; readonly secureLogstash: string; readonly agentPolicy: string; readonly api: string; readonly uninstallAgent: string; readonly installAndUninstallIntegrationAssets: string; readonly elasticAgentInputConfiguration: string; readonly policySecrets: string; readonly remoteESOoutput: string; readonly performancePresets: string; readonly scalingKubernetesResourcesAndLimits: string; readonly roleAndPrivileges: string; readonly proxiesSettings: string; readonly unprivilegedMode: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index 3f6941e5749f1..210ddae50410a 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -21,7 +21,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 49599 | 238 | 37841 | 1886 | +| 49602 | 238 | 37843 | 1887 | ## Plugin Directory @@ -117,7 +117,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 4 | 0 | 4 | 0 | | inputControlVis | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Input Control visualization to Kibana | 0 | 0 | 0 | 0 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 127 | 2 | 100 | 4 | -| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | Plugin implementing the Integration Assistant API and UI | 44 | 0 | 38 | 2 | +| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | Plugin implementing the Integration Assistant API and UI | 47 | 0 | 40 | 3 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides UI and APIs for the interactive setup mode. | 28 | 0 | 18 | 0 | | | [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai-assistant) | - | 95 | 0 | 95 | 4 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 6 | 0 | 6 | 0 | From 00337de019b6a0fe57dc9a7e4b6e6f51b60f537f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Mon, 1 Jul 2024 19:32:22 +0200 Subject: [PATCH 030/128] [Config Service] Ignore unknown config on Serverless (#187006) --- .../core-config-server-internal/index.ts | 5 +- .../src/ensure_valid_configuration.test.ts | 28 +- .../src/ensure_valid_configuration.ts | 30 +- .../core-root-server-internal/src/server.ts | 23 +- .../src/config_service.mock.ts | 1 + packages/kbn-config-schema/README.md | 5 +- packages/kbn-config-schema/index.ts | 1 + packages/kbn-config-schema/src/types/index.ts | 3 +- .../src/types/object_type.test.ts | 53 +- .../src/types/object_type.ts | 14 +- packages/kbn-config-schema/src/types/type.ts | 18 +- packages/kbn-config/src/config_service.ts | 14 +- .../connector_types.test.ts.snap | 985 ------------------ ...s_upgrade_and_rollback_checks.test.ts.snap | 335 ------ 14 files changed, 161 insertions(+), 1354 deletions(-) diff --git a/packages/core/config/core-config-server-internal/index.ts b/packages/core/config/core-config-server-internal/index.ts index 734888dba84e2..a0bf42df31d64 100644 --- a/packages/core/config/core-config-server-internal/index.ts +++ b/packages/core/config/core-config-server-internal/index.ts @@ -7,4 +7,7 @@ */ export { coreDeprecationProvider } from './src/deprecation'; -export { ensureValidConfiguration } from './src/ensure_valid_configuration'; +export { + ensureValidConfiguration, + type EnsureValidConfigurationParameters, +} from './src/ensure_valid_configuration'; diff --git a/packages/core/config/core-config-server-internal/src/ensure_valid_configuration.test.ts b/packages/core/config/core-config-server-internal/src/ensure_valid_configuration.test.ts index c2ed9523c5050..ddfed9489def8 100644 --- a/packages/core/config/core-config-server-internal/src/ensure_valid_configuration.test.ts +++ b/packages/core/config/core-config-server-internal/src/ensure_valid_configuration.test.ts @@ -22,19 +22,19 @@ describe('ensureValidConfiguration', () => { }); it('returns normally when there is no unused keys and when the config validates', async () => { - await expect(ensureValidConfiguration(configService as any)).resolves.toBeUndefined(); + await expect(ensureValidConfiguration(configService)).resolves.toBeUndefined(); - expect(configService.validate).toHaveBeenCalledWith(undefined); + expect(configService.validate).toHaveBeenCalledWith({ logDeprecations: true }); }); it('forwards parameters to the `validate` method', async () => { await expect( - ensureValidConfiguration(configService as any, { logDeprecations: false }) + ensureValidConfiguration(configService, { logDeprecations: false }) ).resolves.toBeUndefined(); expect(configService.validate).toHaveBeenCalledWith({ logDeprecations: false }); await expect( - ensureValidConfiguration(configService as any, { logDeprecations: true }) + ensureValidConfiguration(configService, { logDeprecations: true }) ).resolves.toBeUndefined(); expect(configService.validate).toHaveBeenCalledWith({ logDeprecations: true }); }); @@ -44,7 +44,7 @@ describe('ensureValidConfiguration', () => { throw new Error('some message'); }); - await expect(ensureValidConfiguration(configService as any)).rejects.toMatchInlineSnapshot( + await expect(ensureValidConfiguration(configService)).rejects.toMatchInlineSnapshot( `[Error: some message]` ); }); @@ -57,7 +57,7 @@ describe('ensureValidConfiguration', () => { }); try { - await ensureValidConfiguration(configService as any); + await ensureValidConfiguration(configService); } catch (e) { expect(e).toBeInstanceOf(CriticalError); expect(e.processExitCode).toEqual(78); @@ -67,18 +67,26 @@ describe('ensureValidConfiguration', () => { it('throws when there are some unused keys', async () => { configService.getUnusedPaths.mockResolvedValue(['some.key', 'some.other.key']); - await expect(ensureValidConfiguration(configService as any)).rejects.toMatchInlineSnapshot( + await expect(ensureValidConfiguration(configService)).rejects.toMatchInlineSnapshot( `[Error: Unknown configuration key(s): "some.key", "some.other.key". Check for spelling errors and ensure that expected plugins are installed.]` ); }); + it('does not throw when there are some unused keys and skipUnusedConfigKeysCheck: true', async () => { + configService.getUnusedPaths.mockResolvedValue(['some.key', 'some.other.key']); + + await expect( + ensureValidConfiguration(configService, { logDeprecations: true, stripUnknownKeys: true }) + ).resolves.toBeUndefined(); + }); + it('throws a `CriticalError` with the correct processExitCode value', async () => { expect.assertions(2); configService.getUnusedPaths.mockResolvedValue(['some.key', 'some.other.key']); try { - await ensureValidConfiguration(configService as any); + await ensureValidConfiguration(configService); } catch (e) { expect(e).toBeInstanceOf(CriticalError); expect(e.processExitCode).toEqual(64); @@ -88,13 +96,13 @@ describe('ensureValidConfiguration', () => { it('does not throw when all unused keys are included in the ignored paths', async () => { configService.getUnusedPaths.mockResolvedValue(['dev.someDevKey', 'elastic.apm.enabled']); - await expect(ensureValidConfiguration(configService as any)).resolves.toBeUndefined(); + await expect(ensureValidConfiguration(configService)).resolves.toBeUndefined(); }); it('throws when only some keys are included in the ignored paths', async () => { configService.getUnusedPaths.mockResolvedValue(['dev.someDevKey', 'some.key']); - await expect(ensureValidConfiguration(configService as any)).rejects.toMatchInlineSnapshot( + await expect(ensureValidConfiguration(configService)).rejects.toMatchInlineSnapshot( `[Error: Unknown configuration key(s): "some.key". Check for spelling errors and ensure that expected plugins are installed.]` ); }); diff --git a/packages/core/config/core-config-server-internal/src/ensure_valid_configuration.ts b/packages/core/config/core-config-server-internal/src/ensure_valid_configuration.ts index ddf4573b8de22..2da6731b6d63b 100644 --- a/packages/core/config/core-config-server-internal/src/ensure_valid_configuration.ts +++ b/packages/core/config/core-config-server-internal/src/ensure_valid_configuration.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { ConfigService, ConfigValidateParameters } from '@kbn/config'; +import type { IConfigService, ConfigValidateParameters } from '@kbn/config'; import { CriticalError } from '@kbn/core-base-server-internal'; const ignoredPaths = ['dev.', 'elastic.apm.']; @@ -14,12 +14,32 @@ const ignoredPaths = ['dev.', 'elastic.apm.']; const invalidConfigExitCode = 78; const legacyInvalidConfigExitCode = 64; +/** + * Parameters for the helper {@link ensureValidConfiguration} + * + * @private + */ +export interface EnsureValidConfigurationParameters extends ConfigValidateParameters { + /** + * Set to `true` to ignore any unknown keys and discard them from the final validated config object. + */ + stripUnknownKeys?: boolean; +} + +/** + * Validate the entire Kibana configuration object, including the detection of extra keys. + * @param configService The {@link IConfigService} instance that has the raw configuration preloaded. + * @param params {@link EnsureValidConfigurationParameters | Options} to enable/disable extra edge-cases. + * + * @private + */ export async function ensureValidConfiguration( - configService: ConfigService, - params?: ConfigValidateParameters + configService: IConfigService, + params: EnsureValidConfigurationParameters = { logDeprecations: true } ) { + const { stripUnknownKeys, ...validateParams } = params; try { - await configService.validate(params); + await configService.validate(validateParams); } catch (e) { throw new CriticalError(e.message, 'InvalidConfig', invalidConfigExitCode, e); } @@ -29,7 +49,7 @@ export async function ensureValidConfiguration( return !ignoredPaths.some((ignoredPath) => unusedPath.startsWith(ignoredPath)); }); - if (unusedConfigKeys.length > 0) { + if (unusedConfigKeys.length > 0 && !stripUnknownKeys) { const message = `Unknown configuration key(s): ${unusedConfigKeys .map((key) => `"${key}"`) .join(', ')}. Check for spelling errors and ensure that expected plugins are installed.`; diff --git a/packages/core/root/core-root-server-internal/src/server.ts b/packages/core/root/core-root-server-internal/src/server.ts index 5613a70fce97f..d380bffd8b359 100644 --- a/packages/core/root/core-root-server-internal/src/server.ts +++ b/packages/core/root/core-root-server-internal/src/server.ts @@ -256,7 +256,7 @@ export class Server { const environmentSetup = this.environment.setup(); // Configuration could have changed after preboot. - await ensureValidConfiguration(this.configService); + await this.ensureValidConfiguration(); const { uiPlugins, pluginPaths, pluginTree } = this.discoveredPlugins!.standard; const contextServiceSetup = this.context.setup({ @@ -486,6 +486,27 @@ export class Server { this.userProfile.stop(); } + private async ensureValidConfiguration() { + try { + await ensureValidConfiguration(this.configService); + } catch (validationError) { + if (this.env.packageInfo.buildFlavor !== 'serverless') { + throw validationError; + } + // When running on serverless, we may allow unknown keys, but stripping them from the final config object. + this.configService.setGlobalStripUnknownKeys(true); + await ensureValidConfiguration(this.configService, { + logDeprecations: true, + stripUnknownKeys: true, + }); + this.log + .get('config-validation') + .error( + `Strict config validation failed! Extra unknown keys removed in Serverless-compatible mode. Original error: ${validationError}` + ); + } + } + private registerCoreContext(coreSetup: InternalCoreSetup) { coreSetup.http.registerRouteHandlerContext( coreId, diff --git a/packages/kbn-config-mocks/src/config_service.mock.ts b/packages/kbn-config-mocks/src/config_service.mock.ts index 268f5a4558022..440662ee850eb 100644 --- a/packages/kbn-config-mocks/src/config_service.mock.ts +++ b/packages/kbn-config-mocks/src/config_service.mock.ts @@ -29,6 +29,7 @@ const createConfigServiceMock = ({ getDeprecatedConfigPath$: jest.fn(), addDynamicConfigPaths: jest.fn(), setDynamicConfigOverrides: jest.fn(), + setGlobalStripUnknownKeys: jest.fn(), }; mocked.atPath.mockReturnValue(new BehaviorSubject(atPath)); diff --git a/packages/kbn-config-schema/README.md b/packages/kbn-config-schema/README.md index 45e46c078baee..10d96578d4689 100644 --- a/packages/kbn-config-schema/README.md +++ b/packages/kbn-config-schema/README.md @@ -64,6 +64,8 @@ Every schema instance has a `validate` method that is used to perform a validati * `data: any` - **required**, data to be validated with the schema * `context: Record` - **optional**, object whose properties can be referenced by the [context references](#schemacontextref) * `namespace: string` - **optional**, arbitrary string that is used to prefix every error message thrown during validation +* `validationOptions: SchemaValidationOptions` - **optional**, global options to modify the default validation behavior + * `stripUnknownKeys: boolean` - **optional**, when `true`, it changes the default `unknowns: 'forbid'` to behave like `unknowns: 'ignore'`. This change of behavior only occurs in schemas without an explicit `unknowns` option. Refer to [`schema.object()`](#schemaobject) for more information about the `unknowns` option. ```typescript const valueSchema = schema.object({ @@ -243,7 +245,7 @@ __Output type:__ `{ [K in keyof TProps]: TypeOf } as TObject` __Options:__ * `defaultValue: TObject | Reference | (() => TObject)` - defines a default value, see [Default values](#default-values) section for more details. * `validate: (value: TObject) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details. - * `unknowns: 'allow' | 'ignore' | 'forbid'` - indicates whether unknown object properties should be allowed, ignored, or forbidden. It's `forbid` by default. + * `unknowns: 'allow' | 'ignore' | 'forbid'` - indicates whether unknown object properties and sub-properties should be allowed, ignored, or forbidden. It is `forbid` by default unless the global validation option `stripUnknownKeys` is set to `true` when calling `validate()`. Refer to [the `validate()` API options](#schema-building-blocks) to learn about `stripUnknownKeys`. __Usage:__ ```typescript @@ -255,6 +257,7 @@ const valueSchema = schema.object({ __Notes:__ * Using `unknowns: 'allow'` is discouraged and should only be used in exceptional circumstances. Consider using `schema.recordOf()` instead. +* Bear in mind that specifying `unknowns: 'allow' | 'ignore' | 'forbid'` applies to the entire tree of sub-objects. If you want this option to apply only to the properties in first level, make sure to override this option by setting a new `unknowns` option in the child `schema.object()`s. * Currently `schema.object()` always has a default value of `{}`, but this may change in the near future. Try to not rely on this behaviour and specify default value explicitly or use `schema.maybe()` if the value is optional. * `schema.object()` also supports a json string as input if it can be safely parsed using `JSON.parse` and if the resulting value is a plain object. diff --git a/packages/kbn-config-schema/index.ts b/packages/kbn-config-schema/index.ts index 8195631606381..e0adf545f454f 100644 --- a/packages/kbn-config-schema/index.ts +++ b/packages/kbn-config-schema/index.ts @@ -57,6 +57,7 @@ import { export type { AnyType, ConditionalType, TypeOf, Props, SchemaStructureEntry, NullableProps }; export { ObjectType, Type }; +export type { SchemaValidationOptions } from './src/types'; export { ByteSizeValue } from './src/byte_size_value'; export { SchemaTypeError, ValidationError } from './src/errors'; export { isConfigSchema } from './src/typeguards'; diff --git a/packages/kbn-config-schema/src/types/index.ts b/packages/kbn-config-schema/src/types/index.ts index 4c92bb6d078dc..0cd2e8ec533d2 100644 --- a/packages/kbn-config-schema/src/types/index.ts +++ b/packages/kbn-config-schema/src/types/index.ts @@ -6,8 +6,7 @@ * Side Public License, v 1. */ -export type { TypeOptions } from './type'; -export type { SchemaStructureEntry } from './type'; +export type { SchemaStructureEntry, SchemaValidationOptions, TypeOptions } from './type'; export { Type } from './type'; export { AnyType } from './any_type'; export type { ArrayOptions } from './array_type'; diff --git a/packages/kbn-config-schema/src/types/object_type.test.ts b/packages/kbn-config-schema/src/types/object_type.test.ts index bdf72c585b9eb..d12688fa04099 100644 --- a/packages/kbn-config-schema/src/types/object_type.test.ts +++ b/packages/kbn-config-schema/src/types/object_type.test.ts @@ -338,12 +338,32 @@ test('allow and remove unknown keys when unknowns = `ignore`', () => { }); }); -test('unknowns = `ignore` affects only own keys', () => { +test('unknowns = `ignore` is recursive if no explicit preferences in sub-keys', () => { const type = schema.object( { foo: schema.object({ bar: schema.string() }) }, { unknowns: 'ignore' } ); + expect( + type.validate({ + foo: { + bar: 'bar', + baz: 'baz', + }, + }) + ).toEqual({ + foo: { + bar: 'bar', + }, + }); +}); + +test('unknowns = `ignore` respects local preferences in sub-keys', () => { + const type = schema.object( + { foo: schema.object({ bar: schema.string() }, { unknowns: 'forbid' }) }, + { unknowns: 'ignore' } + ); + expect(() => type.validate({ foo: { @@ -354,7 +374,7 @@ test('unknowns = `ignore` affects only own keys', () => { ).toThrowErrorMatchingInlineSnapshot(`"[foo.baz]: definition for this key is missing"`); }); -describe('nested unknows', () => { +describe('nested unknowns', () => { test('allow unknown keys when unknowns = `allow`', () => { const type = schema.object({ myObj: schema.object({ foo: schema.string({ defaultValue: 'test' }) }, { unknowns: 'allow' }), @@ -427,8 +447,7 @@ describe('nested unknows', () => { }, }); }); - - test('unknowns = `ignore` affects only own keys', () => { + test('unknowns = `ignore` is recursive if no explicit preferences in sub-keys', () => { const type = schema.object({ myObj: schema.object( { foo: schema.object({ bar: schema.string() }) }, @@ -436,6 +455,32 @@ describe('nested unknows', () => { ), }); + expect( + type.validate({ + myObj: { + foo: { + bar: 'bar', + baz: 'baz', + }, + }, + }) + ).toEqual({ + myObj: { + foo: { + bar: 'bar', + }, + }, + }); + }); + + test('unknowns = `ignore` respects local preferences in sub-keys', () => { + const type = schema.object({ + myObj: schema.object( + { foo: schema.object({ bar: schema.string() }, { unknowns: 'forbid' }) }, + { unknowns: 'ignore' } + ), + }); + expect(() => type.validate({ myObj: { diff --git a/packages/kbn-config-schema/src/types/object_type.ts b/packages/kbn-config-schema/src/types/object_type.ts index 76d0bab474ec8..44c81c213fd31 100644 --- a/packages/kbn-config-schema/src/types/object_type.ts +++ b/packages/kbn-config-schema/src/types/object_type.ts @@ -87,16 +87,11 @@ export class ObjectType

      extends Type> constructor(props: P, options: ObjectTypeOptions

      = {}) { const schemaKeys = {} as Record; - const { unknowns = 'forbid', ...typeOptions } = options; + const { unknowns, ...typeOptions } = options; for (const [key, value] of Object.entries(props)) { schemaKeys[key] = value.getSchema(); } - let schema = internals - .object() - .keys(schemaKeys) - .default() - .optional() - .options({ stripUnknown: { objects: unknowns === 'ignore' } }); + let schema = internals.object().keys(schemaKeys).default().optional(); // We need to specify the `.unknown` property only when we want to override the default `forbid` // or it will break `stripUnknown` functionality. @@ -104,6 +99,11 @@ export class ObjectType

      extends Type> schema = schema.unknown(unknowns === 'allow'); } + // Only set stripUnknown if we have an explicit value of `unknowns` + if (unknowns) { + schema = schema.options({ stripUnknown: { objects: unknowns === 'ignore' } }); + } + if (options.meta?.id) { schema = schema.id(options.meta.id); } diff --git a/packages/kbn-config-schema/src/types/type.ts b/packages/kbn-config-schema/src/types/type.ts index 3b8808dba61af..d7e02e439ae3b 100644 --- a/packages/kbn-config-schema/src/types/type.ts +++ b/packages/kbn-config-schema/src/types/type.ts @@ -37,6 +37,16 @@ export interface SchemaStructureEntry { type: string; } +/** + * Global validation Options to be provided when calling the `schema.validate()` method. + */ +export interface SchemaValidationOptions { + /** + * Remove unknown config keys + */ + stripUnknownKeys?: boolean; +} + /** * Options for dealing with unknown keys: * - allow: unknown keys will be permitted @@ -129,10 +139,16 @@ export abstract class Type { * Validates the provided value against this schema. * If valid, the resulting output will be returned, otherwise an exception will be thrown. */ - public validate(value: unknown, context: Record = {}, namespace?: string): V { + public validate( + value: unknown, + context: Record = {}, + namespace?: string, + validationOptions?: SchemaValidationOptions + ): V { const { value: validatedValue, error } = this.internalSchema.validate(value, { context, presence: 'required', + stripUnknown: { objects: validationOptions?.stripUnknownKeys === true }, }); if (error) { diff --git a/packages/kbn-config/src/config_service.ts b/packages/kbn-config/src/config_service.ts index 0ad29cb79343c..51a83f82664e2 100644 --- a/packages/kbn-config/src/config_service.ts +++ b/packages/kbn-config/src/config_service.ts @@ -47,6 +47,7 @@ export class ConfigService { private readonly deprecationLog: Logger; private readonly docLinks: DocLinks; + private stripUnknownKeys = false; private validated = false; private readonly config$: Observable; private lastConfig?: Config; @@ -96,6 +97,14 @@ export class ConfigService { ); } + /** + * Set the global setting for stripUnknownKeys. Useful for running in Serverless-compatible way. + * @param stripUnknownKeys Set to `true` if unknown keys (not explicitly forbidden) should be dropped without failing validation + */ + public setGlobalStripUnknownKeys(stripUnknownKeys: boolean) { + this.stripUnknownKeys = stripUnknownKeys; + } + /** * Set config schema for a path and performs its validation */ @@ -139,7 +148,7 @@ export class ConfigService { public async validate(params: ConfigValidateParameters = { logDeprecations: true }) { const namespaces = [...this.schemas.keys()]; for (let i = 0; i < namespaces.length; i++) { - await this.getValidatedConfigAtPath$(namespaces[i]).pipe(first()).toPromise(); + await firstValueFrom(this.getValidatedConfigAtPath$(namespaces[i])); } if (params.logDeprecations) { @@ -313,7 +322,8 @@ export class ConfigService { serverless: this.env.packageInfo.buildFlavor === 'serverless', ...this.env.packageInfo, }, - `config validation of [${namespace}]` + `config validation of [${namespace}]`, + this.stripUnknownKeys ? { stripUnknownKeys: this.stripUnknownKeys } : {} ); } diff --git a/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap b/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap index f1c55958c9284..e1366f7f9c573 100644 --- a/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap +++ b/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap @@ -75,11 +75,6 @@ Object { "type": "number", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -109,11 +104,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -193,11 +183,6 @@ Object { "type": "number", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -268,11 +253,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, ], @@ -393,11 +373,6 @@ Object { "type": "number", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -468,11 +443,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, ], @@ -593,11 +563,6 @@ Object { "type": "number", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -643,11 +608,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -691,11 +651,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -742,11 +697,6 @@ Object { "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -1001,11 +951,6 @@ Object { "type": "array", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "timeWindow": Object { @@ -1031,11 +976,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -1050,11 +990,6 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -1069,11 +1004,6 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -1120,11 +1050,6 @@ Object { "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -1718,11 +1643,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -1928,11 +1848,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "rules": Array [ Object { "args": Object { @@ -2031,11 +1946,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, ], @@ -2318,27 +2228,12 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -2381,11 +2276,6 @@ Object { "type": "array", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -2527,11 +2417,6 @@ Object { "type": "array", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -2570,11 +2455,6 @@ Object { "type": "array", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -2604,11 +2484,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -2652,11 +2527,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -2703,11 +2573,6 @@ Object { "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -2786,11 +2651,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -2869,11 +2729,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -2903,11 +2758,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -2937,11 +2787,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -2988,11 +2833,6 @@ Object { "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -3272,11 +3112,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -3406,11 +3241,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -3517,11 +3347,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "message": Object { @@ -3616,11 +3441,6 @@ Object { "type": "array", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -3743,11 +3563,6 @@ Object { "type": "number", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -3777,11 +3592,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -3904,11 +3714,6 @@ Object { "type": "number", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -4028,11 +3833,6 @@ Object { "type": "number", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -4152,11 +3952,6 @@ Object { "type": "number", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -4230,11 +4025,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -4264,11 +4054,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -4315,11 +4100,6 @@ Object { "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -4395,11 +4175,6 @@ Object { "type": "boolean", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -4412,11 +4187,6 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -4528,11 +4298,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -4576,11 +4341,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -4624,11 +4384,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -4668,19 +4423,9 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -4728,19 +4473,9 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -4773,19 +4508,9 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -4869,11 +4594,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, ], @@ -5241,27 +4961,12 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -5294,19 +4999,9 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -5354,19 +5049,9 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -5414,19 +5099,9 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -5474,19 +5149,9 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -5505,11 +5170,6 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -5524,11 +5184,6 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -5657,11 +5312,6 @@ Object { "type": "array", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "connector": Object { @@ -5804,19 +5454,9 @@ Object { "type": "array", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -6251,11 +5891,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -6340,11 +5975,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -6383,11 +6013,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -6567,11 +6192,6 @@ Object { "type": "any", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -6610,11 +6230,6 @@ Object { "type": "any", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -6653,11 +6268,6 @@ Object { "type": "any", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -6696,11 +6306,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -6724,11 +6329,6 @@ Object { "type": "array", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -6848,11 +6448,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -6882,11 +6477,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -6916,11 +6506,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -6967,11 +6552,6 @@ Object { "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -7025,11 +6605,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -7059,11 +6634,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -7315,11 +6885,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, ], @@ -7466,11 +7031,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "rules": Array [ Object { "args": Object { @@ -7544,11 +7104,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, ], @@ -7739,19 +7294,9 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -7766,11 +7311,6 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -7785,11 +7325,6 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -7804,11 +7339,6 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -7852,11 +7382,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -7900,11 +7425,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -7951,11 +7471,6 @@ Object { "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -8047,11 +7562,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -8151,11 +7661,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -8221,11 +7726,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -8900,11 +8400,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -14647,11 +14142,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -20359,11 +19849,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -26071,11 +25556,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -26190,11 +25670,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "script": Object { @@ -26408,11 +25883,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "requiresApproval": Object { @@ -26540,19 +26010,9 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -26582,11 +26042,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -26616,11 +26071,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -26667,11 +26117,6 @@ Object { "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -26684,11 +26129,6 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -26701,11 +26141,6 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -26817,11 +26252,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -26981,11 +26411,6 @@ Object { "type": "boolean", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -27191,11 +26616,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -27235,19 +26655,9 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -27295,19 +26705,9 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -27340,19 +26740,9 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -27436,11 +26826,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, ], @@ -27909,27 +27294,12 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -27986,19 +27356,9 @@ Object { "type": "array", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -28119,27 +27479,12 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -28295,11 +27640,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -28505,11 +27845,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -28970,19 +28305,9 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -29039,19 +28364,9 @@ Object { "type": "array", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -29215,11 +28530,6 @@ Object { "type": "boolean", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -29425,11 +28735,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -29469,19 +28774,9 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -29529,19 +28824,9 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -29574,19 +28859,9 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -29670,11 +28945,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, ], @@ -30463,27 +29733,12 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -30540,19 +29795,9 @@ Object { "type": "array", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -30569,11 +29814,6 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -30603,11 +29843,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -30648,11 +29883,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -30734,11 +29964,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, ], @@ -30758,11 +29983,6 @@ Object { "type": "array", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -30803,11 +30023,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -30869,19 +30084,9 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -31016,19 +30221,9 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -31158,19 +30353,9 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -31344,11 +30529,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -31441,11 +30621,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -31538,11 +30713,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -31635,11 +30805,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -31732,11 +30897,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -31829,11 +30989,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -31926,11 +31081,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -31950,19 +31100,9 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -31992,11 +31132,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -32087,11 +31222,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, ], @@ -32389,27 +31519,12 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -32426,11 +31541,6 @@ Object { "presence": "optional", }, "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -32460,11 +31570,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -32505,11 +31610,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -32531,11 +31631,6 @@ Object { "type": "number", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -32631,11 +31726,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "webhookUrl": Object { @@ -32660,11 +31750,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -32760,11 +31845,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "webhookUrl": Object { @@ -32789,11 +31869,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -32823,11 +31898,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -32871,11 +31941,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -32922,11 +31987,6 @@ Object { "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -32956,11 +32016,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -32990,11 +32045,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -33024,11 +32074,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -33332,11 +32377,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -33542,11 +32582,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "rules": Array [ Object { "args": Object { @@ -33591,11 +32626,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -33657,11 +32687,6 @@ Object { "type": "boolean", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -33791,11 +32816,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -33951,11 +32971,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; diff --git a/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap b/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap index c0827083779f3..456b7e00dd1ed 100644 --- a/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap +++ b/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap @@ -183,11 +183,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -457,11 +452,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, ], @@ -814,11 +804,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "rules": Array [ Object { "args": Object { @@ -1129,11 +1114,6 @@ Object { "type": "any", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -1556,11 +1536,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "rules": Array [ Object { "args": Object { @@ -1784,11 +1759,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -1968,11 +1938,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -1981,11 +1946,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "serviceName": Object { @@ -2049,11 +2009,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -2256,11 +2211,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -2269,11 +2219,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "serviceName": Object { @@ -2379,11 +2324,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -2542,11 +2482,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -2555,11 +2490,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "serviceName": Object { @@ -2665,11 +2595,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -2824,11 +2749,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "metric": Object { @@ -2936,11 +2856,6 @@ Object { "type": "array", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, ], @@ -3225,11 +3140,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -3302,11 +3212,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -3352,11 +3257,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, ], @@ -3535,11 +3435,6 @@ Object { "type": "record", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, ], @@ -3712,11 +3607,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -3803,11 +3693,6 @@ Object { "type": "any", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -4021,11 +3906,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "name": Object { @@ -4184,11 +4064,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "popularity": Object { @@ -4239,11 +4114,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "type": Object { @@ -4339,11 +4209,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -4498,11 +4363,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "popularity": Object { @@ -4619,11 +4479,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -4659,11 +4514,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "type": Object { @@ -4677,11 +4527,6 @@ Object { "type": "any", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -4796,11 +4641,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "nested": Object { @@ -4830,11 +4670,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -4843,11 +4678,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "type": Object { @@ -5110,11 +4940,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "popularity": Object { @@ -5165,11 +4990,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "type": Object { @@ -5265,11 +5085,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -5424,11 +5239,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "popularity": Object { @@ -5545,11 +5355,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -5585,11 +5390,6 @@ Object { "x-oas-optional": true, }, ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "type": Object { @@ -5603,11 +5403,6 @@ Object { "type": "any", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -5690,11 +5485,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, ], @@ -5803,11 +5593,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -5858,19 +5643,9 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -7909,11 +7684,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -9928,11 +9698,6 @@ Object { "type": "string", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, ], @@ -10035,11 +9800,6 @@ Object { "type": "number", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, "maxBurnRateThreshold": Object { @@ -10102,30 +9862,15 @@ Object { "type": "number", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, ], "type": "array", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -10255,11 +10000,6 @@ Object { "type": "boolean", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -10304,11 +10044,6 @@ Object { "type": "boolean", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -10353,11 +10088,6 @@ Object { "type": "boolean", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -10377,11 +10107,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -10401,11 +10126,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -10488,11 +10208,6 @@ Object { "type": "array", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "rules": Array [ Object { "args": Object { @@ -10644,11 +10359,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; @@ -10731,11 +10441,6 @@ Object { "type": "array", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "rules": Array [ Object { "args": Object { @@ -10822,11 +10527,6 @@ Object { "type": "array", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "rules": Array [ Object { "args": Object { @@ -10918,11 +10618,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -10967,11 +10662,6 @@ Object { "type": "boolean", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -11092,11 +10782,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -11141,11 +10826,6 @@ Object { "type": "boolean", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -11190,11 +10870,6 @@ Object { "type": "boolean", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -11214,11 +10889,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", }, }, @@ -11238,11 +10908,6 @@ Object { "type": "alternatives", }, }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, "type": "object", } `; From a6cc252a8320cd7f072947fe326950babdd4f772 Mon Sep 17 00:00:00 2001 From: Gloria Hornero Date: Mon, 1 Jul 2024 19:32:56 +0200 Subject: [PATCH 031/128] [Detection Engine] Skips failing tests on the periodic pipeline (#187280) ## Summary Skips failing tests on the periodic pipeline --- .../add_edit_endpoint_exception.cy.ts | 293 +++++++++--------- .../rule_creation/custom_query_rule.cy.ts | 3 +- 2 files changed, 151 insertions(+), 145 deletions(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts index 07be6f79efd45..cf11b9553f845 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts @@ -48,185 +48,190 @@ import { deleteExceptionLists, } from '../../../../../tasks/api_calls/exceptions'; -describe('Add endpoint exception from rule details', { tags: ['@ess', '@serverless'] }, () => { - const ITEM_NAME = 'Sample Exception List Item'; - const NEW_ITEM_NAME = 'Exception item-EDITED'; - const ITEM_FIELD = 'event.code'; - const FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD = 'agent.type'; - - before(() => { - cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); - }); +// https://github.com/elastic/kibana/issues/187279 +describe( + 'Add endpoint exception from rule details', + { tags: ['@ess', '@serverless, @skipInServerlessMKI'] }, + () => { + const ITEM_NAME = 'Sample Exception List Item'; + const NEW_ITEM_NAME = 'Exception item-EDITED'; + const ITEM_FIELD = 'event.code'; + const FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD = 'agent.type'; + + before(() => { + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); + }); - after(() => { - cy.task('esArchiverUnload', { archiveName: 'auditbeat_multiple' }); - }); + after(() => { + cy.task('esArchiverUnload', { archiveName: 'auditbeat_multiple' }); + }); - beforeEach(() => { - deleteExceptionLists(); - deleteEndpointExceptionList(); + beforeEach(() => { + deleteExceptionLists(); + deleteEndpointExceptionList(); - login(); - deleteAlertsAndRules(); - }); + login(); + deleteAlertsAndRules(); + }); - describe('without exception items', () => { - beforeEach(() => { - createEndpointExceptionList().then((response) => { - createRule( - getNewRule({ - query: 'event.code:*', - index: ['auditbeat*'], - exceptions_list: [ - { - id: response.body.id, - list_id: response.body.list_id, - type: response.body.type, - namespace_type: response.body.namespace_type, - }, - ], - rule_id: '2', - enabled: false, - }) - ).then((rule) => visitRuleDetailsPage(rule.body.id, { tab: 'endpoint_exceptions' })); + describe('without exception items', () => { + beforeEach(() => { + createEndpointExceptionList().then((response) => { + createRule( + getNewRule({ + query: 'event.code:*', + index: ['auditbeat*'], + exceptions_list: [ + { + id: response.body.id, + list_id: response.body.list_id, + type: response.body.type, + namespace_type: response.body.namespace_type, + }, + ], + rule_id: '2', + enabled: false, + }) + ).then((rule) => visitRuleDetailsPage(rule.body.id, { tab: 'endpoint_exceptions' })); + }); }); - }); - it('creates an exception item', () => { - // when no exceptions exist, empty component shows with action to add exception + it('creates an exception item', () => { + // when no exceptions exist, empty component shows with action to add exception - cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('exist'); + cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('exist'); - // open add exception modal - openExceptionFlyoutFromEmptyViewerPrompt(); + // open add exception modal + openExceptionFlyoutFromEmptyViewerPrompt(); - // submit button is disabled if no paramerters were added - cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); + // submit button is disabled if no paramerters were added + cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); - // for endpoint exceptions, must specify OS - selectOs('windows'); + // for endpoint exceptions, must specify OS + selectOs('windows'); - // add exception item conditions - addExceptionConditions({ - field: 'event.code', - operator: 'is', - values: ['foo'], - }); + // add exception item conditions + addExceptionConditions({ + field: 'event.code', + operator: 'is', + values: ['foo'], + }); - // Name is required so want to check that submit is still disabled - cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); + // Name is required so want to check that submit is still disabled + cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); - // add exception item name - addExceptionFlyoutItemName(ITEM_NAME); + // add exception item name + addExceptionFlyoutItemName(ITEM_NAME); - // Option to add to rule or add to list should NOT appear - cy.get(ADD_TO_RULE_OR_LIST_SECTION).should('not.exist'); + // Option to add to rule or add to list should NOT appear + cy.get(ADD_TO_RULE_OR_LIST_SECTION).should('not.exist'); - // not testing close alert functionality here, just ensuring that the options appear as expected - cy.get(CLOSE_SINGLE_ALERT_CHECKBOX).should('not.exist'); - cy.get(CLOSE_ALERTS_CHECKBOX).should('exist'); + // not testing close alert functionality here, just ensuring that the options appear as expected + cy.get(CLOSE_SINGLE_ALERT_CHECKBOX).should('not.exist'); + cy.get(CLOSE_ALERTS_CHECKBOX).should('exist'); - // submit - submitNewExceptionItem(); + // submit + submitNewExceptionItem(); - // new exception item displays - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + // new exception item displays + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + }); }); - }); - - // FLAKY: https://github.com/elastic/kibana/issues/179582 - describe.skip('with exception items', () => { - beforeEach(() => { - createEndpointExceptionList().then((response) => { - createEndpointExceptionListItem({ - comments: [], - description: 'Exception list item', - entries: [ - { - field: ITEM_FIELD, - operator: 'included', - type: 'match', - value: 'foo', - }, - ], - name: ITEM_NAME, - tags: [], - type: 'simple', - os_types: ['windows'], - }); - createRule( - getNewRule({ - name: 'Rule with exceptions', - query: 'event.code:*', - index: ['auditbeat*'], - exceptions_list: [ + // FLAKY: https://github.com/elastic/kibana/issues/179582 + describe.skip('with exception items', () => { + beforeEach(() => { + createEndpointExceptionList().then((response) => { + createEndpointExceptionListItem({ + comments: [], + description: 'Exception list item', + entries: [ { - id: response.body.id, - list_id: response.body.list_id, - type: response.body.type, - namespace_type: response.body.namespace_type, + field: ITEM_FIELD, + operator: 'included', + type: 'match', + value: 'foo', }, ], - rule_id: '2', - enabled: false, - }) - ).then((rule) => { - visitRuleDetailsPage(rule.body.id, { tab: 'endpoint_exceptions' }); - waitForRuleDetailsPageToBeLoaded('Rule with exceptions'); + name: ITEM_NAME, + tags: [], + type: 'simple', + os_types: ['windows'], + }); + + createRule( + getNewRule({ + name: 'Rule with exceptions', + query: 'event.code:*', + index: ['auditbeat*'], + exceptions_list: [ + { + id: response.body.id, + list_id: response.body.list_id, + type: response.body.type, + namespace_type: response.body.namespace_type, + }, + ], + rule_id: '2', + enabled: false, + }) + ).then((rule) => { + visitRuleDetailsPage(rule.body.id, { tab: 'endpoint_exceptions' }); + waitForRuleDetailsPageToBeLoaded('Rule with exceptions'); + }); }); }); - }); - it('edits an endpoint exception item', () => { - // displays existing exception items - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist'); - cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', ITEM_NAME); - cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should('have.text', ` ${ITEM_FIELD}IS foo`); + it('edits an endpoint exception item', () => { + // displays existing exception items + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist'); + cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', ITEM_NAME); + cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should('have.text', ` ${ITEM_FIELD}IS foo`); - // open edit exception modal - openEditException(); + // open edit exception modal + openEditException(); - // edit exception item name - editExceptionFlyoutItemName(NEW_ITEM_NAME); + // edit exception item name + editExceptionFlyoutItemName(NEW_ITEM_NAME); - // check that the existing item's field is being populated - cy.get(EXCEPTION_ITEM_CONTAINER) - .eq(0) - .find(FIELD_INPUT_PARENT) - .eq(0) - .should('have.value', ITEM_FIELD); - cy.get(VALUES_INPUT).should('have.value', 'foo'); + // check that the existing item's field is being populated + cy.get(EXCEPTION_ITEM_CONTAINER) + .eq(0) + .find(FIELD_INPUT_PARENT) + .eq(0) + .should('have.value', ITEM_FIELD); + cy.get(VALUES_INPUT).should('have.value', 'foo'); - // edit conditions - editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0); + // edit conditions + editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0); - // submit - submitEditedExceptionItem(); + // submit + submitEditedExceptionItem(); - // new exception item displays - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + // new exception item displays + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - // check that updates stuck - cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', NEW_ITEM_NAME); - cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should('have.text', ' agent.typeIS foo'); - }); + // check that updates stuck + cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', NEW_ITEM_NAME); + cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should('have.text', ' agent.typeIS foo'); + }); - it('allows user to search for items', () => { - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + it('allows user to search for items', () => { + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - // can search for an exception value - searchForExceptionItem('foo'); + // can search for an exception value + searchForExceptionItem('foo'); - // new exception item displays - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + // new exception item displays + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - // displays empty search result view if no matches found - searchForExceptionItem('abc'); + // displays empty search result view if no matches found + searchForExceptionItem('abc'); - // new exception item displays - cy.get(NO_EXCEPTIONS_SEARCH_RESULTS_PROMPT).should('exist'); + // new exception item displays + cy.get(NO_EXCEPTIONS_SEARCH_RESULTS_PROMPT).should('exist'); + }); }); - }); -}); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/custom_query_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/custom_query_rule.cy.ts index 36c84cdf93e2d..4bb45e03a15fa 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/custom_query_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/custom_query_rule.cy.ts @@ -73,7 +73,8 @@ describe('Create custom query rule', { tags: ['@ess', '@serverless'] }, () => { cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should('have.text', 'host.name: exists'); }); - describe('Alert suppression', () => { + // https://github.com/elastic/kibana/issues/187277 + describe('Alert suppression', { tags: ['@skipInServerlessMKI'] }, () => { const SUPPRESS_BY_FIELDS = ['source.ip']; it('creates rule with suppression', () => { From 3506e1413d24499b802813440994b745c6dcce07 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:48:48 +0100 Subject: [PATCH 032/128] [Security Solution][Detection Engine] adds AI Assistant to rule create form (#179091) ## Summary - adds AI assistant for queries for every rule type, apart Machine Learning - AI assistant is shown only when query is not empty and invalid - When user clicks on assistant it records telemetry event `open_assistant_on_rule_query_error ` - hidden behind `AIAssistantOnRuleCreationFormEnabled` feature flag ## Design [Design](https://www.figma.com/file/nbgUduTmTpYNXLf1vDMP7u/General-Enhancements?type=design&node-id=115%3A5166&mode=design&t=2Yi5wvS1aDoYxuyT-1) ![AI assistant help contextual](https://github.com/elastic/kibana/assets/119798995/c2ad0989-dd1a-4082-be83-bed7741131be) ## Demo https://github.com/elastic/kibana/assets/92328789/92435f3b-c51e-471b-940f-604a1f245e94 ## Old Demoes **Note: old demo videos use old UI design, and assistant is shown even for valid queries.**

      list of videos ### ES|QL Case 1 Simple ES|QL query validation error solving There 2 problems in query highlighted by validation. First, missing metadata operator Second, operator `=` instead of `==` By feeding query twice in Ai Assistant, I was able to get working solution https://github.com/elastic/kibana/assets/92328789/1eb49505-b161-4fdb-ac3c-d2833c16e2cd ### ES|QL Case 2 Fixes missing _id field, when metadata operator is present https://github.com/elastic/kibana/assets/92328789/82024fcb-822e-46f1-a80a-8b9f1725816e ### EQL Case 1 fixes EQL typo https://github.com/elastic/kibana/assets/92328789/ea18ceec-92f8-4322-b359-50e689a0ef72
      ### Issues Results might not be always consistent and for more complex queries they might not correct https://github.com/elastic/kibana/assets/92328789/e3bedfd6-943c-4979-8708-f6c33d1756a6 --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../impl/new_chat/index.test.tsx | 30 +++++ .../impl/new_chat/index.tsx | 35 ++++-- .../common/experimental_features.ts | 5 + .../public/common/lib/telemetry/constants.ts | 3 + .../rule_creation/logic/esql_validator.ts | 4 +- .../rule_creation/logic/translations.ts | 11 +- .../components/ai_assistant/index.test.tsx | 42 +++++++ .../components/ai_assistant/index.tsx | 106 ++++++++++++++++++ .../components/ai_assistant/translations.ts | 39 +++++++ .../step_define_rule/index.test.tsx | 61 ++++++++++ .../components/step_define_rule/index.tsx | 12 +- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 14 files changed, 334 insertions(+), 17 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/ai_assistant/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/ai_assistant/index.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/ai_assistant/translations.ts diff --git a/x-pack/packages/kbn-elastic-assistant/impl/new_chat/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/new_chat/index.test.tsx index 730c88413833d..09b186903c0bd 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/new_chat/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/new_chat/index.test.tsx @@ -88,4 +88,34 @@ describe('NewChat', () => { expect(mockUseAssistantOverlay.showAssistantOverlay).toHaveBeenCalledWith(true); }); + + it('renders new chat as link', () => { + render(); + + const newChatLink = screen.getByTestId('newChatLink'); + + expect(newChatLink).toBeInTheDocument(); + }); + + it('calls onShowOverlay callback on click', () => { + const onShowOverlaySpy = jest.fn(); + render(); + + const newChatButton = screen.getByTestId('newChat'); + + userEvent.click(newChatButton); + + expect(onShowOverlaySpy).toHaveBeenCalled(); + }); + + it('calls onShowOverlay callback on click for link', () => { + const onShowOverlaySpy = jest.fn(); + render(); + + const newChatLink = screen.getByTestId('newChatLink'); + + userEvent.click(newChatLink); + + expect(onShowOverlaySpy).toHaveBeenCalled(); + }); }); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/new_chat/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/new_chat/index.tsx index a4793dfd25a9d..d45f94b7d0b49 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/new_chat/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/new_chat/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiButtonEmpty } from '@elastic/eui'; +import { EuiButtonEmpty, EuiLink } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import { PromptContext } from '../assistant/prompt_context/types'; @@ -17,7 +17,7 @@ export type Props = Omit & { children?: React.ReactNode; /** Optionally automatically add this context to a conversation when the assistant is shown */ conversationId?: string; - /** Defaults to `discuss`. If null, the button will not have an icon */ + /** Defaults to `discuss`. If null, the button will not have an icon. Not available for link */ iconType?: string | null; /** Optionally specify a well known ID, or default to a UUID */ promptContextId?: string; @@ -25,6 +25,10 @@ export type Props = Omit & { color?: 'text' | 'accent' | 'primary' | 'success' | 'warning' | 'danger'; /** Required to identify the availability of the Assistant for the current license level */ isAssistantEnabled: boolean; + /** Optionally render new chat as a link */ + asLink?: boolean; + /** Optional callback when overlay shows */ + onShowOverlay?: () => void; }; const NewChatComponent: React.FC = ({ @@ -39,6 +43,8 @@ const NewChatComponent: React.FC = ({ suggestedUserPrompt, tooltip, isAssistantEnabled, + asLink = false, + onShowOverlay, }) => { const { showAssistantOverlay } = useAssistantOverlay( category, @@ -53,7 +59,8 @@ const NewChatComponent: React.FC = ({ const showOverlay = useCallback(() => { showAssistantOverlay(true); - }, [showAssistantOverlay]); + onShowOverlay?.(); + }, [showAssistantOverlay, onShowOverlay]); const icon = useMemo(() => { if (iconType === null) { @@ -64,12 +71,22 @@ const NewChatComponent: React.FC = ({ }, [iconType]); return useMemo( - () => ( - - {children} - - ), - [children, icon, showOverlay, color] + () => + asLink ? ( + + {children} + + ) : ( + + {children} + + ), + [children, icon, showOverlay, color, asLink] ); }; diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 53c5bdd8a657e..526e9943ce703 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -153,6 +153,11 @@ export const allowedExperimentalValues = Object.freeze({ */ protectionUpdatesEnabled: true, + /** + * Enables AI assistant on rule creation form when query has error + */ + AIAssistantOnRuleCreationFormEnabled: false, + /** * Disables the timeline save tour. * This flag is used to disable the tour in cypress tests. diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/constants.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/constants.ts index d16fd182928de..4d7c8f180d0ec 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/constants.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/constants.ts @@ -46,6 +46,9 @@ export enum TELEMETRY_EVENT { ADD_INVESTIGATION_FIELDS = 'add_investigation_fields', SET_INVESTIGATION_FIELDS = 'set_investigation_fields', DELETE_INVESTIGATION_FIELDS = 'delete_investigation_fields', + + // AI assistant on rule creation form + OPEN_ASSISTANT_ON_RULE_QUERY_ERROR = 'open_assistant_on_rule_query_error', } export enum TelemetryEventTypes { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.ts index 1f0bcb6596b40..484f78c53f0e0 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.ts @@ -66,7 +66,7 @@ export const esqlValidator = async ( if (isMissingMetadataOperator) { return { code: ERROR_CODES.ERR_MISSING_ID_FIELD_FROM_RESULT, - message: i18n.ESQL_VALIDATION_MISSING_ID_IN_QUERY_ERROR, + message: i18n.ESQL_VALIDATION_MISSING_METADATA_OPERATOR_IN_QUERY_ERROR, }; } @@ -84,7 +84,7 @@ export const esqlValidator = async ( if (!isEsqlQueryAggregating && !isIdFieldPresent) { return { code: ERROR_CODES.ERR_MISSING_ID_FIELD_FROM_RESULT, - message: i18n.ESQL_VALIDATION_MISSING_ID_IN_QUERY_ERROR, + message: i18n.ESQL_VALIDATION_MISSING_ID_FIELD_IN_QUERY_ERROR, }; } } catch (error) { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/translations.ts index 21dbb474b1235..245924b3d7e0a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/translations.ts @@ -20,8 +20,15 @@ export const esqlValidationErrorMessage = (message: string) => defaultMessage: 'Error validating ES|QL: "{message}"', }); -export const ESQL_VALIDATION_MISSING_ID_IN_QUERY_ERROR = i18n.translate( - 'xpack.securitySolution.detectionEngine.esqlValidation.missingIdInQueryError', +export const ESQL_VALIDATION_MISSING_METADATA_OPERATOR_IN_QUERY_ERROR = i18n.translate( + 'xpack.securitySolution.detectionEngine.esqlValidation.missingMetadataOperatorInQueryError', + { + defaultMessage: `Queries that don’t use the STATS...BY function (non-aggregating queries) must include the "metadata _id, _version, _index" operator after the source command. For example: FROM logs* [metadata _id, _version, _index].`, + } +); + +export const ESQL_VALIDATION_MISSING_ID_FIELD_IN_QUERY_ERROR = i18n.translate( + 'xpack.securitySolution.detectionEngine.esqlValidation.missingIdFieldInQueryError', { defaultMessage: `Queries that don’t use the STATS...BY function (non-aggregating queries) must include the "metadata _id, _version, _index" operator after the source command. For example: FROM logs* metadata _id, _version, _index. In addition, the metadata properties (_id, _version, and _index) must be returned in the query response.`, } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/ai_assistant/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/ai_assistant/index.test.tsx new file mode 100644 index 0000000000000..9ec6cba770c71 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/ai_assistant/index.test.tsx @@ -0,0 +1,42 @@ +/* + * 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 { screen, render } from '@testing-library/react'; + +import { TestProviders } from '../../../../common/mock'; +import { useAssistantAvailability } from '../../../../assistant/use_assistant_availability'; + +import { AiAssistant } from '.'; + +jest.mock('../../../../assistant/use_assistant_availability', () => ({ + useAssistantAvailability: jest.fn(), +})); + +const useAssistantAvailabilityMock = useAssistantAvailability as jest.Mock; + +describe('AiAssistant', () => { + beforeEach(() => { + useAssistantAvailabilityMock.mockReturnValue({ hasAssistantPrivilege: true }); + }); + it('does not render chat component when does not have hasAssistantPrivilege', () => { + useAssistantAvailabilityMock.mockReturnValue({ hasAssistantPrivilege: false }); + + const { container } = render(, { + wrapper: TestProviders, + }); + + expect(container).toBeEmptyDOMElement(); + }); + it('renders chat component when has hasAssistantPrivilege', () => { + render(, { + wrapper: TestProviders, + }); + + expect(screen.getByTestId('newChatLink')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/ai_assistant/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/ai_assistant/index.tsx new file mode 100644 index 0000000000000..83ce75d53f79f --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/ai_assistant/index.tsx @@ -0,0 +1,106 @@ +/* + * 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 } from 'react'; +import { EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { NewChat, AssistantAvatar } from '@kbn/elastic-assistant'; + +import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../../../common/lib/telemetry'; +import { useAssistantAvailability } from '../../../../assistant/use_assistant_availability'; +import * as i18nAssistant from '../../../../detections/pages/detection_engine/rules/translations'; +import type { DefineStepRule } from '../../../../detections/pages/detection_engine/rules/types'; +import type { FormHook, ValidationError } from '../../../../shared_imports'; + +import * as i18n from './translations'; + +const getLanguageName = (language: string | undefined) => { + let modifiedLanguage = language; + if (language === 'eql') { + modifiedLanguage = 'EQL(Event Query Language)'; + } + if (language === 'esql') { + modifiedLanguage = 'ES|QL(The Elasticsearch Query Language)'; + } + + return modifiedLanguage; +}; + +const retrieveErrorMessages = (errors: ValidationError[]): string => + errors + .flatMap(({ message, messages }) => [message, ...(Array.isArray(messages) ? messages : [])]) + .join(', '); + +interface AiAssistantProps { + getFields: FormHook['getFields']; + language?: string | undefined; +} + +const AiAssistantComponent: React.FC = ({ getFields, language }) => { + const { hasAssistantPrivilege, isAssistantEnabled } = useAssistantAvailability(); + + const languageName = getLanguageName(language); + + const getPromptContext = useCallback(async () => { + const queryField = getFields().queryBar; + const { query } = (queryField.value as DefineStepRule['queryBar']).query; + + if (!query) { + return ''; + } + + if (queryField.errors.length === 0) { + return `No errors in ${languageName} language query detected. Current query: ${query.trim()}`; + } + + return `${languageName} language query written for Elastic Security Detection rules: \"${query.trim()}\" +returns validation error on form: \"${retrieveErrorMessages(queryField.errors)}\" +Fix ${languageName} language query and give an example of it in markdown format that can be copied. +Proposed solution should be valid and must not contain new line symbols (\\n)`; + }, [getFields, languageName]); + + const onShowOverlay = useCallback(() => { + track(METRIC_TYPE.COUNT, TELEMETRY_EVENT.OPEN_ASSISTANT_ON_RULE_QUERY_ERROR); + }, []); + + if (!hasAssistantPrivilege) { + return null; + } + + return ( + <> + + + + {i18n.ASK_ASSISTANT_ERROR_BUTTON} + + ), + }} + /> + + ); +}; + +export const AiAssistant = React.memo(AiAssistantComponent); +AiAssistant.displayName = 'AiAssistant'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/ai_assistant/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/ai_assistant/translations.ts new file mode 100644 index 0000000000000..f80f108aefdab --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/ai_assistant/translations.ts @@ -0,0 +1,39 @@ +/* + * 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 ASK_ASSISTANT_ERROR_BUTTON = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.askAssistant', + { + defaultMessage: 'Ask Assistant', + } +); + +export const ASK_ASSISTANT_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.askAssistantDesc', + { + defaultMessage: 'Rule query error', + } +); + +export const ASK_ASSISTANT_USER_PROMPT = (language: string | undefined) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.askAssistantUserPrompt', + { + defaultMessage: + 'Explain all the errors present in the {language} query above. Generate a new working query, making sure all the errors are resolved in your response.', + values: { language }, + } + ); + +export const ASK_ASSISTANT_TOOLTIP = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.askAssistantToolTip', + { + defaultMessage: 'Fix query or generate new one', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx index f9ef3a1cf4643..3a0573f3f9b75 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx @@ -10,6 +10,7 @@ import { screen, fireEvent, render, within, act, waitFor } from '@testing-librar import type { Type as RuleType } from '@kbn/securitysolution-io-ts-alerting-types'; import type { DataViewBase } from '@kbn/es-query'; import { StepDefineRule, aggregatableFields } from '.'; +import type { StepDefineRuleProps } from '.'; import { mockBrowserFields } from '../../../../common/containers/source/mock'; import { useRuleFromTimeline } from '../../../../detections/containers/detection_engine/rules/use_rule_from_timeline'; import { TestProviders } from '../../../../common/mock'; @@ -24,6 +25,7 @@ import { createIndexPatternField, getSelectToggleButtonForName, } from '../../../rule_creation/components/required_fields/required_fields.test'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; // Mocks integrations jest.mock('../../../fleet_integrations/api'); @@ -35,6 +37,23 @@ jest.mock('../../../../common/components/query_bar', () => { }; }); +jest.mock('../../../rule_creation/components/pick_timeline', () => ({ + PickTimeline: 'pick-timeline', +})); + +jest.mock('../ai_assistant', () => { + return { + AiAssistant: jest.fn(() => { + return
      ; + }), + }; +}); + +jest.mock('../../../../common/hooks/use_experimental_features', () => ({ + useIsExperimentalFeatureEnabled: jest.fn(), +})); + +const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock; const mockRedirectLegacyUrl = jest.fn(); const mockGetLegacyUrlConflict = jest.fn(); jest.mock('../../../../common/lib/kibana', () => { @@ -610,6 +629,45 @@ describe('StepDefineRule', () => { ); }); }); + + describe('AI assistant', () => { + beforeEach(() => { + useIsExperimentalFeatureEnabledMock.mockReturnValue(true); + }); + it('renders assistant when query is not valid', () => { + render(, { + wrapper: TestProviders, + }); + + expect(screen.getByTestId('ai-assistant')).toBeInTheDocument(); + }); + + it('does not render assistant when feature flag is disabled', () => { + useIsExperimentalFeatureEnabledMock.mockReturnValue(false); + + render(, { + wrapper: TestProviders, + }); + + expect(screen.queryByTestId('ai-assistant')).toBe(null); + }); + + it('does not render assistant when query is valid', () => { + render(, { + wrapper: TestProviders, + }); + + expect(screen.queryByTestId('ai-assistant')).toBe(null); + }); + + it('does not render assistant for ML rule type', () => { + render(, { + wrapper: TestProviders, + }); + + expect(screen.queryByTestId('ai-assistant')).toBe(null); + }); + }); }); interface TestFormProps { @@ -617,6 +675,7 @@ interface TestFormProps { ruleType?: RuleType; indexPattern?: DataViewBase; onSubmit?: FormSubmitHandler; + formProps?: Partial; } function TestForm({ @@ -624,6 +683,7 @@ function TestForm({ ruleType = stepDefineDefaultValue.ruleType, indexPattern = { fields: [], title: '' }, onSubmit, + formProps, }: TestFormProps): JSX.Element { const [selectedEqlOptions, setSelectedEqlOptions] = useState(stepDefineDefaultValue.eqlOptions); const { form } = useForm({ @@ -658,6 +718,7 @@ function TestForm({ queryBarSavedId="" thresholdFields={[]} enableThresholdSuppression={false} + {...formProps} />
      + ) : undefined + } + onMouseUp={() => { + // when the pin is dropped (on mouse up), cancel any pending debounced changes and force the change + // in value to happen instantly (which, in turn, will re-calculate the min/max for the slider due to + // the `useEffect` above. + debouncedOnChange.cancel(); + onChange(displayedValue); + }} + readOnly={disablePopover} + showInput={'inputWithPopover'} + data-test-subj="rangeSlider__slider" + minInputProps={minInputProps} + maxInputProps={maxInputProps} + value={[displayedValue[0] || displayedMin, displayedValue[1] || displayedMax]} + onChange={([minSelection, maxSelection]: [number | string, number | string]) => { + setDisplayedValue([String(minSelection), String(maxSelection)]); + debouncedOnChange([String(minSelection), String(maxSelection)]); + }} + /> + + ); +}; diff --git a/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.test.tsx b/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.test.tsx new file mode 100644 index 0000000000000..98ce7619dda99 --- /dev/null +++ b/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.test.tsx @@ -0,0 +1,210 @@ +/* + * 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 { estypes } from '@elastic/elasticsearch'; +import { TimeRange } from '@kbn/es-query'; +import { BehaviorSubject, first, of, skip } from 'rxjs'; +import { render, waitFor } from '@testing-library/react'; +import { coreMock } from '@kbn/core/public/mocks'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { ControlGroupApi, DataControlFetchContext } from '../../control_group/types'; +import { getRangesliderControlFactory } from './get_range_slider_control_factory'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; +import { ControlApiRegistration } from '../../types'; +import { RangesliderControlApi, RangesliderControlState } from './types'; +import { StateComparators } from '@kbn/presentation-publishing'; + +const DEFAULT_TOTAL_RESULTS = 20; +const DEFAULT_MIN = 0; +const DEFAULT_MAX = 1000; + +describe('RangesliderControlApi', () => { + const uuid = 'myControl1'; + const dashboardApi = { + timeRange$: new BehaviorSubject(undefined), + }; + const controlGroupApi = { + dataControlFetch$: new BehaviorSubject({}), + ignoreParentSettings$: new BehaviorSubject(undefined), + parentApi: dashboardApi, + } as unknown as ControlGroupApi; + const dataStartServiceMock = dataPluginMock.createStartContract(); + let totalResults = DEFAULT_TOTAL_RESULTS; + let min: estypes.AggregationsSingleMetricAggregateBase['value'] = DEFAULT_MIN; + let max: estypes.AggregationsSingleMetricAggregateBase['value'] = DEFAULT_MAX; + dataStartServiceMock.search.searchSource.create = jest.fn().mockImplementation(() => { + let isAggsRequest = false; + return { + setField: (key: string) => { + if (key === 'aggs') { + isAggsRequest = true; + } + }, + fetch$: () => { + return isAggsRequest + ? of({ + rawResponse: { aggregations: { minAgg: { value: min }, maxAgg: { value: max } } }, + }) + : of({ + rawResponse: { hits: { total: { value: totalResults } } }, + }); + }, + }; + }); + const mockDataViews = dataViewPluginMocks.createStartContract(); + // @ts-ignore + mockDataViews.get = async (id: string): Promise => { + if (id !== 'myDataViewId') { + throw new Error(`Simulated error: no data view found for id ${id}`); + } + return { + id, + getFieldByName: (fieldName: string) => { + return [ + { + displayName: 'My field name', + name: 'myFieldName', + type: 'string', + }, + ].find((field) => fieldName === field.name); + }, + getFormatterForField: () => { + return { + getConverterFor: () => { + return (value: string) => `${value} myUnits`; + }, + }; + }, + } as unknown as DataView; + }; + const factory = getRangesliderControlFactory({ + core: coreMock.createStart(), + data: dataStartServiceMock, + dataViews: mockDataViews, + }); + + beforeEach(() => { + totalResults = DEFAULT_TOTAL_RESULTS; + min = DEFAULT_MIN; + max = DEFAULT_MAX; + }); + + function buildApiMock( + api: ControlApiRegistration, + nextComparitors: StateComparators + ) { + return { + ...api, + uuid, + parentApi: controlGroupApi, + unsavedChanges: new BehaviorSubject | undefined>(undefined), + resetUnsavedChanges: () => {}, + type: factory.type, + }; + } + + describe('filters$', () => { + test('should not set filters$ when value is not provided', (done) => { + const { api } = factory.buildControl( + { + dataViewId: 'myDataView', + fieldName: 'myFieldName', + }, + buildApiMock, + uuid, + controlGroupApi + ); + api.filters$.pipe(skip(1), first()).subscribe((filter) => { + expect(filter).toBeUndefined(); + done(); + }); + }); + + test('should set filters$ when value is provided', (done) => { + const { api } = factory.buildControl( + { + dataViewId: 'myDataViewId', + fieldName: 'myFieldName', + value: ['5', '10'], + }, + buildApiMock, + uuid, + controlGroupApi + ); + api.filters$.pipe(skip(1), first()).subscribe((filter) => { + expect(filter).toEqual([ + { + meta: { + field: 'myFieldName', + index: 'myDataViewId', + key: 'myFieldName', + params: { + gte: 5, + lte: 10, + }, + type: 'range', + }, + query: { + range: { + myFieldName: { + gte: 5, + lte: 10, + }, + }, + }, + }, + ]); + done(); + }); + }); + }); + + describe('selected range has no results', () => { + test('should display invalid state', async () => { + totalResults = 0; // simulate no results by returning hits total of zero + min = null; // simulate no results by returning min aggregation value of null + max = null; // simulate no results by returning max aggregation value of null + const { Component } = factory.buildControl( + { + dataViewId: 'myDataViewId', + fieldName: 'myFieldName', + value: ['5', '10'], + }, + buildApiMock, + uuid, + controlGroupApi + ); + const { findByTestId } = render(); + await waitFor(async () => { + await findByTestId('range-slider-control-invalid-append-myControl1'); + }); + }); + }); + + describe('min max', () => { + test('bounds inputs should display min and max placeholders when there is no selected range', async () => { + const { Component } = factory.buildControl( + { + dataViewId: 'myDataViewId', + fieldName: 'myFieldName', + }, + buildApiMock, + uuid, + controlGroupApi + ); + const { findByTestId } = render(); + await waitFor(async () => { + const minInput = await findByTestId('rangeSlider__lowerBoundFieldNumber'); + expect(minInput).toHaveAttribute('placeholder', String(DEFAULT_MIN)); + const maxInput = await findByTestId('rangeSlider__upperBoundFieldNumber'); + expect(maxInput).toHaveAttribute('placeholder', String(DEFAULT_MAX)); + }); + }); + }); +}); diff --git a/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.tsx b/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.tsx new file mode 100644 index 0000000000000..0fea543373341 --- /dev/null +++ b/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.tsx @@ -0,0 +1,263 @@ +/* + * 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, { useEffect, useMemo } from 'react'; +import deepEqual from 'react-fast-compare'; +import { BehaviorSubject, combineLatest, distinctUntilChanged, map, skip } from 'rxjs'; +import { EuiFieldNumber, EuiFormRow } from '@elastic/eui'; +import { + useBatchedPublishingSubjects, + useStateFromPublishingSubject, +} from '@kbn/presentation-publishing'; +import { buildRangeFilter, Filter, RangeFilterParams } from '@kbn/es-query'; +import { initializeDataControl } from '../initialize_data_control'; +import { DataControlFactory } from '../types'; +import { + RangesliderControlApi, + RangesliderControlState, + RangeValue, + RANGE_SLIDER_CONTROL_TYPE, + Services, +} from './types'; +import { RangeSliderStrings } from './range_slider_strings'; +import { RangeSliderControl } from './components/range_slider_control'; +import { minMax$ } from './min_max'; +import { hasNoResults$ } from './has_no_results'; + +export const getRangesliderControlFactory = ( + services: Services +): DataControlFactory => { + return { + type: RANGE_SLIDER_CONTROL_TYPE, + getIconType: () => 'controlsHorizontal', + getDisplayName: RangeSliderStrings.control.getDisplayName, + isFieldCompatible: (field) => { + return field.aggregatable && field.type === 'number'; + }, + CustomOptionsComponent: ({ stateManager, setControlEditorValid }) => { + const step = useStateFromPublishingSubject(stateManager.step); + + return ( + <> + + { + const newStep = event.target.valueAsNumber; + stateManager.step.next(newStep); + setControlEditorValid(newStep > 0); + }} + min={0} + isInvalid={step === undefined || step <= 0} + data-test-subj="rangeSliderControl__stepAdditionalSetting" + /> + + + ); + }, + buildControl: (initialState, buildApi, uuid, controlGroupApi) => { + const loadingMinMax$ = new BehaviorSubject(false); + const loadingHasNoResults$ = new BehaviorSubject(false); + const dataLoading$ = new BehaviorSubject(undefined); + const step$ = new BehaviorSubject(initialState.step); + const value$ = new BehaviorSubject(initialState.value); + function setValue(nextValue: RangeValue | undefined) { + value$.next(nextValue); + } + + const dataControl = initializeDataControl>( + uuid, + RANGE_SLIDER_CONTROL_TYPE, + initialState, + { + step: step$, + value: value$, + }, + controlGroupApi, + services + ); + + const api = buildApi( + { + ...dataControl.api, + dataLoading: dataLoading$, + getTypeDisplayName: RangeSliderStrings.control.getDisplayName, + serializeState: () => { + const { rawState: dataControlState, references } = dataControl.serialize(); + return { + rawState: { + ...dataControlState, + step: step$.getValue(), + value: value$.getValue(), + }, + references, // does not have any references other than those provided by the data control serializer + }; + }, + clearSelections: () => { + value$.next(undefined); + }, + }, + { + ...dataControl.comparators, + step: [step$, (nextStep: number | undefined) => step$.next(nextStep)], + value: [value$, setValue], + } + ); + + const dataLoadingSubscription = combineLatest([loadingMinMax$, loadingHasNoResults$]) + .pipe( + map((values) => { + return values.some((value) => { + return value; + }); + }) + ) + .subscribe((isLoading) => { + dataLoading$.next(isLoading); + }); + + // Clear state when the field changes + const fieldChangedSubscription = combineLatest([ + dataControl.stateManager.fieldName, + dataControl.stateManager.dataViewId, + ]) + .pipe( + distinctUntilChanged(deepEqual), + skip(1) // skip first filter output because it will have been applied in initialize + ) + .subscribe(() => { + step$.next(1); + value$.next(undefined); + }); + + const max$ = new BehaviorSubject(undefined); + const min$ = new BehaviorSubject(undefined); + const minMaxSubscription = minMax$({ + data: services.data, + dataControlFetch$: controlGroupApi.dataControlFetch$, + dataViews$: dataControl.api.dataViews, + fieldName$: dataControl.stateManager.fieldName, + setIsLoading: (isLoading: boolean) => { + // clear previous loading error on next loading start + if (isLoading && dataControl.api.blockingError.value) { + dataControl.api.setBlockingError(undefined); + } + loadingMinMax$.next(isLoading); + }, + }).subscribe( + ({ + error, + min, + max, + }: { + error?: Error; + min: number | undefined; + max: number | undefined; + }) => { + if (error) { + dataControl.api.setBlockingError(error); + } + max$.next(max); + min$.next(min); + } + ); + + const outputFilterSubscription = combineLatest([ + dataControl.api.dataViews, + dataControl.stateManager.fieldName, + value$, + ]).subscribe(([dataViews, fieldName, value]) => { + const dataView = dataViews?.[0]; + const dataViewField = + dataView && fieldName ? dataView.getFieldByName(fieldName) : undefined; + const gte = parseFloat(value?.[0] ?? ''); + const lte = parseFloat(value?.[1] ?? ''); + + let rangeFilter: Filter | undefined; + if (value && dataView && dataViewField && !isNaN(gte) && !isNaN(lte)) { + const params = { + gte, + lte, + } as RangeFilterParams; + + rangeFilter = buildRangeFilter(dataViewField, params, dataView); + rangeFilter.meta.key = fieldName; + rangeFilter.meta.type = 'range'; + rangeFilter.meta.params = params; + } + api.setOutputFilter(rangeFilter); + }); + + const selectionHasNoResults$ = new BehaviorSubject(false); + const hasNotResultsSubscription = hasNoResults$({ + data: services.data, + dataViews$: dataControl.api.dataViews, + filters$: dataControl.api.filters$, + ignoreParentSettings$: controlGroupApi.ignoreParentSettings$, + dataControlFetch$: controlGroupApi.dataControlFetch$, + setIsLoading: (isLoading: boolean) => { + loadingHasNoResults$.next(isLoading); + }, + }).subscribe((hasNoResults) => { + selectionHasNoResults$.next(hasNoResults); + }); + + return { + api, + Component: () => { + const [dataLoading, dataViews, fieldName, max, min, selectionHasNotResults, step, value] = + useBatchedPublishingSubjects( + dataLoading$, + dataControl.api.dataViews, + dataControl.stateManager.fieldName, + max$, + min$, + selectionHasNoResults$, + step$, + value$ + ); + + useEffect(() => { + return () => { + dataLoadingSubscription.unsubscribe(); + fieldChangedSubscription.unsubscribe(); + hasNotResultsSubscription.unsubscribe(); + minMaxSubscription.unsubscribe(); + outputFilterSubscription.unsubscribe(); + }; + }, []); + + const fieldFormatter = useMemo(() => { + const dataView = dataViews?.[0]; + if (!dataView) { + return undefined; + } + const fieldSpec = dataView.getFieldByName(fieldName); + return fieldSpec + ? dataView.getFormatterForField(fieldSpec).getConverterFor('text') + : undefined; + }, [dataViews, fieldName]); + + return ( + + ); + }, + }; + }, + }; +}; diff --git a/examples/controls_example/public/react_controls/data_controls/range_slider/has_no_results.ts b/examples/controls_example/public/react_controls/data_controls/range_slider/has_no_results.ts new file mode 100644 index 0000000000000..e3567cf3b0132 --- /dev/null +++ b/examples/controls_example/public/react_controls/data_controls/range_slider/has_no_results.ts @@ -0,0 +1,117 @@ +/* + * 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 { estypes } from '@elastic/elasticsearch'; +import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { DataView } from '@kbn/data-views-plugin/public'; +import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; +import { PublishesDataViews } from '@kbn/presentation-publishing'; +import { combineLatest, lastValueFrom, switchMap, tap } from 'rxjs'; +import { ControlGroupApi } from '../../control_group/types'; +import { DataControlApi } from '../types'; + +export function hasNoResults$({ + data, + dataControlFetch$, + dataViews$, + filters$, + ignoreParentSettings$, + setIsLoading, +}: { + data: DataPublicPluginStart; + dataControlFetch$: ControlGroupApi['dataControlFetch$']; + dataViews$?: PublishesDataViews['dataViews']; + filters$: DataControlApi['filters$']; + ignoreParentSettings$: ControlGroupApi['ignoreParentSettings$']; + setIsLoading: (isLoading: boolean) => void; +}) { + let prevRequestAbortController: AbortController | undefined; + return combineLatest([filters$, ignoreParentSettings$, dataControlFetch$]).pipe( + tap(() => { + if (prevRequestAbortController) { + prevRequestAbortController.abort(); + prevRequestAbortController = undefined; + } + }), + switchMap(async ([filters, ignoreParentSettings, dataControlFetchContext]) => { + const dataView = dataViews$?.value?.[0]; + const rangeFilter = filters?.[0]; + if (!dataView || !rangeFilter || ignoreParentSettings?.ignoreValidations) { + return false; + } + + try { + setIsLoading(true); + const abortController = new AbortController(); + prevRequestAbortController = abortController; + return await hasNoResults({ + abortSignal: abortController.signal, + data, + dataView, + rangeFilter, + ...dataControlFetchContext, + }); + } catch (error) { + // Ignore error, validation is not required for control to function properly + return false; + } + }), + tap(() => { + setIsLoading(false); + }) + ); +} + +async function hasNoResults({ + abortSignal, + data, + dataView, + unifiedSearchFilters, + query, + rangeFilter, + timeRange, +}: { + abortSignal: AbortSignal; + data: DataPublicPluginStart; + dataView: DataView; + unifiedSearchFilters?: Filter[]; + query?: Query | AggregateQuery; + rangeFilter: Filter; + timeRange?: TimeRange; +}): Promise { + const searchSource = await data.search.searchSource.create(); + searchSource.setField('size', 0); + searchSource.setField('index', dataView); + // Tracking total hits accurately has a performance cost + // Setting 'trackTotalHits' to 1 since we just want to know + // "has no results" or "has results" vs the actual count + searchSource.setField('trackTotalHits', 1); + + const allFilters = unifiedSearchFilters ? unifiedSearchFilters : []; + allFilters.push(rangeFilter); + if (timeRange) { + const timeFilter = data.query.timefilter.timefilter.createFilter(dataView, timeRange); + if (timeFilter) allFilters.push(timeFilter); + } + if (allFilters.length) { + searchSource.setField('filter', allFilters); + } + + if (query) { + searchSource.setField('query', query); + } + + const resp = await lastValueFrom( + searchSource.fetch$({ + abortSignal, + legacyHitsTotal: false, + }) + ); + const count = (resp?.rawResponse?.hits?.total as estypes.SearchTotalHits)?.value ?? 0; + return count === 0; +} diff --git a/examples/controls_example/public/react_controls/data_controls/range_slider/min_max.ts b/examples/controls_example/public/react_controls/data_controls/range_slider/min_max.ts new file mode 100644 index 0000000000000..a35fb8667eaa0 --- /dev/null +++ b/examples/controls_example/public/react_controls/data_controls/range_slider/min_max.ts @@ -0,0 +1,129 @@ +/* + * 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 { estypes } from '@elastic/elasticsearch'; +import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { DataView, DataViewField } from '@kbn/data-views-plugin/public'; +import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; +import { PublishesDataViews, PublishingSubject } from '@kbn/presentation-publishing'; +import { combineLatest, lastValueFrom, switchMap, tap } from 'rxjs'; +import { ControlGroupApi } from '../../control_group/types'; + +export function minMax$({ + data, + dataControlFetch$, + dataViews$, + fieldName$, + setIsLoading, +}: { + data: DataPublicPluginStart; + dataControlFetch$: ControlGroupApi['dataControlFetch$']; + dataViews$: PublishesDataViews['dataViews']; + fieldName$: PublishingSubject; + setIsLoading: (isLoading: boolean) => void; +}) { + let prevRequestAbortController: AbortController | undefined; + return combineLatest([dataViews$, fieldName$, dataControlFetch$]).pipe( + tap(() => { + if (prevRequestAbortController) { + prevRequestAbortController.abort(); + prevRequestAbortController = undefined; + } + }), + switchMap(async ([dataViews, fieldName, dataControlFetchContext]) => { + const dataView = dataViews?.[0]; + const dataViewField = dataView && fieldName ? dataView.getFieldByName(fieldName) : undefined; + if (!dataView || !dataViewField) { + return { max: undefined, min: undefined }; + } + + try { + setIsLoading(true); + const abortController = new AbortController(); + prevRequestAbortController = abortController; + return await getMinMax({ + abortSignal: abortController.signal, + data, + dataView, + field: dataViewField, + ...dataControlFetchContext, + }); + } catch (error) { + return { error, max: undefined, min: undefined }; + } + }), + tap(() => { + setIsLoading(false); + }) + ); +} + +export async function getMinMax({ + abortSignal, + data, + dataView, + field, + unifiedSearchFilters, + query, + timeRange, +}: { + abortSignal: AbortSignal; + data: DataPublicPluginStart; + dataView: DataView; + field: DataViewField; + unifiedSearchFilters?: Filter[]; + query?: Query | AggregateQuery; + timeRange?: TimeRange; +}): Promise<{ min: number | undefined; max: number | undefined }> { + const searchSource = await data.search.searchSource.create(); + searchSource.setField('size', 0); + searchSource.setField('index', dataView); + + const allFilters = unifiedSearchFilters ? unifiedSearchFilters : []; + if (timeRange) { + const timeFilter = data.query.timefilter.timefilter.createFilter(dataView, timeRange); + if (timeFilter) allFilters.push(timeFilter); + } + if (allFilters.length) { + searchSource.setField('filter', allFilters); + } + + if (query) { + searchSource.setField('query', query); + } + + const aggBody: any = {}; + if (field.scripted) { + aggBody.script = { + source: field.script, + lang: field.lang, + }; + } else { + aggBody.field = field.name; + } + + const aggs = { + maxAgg: { + max: aggBody, + }, + minAgg: { + min: aggBody, + }, + }; + searchSource.setField('aggs', aggs); + + const resp = await lastValueFrom(searchSource.fetch$({ abortSignal })); + return { + min: + (resp.rawResponse?.aggregations?.minAgg as estypes.AggregationsSingleMetricAggregateBase) + ?.value ?? undefined, + max: + (resp.rawResponse?.aggregations?.maxAgg as estypes.AggregationsSingleMetricAggregateBase) + ?.value ?? undefined, + }; +} diff --git a/examples/controls_example/public/react_controls/data_controls/range_slider/range_slider_strings.ts b/examples/controls_example/public/react_controls/data_controls/range_slider/range_slider_strings.ts new file mode 100644 index 0000000000000..cdf64fee21fd5 --- /dev/null +++ b/examples/controls_example/public/react_controls/data_controls/range_slider/range_slider_strings.ts @@ -0,0 +1,34 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const RangeSliderStrings = { + control: { + getDisplayName: () => + i18n.translate('controlsExamples.rangeSliderControl.displayName', { + defaultMessage: 'Range slider', + }), + getInvalidSelectionWarningLabel: () => + i18n.translate('controlsExamples.rangeSlider.control.invalidSelectionWarningLabel', { + defaultMessage: 'Selected range returns no results.', + }), + }, + editor: { + getStepTitle: () => + i18n.translate('controlsExamples.rangeSlider.editor.stepSizeTitle', { + defaultMessage: 'Step size', + }), + }, + popover: { + getNoAvailableDataHelpText: () => + i18n.translate('controlsExamples.rangeSlider.popover.noAvailableDataHelpText', { + defaultMessage: 'There is no data to display. Adjust the time range and filters.', + }), + }, +}; diff --git a/examples/controls_example/public/react_controls/data_controls/range_slider/types.ts b/examples/controls_example/public/react_controls/data_controls/range_slider/types.ts new file mode 100644 index 0000000000000..2eb7f70d348e0 --- /dev/null +++ b/examples/controls_example/public/react_controls/data_controls/range_slider/types.ts @@ -0,0 +1,29 @@ +/* + * 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 { CoreStart } from '@kbn/core/public'; +import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { DataControlApi, DefaultDataControlState } from '../types'; + +export const RANGE_SLIDER_CONTROL_TYPE = 'rangeSlider'; + +export type RangeValue = [string, string]; + +export interface RangesliderControlState extends DefaultDataControlState { + value?: RangeValue; + step?: number; +} + +export type RangesliderControlApi = DataControlApi; + +export interface Services { + core: CoreStart; + data: DataPublicPluginStart; + dataViews: DataViewsPublicPluginStart; +} From 4e785c669c4a947a7c0bc7ee7524ab666985f9dd Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 1 Jul 2024 15:21:38 -0600 Subject: [PATCH 039/128] Embeddable error handling documentation (#187164) Closes https://github.com/elastic/kibana/issues/187090 PR adds error handling to embeddable readme best practices section. --------- Co-authored-by: Elastic Machine --- src/plugins/embeddable/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/embeddable/README.md b/src/plugins/embeddable/README.md index 0612226664da4..91627a5f0e6e9 100644 --- a/src/plugins/embeddable/README.md +++ b/src/plugins/embeddable/README.md @@ -41,6 +41,9 @@ Break your Component into a Package or another plugin to avoid circular plugin d #### Minimal API surface area Embeddable APIs are accessable to all Kibana systems and all embeddable siblings and parents. Functions and state that are internal to an embeddable including any child components should not be added to the API. Consider passing internal state to child as props or react context. +#### Error handling +Embeddables should never throw. Instead, use [PublishesBlockingError](https://github.com/elastic/kibana/blob/main/packages/presentation/presentation_publishing/interfaces/publishes_blocking_error.ts) interface to surface unrecoverable errors. When an embeddable publishes a blocking error, the parent component will display an error component instead of the embeddable Component. Be thoughtful about which errors are surfaced with the PublishesBlockingError interface. If the embeddable can still render, use less invasive error handling such as a warning toast or notifications in the embeddable Component UI. + ### Examples Examples available at [/examples/embeddable_examples](https://github.com/elastic/kibana/tree/main/examples/embeddable_examples) From bda4a6282eadf34e09f6665051c8c79861c7dde6 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Mon, 1 Jul 2024 16:33:27 -0500 Subject: [PATCH 040/128] [Search][Notebooks] Introduce support for lists (#187163) ## Summary This PR introduces support for notebook lists that can be set via query parameter or via a function exported from the `search_notebooks` plugin start object. This will be used to allow setting a different list of notebooks for specific pages that allow the notebooks shown to be contextual to the current user task. This PR does not add any new lists of notebooks, as that will be done in another PR for the static notebooks and done via the S3 file for dynamic notebooks. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../plugins/search_notebooks/common/types.ts | 7 ++ .../public/components/notebooks_button.tsx | 18 ++- .../public/components/notebooks_view.tsx | 10 +- .../search_notebooks/public/console_view.tsx | 26 +++- .../public/hooks/use_kibana.ts | 5 + .../public/hooks/use_notebook_catalog.ts | 13 +- .../public/hooks/use_notebooks_list.ts | 32 +++++ .../plugins/search_notebooks/public/plugin.ts | 28 ++++- .../plugins/search_notebooks/public/types.ts | 8 +- .../server/lib/notebook_catalog.test.ts | 119 +++++++++++++++++- .../server/lib/notebook_catalog.ts | 44 +++++-- .../search_notebooks/server/routes/index.ts | 18 ++- .../plugins/search_notebooks/server/utils.ts | 81 +++++++++++- 13 files changed, 371 insertions(+), 38 deletions(-) create mode 100644 x-pack/plugins/search_notebooks/public/hooks/use_notebooks_list.ts diff --git a/x-pack/plugins/search_notebooks/common/types.ts b/x-pack/plugins/search_notebooks/common/types.ts index 58922ed4abc6c..8b46a7e4361cb 100644 --- a/x-pack/plugins/search_notebooks/common/types.ts +++ b/x-pack/plugins/search_notebooks/common/types.ts @@ -17,8 +17,12 @@ export interface NotebookInformation { url: string; }; } + +export type NotebookCatalogResponse = Pick; + export interface NotebookCatalog { notebooks: NotebookInformation[]; + lists?: Record; } export interface Notebook extends NotebookInformation { @@ -47,6 +51,9 @@ export const NotebookCatalogSchema = schema.object( ), { minSize: 1 } ), + lists: schema.maybe( + schema.recordOf(schema.string(), schema.arrayOf(schema.string(), { minSize: 1 })) + ), }, { unknowns: 'allow', diff --git a/x-pack/plugins/search_notebooks/public/components/notebooks_button.tsx b/x-pack/plugins/search_notebooks/public/components/notebooks_button.tsx index 2e01f27beed4f..66fc50a03ddd7 100644 --- a/x-pack/plugins/search_notebooks/public/components/notebooks_button.tsx +++ b/x-pack/plugins/search_notebooks/public/components/notebooks_button.tsx @@ -10,7 +10,23 @@ import { i18n } from '@kbn/i18n'; import { EuiButton, EuiButtonEmpty } from '@elastic/eui'; import { EmbeddedConsoleViewButtonProps } from '@kbn/console-plugin/public'; -export const SearchNotebooksButton = ({ activeView, onClick }: EmbeddedConsoleViewButtonProps) => { +export interface SearchNotebooksButtonProps extends EmbeddedConsoleViewButtonProps { + clearNotebookList: () => void; +} + +export const SearchNotebooksButton = ({ + activeView, + onClick, + clearNotebookList, +}: SearchNotebooksButtonProps) => { + React.useEffect(() => { + return () => { + // When the Notebooks button is unmounted we want to clear + // any page specific contextual notebook list that was set. + clearNotebookList(); + }; + }, [clearNotebookList]); + if (activeView) { return ( NotebookListValue; } -export const SearchNotebooksView = ({ core, queryClient }: SearchNotebooksViewProps) => ( +export const SearchNotebooksView = ({ + core, + queryClient, + getNotebookList, +}: SearchNotebooksViewProps) => ( - + diff --git a/x-pack/plugins/search_notebooks/public/console_view.tsx b/x-pack/plugins/search_notebooks/public/console_view.tsx index 4d2dba74cf682..99575c8e93948 100644 --- a/x-pack/plugins/search_notebooks/public/console_view.tsx +++ b/x-pack/plugins/search_notebooks/public/console_view.tsx @@ -7,22 +7,38 @@ import React from 'react'; import { CoreStart } from '@kbn/core/public'; -import { EmbeddedConsoleView } from '@kbn/console-plugin/public'; +import type { + EmbeddedConsoleView, + EmbeddedConsoleViewButtonProps, +} from '@kbn/console-plugin/public'; import { dynamic } from '@kbn/shared-ux-utility'; import { QueryClient } from '@tanstack/react-query'; -import { SearchNotebooksButton } from './components/notebooks_button'; +import { NotebookListValue } from './types'; +const SearchNotebooksButton = dynamic(async () => ({ + default: (await import('./components/notebooks_button')).SearchNotebooksButton, +})); const SearchNotebooksView = dynamic(async () => ({ default: (await import('./components/notebooks_view')).SearchNotebooksView, })); export const notebooksConsoleView = ( core: CoreStart, - queryClient: QueryClient + queryClient: QueryClient, + clearNotebookList: () => void, + getNotebookListValue: () => NotebookListValue ): EmbeddedConsoleView => { return { - ActivationButton: SearchNotebooksButton, - ViewContent: () => , + ActivationButton: (props: EmbeddedConsoleViewButtonProps) => ( + + ), + ViewContent: () => ( + + ), }; }; diff --git a/x-pack/plugins/search_notebooks/public/hooks/use_kibana.ts b/x-pack/plugins/search_notebooks/public/hooks/use_kibana.ts index c584dcb0013c3..b490e61f41490 100644 --- a/x-pack/plugins/search_notebooks/public/hooks/use_kibana.ts +++ b/x-pack/plugins/search_notebooks/public/hooks/use_kibana.ts @@ -9,8 +9,13 @@ import type { ConsolePluginStart } from '@kbn/console-plugin/public'; import type { CoreStart } from '@kbn/core/public'; import { useKibana as useKibanaBase } from '@kbn/kibana-react-plugin/public'; +import { NotebookListValue } from '../types'; + export interface SearchNotebooksContext { console: ConsolePluginStart; + notebooks: { + getNotebookList: () => NotebookListValue; + }; } type ServerlessSearchKibanaContext = CoreStart & SearchNotebooksContext; diff --git a/x-pack/plugins/search_notebooks/public/hooks/use_notebook_catalog.ts b/x-pack/plugins/search_notebooks/public/hooks/use_notebook_catalog.ts index fbcd298f8fad0..0724181844f6b 100644 --- a/x-pack/plugins/search_notebooks/public/hooks/use_notebook_catalog.ts +++ b/x-pack/plugins/search_notebooks/public/hooks/use_notebook_catalog.ts @@ -6,13 +6,20 @@ */ import { useQuery } from '@tanstack/react-query'; -import { NotebookCatalog } from '../../common/types'; +import { NotebookCatalogResponse } from '../../common/types'; import { useKibanaServices } from './use_kibana'; +import { useNotebookList } from './use_notebooks_list'; export const useNotebooksCatalog = () => { const { http } = useKibanaServices(); + const list = useNotebookList(); return useQuery({ - queryKey: ['fetchNotebooksCatalog'], - queryFn: () => http.get('/internal/search_notebooks/notebooks'), + queryKey: [`fetchNotebooksCatalog-${list ?? 'default'}`], + queryFn: () => + http.get('/internal/search_notebooks/notebooks', { + query: { + list, + }, + }), }); }; diff --git a/x-pack/plugins/search_notebooks/public/hooks/use_notebooks_list.ts b/x-pack/plugins/search_notebooks/public/hooks/use_notebooks_list.ts new file mode 100644 index 0000000000000..df903f2a00a5f --- /dev/null +++ b/x-pack/plugins/search_notebooks/public/hooks/use_notebooks_list.ts @@ -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 { useMemo } from 'react'; +import { parse } from 'query-string'; +import { useKibanaServices } from './use_kibana'; + +export const readNotebookListFromParam = () => { + const [, queryString] = (window.location.search || window.location.hash || '').split('?'); + + const queryParams = parse(queryString || '', { sort: false }); + if (queryParams && queryParams.nblist && typeof queryParams.nblist === 'string') { + return queryParams.nblist; + } + return undefined; +}; + +export const useNotebookList = () => { + const { + notebooks: { getNotebookList }, + } = useKibanaServices(); + const nbList = useMemo(() => getNotebookList(), [getNotebookList]); + const nbListQueryParam = useMemo(() => readNotebookListFromParam(), []); + + if (nbListQueryParam) return nbListQueryParam; + if (nbList) return nbList; + return undefined; +}; diff --git a/x-pack/plugins/search_notebooks/public/plugin.ts b/x-pack/plugins/search_notebooks/public/plugin.ts index 8874cd936c2c5..3b71193ed3add 100644 --- a/x-pack/plugins/search_notebooks/public/plugin.ts +++ b/x-pack/plugins/search_notebooks/public/plugin.ts @@ -13,13 +13,16 @@ import { SearchNotebooksPluginSetup, SearchNotebooksPluginStart, SearchNotebooksPluginStartDependencies, + NotebookListValue, } from './types'; import { getErrorCode, getErrorMessage, isKibanaServerError } from './utils/get_error_message'; export class SearchNotebooksPlugin implements Plugin { + private notebooksList: NotebookListValue = null; private queryClient: QueryClient | undefined; + public setup(core: CoreSetup): SearchNotebooksPluginSetup { this.queryClient = new QueryClient({ mutationCache: new MutationCache({ @@ -55,10 +58,31 @@ export class SearchNotebooksPlugin ): SearchNotebooksPluginStart { if (deps.console?.registerEmbeddedConsoleAlternateView) { deps.console.registerEmbeddedConsoleAlternateView( - notebooksConsoleView(core, this.queryClient!) + notebooksConsoleView( + core, + this.queryClient!, + this.clearNotebookList.bind(this), + this.getNotebookList.bind(this) + ) ); } - return {}; + return { + setNotebookList: (value: NotebookListValue) => { + this.setNotebookList(value); + }, + }; } public stop() {} + + private clearNotebookList() { + this.setNotebookList(null); + } + + private setNotebookList(value: NotebookListValue) { + this.notebooksList = value; + } + + private getNotebookList(): NotebookListValue { + return this.notebooksList; + } } diff --git a/x-pack/plugins/search_notebooks/public/types.ts b/x-pack/plugins/search_notebooks/public/types.ts index 74dbc5250d44c..150d08a5d463f 100644 --- a/x-pack/plugins/search_notebooks/public/types.ts +++ b/x-pack/plugins/search_notebooks/public/types.ts @@ -9,9 +9,13 @@ import type { ConsolePluginStart } from '@kbn/console-plugin/public'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface SearchNotebooksPluginSetup {} -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface SearchNotebooksPluginStart {} + +export interface SearchNotebooksPluginStart { + setNotebookList: (value: NotebookListValue) => void; +} export interface SearchNotebooksPluginStartDependencies { console: ConsolePluginStart; } + +export type NotebookListValue = string | null; diff --git a/x-pack/plugins/search_notebooks/server/lib/notebook_catalog.test.ts b/x-pack/plugins/search_notebooks/server/lib/notebook_catalog.test.ts index 7b0e345ece029..5f5bffe3b25f9 100644 --- a/x-pack/plugins/search_notebooks/server/lib/notebook_catalog.test.ts +++ b/x-pack/plugins/search_notebooks/server/lib/notebook_catalog.test.ts @@ -23,6 +23,7 @@ import { getNotebook, getNotebookCatalog, DEFAULT_NOTEBOOKS, + NOTEBOOKS_MAP, NotebookCatalogFetchOptions, getNotebookMetadata, } from './notebook_catalog'; @@ -65,7 +66,32 @@ describe('Notebook Catalog', () => { describe('getNotebookCatalog', () => { describe('static notebooks', () => { it('returns default notebooks when theres an empty catalog config', async () => { - await expect(getNotebookCatalog(staticOptions)).resolves.toBe(DEFAULT_NOTEBOOKS); + await expect(getNotebookCatalog(staticOptions)).resolves.toMatchObject(DEFAULT_NOTEBOOKS); + }); + it.skip('returns requested list of notebooks when it exists', async () => { + // Re-enable this with actual list when we implement them + await expect( + getNotebookCatalog({ ...staticOptions, notebookList: 'ml' }) + ).resolves.toMatchObject({ + notebooks: [ + NOTEBOOKS_MAP['03_elser'], + NOTEBOOKS_MAP['02_hybrid_search'], + NOTEBOOKS_MAP['04_multilingual'], + ], + }); + }); + it('returns default list if requested list doesnt exist', async () => { + await expect( + getNotebookCatalog({ ...staticOptions, notebookList: 'foo' }) + ).resolves.toMatchObject({ + notebooks: [ + NOTEBOOKS_MAP['00_quick_start'], + NOTEBOOKS_MAP['01_keyword_querying_filtering'], + NOTEBOOKS_MAP['02_hybrid_search'], + NOTEBOOKS_MAP['03_elser'], + NOTEBOOKS_MAP['04_multilingual'], + ], + }); }); }); @@ -310,6 +336,97 @@ describe('Notebook Catalog', () => { timestamp: fakeNow, }); }); + describe('supports notebook lists', () => { + beforeEach(() => { + const mockCatalog: RemoteNotebookCatalog = { + notebooks: [ + { + id: 'unit-test', + title: 'Test', + description: 'Test notebook', + url: 'http://localhost:3000/my_notebook.ipynb', + }, + { + id: 'unit-test-002', + title: 'Test', + description: 'Test notebook 2', + url: 'http://localhost:3000/my_other_notebook.ipynb', + }, + ], + lists: { + default: ['unit-test', 'unit-test-002'], + test: ['unit-test-002'], + vector: ['unit-test'], + }, + }; + const mockResp = { + status: 200, + statusText: 'OK', + ok: true, + json: jest.fn().mockResolvedValue(mockCatalog), + }; + fetchMock.mockResolvedValue(mockResp); + }); + + it('can return a custom notebook list', async () => { + await expect( + getNotebookCatalog({ ...dynamicOptions, notebookList: 'test' }) + ).resolves.toEqual({ + notebooks: [ + { + id: 'unit-test-002', + title: 'Test', + description: 'Test notebook 2', + }, + ], + }); + await expect( + getNotebookCatalog({ ...dynamicOptions, notebookList: 'vector' }) + ).resolves.toEqual({ + notebooks: [ + { + id: 'unit-test', + title: 'Test', + description: 'Test notebook', + }, + ], + }); + }); + it('returns default list when requested list not defined', async () => { + await expect( + getNotebookCatalog({ ...dynamicOptions, notebookList: 'foo' }) + ).resolves.toEqual({ + notebooks: [ + { + id: 'unit-test', + title: 'Test', + description: 'Test notebook', + }, + { + id: 'unit-test-002', + title: 'Test', + description: 'Test notebook 2', + }, + ], + }); + }); + it('returns default list when list is not specified', async () => { + await expect(getNotebookCatalog(dynamicOptions)).resolves.toEqual({ + notebooks: [ + { + id: 'unit-test', + title: 'Test', + description: 'Test notebook', + }, + { + id: 'unit-test-002', + title: 'Test', + description: 'Test notebook 2', + }, + ], + }); + }); + }); }); }); diff --git a/x-pack/plugins/search_notebooks/server/lib/notebook_catalog.ts b/x-pack/plugins/search_notebooks/server/lib/notebook_catalog.ts index 6ee2a628f04db..54d2a1acd055f 100644 --- a/x-pack/plugins/search_notebooks/server/lib/notebook_catalog.ts +++ b/x-pack/plugins/search_notebooks/server/lib/notebook_catalog.ts @@ -14,8 +14,8 @@ import { NotebookDefinition } from '@kbn/ipynb'; import { NotebookCatalog, + NotebookCatalogResponse, NotebookInformation, - NotebookCatalogSchema, NotebookSchema, } from '../../common/types'; @@ -23,9 +23,10 @@ import type { SearchNotebooksConfig } from '../config'; import type { NotebooksCache, RemoteNotebookCatalog } from '../types'; import { cleanCachedNotebook, - cleanCachedNotebookCatalog, cleanNotebookMetadata, dateWithinTTL, + notebookCatalogResponse, + validateRemoteNotebookCatalog, } from '../utils'; const NOTEBOOKS_DATA_DIR = '../data'; @@ -38,8 +39,10 @@ export interface NotebookCatalogFetchOptions { cache: NotebooksCache; config: SearchNotebooksConfig; logger: Logger; + notebookList?: string; } +// Notebook catalog v1, leaving to ensure backward-compatibility export const DEFAULT_NOTEBOOKS: NotebookCatalog = { notebooks: [ { @@ -102,26 +105,40 @@ export const DEFAULT_NOTEBOOKS: NotebookCatalog = { }, ], }; +// Notebook catalog v1.1 with lists for contextual notebooks +export const DEFAULT_NOTEBOOK_CATALOG: NotebookCatalog = { + notebooks: [...DEFAULT_NOTEBOOKS.notebooks], + lists: { + default: [ + '00_quick_start', + '01_keyword_querying_filtering', + '02_hybrid_search', + '03_elser', + '04_multilingual', + ], + }, +}; export const NOTEBOOKS_MAP: Record = - DEFAULT_NOTEBOOKS.notebooks.reduce((nbMap, nb) => { + DEFAULT_NOTEBOOK_CATALOG.notebooks.reduce((nbMap, nb) => { nbMap[nb.id] = nb; return nbMap; }, {} as Record); -const NOTEBOOK_IDS = DEFAULT_NOTEBOOKS.notebooks.map(({ id }) => id); +const NOTEBOOK_IDS = DEFAULT_NOTEBOOK_CATALOG.notebooks.map(({ id }) => id); export const getNotebookCatalog = async ({ config, cache, logger, -}: NotebookCatalogFetchOptions) => { + notebookList, +}: NotebookCatalogFetchOptions): Promise => { if (config.catalog && config.catalog.url) { - const catalog = await fetchNotebookCatalog(config.catalog, cache, logger); + const catalog = await fetchNotebookCatalog(config.catalog, cache, logger, notebookList); if (catalog) { return catalog; } } - return DEFAULT_NOTEBOOKS; + return notebookCatalogResponse(DEFAULT_NOTEBOOK_CATALOG, notebookList); }; export const getNotebook = async ( @@ -179,19 +196,20 @@ type CatalogConfig = Readonly<{ export const fetchNotebookCatalog = async ( catalogConfig: CatalogConfig, cache: NotebooksCache, - logger: Logger -): Promise => { + logger: Logger, + notebookList?: string +): Promise => { if (cache.catalog && dateWithinTTL(cache.catalog.timestamp, catalogConfig.ttl)) { - return cleanCachedNotebookCatalog(cache.catalog); + return notebookCatalogResponse(cache.catalog, notebookList); } try { const resp = await fetch(catalogConfig.url, FETCH_OPTIONS); if (resp.ok) { const respJson = await resp.json(); - const catalog: RemoteNotebookCatalog = NotebookCatalogSchema.validate(respJson); + const catalog: RemoteNotebookCatalog = validateRemoteNotebookCatalog(respJson); cache.catalog = { ...catalog, timestamp: new Date() }; - return cleanCachedNotebookCatalog(cache.catalog); + return notebookCatalogResponse(cache.catalog, notebookList); } else { throw new Error(`Failed to fetch notebook ${resp.status} ${resp.statusText}`); } @@ -203,7 +221,7 @@ export const fetchNotebookCatalog = async ( if (cache.catalog && dateWithinTTL(cache.catalog.timestamp, catalogConfig.errorTTL)) { // If we can't fetch the catalog but we have it cached and it's within the error TTL, // returned the cached value. - return cleanCachedNotebookCatalog(cache.catalog); + return notebookCatalogResponse(cache.catalog, notebookList); } } diff --git a/x-pack/plugins/search_notebooks/server/routes/index.ts b/x-pack/plugins/search_notebooks/server/routes/index.ts index 0e5294b71e2df..b6d1f8a28ddf9 100644 --- a/x-pack/plugins/search_notebooks/server/routes/index.ts +++ b/x-pack/plugins/search_notebooks/server/routes/index.ts @@ -16,16 +16,26 @@ export function defineRoutes({ config, notebooksCache, logger, router }: RouteDe router.get( { path: '/internal/search_notebooks/notebooks', - validate: {}, + validate: { + query: schema.object({ + list: schema.maybe(schema.string()), + }), + }, options: { access: 'internal', }, }, - async (_context, _request, response) => { - const notebooks = await getNotebookCatalog({ cache: notebooksCache, config, logger }); + async (_context, request, response) => { + const { list } = request.query; + const resp = await getNotebookCatalog({ + cache: notebooksCache, + config, + logger, + notebookList: list, + }); return response.ok({ - body: notebooks, + body: resp, headers: { 'content-type': 'application/json' }, }); } diff --git a/x-pack/plugins/search_notebooks/server/utils.ts b/x-pack/plugins/search_notebooks/server/utils.ts index 4028d6776dd03..c777e07c7b272 100644 --- a/x-pack/plugins/search_notebooks/server/utils.ts +++ b/x-pack/plugins/search_notebooks/server/utils.ts @@ -6,11 +6,17 @@ */ import { NotebookDefinition } from '@kbn/ipynb'; -import { NotebookCatalog, NotebookInformation } from '../common/types'; +import { + NotebookCatalog, + NotebookCatalogResponse, + NotebookCatalogSchema, + NotebookInformation, +} from '../common/types'; import type { CachedNotebook, CachedNotebookCatalog, NotebooksCache, + RemoteNotebookCatalog, RemoteNotebookInformation, } from './types'; @@ -24,10 +30,12 @@ export function dateWithinTTL(value: Date, ttl: number) { return (Date.now() - value.getTime()) / 1000 <= ttl; } -export function cleanCachedNotebookCatalog(catalog: CachedNotebookCatalog): NotebookCatalog { - const notebooks = catalog.notebooks.map(cleanNotebookMetadata); +export function cleanCachedNotebookCatalog( + notebooks: Array +): NotebookCatalogResponse { + const cleanedNotebooks = notebooks.map(cleanNotebookMetadata); return { - notebooks, + notebooks: cleanedNotebooks, }; } export function cleanCachedNotebook(notebook: CachedNotebook): NotebookDefinition { @@ -35,7 +43,9 @@ export function cleanCachedNotebook(notebook: CachedNotebook): NotebookDefinitio return result; } -export function cleanNotebookMetadata(nb: RemoteNotebookInformation): NotebookInformation { +export function cleanNotebookMetadata( + nb: RemoteNotebookInformation | NotebookInformation +): NotebookInformation { const { id, title, description, link } = nb; return { description, @@ -44,3 +54,64 @@ export function cleanNotebookMetadata(nb: RemoteNotebookInformation): NotebookIn title, }; } + +export function isCachedNotebookCatalog( + catalog: CachedNotebookCatalog | NotebookCatalog +): catalog is CachedNotebookCatalog { + return 'timestamp' in catalog; +} + +const DEFAULT_NOTEBOOK_LIST_KEY = 'default'; +export function notebookCatalogResponse( + catalog: CachedNotebookCatalog | NotebookCatalog, + list: string = DEFAULT_NOTEBOOK_LIST_KEY +): NotebookCatalogResponse { + if (!catalog.lists) { + return isCachedNotebookCatalog(catalog) + ? cleanCachedNotebookCatalog(catalog.notebooks) + : catalog; + } + + const listOfNotebookIds = getListOfNotebookIds(catalog.lists, list); + const notebookIndexMap = (catalog.notebooks as NotebookInformation[]).reduce( + (indexMap, nb, i) => { + indexMap[nb.id] = i; + return indexMap; + }, + {} as Record + ); + const notebooks = listOfNotebookIds + .map((id) => { + const nbIndex = notebookIndexMap[id]; + if (nbIndex === undefined) return undefined; + return catalog.notebooks[nbIndex] ?? undefined; + }) + .filter( + (nbInfo): nbInfo is RemoteNotebookInformation | NotebookInformation => nbInfo !== undefined + ); + return cleanCachedNotebookCatalog(notebooks); +} + +function getListOfNotebookIds( + catalogLists: NonNullable, + list: string +): string[] { + if (list in catalogLists && catalogLists[list]) return catalogLists[list]!; + if (DEFAULT_NOTEBOOK_LIST_KEY in catalogLists && catalogLists[DEFAULT_NOTEBOOK_LIST_KEY]) + return catalogLists[DEFAULT_NOTEBOOK_LIST_KEY]; + + // This should not happen as we should not load a catalog with lists thats missing the default list as valid, + // but handling this case for code completeness. + throw new Error('Notebook catalog has lists, but is missing default list'); // TODO: ?translate +} + +export function validateRemoteNotebookCatalog(respJson: any): RemoteNotebookCatalog { + const catalog: RemoteNotebookCatalog = NotebookCatalogSchema.validate(respJson); + if (catalog.lists && !(DEFAULT_NOTEBOOK_LIST_KEY in catalog.lists)) { + // TODO: translate error message + throw new Error( + 'Invalid remote notebook catalog. Catalog defines lists, but is missing the default list.' + ); + } + return catalog; +} From d864fedd5de1ac7ba09a75679b71f2e5f6e67523 Mon Sep 17 00:00:00 2001 From: Alexi Doak <109488926+doakalexi@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:44:39 -0400 Subject: [PATCH 041/128] [ResponseOps] Mark cloud SMTP rate limiting and recipient address rejected errors as user errors (#186199) Resolves https://github.com/elastic/response-ops-team/issues/212 ## Summary There were some serverless alerts that were triggered by projects encountering rate limiting (SMTP 450) failures and failures due to the recipient address being rejected (SMTP 554) when using the Cloud SMTP preconfigured connector. Those errors are currently flagged as framework errors by default and this PR adds code to mark these as user errors. ### Checklist - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### To verify - You can use the cloud deploy from this PR and the following request to test the rate limiting error (SMTP 450) and verify the `errorSource: 'user'`. The cloud email service rate limits to 500 emails per 15 minutes. ``` curl -X POST -u $USERNAME:$PASSWORD $CLOUD_URL/api/actions/connector/elastic-cloud-email/_execute \ -H 'kbn-xsrf: true' \ -H 'Content-Type: application/json' \ -d ' { "params": { "to": ["$YOUR_EMAIL"], "subject": "testing email", "message": "test" } } ' ``` - I haven't been able to test the SMTP 554, but it should work the same as the SMTP 450 error. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../connector_types/email/index.test.ts | 70 +++++++++++++++++++ .../server/connector_types/email/index.ts | 14 +++- x-pack/plugins/stack_connectors/tsconfig.json | 1 + 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/stack_connectors/server/connector_types/email/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/email/index.test.ts index fe18cf956c0e4..f3787e8d367d9 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/email/index.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/email/index.test.ts @@ -828,6 +828,76 @@ describe('execute()', () => { `); }); + test('returns expected result when a 450 error is thrown', async () => { + const customExecutorOptions: EmailConnectorTypeExecutorOptions = { + ...executorOptions, + config: { + ...config, + hasAuth: false, + }, + secrets: { + ...secrets, + user: null, + password: null, + }, + }; + + const errorResponse = { + message: 'Mail command failed: 450 4.7.1 Error: too much mail', + response: { + status: 450, + }, + }; + + sendEmailMock.mockReset(); + sendEmailMock.mockRejectedValue(errorResponse); + const result = await connectorType.executor(customExecutorOptions); + expect(result).toMatchInlineSnapshot(` + Object { + "actionId": "some-id", + "errorSource": "user", + "message": "error sending email", + "serviceMessage": "Mail command failed: 450 4.7.1 Error: too much mail", + "status": "error", + } + `); + }); + + test('returns expected result when a 554 error is thrown', async () => { + const customExecutorOptions: EmailConnectorTypeExecutorOptions = { + ...executorOptions, + config: { + ...config, + hasAuth: false, + }, + secrets: { + ...secrets, + user: null, + password: null, + }, + }; + + const errorResponse = { + message: "Can't send mail - all recipients were rejected: 554 5.7.1", + response: { + status: 554, + }, + }; + + sendEmailMock.mockReset(); + sendEmailMock.mockRejectedValue(errorResponse); + const result = await connectorType.executor(customExecutorOptions); + expect(result).toMatchInlineSnapshot(` + Object { + "actionId": "some-id", + "errorSource": "user", + "message": "error sending email", + "serviceMessage": "Can't send mail - all recipients were rejected: 554 5.7.1", + "status": "error", + } + `); + }); + test('renders parameter templates as expected', async () => { expect(connectorType.renderParameterTemplates).toBeTruthy(); const paramsWithTemplates = { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/email/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/email/index.ts index 1705342e73742..785ace370323f 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/email/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/email/index.ts @@ -28,6 +28,7 @@ import { renderMustacheString, } from '@kbn/actions-plugin/server/lib/mustache_renderer'; import { ActionExecutionSourceType } from '@kbn/actions-plugin/server/types'; +import { TaskErrorSource } from '@kbn/task-manager-plugin/common'; import { AdditionalEmailServices } from '../../../common'; import { sendEmail, JSON_TRANSPORT_SERVICE, SendEmailOptions, Transport } from './send_email'; import { portSchema } from '../lib/schemas'; @@ -370,12 +371,23 @@ async function executor( const message = i18n.translate('xpack.stackConnectors.email.errorSendingErrorMessage', { defaultMessage: 'error sending email', }); - return { + const errorResult: ConnectorTypeExecutorResult = { status: 'error', actionId, message, serviceMessage: err.message, }; + + // Mark 4xx and 5xx errors as user errors + const statusCode = err?.response?.status; + if (statusCode >= 400 && statusCode < 600) { + return { + ...errorResult, + errorSource: TaskErrorSource.USER, + }; + } + + return errorResult; } return { status: 'ok', data: result, actionId }; diff --git a/x-pack/plugins/stack_connectors/tsconfig.json b/x-pack/plugins/stack_connectors/tsconfig.json index 7a6898a5ce829..8a37f4edaa0b0 100644 --- a/x-pack/plugins/stack_connectors/tsconfig.json +++ b/x-pack/plugins/stack_connectors/tsconfig.json @@ -40,6 +40,7 @@ "@kbn/cases-components", "@kbn/code-editor-mock", "@kbn/utility-types", + "@kbn/task-manager-plugin", "@kbn/alerting-types", ], "exclude": [ From c209f3442d96f416f417ec4f67d633c68e10d314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?= Date: Tue, 2 Jul 2024 00:09:18 +0200 Subject: [PATCH 042/128] [EDR Workflows][Process descendants filter] Transform event filter entry for filtering process descendant (#187141) ## Summary This PR adds logic to server side artifact logic, in order to transform Event Filter entries to the expected format, if process descendant filtering is enabled. Conditions: - feature flag is enabled: `xpack.securitySolution.enableExperimental.filterProcessDescendantsForEventFiltersEnabled` - amongst the `tags`, there is the `filter_process_descendants` tag Output: the output contains a new operator called `descendent_of`, with all the original entries listed as the operator's values, and one additional `event.category is process` entry. Example input entry (only relevant fields): ```json "entries": [ { "field": "process.args", "operator": "included", "type": "match", "value": "IGNOREMYCHILDREN" } ], "tags": [ "filter_process_descendants" ], ``` Example output entry: ```json { "entries": [ { "type": "simple", "entries": [ { "operator": "included", "type": "descendent_of", // note: writing descendEnt here to harmonise with endpoint implementation "value": { "entries": [ { "type": "simple", "entries": [ { "field": "process.args", "operator": "included", "type": "exact_cased", "value": "IGNOREMYCHILDREN" }, { "field": "event.category", "operator": "included", "type": "exact_cased", "value": "process" } ] } ] } } ] } ] } ``` ### e2e testing - spin up Kibana with the FF enabled - spin up a v15-snapshot Agent with Endpoint integration on VM, enroll it - create an Event Filter with the following setup, where '_Process descendants_' is selected and `process.args` is `IGNOREMYCHILDREN`: image - create a `test.sh` on your VM with the following content: ```sh echo "Creating file test_file_$1.tmp" touch test_file_$1.tmp # recursively call the original script if [ $1 -ge 1 ] then sh test.sh $(( $1 - 1 )) $2 fi ``` (alternatively you can use the python script from the endpoint tests, https://github.com/elastic/endpoint-dev/blob/790c611325998c6ef8578144844d9260b8f73044/Python/endpoint/test/test_event_filtering.py#L439-L450) - run the script `sh test.sh 6 DO_NOT_IGNOREMYCHILDREN` image - go to Security -> Explore -> Hosts to see the reported events - I added some filters to easily see the events image - you should see that all events are there image - now try with `sh test.sh 9 IGNOREMYCHILDREN` image - now only the events of the top process are visible, the events from descendants are not image ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../endpoint/service/artifacts/constants.ts | 17 ++ .../endpoint/service/artifacts/utils.ts | 4 +- .../event_filters/view/components/form.tsx | 12 +- .../endpoint/lib/artifacts/lists.test.ts | 269 ++++++++++++++++-- .../server/endpoint/lib/artifacts/lists.ts | 83 ++++-- .../endpoint/schemas/artifacts/lists.ts | 25 ++ .../manifest_manager/manifest_manager.test.ts | 85 ++++-- .../manifest_manager/manifest_manager.ts | 2 +- 8 files changed, 409 insertions(+), 88 deletions(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/service/artifacts/constants.ts b/x-pack/plugins/security_solution/common/endpoint/service/artifacts/constants.ts index 362156ce3b5f7..9917742d7b7cc 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/artifacts/constants.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/artifacts/constants.ts @@ -5,7 +5,9 @@ * 2.0. */ +import type { EntryMatch } from '@kbn/securitysolution-io-ts-list-types'; import { ENDPOINT_ARTIFACT_LIST_IDS } from '@kbn/securitysolution-list-constants'; +import { EVENT_FILTERS_OPERATORS } from '@kbn/securitysolution-list-utils'; export const BY_POLICY_ARTIFACT_TAG_PREFIX = 'policy:'; @@ -13,6 +15,21 @@ export const GLOBAL_ARTIFACT_TAG = `${BY_POLICY_ARTIFACT_TAG_PREFIX}all`; export const FILTER_PROCESS_DESCENDANTS_TAG = 'filter_process_descendants'; +export const PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY: EntryMatch = Object.freeze({ + field: 'event.category', + operator: 'included', + type: 'match', + value: 'process', +}); + +export const PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY_TEXT: string = `${ + PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY.field +} ${ + EVENT_FILTERS_OPERATORS.find( + ({ type }) => type === PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY.type + )?.message +} ${PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY.value}`; + // TODO: refact all uses of `ALL_ENDPOINT_ARTIFACTS_LIST_IDS to sue new const from shared package export const ALL_ENDPOINT_ARTIFACT_LIST_IDS = ENDPOINT_ARTIFACT_LIST_IDS; diff --git a/x-pack/plugins/security_solution/common/endpoint/service/artifacts/utils.ts b/x-pack/plugins/security_solution/common/endpoint/service/artifacts/utils.ts index 50b4861a345ce..322cd87fd7bea 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/artifacts/utils.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/artifacts/utils.ts @@ -23,7 +23,7 @@ export type TagFilter = (tag: string) => boolean; const POLICY_ID_START_POSITION = BY_POLICY_ARTIFACT_TAG_PREFIX.length; export const isArtifactGlobal = (item: Partial>): boolean => { - return (item.tags ?? []).find((tag) => tag === GLOBAL_ARTIFACT_TAG) !== undefined; + return (item.tags ?? []).includes(GLOBAL_ARTIFACT_TAG); }; export const isArtifactByPolicy = (item: Pick): boolean => { @@ -96,7 +96,7 @@ export const getEffectedPolicySelectionByTags = ( export const isFilterProcessDescendantsEnabled = ( item: Partial> -): boolean => (item.tags ?? []).find((tag) => tag === FILTER_PROCESS_DESCENDANTS_TAG) !== undefined; +): boolean => (item.tags ?? []).includes(FILTER_PROCESS_DESCENDANTS_TAG); export const isFilterProcessDescendantsTag: TagFilter = (tag) => tag === FILTER_PROCESS_DESCENDANTS_TAG; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx index 2413007fd693a..ecb54e57baf4b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx @@ -41,7 +41,10 @@ import type { OnChangeProps } from '@kbn/lists-plugin/public'; import type { ValueSuggestionsGetFn } from '@kbn/unified-search-plugin/public/autocomplete/providers/value_suggestion_provider'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import { useGetUpdatedTags } from '../../../../hooks/artifacts'; -import { FILTER_PROCESS_DESCENDANTS_TAG } from '../../../../../../common/endpoint/service/artifacts/constants'; +import { + FILTER_PROCESS_DESCENDANTS_TAG, + PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY_TEXT, +} from '../../../../../../common/endpoint/service/artifacts/constants'; import { isFilterProcessDescendantsEnabled, isFilterProcessDescendantsTag, @@ -547,12 +550,7 @@ export const EventFiltersForm: React.FC - - - + {PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY_TEXT} )} diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts index bb46bc2da92b4..be85d8b83210c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts @@ -19,22 +19,28 @@ import { import type { TranslatedEntry, TranslatedExceptionListItem } from '../../schemas/artifacts'; import { ArtifactConstants } from './common'; import { + ENDPOINT_ARTIFACT_LISTS, ENDPOINT_BLOCKLISTS_LIST_ID, ENDPOINT_EVENT_FILTERS_LIST_ID, ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID, ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID, } from '@kbn/securitysolution-list-constants'; +import { FILTER_PROCESS_DESCENDANTS_TAG } from '../../../../common/endpoint/service/artifacts/constants'; +import type { ExperimentalFeatures } from '../../../../common'; +import { allowedExperimentalValues } from '../../../../common'; describe('artifacts lists', () => { let mockExceptionClient: ExceptionListClient; + let defaultFeatures: ExperimentalFeatures; beforeEach(() => { jest.clearAllMocks(); mockExceptionClient = listMock.getExceptionListClient(); + defaultFeatures = allowedExperimentalValues; }); - describe('getFilteredEndpointExceptionListRaw', () => { + describe('getFilteredEndpointExceptionListRaw + convertExceptionsToEndpointFormat', () => { const TEST_FILTER = 'exception-list-agnostic.attributes.os_types:"linux"'; test('it should get convert the exception lists response to the proper endpoint format', async () => { @@ -69,7 +75,7 @@ describe('artifacts lists', () => { filter: TEST_FILTER, listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -115,7 +121,7 @@ describe('artifacts lists', () => { filter: TEST_FILTER, listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -166,7 +172,7 @@ describe('artifacts lists', () => { filter: TEST_FILTER, listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -219,7 +225,7 @@ describe('artifacts lists', () => { filter: TEST_FILTER, listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -271,7 +277,7 @@ describe('artifacts lists', () => { filter: TEST_FILTER, listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -314,7 +320,7 @@ describe('artifacts lists', () => { filter: TEST_FILTER, listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -357,7 +363,7 @@ describe('artifacts lists', () => { filter: TEST_FILTER, listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -386,7 +392,7 @@ describe('artifacts lists', () => { filter: TEST_FILTER, listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); // Expect 1 exceptions, the first two calls returned the same exception list items expect(translated.entries.length).toEqual(1); @@ -402,7 +408,7 @@ describe('artifacts lists', () => { filter: TEST_FILTER, listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated.entries.length).toEqual(0); }); @@ -516,6 +522,215 @@ describe('artifacts lists', () => { ); expect(artifact1.decodedSha256).toEqual(artifact2.decodedSha256); }); + + describe('`descendant_of` operator', () => { + let enabledProcessDescendant: ExperimentalFeatures; + + beforeEach(() => { + enabledProcessDescendant = { + ...defaultFeatures, + filterProcessDescendantsForEventFiltersEnabled: true, + }; + }); + + test('when feature flag is disabled, it should not convert `descendant_of`', async () => { + const expectedEndpointExceptions: TranslatedExceptionListItem = { + type: 'simple', + entries: [ + { + field: 'process.executable', + operator: 'included', + type: 'exact_caseless', + value: 'C:\\Windows\\System32\\ping.exe', + }, + ], + }; + + const inputEntry: EntriesArray = [ + { + field: 'process.executable.text', + operator: 'included', + type: 'match', + value: 'C:\\Windows\\System32\\ping.exe', + }, + ]; + + const exceptionMock = getFoundExceptionListItemSchemaMock(); + exceptionMock.data[0].tags.push(FILTER_PROCESS_DESCENDANTS_TAG); + exceptionMock.data[0].list_id = ENDPOINT_ARTIFACT_LISTS.eventFilters.id; + exceptionMock.data[0].entries = inputEntry; + mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(exceptionMock); + + const resp = await getFilteredEndpointExceptionListRaw({ + elClient: mockExceptionClient, + filter: TEST_FILTER, + listId: ENDPOINT_LIST_ID, + }); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', { + filterProcessDescendantsForEventFiltersEnabled: false, + } as ExperimentalFeatures); + + expect(translated).toEqual({ entries: [expectedEndpointExceptions] }); + }); + + test.each([ + ENDPOINT_ARTIFACT_LISTS.blocklists.id, + ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id, + ENDPOINT_ARTIFACT_LISTS.trustedApps.id, + ])('when %s, it should not convert `descendant_of`', async (listId) => { + const expectedEndpointExceptions: TranslatedExceptionListItem = { + type: 'simple', + entries: [ + { + field: 'process.executable', + operator: 'included', + type: 'exact_caseless', + value: 'C:\\Windows\\System32\\ping.exe', + }, + ], + }; + + const inputEntry: EntriesArray = [ + { + field: 'process.executable.text', + operator: 'included', + type: 'match', + value: 'C:\\Windows\\System32\\ping.exe', + }, + ]; + + const exceptionMock = getFoundExceptionListItemSchemaMock(); + exceptionMock.data[0].tags.push(FILTER_PROCESS_DESCENDANTS_TAG); + exceptionMock.data[0].list_id = listId; + exceptionMock.data[0].entries = inputEntry; + mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(exceptionMock); + + const resp = await getFilteredEndpointExceptionListRaw({ + elClient: mockExceptionClient, + filter: TEST_FILTER, + listId: ENDPOINT_LIST_ID, + }); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', enabledProcessDescendant); + + expect(translated).toEqual({ entries: [expectedEndpointExceptions] }); + }); + + test('it should convert `descendant_of` to the expected format', async () => { + const expectedEndpointExceptions: TranslatedExceptionListItem = { + type: 'simple', + entries: [ + { + operator: 'included', + type: 'descendent_of', + value: { + entries: [ + { + type: 'simple', + entries: [ + { + field: 'process.executable', + operator: 'included', + type: 'exact_caseless', + value: 'C:\\Windows\\System32\\ping.exe', + }, + { + field: 'event.category', + operator: 'included', + type: 'exact_cased', + value: 'process', + }, + ], + }, + ], + }, + }, + ], + }; + + const inputEntry: EntriesArray = [ + { + field: 'process.executable.text', + operator: 'included', + type: 'match', + value: 'C:\\Windows\\System32\\ping.exe', + }, + ]; + + const exceptionMock = getFoundExceptionListItemSchemaMock(); + exceptionMock.data[0].tags.push(FILTER_PROCESS_DESCENDANTS_TAG); + exceptionMock.data[0].list_id = ENDPOINT_ARTIFACT_LISTS.eventFilters.id; + exceptionMock.data[0].entries = inputEntry; + mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(exceptionMock); + + const resp = await getFilteredEndpointExceptionListRaw({ + elClient: mockExceptionClient, + filter: TEST_FILTER, + listId: ENDPOINT_LIST_ID, + }); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', enabledProcessDescendant); + + expect(translated).toEqual({ entries: [expectedEndpointExceptions] }); + }); + + test('it should handle nested entries properly', async () => { + const expectedEndpointExceptions: TranslatedExceptionListItem = { + type: 'simple', + entries: [ + { + operator: 'included', + type: 'descendent_of', + value: { + entries: [ + { + type: 'simple', + entries: [ + { + entries: [ + { + field: 'nested.field', + operator: 'included', + type: 'exact_cased', + value: 'some value', + }, + ], + field: 'some.parentField', + type: 'nested', + }, + { + field: 'some.not.nested.field', + operator: 'included', + type: 'exact_cased', + value: 'some value', + }, + { + field: 'event.category', + operator: 'included', + type: 'exact_cased', + value: 'process', + }, + ], + }, + ], + }, + }, + ], + }; + + const exceptionMock = getFoundExceptionListItemSchemaMock(); + exceptionMock.data[0].tags.push(FILTER_PROCESS_DESCENDANTS_TAG); + exceptionMock.data[0].list_id = ENDPOINT_ARTIFACT_LISTS.eventFilters.id; + mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(exceptionMock); + + const resp = await getFilteredEndpointExceptionListRaw({ + elClient: mockExceptionClient, + filter: TEST_FILTER, + listId: ENDPOINT_LIST_ID, + }); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', enabledProcessDescendant); + + expect(translated).toEqual({ entries: [expectedEndpointExceptions] }); + }); + }); }); describe('Endpoint Artifacts', () => { @@ -562,7 +777,7 @@ describe('artifacts lists', () => { filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`, listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -607,7 +822,7 @@ describe('artifacts lists', () => { filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`, listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -646,7 +861,7 @@ describe('artifacts lists', () => { filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`, listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -710,7 +925,7 @@ describe('artifacts lists', () => { filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`, listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], @@ -750,7 +965,7 @@ describe('artifacts lists', () => { filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`, listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -815,7 +1030,7 @@ describe('artifacts lists', () => { listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -864,7 +1079,7 @@ describe('artifacts lists', () => { filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`, listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -911,7 +1126,7 @@ describe('artifacts lists', () => { filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`, listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -952,7 +1167,7 @@ describe('artifacts lists', () => { filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`, listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -1019,7 +1234,7 @@ describe('artifacts lists', () => { listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -1059,7 +1274,7 @@ describe('artifacts lists', () => { filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`, listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -1126,7 +1341,7 @@ describe('artifacts lists', () => { listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual({ entries: [expectedEndpointExceptions], }); @@ -1174,7 +1389,7 @@ describe('artifacts lists', () => { listId: ENDPOINT_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual(TEST_EXCEPTION_LIST_ITEM); expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({ @@ -1198,7 +1413,7 @@ describe('artifacts lists', () => { os: 'macos', listId: ENDPOINT_TRUSTED_APPS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual(TEST_EXCEPTION_LIST_ITEM); @@ -1224,7 +1439,7 @@ describe('artifacts lists', () => { listId: ENDPOINT_EVENT_FILTERS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual(TEST_EXCEPTION_LIST_ITEM); expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({ @@ -1249,7 +1464,7 @@ describe('artifacts lists', () => { listId: ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual(TEST_EXCEPTION_LIST_ITEM); expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({ @@ -1274,7 +1489,7 @@ describe('artifacts lists', () => { listId: ENDPOINT_BLOCKLISTS_LIST_ID, }); - const translated = convertExceptionsToEndpointFormat(resp, 'v1'); + const translated = convertExceptionsToEndpointFormat(resp, 'v1', defaultFeatures); expect(translated).toEqual(TEST_EXCEPTION_LIST_ITEM); expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({ diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts index 3b9690c902fc8..8040a4f596988 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts @@ -26,6 +26,9 @@ import { } from '@kbn/securitysolution-list-constants'; import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import { validate } from '@kbn/securitysolution-io-ts-utils'; +import { PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY } from '../../../../common/endpoint/service/artifacts/constants'; +import type { ExperimentalFeatures } from '../../../../common'; +import { isFilterProcessDescendantsEnabled } from '../../../../common/endpoint/service/artifacts/utils'; import type { InternalArtifactCompleteSchema, TranslatedEntry, @@ -36,6 +39,7 @@ import type { TranslatedEntryNestedEntry, TranslatedExceptionListItem, WrappedTranslatedExceptionList, + TranslatedEntriesOfDescendantOf, } from '../../schemas'; import { translatedPerformantEntries as translatedPerformantEntriesType, @@ -78,10 +82,11 @@ export type ArtifactListId = export function convertExceptionsToEndpointFormat( exceptions: ExceptionListItemSchema[], - schemaVersion: string + schemaVersion: string, + experimentalFeatures: ExperimentalFeatures ) { const translatedExceptions = { - entries: translateToEndpointExceptions(exceptions, schemaVersion), + entries: translateToEndpointExceptions(exceptions, schemaVersion, experimentalFeatures), }; const [validated, errors] = validate(translatedExceptions, wrappedTranslatedExceptionList); if (errors != null) { @@ -151,13 +156,24 @@ export async function getAllItemsFromEndpointExceptionList({ * Translates Exception list items to Exceptions the endpoint can understand * @param exceptions * @param schemaVersion + * @param experimentalFeatures */ export function translateToEndpointExceptions( exceptions: ExceptionListItemSchema[], - schemaVersion: string + schemaVersion: string, + experimentalFeatures: ExperimentalFeatures ): TranslatedExceptionListItem[] { const entrySet = new Set(); - const entriesFiltered: TranslatedExceptionListItem[] = []; + const uniqueItems: TranslatedExceptionListItem[] = []; + const storeUniqueItem = (item: TranslatedExceptionListItem) => { + const entryHash = createHash('sha256').update(JSON.stringify(item)).digest('hex'); + + if (!entrySet.has(entryHash)) { + uniqueItems.push(item); + entrySet.add(entryHash); + } + }; + if (schemaVersion === 'v1') { exceptions.forEach((entry) => { // For Blocklist, we create a single entry for each blocklist entry item @@ -172,30 +188,51 @@ export function translateToEndpointExceptions( ...entry, entries: [blocklistSingleEntry], }); - const entryHash = createHash('sha256') - .update(JSON.stringify(translatedItem)) - .digest('hex'); - if (!entrySet.has(entryHash)) { - entriesFiltered.push(translatedItem); - entrySet.add(entryHash); - } + + storeUniqueItem(translatedItem); }); + } else if ( + experimentalFeatures.filterProcessDescendantsForEventFiltersEnabled && + entry.list_id === ENDPOINT_ARTIFACT_LISTS.eventFilters.id && + isFilterProcessDescendantsEnabled(entry) + ) { + const translatedItem = translateProcessDescendantEventFilter(schemaVersion, entry); + storeUniqueItem(translatedItem); } else { const translatedItem = translateItem(schemaVersion, entry); - const entryHash = createHash('sha256').update(JSON.stringify(translatedItem)).digest('hex'); - if (!entrySet.has(entryHash)) { - entriesFiltered.push(translatedItem); - entrySet.add(entryHash); - } + storeUniqueItem(translatedItem); } }); - return entriesFiltered; + return uniqueItems; } else { throw new Error('unsupported schemaVersion'); } } +function translateProcessDescendantEventFilter( + schemaVersion: string, + entry: ExceptionListItemSchema +): TranslatedExceptionListItem { + const translatedEntries: TranslatedEntriesOfDescendantOf = translateItem(schemaVersion, { + ...entry, + entries: [...entry.entries, PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY], + }) as TranslatedEntriesOfDescendantOf; + + return { + type: entry.type, + entries: [ + { + operator: 'included', + type: 'descendent_of', + value: { + entries: [translatedEntries], + }, + }, + ], + }; +} + function getMatcherFunction({ field, matchAny, @@ -246,8 +283,9 @@ function translateItem( item: ExceptionListItemSchema ): TranslatedExceptionListItem { const itemSet = new Set(); - const getEntries = (): TranslatedExceptionListItem['entries'] => { - return item.entries.reduce((translatedEntries, entry) => { + + const entries: TranslatedExceptionListItem['entries'] = item.entries.reduce( + (translatedEntries, entry) => { const translatedEntry = translateEntry(schemaVersion, item.entries, entry, item.os_types[0]); if (translatedEntry !== undefined) { @@ -272,12 +310,13 @@ function translateItem( } return translatedEntries; - }, []); - }; + }, + [] + ); return { type: item.type, - entries: getEntries(), + entries, }; } diff --git a/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/lists.ts b/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/lists.ts index 3abbbe1292885..262ead3493dff 100644 --- a/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/lists.ts +++ b/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/lists.ts @@ -84,11 +84,36 @@ export const translatedEntryNested = t.exact( }) ); +const translatedEntriesOfDescendantOf = t.type({ + type: t.string, + entries: t.array( + t.union([ + translatedEntryNested, + translatedEntryMatch, + translatedEntryMatchWildcard, + translatedEntryMatchAny, + ]) + ), +}); +export type TranslatedEntriesOfDescendantOf = t.TypeOf; + +export const translatedEntryDescendantOf = t.exact( + t.type({ + operator, + type: t.keyof({ descendent_of: null }), + value: t.type({ + entries: t.array(translatedEntriesOfDescendantOf), + }), + }) +); +export type TranslatedEntryDescendantOf = t.TypeOf; + export const translatedEntry = t.union([ translatedEntryNested, translatedEntryMatch, translatedEntryMatchWildcard, translatedEntryMatchAny, + translatedEntryDescendantOf, ]); export type TranslatedEntry = t.TypeOf; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts index a7a0048df0f43..bb2ea455675c0 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts @@ -44,6 +44,8 @@ import { createFetchAllArtifactsIterableMock, generateArtifactMock, } from '@kbn/fleet-plugin/server/services/artifacts/mocks'; +import type { ExperimentalFeatures } from '../../../../../common'; +import { allowedExperimentalValues } from '../../../../../common'; const getArtifactObject = (artifact: InternalArtifactSchema) => JSON.parse(Buffer.from(artifact.body!, 'base64').toString()); @@ -93,6 +95,8 @@ describe('ManifestManager', () => { let ARTIFACT_TRUSTED_APPS_MACOS: InternalArtifactCompleteSchema; let ARTIFACT_TRUSTED_APPS_WINDOWS: InternalArtifactCompleteSchema; + let defaultFeatures: ExperimentalFeatures; + beforeAll(async () => { ARTIFACTS = await getMockArtifacts(); ARTIFACTS_BY_ID = { @@ -106,6 +110,7 @@ describe('ManifestManager', () => { ARTIFACT_EXCEPTIONS_WINDOWS = ARTIFACTS[1]; ARTIFACT_TRUSTED_APPS_MACOS = ARTIFACTS[3]; ARTIFACT_TRUSTED_APPS_WINDOWS = ARTIFACTS[4]; + defaultFeatures = allowedExperimentalValues; }); describe('getLastComputedManifest from Unified Manifest SO', () => { @@ -463,29 +468,33 @@ describe('ManifestManager', () => { expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES); expect(getArtifactObject(artifacts[0])).toStrictEqual({ - entries: translateToEndpointExceptions([exceptionListItem], 'v1'), + entries: translateToEndpointExceptions([exceptionListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[1])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[2])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[3])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[4])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[5])).toStrictEqual({ - entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), + entries: translateToEndpointExceptions([trustedAppListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[6])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[8])).toStrictEqual({ - entries: translateToEndpointExceptions([eventFiltersListItem], 'v1'), + entries: translateToEndpointExceptions([eventFiltersListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[9])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[10])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[11])).toStrictEqual({ - entries: translateToEndpointExceptions([hostIsolationExceptionsItem], 'v1'), + entries: translateToEndpointExceptions( + [hostIsolationExceptionsItem], + 'v1', + defaultFeatures + ), }); expect(getArtifactObject(artifacts[12])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[13])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[14])).toStrictEqual({ - entries: translateToEndpointExceptions([blocklistsListItem], 'v1'), + entries: translateToEndpointExceptions([blocklistsListItem], 'v1', defaultFeatures), }); for (const artifact of artifacts) { @@ -553,22 +562,26 @@ describe('ManifestManager', () => { expect(getArtifactObject(artifacts[3])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[4])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[5])).toStrictEqual({ - entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), + entries: translateToEndpointExceptions([trustedAppListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[6])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[8])).toStrictEqual({ - entries: translateToEndpointExceptions([eventFiltersListItem], 'v1'), + entries: translateToEndpointExceptions([eventFiltersListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[9])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[10])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[11])).toStrictEqual({ - entries: translateToEndpointExceptions([hostIsolationExceptionsItem], 'v1'), + entries: translateToEndpointExceptions( + [hostIsolationExceptionsItem], + 'v1', + defaultFeatures + ), }); expect(getArtifactObject(artifacts[12])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[13])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[14])).toStrictEqual({ - entries: translateToEndpointExceptions([blocklistsListItem], 'v1'), + entries: translateToEndpointExceptions([blocklistsListItem], 'v1', defaultFeatures), }); for (const artifact of artifacts) { @@ -647,16 +660,20 @@ describe('ManifestManager', () => { expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES); expect(getArtifactObject(artifacts[0])).toStrictEqual({ - entries: translateToEndpointExceptions([exceptionListItem], 'v1'), + entries: translateToEndpointExceptions([exceptionListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[1])).toStrictEqual({ - entries: translateToEndpointExceptions([duplicatedEndpointExceptionInDifferentOS], 'v1'), + entries: translateToEndpointExceptions( + [duplicatedEndpointExceptionInDifferentOS], + 'v1', + defaultFeatures + ), }); expect(getArtifactObject(artifacts[2])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[3])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[4])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[5])).toStrictEqual({ - entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), + entries: translateToEndpointExceptions([trustedAppListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[6])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] }); @@ -664,16 +681,21 @@ describe('ManifestManager', () => { expect(getArtifactObject(artifacts[9])).toStrictEqual({ entries: translateToEndpointExceptions( [eventFiltersListItem, duplicatedEventFilterInDifferentPolicy], - 'v1' + 'v1', + defaultFeatures ), }); expect(getArtifactObject(artifacts[10])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[11])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[12])).toStrictEqual({ - entries: translateToEndpointExceptions([hostIsolationExceptionsItem], 'v1'), + entries: translateToEndpointExceptions( + [hostIsolationExceptionsItem], + 'v1', + defaultFeatures + ), }); expect(getArtifactObject(artifacts[13])).toStrictEqual({ - entries: translateToEndpointExceptions([blocklistsListItem], 'v1'), + entries: translateToEndpointExceptions([blocklistsListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[14])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[15])).toStrictEqual({ entries: [] }); @@ -750,19 +772,20 @@ describe('ManifestManager', () => { expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES); expect(getArtifactObject(artifacts[0])).toStrictEqual({ - entries: translateToEndpointExceptions([exceptionListItem], 'v1'), + entries: translateToEndpointExceptions([exceptionListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[1])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[2])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[3])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[4])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[5])).toStrictEqual({ - entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), + entries: translateToEndpointExceptions([trustedAppListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[6])).toStrictEqual({ entries: translateToEndpointExceptions( [trustedAppListItem, trustedAppListItemPolicy2], - 'v1' + 'v1', + defaultFeatures ), }); expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] }); @@ -855,19 +878,19 @@ describe('ManifestManager', () => { expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES); expect(getArtifactObject(artifacts[0])).toStrictEqual({ - entries: translateToEndpointExceptions([exceptionListItem], 'v1'), + entries: translateToEndpointExceptions([exceptionListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[1])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[2])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[3])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[4])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[5])).toStrictEqual({ - entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), + entries: translateToEndpointExceptions([trustedAppListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[6])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[8])).toStrictEqual({ - entries: translateToEndpointExceptions([eventFiltersListItem], 'v1'), + entries: translateToEndpointExceptions([eventFiltersListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[9])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[10])).toStrictEqual({ entries: [] }); @@ -875,7 +898,7 @@ describe('ManifestManager', () => { expect(getArtifactObject(artifacts[12])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[13])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[14])).toStrictEqual({ - entries: translateToEndpointExceptions([blocklistsListItem], 'v1'), + entries: translateToEndpointExceptions([blocklistsListItem], 'v1', defaultFeatures), }); for (const artifact of artifacts) { @@ -936,29 +959,33 @@ describe('ManifestManager', () => { expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES); expect(getArtifactObject(artifacts[0])).toStrictEqual({ - entries: translateToEndpointExceptions([exceptionListItem], 'v1'), + entries: translateToEndpointExceptions([exceptionListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[1])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[2])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[3])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[4])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[5])).toStrictEqual({ - entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), + entries: translateToEndpointExceptions([trustedAppListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[6])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[8])).toStrictEqual({ - entries: translateToEndpointExceptions([eventFiltersListItem], 'v1'), + entries: translateToEndpointExceptions([eventFiltersListItem], 'v1', defaultFeatures), }); expect(getArtifactObject(artifacts[9])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[10])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[11])).toStrictEqual({ - entries: translateToEndpointExceptions([hostIsolationExceptionsItem], 'v1'), + entries: translateToEndpointExceptions( + [hostIsolationExceptionsItem], + 'v1', + defaultFeatures + ), }); expect(getArtifactObject(artifacts[12])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[13])).toStrictEqual({ entries: [] }); expect(getArtifactObject(artifacts[14])).toStrictEqual({ - entries: translateToEndpointExceptions([blocklistsListItem], 'v1'), + entries: translateToEndpointExceptions([blocklistsListItem], 'v1', defaultFeatures), }); for (const artifact of artifacts) { @@ -1079,7 +1106,7 @@ describe('ManifestManager', () => { expect(artifacts.length).toBe(15); expect(getArtifactObject(artifacts[0])).toStrictEqual({ - entries: translateToEndpointExceptions([expectedExceptionListItem], 'v1'), + entries: translateToEndpointExceptions([expectedExceptionListItem], 'v1', defaultFeatures), }); }); @@ -1119,7 +1146,7 @@ describe('ManifestManager', () => { expect(artifacts.length).toBe(15); expect(getArtifactObject(artifacts[0])).toStrictEqual({ - entries: translateToEndpointExceptions([expectedExceptionListItem], 'v1'), + entries: translateToEndpointExceptions([expectedExceptionListItem], 'v1', defaultFeatures), }); }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts index 3a6cfc5be280c..f10dbb1ab3a50 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts @@ -189,7 +189,7 @@ export class ManifestManager { const exceptions: ExceptionListItemSchema[] = listId === ENDPOINT_LIST_ID ? allExceptionsByListId : allExceptionsByListId.filter(filter); - return convertExceptionsToEndpointFormat(exceptions, schemaVersion); + return convertExceptionsToEndpointFormat(exceptions, schemaVersion, this.experimentalFeatures); } /** From 01cce981bc3c9fb803598ffa5e51a714e95c5895 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Mon, 1 Jul 2024 17:05:32 -0700 Subject: [PATCH 043/128] [Alerting Plugin] Migrate authc.getCurrentUser usage to core.security (#187020) Part of https://github.com/elastic/kibana/issues/186574 ## Summary This PR migrates the Alerting Plugin's server-side code that consumes `authc.getCurrentUser` to use coreContext.security. Background: This PR serves as an example of a plugin migrating away from depending on the Security plugin, which is a high priority effort for the last release before 9.0. ### Checklist Delete any items that are not applicable to this PR. - [X] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../maintenance_window_client_factory.test.ts | 22 ++++++------------- .../maintenance_window_client_factory.ts | 17 ++++++-------- x-pack/plugins/alerting/server/plugin.ts | 5 +++-- .../server/rules_client_factory.test.ts | 16 ++++++++++---- .../alerting/server/rules_client_factory.ts | 14 ++++++------ .../rules_settings_client_factory.test.ts | 22 ++++++------------- .../server/rules_settings_client_factory.ts | 17 ++++++-------- 7 files changed, 50 insertions(+), 63 deletions(-) diff --git a/x-pack/plugins/alerting/server/maintenance_window_client_factory.test.ts b/x-pack/plugins/alerting/server/maintenance_window_client_factory.test.ts index e25fcc34a46cc..85ac758da1737 100644 --- a/x-pack/plugins/alerting/server/maintenance_window_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/maintenance_window_client_factory.test.ts @@ -14,9 +14,9 @@ import { savedObjectsServiceMock, loggingSystemMock, uiSettingsServiceMock, + securityServiceMock, } from '@kbn/core/server/mocks'; import { AuthenticatedUser } from '@kbn/security-plugin/common'; -import { securityMock } from '@kbn/security-plugin/server/mocks'; import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import { MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE } from '../common'; @@ -24,12 +24,13 @@ jest.mock('./maintenance_window_client'); const savedObjectsClient = savedObjectsClientMock.create(); const savedObjectsService = savedObjectsServiceMock.createInternalStartContract(); -const securityPluginStart = securityMock.createStart(); +const securityService = securityServiceMock.createStart(); const uiSettings = uiSettingsServiceMock.createStartContract(); const maintenanceWindowClientFactoryParams: jest.Mocked = { logger: loggingSystemMock.create().get(), savedObjectsService, + securityService, uiSettings, }; @@ -39,10 +40,7 @@ beforeEach(() => { test('creates a maintenance window client with proper constructor arguments when security is enabled', async () => { const factory = new MaintenanceWindowClientFactory(); - factory.initialize({ - securityPluginStart, - ...maintenanceWindowClientFactoryParams, - }); + factory.initialize(maintenanceWindowClientFactoryParams); const request = mockRouter.createKibanaRequest(); savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient); @@ -86,10 +84,7 @@ test('creates a maintenance window client with proper constructor arguments', as test('creates an unauthorized maintenance window client', async () => { const factory = new MaintenanceWindowClientFactory(); - factory.initialize({ - securityPluginStart, - ...maintenanceWindowClientFactoryParams, - }); + factory.initialize(maintenanceWindowClientFactoryParams); const request = mockRouter.createKibanaRequest(); savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient); @@ -125,10 +120,7 @@ test('getUserName() returns null when security is disabled', async () => { test('getUserName() returns a name when security is enabled', async () => { const factory = new MaintenanceWindowClientFactory(); - factory.initialize({ - securityPluginStart, - ...maintenanceWindowClientFactoryParams, - }); + factory.initialize(maintenanceWindowClientFactoryParams); const request = mockRouter.createKibanaRequest(); factory.createWithAuthorization(request); @@ -136,7 +128,7 @@ test('getUserName() returns a name when security is enabled', async () => { const constructorCall = jest.requireMock('./maintenance_window_client').MaintenanceWindowClient .mock.calls[0][0]; - securityPluginStart.authc.getCurrentUser.mockReturnValueOnce({ + securityService.authc.getCurrentUser.mockReturnValueOnce({ username: 'testname', } as unknown as AuthenticatedUser); const userNameResult = await constructorCall.getUserName(); diff --git a/x-pack/plugins/alerting/server/maintenance_window_client_factory.ts b/x-pack/plugins/alerting/server/maintenance_window_client_factory.ts index 4e040f50ec3ca..5cd013784637c 100644 --- a/x-pack/plugins/alerting/server/maintenance_window_client_factory.ts +++ b/x-pack/plugins/alerting/server/maintenance_window_client_factory.ts @@ -10,16 +10,16 @@ import { Logger, SavedObjectsServiceStart, SECURITY_EXTENSION_ID, + SecurityServiceStart, UiSettingsServiceStart, } from '@kbn/core/server'; -import { SecurityPluginStart } from '@kbn/security-plugin/server'; import { MaintenanceWindowClient } from './maintenance_window_client'; import { MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE } from '../common'; export interface MaintenanceWindowClientFactoryOpts { logger: Logger; savedObjectsService: SavedObjectsServiceStart; - securityPluginStart?: SecurityPluginStart; + securityService: SecurityServiceStart; uiSettings: UiSettingsServiceStart; } @@ -27,7 +27,7 @@ export class MaintenanceWindowClientFactory { private isInitialized = false; private logger!: Logger; private savedObjectsService!: SavedObjectsServiceStart; - private securityPluginStart?: SecurityPluginStart; + private securityService!: SecurityServiceStart; private uiSettings!: UiSettingsServiceStart; public initialize(options: MaintenanceWindowClientFactoryOpts) { @@ -37,12 +37,12 @@ export class MaintenanceWindowClientFactory { this.isInitialized = true; this.logger = options.logger; this.savedObjectsService = options.savedObjectsService; - this.securityPluginStart = options.securityPluginStart; + this.securityService = options.securityService; this.uiSettings = options.uiSettings; } private createMaintenanceWindowClient(request: KibanaRequest, withAuth: boolean) { - const { securityPluginStart } = this; + const { securityService } = this; const savedObjectsClient = this.savedObjectsService.getScopedClient(request, { includedHiddenTypes: [MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE], ...(withAuth ? {} : { excludedExtensions: [SECURITY_EXTENSION_ID] }), @@ -55,11 +55,8 @@ export class MaintenanceWindowClientFactory { savedObjectsClient, uiSettings: uiSettingClient, async getUserName() { - if (!securityPluginStart || !request) { - return null; - } - const user = securityPluginStart.authc.getCurrentUser(request); - return user ? user.username : null; + const user = securityService.authc.getCurrentUser(request); + return user?.username ?? null; }, }); } diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 1eb130f523c9c..3c996c532acf2 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -541,19 +541,20 @@ export class AlertingPlugin { backfillClient: this.backfillClient!, connectorAdapterRegistry: this.connectorAdapterRegistry, uiSettings: core.uiSettings, + securityService: core.security, }); rulesSettingsClientFactory.initialize({ logger: this.logger, savedObjectsService: core.savedObjects, - securityPluginStart: plugins.security, + securityService: core.security, isServerless: !!plugins.serverless, }); maintenanceWindowClientFactory.initialize({ logger: this.logger, savedObjectsService: core.savedObjects, - securityPluginStart: plugins.security, + securityService: core.security, uiSettings: core.uiSettings, }); diff --git a/x-pack/plugins/alerting/server/rules_client_factory.test.ts b/x-pack/plugins/alerting/server/rules_client_factory.test.ts index 9be3e17e6371f..4cd7ffbcf0c6c 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.test.ts @@ -14,6 +14,7 @@ import { loggingSystemMock, savedObjectsRepositoryMock, uiSettingsServiceMock, + securityServiceMock, } from '@kbn/core/server/mocks'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { AuthenticatedUser } from '@kbn/security-plugin/common'; @@ -43,6 +44,7 @@ const savedObjectsService = savedObjectsServiceMock.createInternalStartContract( const securityPluginSetup = securityMock.createSetup(); const securityPluginStart = securityMock.createStart(); +const securityService = securityServiceMock.createStart(); const alertingAuthorization = alertingAuthorizationMock.create(); const alertingAuthorizationClientFactory = alertingAuthorizationClientFactoryMock.createFactory(); @@ -67,6 +69,7 @@ const rulesClientFactoryParams: jest.Mocked = { backfillClient, connectorAdapterRegistry: new ConnectorAdapterRegistry(), uiSettings: uiSettingsServiceMock.createStartContract(), + securityService: securityServiceMock.createStart(), getAlertIndicesAlias: jest.fn(), alertsService: null, }; @@ -87,7 +90,11 @@ beforeEach(() => { test('creates a rules client with proper constructor arguments when security is enabled', async () => { const factory = new RulesClientFactory(); - factory.initialize({ securityPluginSetup, securityPluginStart, ...rulesClientFactoryParams }); + factory.initialize({ + securityPluginSetup, + securityPluginStart, + ...rulesClientFactoryParams, + }); const request = mockRouter.createKibanaRequest(); savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient); @@ -207,13 +214,12 @@ test('getUserName() returns a name when security is enabled', async () => { const factory = new RulesClientFactory(); factory.initialize({ ...rulesClientFactoryParams, - securityPluginSetup, - securityPluginStart, + securityService, }); factory.create(mockRouter.createKibanaRequest(), savedObjectsService); const constructorCall = jest.requireMock('./rules_client').RulesClient.mock.calls[0][0]; - securityPluginStart.authc.getCurrentUser.mockReturnValueOnce({ + securityService.authc.getCurrentUser.mockReturnValueOnce({ username: 'bob', } as unknown as AuthenticatedUser); const userNameResult = await constructorCall.getUserName(); @@ -255,6 +261,7 @@ test('createAPIKey() returns an API key when security is enabled', async () => { const factory = new RulesClientFactory(); factory.initialize({ ...rulesClientFactoryParams, + securityService, securityPluginSetup, securityPluginStart, }); @@ -285,6 +292,7 @@ test('createAPIKey() throws when security plugin createAPIKey throws an error', const factory = new RulesClientFactory(); factory.initialize({ ...rulesClientFactoryParams, + securityService, securityPluginSetup, securityPluginStart, }); diff --git a/x-pack/plugins/alerting/server/rules_client_factory.ts b/x-pack/plugins/alerting/server/rules_client_factory.ts index 50a11dd178c38..f28170b277ac4 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.ts @@ -58,6 +58,7 @@ export interface RulesClientFactoryOpts { backfillClient: BackfillClient; connectorAdapterRegistry: ConnectorAdapterRegistry; uiSettings: CoreStart['uiSettings']; + securityService: CoreStart['security']; } export class RulesClientFactory { @@ -83,6 +84,7 @@ export class RulesClientFactory { private backfillClient!: BackfillClient; private connectorAdapterRegistry!: ConnectorAdapterRegistry; private uiSettings!: CoreStart['uiSettings']; + private securityService!: CoreStart['security']; public initialize(options: RulesClientFactoryOpts) { if (this.isInitialized) { @@ -110,10 +112,11 @@ export class RulesClientFactory { this.backfillClient = options.backfillClient; this.connectorAdapterRegistry = options.connectorAdapterRegistry; this.uiSettings = options.uiSettings; + this.securityService = options.securityService; } public create(request: KibanaRequest, savedObjects: SavedObjectsServiceStart): RulesClient { - const { securityPluginSetup, securityPluginStart, actions, eventLog } = this; + const { securityPluginSetup, securityService, securityPluginStart, actions, eventLog } = this; const spaceId = this.getSpaceId(request); if (!this.authorization) { @@ -149,11 +152,8 @@ export class RulesClientFactory { uiSettings: this.uiSettings, async getUserName() { - if (!securityPluginStart) { - return null; - } - const user = await securityPluginStart.authc.getCurrentUser(request); - return user ? user.username : null; + const user = securityService.authc.getCurrentUser(request); + return user?.username ?? null; }, async createAPIKey(name: string) { if (!securityPluginStart) { @@ -185,7 +185,7 @@ export class RulesClientFactory { if (!securityPluginStart) { return false; } - const user = securityPluginStart.authc.getCurrentUser(request); + const user = securityService.authc.getCurrentUser(request); return user && user.authentication_type ? user.authentication_type === 'api_key' : false; }, getAuthenticationAPIKey(name: string) { diff --git a/x-pack/plugins/alerting/server/rules_settings_client_factory.test.ts b/x-pack/plugins/alerting/server/rules_settings_client_factory.test.ts index 6f713f20530e5..f107fbac3a541 100644 --- a/x-pack/plugins/alerting/server/rules_settings_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/rules_settings_client_factory.test.ts @@ -14,9 +14,9 @@ import { savedObjectsClientMock, savedObjectsServiceMock, loggingSystemMock, + securityServiceMock, } from '@kbn/core/server/mocks'; import { AuthenticatedUser } from '@kbn/security-plugin/common'; -import { securityMock } from '@kbn/security-plugin/server/mocks'; import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import { RULES_SETTINGS_SAVED_OBJECT_TYPE } from '../common'; @@ -25,11 +25,12 @@ jest.mock('./rules_settings_client'); const savedObjectsClient = savedObjectsClientMock.create(); const savedObjectsService = savedObjectsServiceMock.createInternalStartContract(); -const securityPluginStart = securityMock.createStart(); +const securityService = securityServiceMock.createStart(); const rulesSettingsClientFactoryParams: jest.Mocked = { logger: loggingSystemMock.create().get(), savedObjectsService, + securityService, isServerless: false, }; @@ -39,10 +40,7 @@ beforeEach(() => { test('creates a rules settings client with proper constructor arguments when security is enabled', async () => { const factory = new RulesSettingsClientFactory(); - factory.initialize({ - securityPluginStart, - ...rulesSettingsClientFactoryParams, - }); + factory.initialize(rulesSettingsClientFactoryParams); const request = mockRouter.createKibanaRequest(); savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient); @@ -88,10 +86,7 @@ test('creates a rules settings client with proper constructor arguments', async test('creates an unauthorized rules settings client', async () => { const factory = new RulesSettingsClientFactory(); - factory.initialize({ - securityPluginStart, - ...rulesSettingsClientFactoryParams, - }); + factory.initialize(rulesSettingsClientFactoryParams); const request = mockRouter.createKibanaRequest(); savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient); @@ -128,10 +123,7 @@ test('getUserName() returns null when security is disabled', async () => { test('getUserName() returns a name when security is enabled', async () => { const factory = new RulesSettingsClientFactory(); - factory.initialize({ - securityPluginStart, - ...rulesSettingsClientFactoryParams, - }); + factory.initialize(rulesSettingsClientFactoryParams); const request = mockRouter.createKibanaRequest(); factory.createWithAuthorization(request); @@ -139,7 +131,7 @@ test('getUserName() returns a name when security is enabled', async () => { const constructorCall = jest.requireMock('./rules_settings_client').RulesSettingsClient.mock.calls[0][0]; - securityPluginStart.authc.getCurrentUser.mockReturnValueOnce({ + securityService.authc.getCurrentUser.mockReturnValueOnce({ username: 'testname', } as unknown as AuthenticatedUser); const userNameResult = await constructorCall.getUserName(); diff --git a/x-pack/plugins/alerting/server/rules_settings_client_factory.ts b/x-pack/plugins/alerting/server/rules_settings_client_factory.ts index f69068ee3cb65..707c38b508c1a 100644 --- a/x-pack/plugins/alerting/server/rules_settings_client_factory.ts +++ b/x-pack/plugins/alerting/server/rules_settings_client_factory.ts @@ -10,8 +10,8 @@ import { Logger, SavedObjectsServiceStart, SECURITY_EXTENSION_ID, + SecurityServiceStart, } from '@kbn/core/server'; -import { SecurityPluginStart } from '@kbn/security-plugin/server'; import { RulesSettingsClient } from './rules_settings_client'; import { RULES_SETTINGS_SAVED_OBJECT_TYPE } from '../common'; @@ -19,14 +19,14 @@ export interface RulesSettingsClientFactoryOpts { logger: Logger; savedObjectsService: SavedObjectsServiceStart; isServerless: boolean; - securityPluginStart?: SecurityPluginStart; + securityService: SecurityServiceStart; } export class RulesSettingsClientFactory { private isInitialized = false; private logger!: Logger; private savedObjectsService!: SavedObjectsServiceStart; - private securityPluginStart?: SecurityPluginStart; + private securityService!: SecurityServiceStart; private isServerless = false; public initialize(options: RulesSettingsClientFactoryOpts) { @@ -36,12 +36,12 @@ export class RulesSettingsClientFactory { this.isInitialized = true; this.logger = options.logger; this.savedObjectsService = options.savedObjectsService; - this.securityPluginStart = options.securityPluginStart; + this.securityService = options.securityService; this.isServerless = options.isServerless; } private createRulesSettingsClient(request: KibanaRequest, withAuth: boolean) { - const { securityPluginStart } = this; + const { securityService } = this; const savedObjectsClient = this.savedObjectsService.getScopedClient(request, { includedHiddenTypes: [RULES_SETTINGS_SAVED_OBJECT_TYPE], ...(withAuth ? {} : { excludedExtensions: [SECURITY_EXTENSION_ID] }), @@ -51,11 +51,8 @@ export class RulesSettingsClientFactory { logger: this.logger, savedObjectsClient, async getUserName() { - if (!securityPluginStart || !request) { - return null; - } - const user = securityPluginStart.authc.getCurrentUser(request); - return user ? user.username : null; + const user = securityService.authc.getCurrentUser(request); + return user?.username ?? null; }, isServerless: this.isServerless, }); From 17de403a69dab6868c79bd025ba9df55f1db7bb8 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Mon, 1 Jul 2024 17:30:05 -0700 Subject: [PATCH 044/128] [Cloud Defend Plugin] Migrate authc.getCurrentUser usage to core.security (#187023) Part of https://github.com/elastic/kibana/issues/186574 ## Summary This PR migrates the Cloud Defend Plugin's route handler that consumes `authc.getCurrentUser` to use `coreContext.security`. Background: This PR serves as an example of a plugin migrating away from depending on the Security plugin, which is a high priority effort for the last release before 9.0. ### Checklist Delete any items that are not applicable to this PR. - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/cloud_defend/server/routes/setup_routes.ts | 9 ++++----- x-pack/plugins/cloud_defend/server/types.ts | 3 ++- x-pack/plugins/cloud_defend/tsconfig.json | 3 ++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/cloud_defend/server/routes/setup_routes.ts b/x-pack/plugins/cloud_defend/server/routes/setup_routes.ts index 1a9ef57ba83c7..6e5106c2df5c2 100644 --- a/x-pack/plugins/cloud_defend/server/routes/setup_routes.ts +++ b/x-pack/plugins/cloud_defend/server/routes/setup_routes.ts @@ -5,8 +5,7 @@ * 2.0. */ -import type { CoreSetup, Logger } from '@kbn/core/server'; -import type { AuthenticatedUser } from '@kbn/security-plugin/common'; +import type { AuthenticatedUser, CoreSetup, Logger } from '@kbn/core/server'; import type { CloudDefendRequestHandlerContext, CloudDefendPluginStart, @@ -33,8 +32,8 @@ export function setupRoutes({ core.http.registerRouteHandlerContext( PLUGIN_ID, - async (context, request) => { - const [, { security, fleet }] = await core.getStartServices(); + async (context, _request) => { + const [_, { fleet }] = await core.getStartServices(); const coreContext = await context.core; await fleet.fleetSetupCompleted(); @@ -44,7 +43,7 @@ export function setupRoutes({ get user() { // We want to call getCurrentUser only when needed and only once if (!user) { - user = security.authc.getCurrentUser(request); + user = coreContext.security.authc.getCurrentUser(); } return user; }, diff --git a/x-pack/plugins/cloud_defend/server/types.ts b/x-pack/plugins/cloud_defend/server/types.ts index e6a5d9454ba81..6b341c552f532 100644 --- a/x-pack/plugins/cloud_defend/server/types.ts +++ b/x-pack/plugins/cloud_defend/server/types.ts @@ -5,6 +5,7 @@ * 2.0. */ import type { CloudSetup } from '@kbn/cloud-plugin/server'; +import type { AuthenticatedUser } from '@kbn/core-security-common'; import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; import type { IRouter, @@ -48,7 +49,7 @@ export interface CloudDefendPluginStartDeps { } export interface CloudDefendApiRequestHandlerContext { - user: ReturnType; + user: AuthenticatedUser | null; logger: Logger; esClient: IScopedClusterClient; soClient: SavedObjectsClientContract; diff --git a/x-pack/plugins/cloud_defend/tsconfig.json b/x-pack/plugins/cloud_defend/tsconfig.json index f63d751ec2a44..2e71cfde9128d 100755 --- a/x-pack/plugins/cloud_defend/tsconfig.json +++ b/x-pack/plugins/cloud_defend/tsconfig.json @@ -37,7 +37,8 @@ "@kbn/core-http-router-server-mocks", "@kbn/core-elasticsearch-server", "@kbn/code-editor", - "@kbn/code-editor-mock" + "@kbn/code-editor-mock", + "@kbn/core-security-common" ], "exclude": ["target/**/*"] } From 19463b243e0b1393e88b11c992b9a1e62c575e5c Mon Sep 17 00:00:00 2001 From: "Joey F. Poon" Date: Mon, 1 Jul 2024 18:44:59 -0700 Subject: [PATCH 045/128] [Security Solution] make endpoint heartbeat cypress indexing more explicit (#187071) ## Summary Adds a new `unbilledCount` param to the `indexEndpointHeartbeats` cypress heartbeart indexing task to make the indexed `billable: false` docs functionality more explicit rather than a side effect as [suggested in the last PR](https://github.com/elastic/kibana/pull/186580#discussion_r1654551530). ### Checklist - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../data_loaders/index_endpoint_hearbeats.ts | 20 ++++++++++--------- .../cypress/e2e/serverless/metering.cy.ts | 2 ++ .../cypress/support/data_loaders.ts | 5 +++-- .../tasks/index_endpoint_heartbeats.ts | 1 + 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hearbeats.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hearbeats.ts index 10a3af979a125..ff6381cb9d3df 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hearbeats.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hearbeats.ts @@ -34,7 +34,8 @@ interface EndpointHeartbeat { export const indexEndpointHeartbeats = async ( esClient: Client, log: ToolingLog, - count: number + count: number = 1, + unbilledCount: number = 1 ): Promise => { log.debug(`Indexing ${count} endpoint heartbeats`); const startTime = new Date(); @@ -59,22 +60,23 @@ export const indexEndpointHeartbeats = async ( return heartbeatDoc; }); - // billable: false are not billed - const invalidDocs: EndpointHeartbeat[] = [ - { + const unbilledDocs: EndpointHeartbeat[] = Array.from({ length: unbilledCount }).map((_, i) => { + const ingested = new Date(startTime.getTime() + i).toISOString(); + + return { '@timestamp': '2024-06-11T13:03:37Z', agent: { - id: 'agent-billable-false', + id: `agent-billable-false-${i}`, }, event: { agent_id_status: 'auth_metadata_missing', - ingested: new Date().toISOString(), + ingested, }, billable: false, - }, - ]; + }; + }); - const operations = docs.concat(invalidDocs).flatMap((doc) => [ + const operations = docs.concat(unbilledDocs).flatMap((doc) => [ { index: { _index: ENDPOINT_HEARTBEAT_INDEX, diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/metering.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/metering.cy.ts index 7a0533e85c1e4..d55cbe57d506d 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/metering.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/metering.cy.ts @@ -32,6 +32,7 @@ describe( }, () => { const HEARTBEAT_COUNT = 2001; + const UNBILLED_COUNT = 5; let endpointData: ReturnTypeFromChainable | undefined; @@ -40,6 +41,7 @@ describe( startTransparentApiProxy({ port: 3623 }); indexEndpointHeartbeats({ count: HEARTBEAT_COUNT, + unbilledCount: UNBILLED_COUNT, }).then((indexedHeartbeats) => { endpointData = indexedHeartbeats; }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts b/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts index 2ce2f8140184c..2ed45297fc531 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts @@ -231,9 +231,10 @@ export const dataLoaders = ( return deleteIndexedHostsAndAlerts(esClient, kbnClient, indexedData); }, - indexEndpointHeartbeats: async (options: { count?: number }) => { + indexEndpointHeartbeats: async (options: { count?: number; unbilledCount?: number }) => { const { esClient, log } = await setupStackServicesUsingCypressConfig(config); - return (await indexEndpointHeartbeats(esClient, log, options.count || 1)).data; + return (await indexEndpointHeartbeats(esClient, log, options.count, options.unbilledCount)) + .data; }, deleteIndexedEndpointHeartbeats: async ( diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/index_endpoint_heartbeats.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/index_endpoint_heartbeats.ts index 090b664a12227..985170c78ae2b 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/index_endpoint_heartbeats.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/index_endpoint_heartbeats.ts @@ -12,6 +12,7 @@ import type { export const indexEndpointHeartbeats = (options: { count?: number; + unbilledCount?: number; }): Cypress.Chainable< Pick & { cleanup: () => Cypress.Chainable; From 7322123df5fd136f3e4168b28fdc1d888e0d483d Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 2 Jul 2024 03:50:37 +0200 Subject: [PATCH 046/128] [ES|QL] Update function metadata (#187259) This PR updates the function definitions and inline docs based on the latest metadata from Elasticsearch. --------- Co-authored-by: Drew Tate --- packages/kbn-esql-ast/src/ast_walker.ts | 1 + packages/kbn-esql-ast/src/types.ts | 2 +- .../scripts/generate_function_definitions.ts | 6 +- .../src/definitions/functions.ts | 82 ++++++++++++++-- .../esql_validation_meta_tests.json | 98 +++++++++++++++++++ .../src/validation/validation.test.ts | 81 +++++++++++++++ .../src/esql_documentation_sections.tsx | 70 +++++++++++++ 7 files changed, 330 insertions(+), 10 deletions(-) diff --git a/packages/kbn-esql-ast/src/ast_walker.ts b/packages/kbn-esql-ast/src/ast_walker.ts index e93c411541feb..870301b938a65 100644 --- a/packages/kbn-esql-ast/src/ast_walker.ts +++ b/packages/kbn-esql-ast/src/ast_walker.ts @@ -356,6 +356,7 @@ function getConstant(ctx: ConstantContext): ESQLAstItem { paramType: 'unnamed', text: ctx.getText(), name: '', + value: '', location: getPosition(ctx.start, ctx.stop), incomplete: Boolean(ctx.exception), }; diff --git a/packages/kbn-esql-ast/src/types.ts b/packages/kbn-esql-ast/src/types.ts index 5c6b1d2fdbb8f..e61935f0b8a86 100644 --- a/packages/kbn-esql-ast/src/types.ts +++ b/packages/kbn-esql-ast/src/types.ts @@ -147,7 +147,7 @@ export interface ESQLParamLiteral extends ESQ type: 'literal'; literalType: 'param'; paramType: ParamType; - value?: string | number; + value: string | number; } /** diff --git a/packages/kbn-esql-validation-autocomplete/scripts/generate_function_definitions.ts b/packages/kbn-esql-validation-autocomplete/scripts/generate_function_definitions.ts index 6806b11e49807..d50d7cc903fce 100644 --- a/packages/kbn-esql-validation-autocomplete/scripts/generate_function_definitions.ts +++ b/packages/kbn-esql-validation-autocomplete/scripts/generate_function_definitions.ts @@ -347,7 +347,11 @@ import type { FunctionDefinition } from './types'; const evalFunctionDefinitions: FunctionDefinition[] = []; for (const ESDefinition of ESFunctionDefinitions) { - if (aliases.has(ESDefinition.name) || excludedFunctions.has(ESDefinition.name)) { + if ( + aliases.has(ESDefinition.name) || + excludedFunctions.has(ESDefinition.name) || + ESDefinition.type !== 'eval' + ) { continue; } diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/functions.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/functions.ts index 718174ae09f41..ceb5789e0dd39 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/functions.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/functions.ts @@ -6,8 +6,24 @@ * Side Public License, v 1. */ -// NOTE: This file is generated by the generate_function_definitions.ts script -// Do not edit it manually +/** + * __AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.__ + * + * @note This file is generated by the `generate_function_definitions.ts` + * script. Do not edit it manually. + * + * + * + * + * + * + * + * + * + * + * + * + */ import type { ESQLFunction } from '@kbn/esql-ast'; import { i18n } from '@kbn/i18n'; @@ -447,7 +463,7 @@ const coalesceDefinition: FunctionDefinition = { minParams: 1, }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=null, b="b"\n| EVAL COALESCE(a, b)'], @@ -1052,7 +1068,7 @@ const ipPrefixDefinition: FunctionDefinition = { returnType: 'ip', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -1345,7 +1361,7 @@ const logDefinition: FunctionDefinition = { // do not really care here about the base and field // just need to check both values are not negative for (const arg of fnDef.args) { - if (isLiteralItem(arg) && Number(arg.value) < 0) { + if (isLiteralItem(arg) && arg.value < 0) { messages.push({ type: 'warning' as const, code: 'logOfNegativeValue', @@ -1398,7 +1414,7 @@ const log10Definition: FunctionDefinition = { // do not really care here about the base and field // just need to check both values are not negative for (const arg of fnDef.args) { - if (isLiteralItem(arg) && Number(arg.value) < 0) { + if (isLiteralItem(arg) && arg.value < 0) { messages.push({ type: 'warning' as const, code: 'logOfNegativeValue', @@ -1608,7 +1624,7 @@ const mvAppendDefinition: FunctionDefinition = { returnType: 'version', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [], @@ -2846,7 +2862,7 @@ const repeatDefinition: FunctionDefinition = { returnType: 'string', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a = "Hello!"\n| EVAL triple_a = REPEAT(a, 3);'], @@ -3410,6 +3426,55 @@ const stDisjointDefinition: FunctionDefinition = { ], }; +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const stDistanceDefinition: FunctionDefinition = { + type: 'eval', + name: 'st_distance', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.st_distance', { + defaultMessage: + 'Computes the distance between two points.\nFor cartesian geometries, this is the pythagorean distance in the same units as the original coordinates.\nFor geographic geometries, this is the circular distance along the great circle in meters.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'geomA', + type: 'cartesian_point', + optional: false, + }, + { + name: 'geomB', + type: 'cartesian_point', + optional: false, + }, + ], + returnType: 'number', + }, + { + params: [ + { + name: 'geomA', + type: 'geo_point', + optional: false, + }, + { + name: 'geomB', + type: 'geo_point', + optional: false, + }, + ], + returnType: 'number', + }, + ], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: [ + 'FROM airports\n| WHERE abbrev == "CPH"\n| EVAL distance = ST_DISTANCE(location, city_location)\n| KEEP abbrev, name, location, city_location, distance', + ], +}; + // Do not edit this manually... generated by scripts/generate_function_definitions.ts const stIntersectsDefinition: FunctionDefinition = { type: 'eval', @@ -4865,6 +4930,7 @@ export const evalFunctionDefinitions = [ sqrtDefinition, stContainsDefinition, stDisjointDefinition, + stDistanceDefinition, stIntersectsDefinition, stWithinDefinition, stXDefinition, diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json index 0c84fe2bf113d..86f0848c3a94f 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json +++ b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json @@ -25901,6 +25901,104 @@ "error": [], "warning": [] }, + { + "query": "row var = st_distance(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_distance(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_distance(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_distance(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_distance(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_distance(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_distance(true, true)", + "error": [ + "Argument of [st_distance] must be [cartesian_point], found value [true] type [boolean]", + "Argument of [st_distance] must be [cartesian_point], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = st_distance(cartesianPointField, cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_distance(cartesianPointField, cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_distance(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_distance(booleanField, booleanField)", + "error": [ + "Argument of [st_distance] must be [cartesian_point], found value [booleanField] type [boolean]", + "Argument of [st_distance] must be [cartesian_point], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = st_distance(geoPointField, geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_distance(geoPointField, geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_distance(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_distance(cartesianPointField, cartesianPointField, extraArg)", + "error": [ + "Error: [st_distance] function expects exactly 2 arguments, got 3." + ], + "warning": [] + }, + { + "query": "from a_index | sort st_distance(cartesianPointField, cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_distance(null, null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval st_distance(nullVar, nullVar)", + "error": [], + "warning": [] + }, { "query": "f", "error": [ diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts index f866d82767890..1d99efd12e9ab 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts @@ -10156,6 +10156,87 @@ describe('validation logic', () => { testErrorsAndWarnings('from a_index | eval repeat(null, null)', []); testErrorsAndWarnings('row nullVar = null | eval repeat(nullVar, nullVar)', []); }); + + describe('st_distance', () => { + testErrorsAndWarnings( + 'row var = st_distance(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_distance(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_distance(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_distance(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_distance(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_distance(to_geopoint(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings('row var = st_distance(true, true)', [ + 'Argument of [st_distance] must be [cartesian_point], found value [true] type [boolean]', + 'Argument of [st_distance] must be [cartesian_point], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings( + 'from a_index | eval var = st_distance(cartesianPointField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_distance(cartesianPointField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_distance(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval st_distance(booleanField, booleanField)', [ + 'Argument of [st_distance] must be [cartesian_point], found value [booleanField] type [boolean]', + 'Argument of [st_distance] must be [cartesian_point], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings( + 'from a_index | eval var = st_distance(geoPointField, geoPointField)', + [] + ); + testErrorsAndWarnings('from a_index | eval st_distance(geoPointField, geoPointField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = st_distance(to_geopoint(geoPointField), to_geopoint(geoPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_distance(cartesianPointField, cartesianPointField, extraArg)', + ['Error: [st_distance] function expects exactly 2 arguments, got 3.'] + ); + + testErrorsAndWarnings( + 'from a_index | sort st_distance(cartesianPointField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings('from a_index | eval st_distance(null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval st_distance(nullVar, nullVar)', []); + }); }); }); diff --git a/packages/kbn-text-based-editor/src/esql_documentation_sections.tsx b/packages/kbn-text-based-editor/src/esql_documentation_sections.tsx index d1c3788c39a89..080151acbb829 100644 --- a/packages/kbn-text-based-editor/src/esql_documentation_sections.tsx +++ b/packages/kbn-text-based-editor/src/esql_documentation_sections.tsx @@ -2786,6 +2786,43 @@ export const functions = { | WHERE ST_DISJOINT(city_boundary, TO_GEOSHAPE("POLYGON((-10 -60, 120 -60, 120 60, -10 60, -10 -60))")) | KEEP abbrev, airport, region, city, city_location \`\`\` + `, + description: + 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', + ignoreTag: true, + } + )} + /> + ), + }, + // Do not edit manually... automatically generated by scripts/generate_esql_docs.ts + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.st_distance', + { + defaultMessage: 'ST_DISTANCE', + } + ), + description: ( + + + ### ST_DISTANCE + Computes the distance between two points. + For cartesian geometries, this is the pythagorean distance in the same units as the original coordinates. + For geographic geometries, this is the circular distance along the great circle in meters. + + \`\`\` + FROM airports + | WHERE abbrev == "CPH" + | EVAL distance = ST_DISTANCE(location, city_location) + | KEEP abbrev, name, location, city_location, distance + \`\`\` `, description: 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', @@ -3706,6 +3743,39 @@ export const functions = { \`\`\` ROW v = TO_VERSION("1.2.3") \`\`\` + `, + description: + 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', + ignoreTag: true, + } + )} + /> + ), + }, + // Do not edit manually... automatically generated by scripts/generate_esql_docs.ts + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.top_list', + { + defaultMessage: 'TOP_LIST', + } + ), + description: ( + + + ### TOP_LIST + Collects the top values for a field. Includes repeated values. + + \`\`\` + FROM employees + | STATS top_salaries = TOP_LIST(salary, 3, "desc"), top_salary = MAX(salary) + \`\`\` `, description: 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', From d00f36efc2e1c8ebce786cac9d16b82b60543c65 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 20:55:33 -0500 Subject: [PATCH 047/128] Update dependency @elastic/charts to v66.0.5 (main) (#187239) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [@elastic/charts](https://togithub.com/elastic/elastic-charts) | [`66.0.4` -> `66.0.5`](https://renovatebot.com/diffs/npm/@elastic%2fcharts/66.0.4/66.0.5) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@elastic%2fcharts/66.0.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@elastic%2fcharts/66.0.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@elastic%2fcharts/66.0.4/66.0.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@elastic%2fcharts/66.0.4/66.0.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
      elastic/elastic-charts (@​elastic/charts) ### [`v66.0.5`](https://togithub.com/elastic/elastic-charts/blob/HEAD/CHANGELOG.md#6605-2024-07-01) [Compare Source](https://togithub.com/elastic/elastic-charts/compare/v66.0.4...v66.0.5) ##### Bug Fixes - (Legend values) percentage formatter should be distict from tick formatter ([#​2474](https://togithub.com/elastic/elastic-charts/issues/2474)) ([b5bd998](https://togithub.com/elastic/elastic-charts/commit/b5bd998f23ffa4a9032442728dae8fbcf49604eb))
      --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/elastic/kibana). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index dfbfcd6bec3a3..e4e89ddceb856 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "@elastic/apm-rum": "^5.16.1", "@elastic/apm-rum-core": "^5.21.0", "@elastic/apm-rum-react": "^2.0.3", - "@elastic/charts": "66.0.4", + "@elastic/charts": "66.0.5", "@elastic/datemath": "5.0.3", "@elastic/ecs": "^8.11.1", "@elastic/elasticsearch": "^8.14.0", diff --git a/yarn.lock b/yarn.lock index 9de08dc5d49d5..17882dae4ca64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1658,10 +1658,10 @@ dependencies: object-hash "^1.3.0" -"@elastic/charts@66.0.4": - version "66.0.4" - resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-66.0.4.tgz#aabb145687805ca7f491df22c15d7d535ca80031" - integrity sha512-fxOivMRUtcTrSOKTqxxDN5XVCzaW0lVrydCxGGHg3NoYEBtOktnF8SWATO8EYfg5cSBDcVbLGOnscqM84Q2aDA== +"@elastic/charts@66.0.5": + version "66.0.5" + resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-66.0.5.tgz#0913cd763fc4a4a6570fa617dbb513eba069c45c" + integrity sha512-a5TPbt7qWl0zFXT5jUqtHkRalc6OUppEKm+oZDKZNdwwYOVn4zb/uOvTQoBRYDHb7lqX/1Es1wVwsiIagifvEA== dependencies: "@popperjs/core" "^2.11.8" bezier-easing "^2.1.0" From d5a91fcc5d435c35a08f36112d8208b2753bae41 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Mon, 1 Jul 2024 20:17:07 -0700 Subject: [PATCH 048/128] [Security AI Assistant] Chat complete API (#184485) --- .../kbn-elastic-assistant-common/constants.ts | 4 +- .../chat/post_chat_complete_route.gen.ts | 69 +++ .../chat/post_chat_complete_route.schema.yaml | 109 ++++ .../impl/schemas/index.ts | 3 + .../language_models/chat_openai.test.ts | 41 +- .../server/language_models/chat_openai.ts | 22 +- .../server/language_models/llm.test.ts | 75 +-- .../server/language_models/llm.ts | 22 +- .../language_models/simple_chat_model.test.ts | 78 ++- .../language_models/simple_chat_model.ts | 20 +- x-pack/packages/kbn-langchain/tsconfig.json | 2 +- .../conversations/index.ts | 11 +- .../server/lib/executor.test.ts | 127 ++--- .../elastic_assistant/server/lib/executor.ts | 12 +- .../execute_custom_llm_chain/index.test.ts | 19 +- .../execute_custom_llm_chain/index.ts | 19 +- .../executors/openai_functions_executor.ts | 5 +- .../server/lib/langchain/executors/types.ts | 8 +- .../graphs/default_assistant_graph/graph.ts | 88 ++- .../graphs/default_assistant_graph/helpers.ts | 43 +- .../graphs/default_assistant_graph/index.ts | 30 +- .../nodes/generate_chat_title.ts | 27 +- .../nodes/get_persisted_conversation.ts | 57 ++ .../nodes/persist_conversation_changes.ts | 78 +++ .../nodes/run_agent.ts | 6 +- .../nodes/should_continue.ts | 29 + .../graphs/default_assistant_graph/types.ts | 3 + .../routes/attack_discovery/helpers.test.ts | 14 +- .../server/routes/attack_discovery/helpers.ts | 10 +- .../post_attack_discovery.test.ts | 2 + .../attack_discovery/post_attack_discovery.ts | 3 +- .../routes/chat/chat_complete_route.test.ts | 456 +++++++++++++++ .../server/routes/chat/chat_complete_route.ts | 249 ++++++++ .../server/routes/evaluate/post_evaluate.ts | 5 +- .../server/routes/helpers.ts | 536 +++++++++++++++++- .../post_actions_connector_execute.test.ts | 80 +-- .../routes/post_actions_connector_execute.ts | 375 +++--------- .../server/routes/register_routes.ts | 8 +- .../routes/user_conversations/create_route.ts | 31 +- .../routes/user_conversations/update_route.ts | 28 +- .../server/routes/categorization_routes.ts | 3 +- .../server/routes/ecs_routes.ts | 3 +- .../server/routes/related_routes.ts | 3 +- .../server/lib/get_chat_params.test.ts | 4 +- .../server/lib/get_chat_params.ts | 6 +- 45 files changed, 2090 insertions(+), 733 deletions(-) create mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts create mode 100644 x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.schema.yaml create mode 100644 x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/get_persisted_conversation.ts create mode 100644 x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/persist_conversation_changes.ts create mode 100644 x-pack/plugins/elastic_assistant/server/routes/chat/chat_complete_route.test.ts create mode 100644 x-pack/plugins/elastic_assistant/server/routes/chat/chat_complete_route.ts diff --git a/x-pack/packages/kbn-elastic-assistant-common/constants.ts b/x-pack/packages/kbn-elastic-assistant-common/constants.ts index 74da6ab2476e2..96af59095ab87 100755 --- a/x-pack/packages/kbn-elastic-assistant-common/constants.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/constants.ts @@ -7,13 +7,15 @@ export const ELASTIC_AI_ASSISTANT_INTERNAL_API_VERSION = '1'; -export const ELASTIC_AI_ASSISTANT_URL = '/api/elastic_assistant'; +export const ELASTIC_AI_ASSISTANT_URL = '/api/security_ai_assistant'; export const ELASTIC_AI_ASSISTANT_INTERNAL_URL = '/internal/elastic_assistant'; export const ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL = `${ELASTIC_AI_ASSISTANT_INTERNAL_URL}/current_user/conversations`; export const ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_BY_ID = `${ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL}/{id}`; export const ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_BY_ID_MESSAGES = `${ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_BY_ID}/messages`; +export const ELASTIC_AI_ASSISTANT_CHAT_COMPLETE_URL = `${ELASTIC_AI_ASSISTANT_URL}/chat/complete`; + export const ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_BULK_ACTION = `${ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL}/_bulk_action`; export const ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL_FIND = `${ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL}/_find`; diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts new file mode 100644 index 0000000000000..0b6c3bbe6cbb3 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Chat Complete API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +export type RootContext = z.infer; +export const RootContext = z.literal('security'); + +/** + * Message role. + */ +export type ChatMessageRole = z.infer; +export const ChatMessageRole = z.enum(['system', 'user', 'assistant']); +export type ChatMessageRoleEnum = typeof ChatMessageRole.enum; +export const ChatMessageRoleEnum = ChatMessageRole.enum; + +export type MessageData = z.infer; +export const MessageData = z.object({}).catchall(z.unknown()); + +/** + * AI assistant message. + */ +export type ChatMessage = z.infer; +export const ChatMessage = z.object({ + /** + * Message content. + */ + content: z.string().optional(), + /** + * Message role. + */ + role: ChatMessageRole, + /** + * ECS object to attach to the context of the message. + */ + data: MessageData.optional(), + fields_to_anonymize: z.array(z.string()).optional(), +}); + +export type ChatCompleteProps = z.infer; +export const ChatCompleteProps = z.object({ + conversationId: z.string().optional(), + promptId: z.string().optional(), + isStream: z.boolean().optional(), + responseLanguage: z.string().optional(), + langSmithProject: z.string().optional(), + langSmithApiKey: z.string().optional(), + connectorId: z.string(), + model: z.string().optional(), + persist: z.boolean(), + messages: z.array(ChatMessage), +}); + +export type ChatCompleteRequestBody = z.infer; +export const ChatCompleteRequestBody = ChatCompleteProps; +export type ChatCompleteRequestBodyInput = z.input; diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.schema.yaml b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.schema.yaml new file mode 100644 index 0000000000000..21c348251b039 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.schema.yaml @@ -0,0 +1,109 @@ +openapi: 3.0.0 +info: + title: Chat Complete API endpoint + version: '2023-10-31' +paths: + /api/elastic_assistant/chat/complete: + post: + operationId: ChatComplete + x-codegen-enabled: true + description: Creates a model response for the given chat conversation. + summary: Creates a model response for the given chat conversation. + tags: + - Chat Complete API + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ChatCompleteProps' + responses: + 200: + description: Indicates a successful call. + content: + application/octet-stream: + schema: + type: string + format: binary + 400: + description: Generic Error + content: + application/json: + schema: + type: object + properties: + statusCode: + type: number + error: + type: string + message: + type: string + +components: + schemas: + RootContext: + type: string + enum: + - security + + ChatMessageRole: + type: string + description: Message role. + enum: + - system + - user + - assistant + + MessageData: + type: object + additionalProperties: true + + ChatMessage: + type: object + description: AI assistant message. + required: + - 'role' + properties: + content: + type: string + description: Message content. + role: + $ref: '#/components/schemas/ChatMessageRole' + description: Message role. + data: + description: ECS object to attach to the context of the message. + $ref: '#/components/schemas/MessageData' + fields_to_anonymize: + type: array + items: + type: string + + ChatCompleteProps: + type: object + properties: + conversationId: + type: string + promptId: + type: string + isStream: + type: boolean + responseLanguage: + type: string + langSmithProject: + type: string + langSmithApiKey: + type: string + connectorId: + type: string + model: + type: string + persist: + type: boolean + messages: + type: array + items: + $ref: '#/components/schemas/ChatMessage' + required: + - messages + - persist + - connectorId diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/index.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/index.ts index 8f47731694cf3..eb5d0738f378b 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/index.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/index.ts @@ -27,6 +27,9 @@ export * from './attack_discovery/get_attack_discovery_route.gen'; export * from './attack_discovery/post_attack_discovery_route.gen'; export * from './attack_discovery/cancel_attack_discovery_route.gen'; +// Chat Schemas +export * from './chat/post_chat_complete_route.gen'; + // Evaluation Schemas export * from './evaluation/post_evaluate_route.gen'; export * from './evaluation/get_evaluate_route.gen'; diff --git a/x-pack/packages/kbn-langchain/server/language_models/chat_openai.test.ts b/x-pack/packages/kbn-langchain/server/language_models/chat_openai.test.ts index 5c6d389a4ccc4..7d01468b4d6f7 100644 --- a/x-pack/packages/kbn-langchain/server/language_models/chat_openai.test.ts +++ b/x-pack/packages/kbn-langchain/server/language_models/chat_openai.test.ts @@ -7,10 +7,10 @@ import type OpenAI from 'openai'; import { Stream } from 'openai/streaming'; -import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; import { loggerMock } from '@kbn/logging-mocks'; +import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client/actions_client.mock'; -import { ActionsClientChatOpenAI, ActionsClientChatOpenAIParams } from './chat_openai'; +import { ActionsClientChatOpenAI } from './chat_openai'; import { mockActionResponse, mockChatCompletion } from './mocks'; const connectorId = 'mock-connector-id'; @@ -19,11 +19,8 @@ const mockExecute = jest.fn(); const mockLogger = loggerMock.create(); -const mockActions = { - getActionsClientWithRequest: jest.fn().mockImplementation(() => ({ - execute: mockExecute, - })), -} as unknown as ActionsPluginStart; +const actionsClient = actionsClientMock.create(); + const chunk = { object: 'chat.completion.chunk', choices: [ @@ -40,30 +37,15 @@ export async function* asyncGenerator() { yield chunk; } const mockStreamExecute = jest.fn(); -const mockStreamActions = { - getActionsClientWithRequest: jest.fn().mockImplementation(() => ({ - execute: mockStreamExecute, - })), -} as unknown as ActionsPluginStart; const prompt = 'Do you know my name?'; const { signal } = new AbortController(); -const mockRequest = { - params: { connectorId }, - body: { - message: prompt, - subAction: 'invokeAI', - isEnabledKnowledgeBase: true, - }, -} as ActionsClientChatOpenAIParams['request']; - const defaultArgs = { - actions: mockActions, + actionsClient, connectorId, logger: mockLogger, - request: mockRequest, streaming: false, signal, timeout: 999999, @@ -77,6 +59,7 @@ describe('ActionsClientChatOpenAI', () => { data: mockChatCompletion, status: 'ok', })); + actionsClient.execute.mockImplementation(mockExecute); }); describe('_llmType', () => { @@ -116,10 +99,11 @@ describe('ActionsClientChatOpenAI', () => { functions: [jest.fn()], }; it('returns the expected data', async () => { + actionsClient.execute.mockImplementation(mockStreamExecute); const actionsClientChatOpenAI = new ActionsClientChatOpenAI({ ...defaultArgs, streaming: true, - actions: mockStreamActions, + actionsClient, }); const result: AsyncIterable = @@ -178,16 +162,11 @@ describe('ActionsClientChatOpenAI', () => { serviceMessage: 'action-result-service-message', status: 'error', // <-- error status })); - - const badActions = { - getActionsClientWithRequest: jest.fn().mockImplementation(() => ({ - execute: hasErrorStatus, - })), - } as unknown as ActionsPluginStart; + actionsClient.execute.mockRejectedValueOnce(hasErrorStatus); const actionsClientChatOpenAI = new ActionsClientChatOpenAI({ ...defaultArgs, - actions: badActions, + actionsClient, }); expect(actionsClientChatOpenAI.completionWithRetry(defaultNonStreamingArgs)) diff --git a/x-pack/packages/kbn-langchain/server/language_models/chat_openai.ts b/x-pack/packages/kbn-langchain/server/language_models/chat_openai.ts index c2dada0dafa3b..391609db21565 100644 --- a/x-pack/packages/kbn-langchain/server/language_models/chat_openai.ts +++ b/x-pack/packages/kbn-langchain/server/language_models/chat_openai.ts @@ -6,24 +6,24 @@ */ import { v4 as uuidv4 } from 'uuid'; -import { KibanaRequest, Logger } from '@kbn/core/server'; -import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; +import { Logger } from '@kbn/core/server'; +import type { ActionsClient } from '@kbn/actions-plugin/server'; import { get } from 'lodash/fp'; import { ChatOpenAI } from '@langchain/openai'; import { Stream } from 'openai/streaming'; import type OpenAI from 'openai'; +import { PublicMethodsOf } from '@kbn/utility-types'; import { DEFAULT_OPEN_AI_MODEL, DEFAULT_TIMEOUT } from './constants'; import { InvokeAIActionParamsSchema, RunActionParamsSchema } from './types'; const LLM_TYPE = 'ActionsClientChatOpenAI'; export interface ActionsClientChatOpenAIParams { - actions: ActionsPluginStart; + actionsClient: PublicMethodsOf; connectorId: string; llmType?: string; logger: Logger; - request: KibanaRequest; streaming?: boolean; traceId?: string; maxRetries?: number; @@ -54,22 +54,20 @@ export class ActionsClientChatOpenAI extends ChatOpenAI { #temperature?: number; // Kibana variables - #actions: ActionsPluginStart; + #actionsClient: PublicMethodsOf; #connectorId: string; #logger: Logger; - #request: KibanaRequest; #actionResultData: string; #traceId: string; #signal?: AbortSignal; #timeout?: number; constructor({ - actions, + actionsClient, connectorId, traceId = uuidv4(), llmType, logger, - request, maxRetries, model, signal, @@ -92,12 +90,11 @@ export class ActionsClientChatOpenAI extends ChatOpenAI { azureOpenAIApiVersion: 'nothing', openAIApiKey: '', }); - this.#actions = actions; + this.#actionsClient = actionsClient; this.#connectorId = connectorId; this.#traceId = traceId; this.llmType = llmType ?? LLM_TYPE; this.#logger = logger; - this.#request = request; this.#timeout = timeout; this.#actionResultData = ''; this.streaming = streaming; @@ -146,10 +143,7 @@ export class ActionsClientChatOpenAI extends ChatOpenAI { )} ` ); - // create an actions client from the authenticated request context: - const actionsClient = await this.#actions.getActionsClientWithRequest(this.#request); - - const actionResult = await actionsClient.execute(requestBody); + const actionResult = await this.#actionsClient.execute(requestBody); if (actionResult.status === 'error') { throw new Error(`${LLM_TYPE}: ${actionResult?.message} - ${actionResult?.serviceMessage}`); diff --git a/x-pack/packages/kbn-langchain/server/language_models/llm.test.ts b/x-pack/packages/kbn-langchain/server/language_models/llm.test.ts index e0f7b764b625a..aa33bbf7a6d44 100644 --- a/x-pack/packages/kbn-langchain/server/language_models/llm.test.ts +++ b/x-pack/packages/kbn-langchain/server/language_models/llm.test.ts @@ -5,39 +5,27 @@ * 2.0. */ -import { KibanaRequest } from '@kbn/core/server'; -import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; import { loggerMock } from '@kbn/logging-mocks'; +import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client/actions_client.mock'; import { ActionsClientLlm } from './llm'; import { mockActionResponse } from './mocks'; const connectorId = 'mock-connector-id'; -const mockExecute = jest.fn().mockImplementation(() => ({ - data: mockActionResponse, - status: 'ok', -})); +const actionsClient = actionsClientMock.create(); -const mockLogger = loggerMock.create(); +actionsClient.execute.mockImplementation( + jest.fn().mockImplementation(() => ({ + data: mockActionResponse, + status: 'ok', + })) +); -const mockActions = { - getActionsClientWithRequest: jest.fn().mockImplementation(() => ({ - execute: mockExecute, - })), -} as unknown as ActionsPluginStart; +const mockLogger = loggerMock.create(); const prompt = 'Do you know my name?'; -const mockRequest: KibanaRequest = { - params: { connectorId }, - body: { - message: prompt, - subAction: 'invokeAI', - isEnabledKnowledgeBase: true, - }, -} as KibanaRequest; - describe('ActionsClientLlm', () => { beforeEach(() => { jest.clearAllMocks(); @@ -46,10 +34,9 @@ describe('ActionsClientLlm', () => { describe('getActionResultData', () => { it('returns the expected data', async () => { const actionsClientLlm = new ActionsClientLlm({ - actions: mockActions, + actionsClient, connectorId, logger: mockLogger, - request: mockRequest, }); const result = await actionsClientLlm._call(prompt); // ignore the result @@ -61,10 +48,9 @@ describe('ActionsClientLlm', () => { describe('_llmType', () => { it('returns the expected LLM type', () => { const actionsClientLlm = new ActionsClientLlm({ - actions: mockActions, + actionsClient, connectorId, logger: mockLogger, - request: mockRequest, }); expect(actionsClientLlm._llmType()).toEqual('ActionsClientLlm'); @@ -72,11 +58,10 @@ describe('ActionsClientLlm', () => { it('returns the expected LLM type when overridden', () => { const actionsClientLlm = new ActionsClientLlm({ - actions: mockActions, + actionsClient, connectorId, llmType: 'special-llm-type', logger: mockLogger, - request: mockRequest, }); expect(actionsClientLlm._llmType()).toEqual('special-llm-type'); @@ -86,10 +71,9 @@ describe('ActionsClientLlm', () => { describe('_call', () => { it('returns the expected content when _call is invoked', async () => { const actionsClientLlm = new ActionsClientLlm({ - actions: mockActions, + actionsClient, connectorId, logger: mockLogger, - request: mockRequest, }); const result = await actionsClientLlm._call(prompt); @@ -98,23 +82,15 @@ describe('ActionsClientLlm', () => { }); it('rejects with the expected error when the action result status is error', async () => { - const hasErrorStatus = jest.fn().mockImplementation(() => ({ - message: 'action-result-message', - serviceMessage: 'action-result-service-message', - status: 'error', // <-- error status - })); - - const badActions = { - getActionsClientWithRequest: jest.fn().mockImplementation(() => ({ - execute: hasErrorStatus, - })), - } as unknown as ActionsPluginStart; - + actionsClient.execute.mockImplementation(() => { + throw new Error( + 'ActionsClientLlm: action result status is error: action-result-message - action-result-service-message' + ); + }); const actionsClientLlm = new ActionsClientLlm({ - actions: badActions, + actionsClient, connectorId, logger: mockLogger, - request: mockRequest, }); await expect(actionsClientLlm._call(prompt)).rejects.toThrowError( @@ -125,16 +101,17 @@ describe('ActionsClientLlm', () => { it('rejects with the expected error the message has invalid content', async () => { const invalidContent = { message: 1234 }; - mockExecute.mockImplementation(() => ({ - data: invalidContent, - status: 'ok', - })); + actionsClient.execute.mockImplementation( + jest.fn().mockResolvedValue({ + data: invalidContent, + status: 'ok', + }) + ); const actionsClientLlm = new ActionsClientLlm({ - actions: mockActions, + actionsClient, connectorId, logger: mockLogger, - request: mockRequest, }); await expect(actionsClientLlm._call(prompt)).rejects.toThrowError( diff --git a/x-pack/packages/kbn-langchain/server/language_models/llm.ts b/x-pack/packages/kbn-langchain/server/language_models/llm.ts index 9708a8d3a5d7f..bad538821ff1d 100644 --- a/x-pack/packages/kbn-langchain/server/language_models/llm.ts +++ b/x-pack/packages/kbn-langchain/server/language_models/llm.ts @@ -5,11 +5,12 @@ * 2.0. */ -import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; -import { KibanaRequest, Logger } from '@kbn/core/server'; +import type { ActionsClient } from '@kbn/actions-plugin/server'; +import { Logger } from '@kbn/core/server'; import { LLM } from '@langchain/core/language_models/llms'; import { get } from 'lodash/fp'; import { v4 as uuidv4 } from 'uuid'; +import { PublicMethodsOf } from '@kbn/utility-types'; import { DEFAULT_TIMEOUT, getDefaultArguments } from './constants'; import { getMessageContentAndRole } from './helpers'; @@ -18,11 +19,10 @@ import { TraceOptions } from './types'; const LLM_TYPE = 'ActionsClientLlm'; interface ActionsClientLlmParams { - actions: ActionsPluginStart; + actionsClient: PublicMethodsOf; connectorId: string; llmType?: string; logger: Logger; - request: KibanaRequest; model?: string; temperature?: number; timeout?: number; @@ -31,10 +31,9 @@ interface ActionsClientLlmParams { } export class ActionsClientLlm extends LLM { - #actions: ActionsPluginStart; + #actionsClient: PublicMethodsOf; #connectorId: string; #logger: Logger; - #request: KibanaRequest; #traceId: string; #timeout?: number; @@ -46,13 +45,12 @@ export class ActionsClientLlm extends LLM { temperature?: number; constructor({ - actions, + actionsClient, connectorId, traceId = uuidv4(), llmType, logger, model, - request, temperature, timeout, traceOptions, @@ -61,12 +59,11 @@ export class ActionsClientLlm extends LLM { callbacks: [...(traceOptions?.tracers ?? [])], }); - this.#actions = actions; + this.#actionsClient = actionsClient; this.#connectorId = connectorId; this.#traceId = traceId; this.llmType = llmType ?? LLM_TYPE; this.#logger = logger; - this.#request = request; this.#timeout = timeout; this.model = model; this.temperature = temperature; @@ -107,10 +104,7 @@ export class ActionsClientLlm extends LLM { }, }; - // create an actions client from the authenticated request context: - const actionsClient = await this.#actions.getActionsClientWithRequest(this.#request); - - const actionResult = await actionsClient.execute(requestBody); + const actionResult = await this.#actionsClient.execute(requestBody); if (actionResult.status === 'error') { throw new Error( diff --git a/x-pack/packages/kbn-langchain/server/language_models/simple_chat_model.test.ts b/x-pack/packages/kbn-langchain/server/language_models/simple_chat_model.test.ts index 7ec9f1e773340..98da9a4e81b53 100644 --- a/x-pack/packages/kbn-langchain/server/language_models/simple_chat_model.test.ts +++ b/x-pack/packages/kbn-langchain/server/language_models/simple_chat_model.test.ts @@ -6,10 +6,10 @@ */ import { PassThrough } from 'stream'; -import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; import { loggerMock } from '@kbn/logging-mocks'; +import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client/actions_client.mock'; -import { ActionsClientSimpleChatModel, CustomChatModelInput } from './simple_chat_model'; +import { ActionsClientSimpleChatModel } from './simple_chat_model'; import { mockActionResponse } from './mocks'; import { BaseMessage } from '@langchain/core/messages'; import { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager'; @@ -19,26 +19,15 @@ import { parseGeminiStream } from '../utils/gemini'; const connectorId = 'mock-connector-id'; const mockExecute = jest.fn(); +const actionsClient = actionsClientMock.create(); const mockLogger = loggerMock.create(); -const mockActions = { - getActionsClientWithRequest: jest.fn().mockImplementation(() => ({ - execute: mockExecute, - })), -} as unknown as ActionsPluginStart; - const mockStreamExecute = jest.fn().mockImplementation(() => ({ data: new PassThrough(), status: 'ok', })); -const mockStreamActions = { - getActionsClientWithRequest: jest.fn().mockImplementation(() => ({ - execute: mockStreamExecute, - })), -} as unknown as ActionsPluginStart; -const prompt = 'Do you know my name?'; const callMessages = [ { lc_serializable: true, @@ -78,20 +67,10 @@ const callRunManager = { handleLLMNewToken, } as unknown as CallbackManagerForLLMRun; -const mockRequest: CustomChatModelInput['request'] = { - params: { connectorId }, - body: { - message: prompt, - subAction: 'invokeAI', - isEnabledKnowledgeBase: true, - }, -} as CustomChatModelInput['request']; - const defaultArgs = { - actions: mockActions, + actionsClient, connectorId, logger: mockLogger, - request: mockRequest, streaming: false, }; jest.mock('../utils/bedrock'); @@ -100,6 +79,12 @@ jest.mock('../utils/gemini'); describe('ActionsClientSimpleChatModel', () => { beforeEach(() => { jest.clearAllMocks(); + actionsClient.execute.mockImplementation( + jest.fn().mockImplementation(() => ({ + data: mockActionResponse, + status: 'ok', + })) + ); mockExecute.mockImplementation(() => ({ data: mockActionResponse, status: 'ok', @@ -146,28 +131,24 @@ describe('ActionsClientSimpleChatModel', () => { callOptions, callRunManager ); - const subAction = mockExecute.mock.calls[0][0].params.subAction; + const subAction = actionsClient.execute.mock.calls[0][0].params.subAction; expect(subAction).toEqual('invokeAI'); expect(result).toEqual(mockActionResponse.message); }); it('rejects with the expected error when the action result status is error', async () => { - const hasErrorStatus = jest.fn().mockImplementation(() => ({ - message: 'action-result-message', - serviceMessage: 'action-result-service-message', - status: 'error', // <-- error status - })); + const hasErrorStatus = jest.fn().mockImplementation(() => { + throw Error( + 'ActionsClientSimpleChatModel: action result status is error: action-result-message - action-result-service-message' + ); + }); - const badActions = { - getActionsClientWithRequest: jest.fn().mockImplementation(() => ({ - execute: hasErrorStatus, - })), - } as unknown as ActionsPluginStart; + actionsClient.execute.mockRejectedValueOnce(hasErrorStatus); const actionsClientSimpleChatModel = new ActionsClientSimpleChatModel({ ...defaultArgs, - actions: badActions, + actionsClient, }); await expect( @@ -180,10 +161,12 @@ describe('ActionsClientSimpleChatModel', () => { it('rejects with the expected error the message has invalid content', async () => { const invalidContent = { message: 1234 }; - mockExecute.mockImplementation(() => ({ - data: invalidContent, - status: 'ok', - })); + actionsClient.execute.mockImplementation( + jest.fn().mockResolvedValue({ + data: invalidContent, + status: 'ok', + }) + ); const actionsClientSimpleChatModel = new ActionsClientSimpleChatModel(defaultArgs); @@ -221,9 +204,10 @@ describe('ActionsClientSimpleChatModel', () => { (parseGeminiStream as jest.Mock).mockResolvedValue(mockActionResponse.message); }); it('returns the expected content when _call is invoked with streaming and llmType is Bedrock', async () => { + actionsClient.execute.mockImplementationOnce(mockStreamExecute); const actionsClientSimpleChatModel = new ActionsClientSimpleChatModel({ ...defaultArgs, - actions: mockStreamActions, + actionsClient, llmType: 'bedrock', streaming: true, maxTokens: 333, @@ -248,9 +232,10 @@ describe('ActionsClientSimpleChatModel', () => { expect(result).toEqual(mockActionResponse.message); }); it('returns the expected content when _call is invoked with streaming and llmType is Gemini', async () => { + actionsClient.execute.mockImplementationOnce(mockStreamExecute); const actionsClientSimpleChatModel = new ActionsClientSimpleChatModel({ ...defaultArgs, - actions: mockStreamActions, + actionsClient, llmType: 'gemini', streaming: true, maxTokens: 333, @@ -283,9 +268,11 @@ describe('ActionsClientSimpleChatModel', () => { handleToken(`, "action_input": "`); handleToken('token6'); }); + actionsClient.execute.mockImplementationOnce(mockStreamExecute); + const actionsClientSimpleChatModel = new ActionsClientSimpleChatModel({ ...defaultArgs, - actions: mockStreamActions, + actionsClient, llmType: 'bedrock', streaming: true, }); @@ -303,9 +290,10 @@ describe('ActionsClientSimpleChatModel', () => { handleToken('"'); handleToken('token7'); }); + actionsClient.execute.mockImplementationOnce(mockStreamExecute); const actionsClientSimpleChatModel = new ActionsClientSimpleChatModel({ ...defaultArgs, - actions: mockStreamActions, + actionsClient, llmType: 'bedrock', streaming: true, }); diff --git a/x-pack/packages/kbn-langchain/server/language_models/simple_chat_model.ts b/x-pack/packages/kbn-langchain/server/language_models/simple_chat_model.ts index 97f7c20cc110a..ed38723993876 100644 --- a/x-pack/packages/kbn-langchain/server/language_models/simple_chat_model.ts +++ b/x-pack/packages/kbn-langchain/server/language_models/simple_chat_model.ts @@ -11,12 +11,12 @@ import { type BaseChatModelParams, } from '@langchain/core/language_models/chat_models'; import { type BaseMessage } from '@langchain/core/messages'; -import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; +import type { ActionsClient } from '@kbn/actions-plugin/server'; import { Logger } from '@kbn/logging'; -import { KibanaRequest } from '@kbn/core-http-server'; import { v4 as uuidv4 } from 'uuid'; import { get } from 'lodash/fp'; import { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager'; +import { PublicMethodsOf } from '@kbn/utility-types'; import { parseGeminiStream } from '../utils/gemini'; import { parseBedrockStream } from '../utils/bedrock'; import { getDefaultArguments } from './constants'; @@ -27,23 +27,21 @@ export const getMessageContentAndRole = (prompt: string, role = 'user') => ({ }); export interface CustomChatModelInput extends BaseChatModelParams { - actions: ActionsPluginStart; + actionsClient: PublicMethodsOf; connectorId: string; logger: Logger; llmType?: string; signal?: AbortSignal; model?: string; temperature?: number; - request: KibanaRequest; streaming: boolean; maxTokens?: number; } export class ActionsClientSimpleChatModel extends SimpleChatModel { - #actions: ActionsPluginStart; + #actionsClient: PublicMethodsOf; #connectorId: string; #logger: Logger; - #request: KibanaRequest; #traceId: string; #signal?: AbortSignal; #maxTokens?: number; @@ -53,12 +51,11 @@ export class ActionsClientSimpleChatModel extends SimpleChatModel { temperature?: number; constructor({ - actions, + actionsClient, connectorId, llmType, logger, model, - request, temperature, signal, streaming, @@ -66,12 +63,11 @@ export class ActionsClientSimpleChatModel extends SimpleChatModel { }: CustomChatModelInput) { super({}); - this.#actions = actions; + this.#actionsClient = actionsClient; this.#connectorId = connectorId; this.#traceId = uuidv4(); this.#logger = logger; this.#signal = signal; - this.#request = request; this.#maxTokens = maxTokens; this.llmType = llmType ?? 'ActionsClientSimpleChatModel'; this.model = model; @@ -122,10 +118,8 @@ export class ActionsClientSimpleChatModel extends SimpleChatModel { }, }, }; - // create an actions client from the authenticated request context: - const actionsClient = await this.#actions.getActionsClientWithRequest(this.#request); - const actionResult = await actionsClient.execute(requestBody); + const actionResult = await this.#actionsClient.execute(requestBody); if (actionResult.status === 'error') { throw new Error( diff --git a/x-pack/packages/kbn-langchain/tsconfig.json b/x-pack/packages/kbn-langchain/tsconfig.json index 92dc5ebd33911..949aca47794ec 100644 --- a/x-pack/packages/kbn-langchain/tsconfig.json +++ b/x-pack/packages/kbn-langchain/tsconfig.json @@ -18,6 +18,6 @@ "@kbn/logging", "@kbn/actions-plugin", "@kbn/logging-mocks", - "@kbn/core-http-server" + "@kbn/utility-types" ] } diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/index.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/index.ts index 72c51d8f917aa..7c4f9708862a5 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/index.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/conversations/index.ts @@ -43,7 +43,7 @@ export class AIAssistantConversationsDataClient extends AIAssistantDataClient { logger: this.options.logger, conversationIndex: this.indexTemplateAndPattern.alias, id, - user: authenticatedUser, + user: authenticatedUser ?? this.options.currentUser, }); }; @@ -84,18 +84,19 @@ export class AIAssistantConversationsDataClient extends AIAssistantDataClient { */ public createConversation = async ({ conversation, - authenticatedUser, }: { conversation: ConversationCreateProps; - authenticatedUser: AuthenticatedUser; }): Promise => { + if (!this.options.currentUser) { + throw new Error('AIAssistantConversationsDataClient currentUser is not defined.'); + } const esClient = await this.options.elasticsearchClientPromise; return createConversation({ esClient, logger: this.options.logger, conversationIndex: this.indexTemplateAndPattern.alias, spaceId: this.spaceId, - user: authenticatedUser, + user: this.options.currentUser, conversation, }); }; @@ -128,7 +129,7 @@ export class AIAssistantConversationsDataClient extends AIAssistantDataClient { conversationIndex: this.indexTemplateAndPattern.alias, conversationUpdateProps, isPatch, - user: authenticatedUser, + user: authenticatedUser ?? this.options.currentUser ?? undefined, }); }; diff --git a/x-pack/plugins/elastic_assistant/server/lib/executor.test.ts b/x-pack/plugins/elastic_assistant/server/lib/executor.test.ts index bacdd6cac1b49..a01ac3d126e59 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/executor.test.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/executor.test.ts @@ -13,17 +13,10 @@ import { executeAction, Props } from './executor'; import { PassThrough } from 'stream'; -import { KibanaRequest } from '@kbn/core-http-server'; -import { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; -import { ExecuteConnectorRequestBody } from '@kbn/elastic-assistant-common'; +import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client/actions_client.mock'; import { loggerMock } from '@kbn/logging-mocks'; import * as ParseStream from './parse_stream'; -const request = { - body: { - subAction: 'invokeAI', - message: 'hello', - }, -} as KibanaRequest; + const onLlmResponse = jest.fn(async () => {}); // We need it to be a promise, or it'll crash because of missing `.catch` const connectorId = 'testConnectorId'; const mockLogger = loggerMock.create(); @@ -33,8 +26,8 @@ const testProps: Omit = { subActionParams: { messages: [{ content: 'hello', role: 'user' }] }, }, actionTypeId: '.bedrock', - request, connectorId, + actionsClient: actionsClientMock.create(), onLlmResponse, logger: mockLogger, }; @@ -46,17 +39,13 @@ describe('executeAction', () => { jest.clearAllMocks(); }); it('should execute an action and return a StaticResponse when the response from the actions framework is a string', async () => { - const actions = { - getActionsClientWithRequest: jest.fn().mockResolvedValue({ - execute: jest.fn().mockResolvedValue({ - data: { - message: 'Test message', - }, - }), - }), - } as unknown as Props['actions']; - - const result = await executeAction({ ...testProps, actions }); + testProps.actionsClient.execute = jest.fn().mockResolvedValue({ + data: { + message: 'Test message', + }, + }); + + const result = await executeAction({ ...testProps }); expect(result).toEqual({ connector_id: connectorId, @@ -68,15 +57,15 @@ describe('executeAction', () => { it('should execute an action and return a Readable object when the response from the actions framework is a stream', async () => { const readableStream = new PassThrough(); - const actions = { - getActionsClientWithRequest: jest.fn().mockResolvedValue({ - execute: jest.fn().mockResolvedValue({ - data: readableStream, - }), - }), - } as unknown as Props['actions']; + const actionsClient = actionsClientMock.create(); + actionsClient.execute.mockImplementationOnce( + jest.fn().mockResolvedValue({ + status: 'ok', + data: readableStream, + }) + ); - const result = await executeAction({ ...testProps, actions }); + const result = await executeAction({ ...testProps, actionsClient }); expect(JSON.stringify(result)).toStrictEqual( JSON.stringify(readableStream.pipe(new PassThrough())) @@ -90,83 +79,69 @@ describe('executeAction', () => { }); }); - it('should throw an error if the actions plugin fails to retrieve the actions client', async () => { - const actions = { - getActionsClientWithRequest: jest - .fn() - .mockRejectedValue(new Error('Failed to retrieve actions client')), - } as unknown as Props['actions']; - - await expect(executeAction({ ...testProps, actions })).rejects.toThrowError( - 'Failed to retrieve actions client' - ); - }); - it('should throw an error if the actions client fails to execute the action', async () => { - const actions = { - getActionsClientWithRequest: jest.fn().mockResolvedValue({ - execute: jest.fn().mockRejectedValue(new Error('Failed to execute action')), - }), - } as unknown as Props['actions']; + const actionsClient = actionsClientMock.create(); + actionsClient.execute.mockRejectedValue(new Error('Failed to execute action')); + testProps.actionsClient = actionsClient; - await expect(executeAction({ ...testProps, actions })).rejects.toThrowError( + await expect(executeAction({ ...testProps, actionsClient })).rejects.toThrowError( 'Failed to execute action' ); }); it('should throw an error when the response from the actions framework is null or undefined', async () => { - const actions = { - getActionsClientWithRequest: jest.fn().mockResolvedValue({ - execute: jest.fn().mockResolvedValue({ - data: null, - }), - }), - } as unknown as Props['actions']; + const actionsClient = actionsClientMock.create(); + actionsClient.execute.mockImplementationOnce( + jest.fn().mockResolvedValue({ + data: null, + }) + ); + testProps.actionsClient = actionsClient; try { - await executeAction({ ...testProps, actions }); + await executeAction({ ...testProps, actionsClient }); } catch (e) { expect(e.message).toBe('Action result status is error: result is not streamable'); } }); it('should throw an error if action result status is "error"', async () => { - const actions = { - getActionsClientWithRequest: jest.fn().mockResolvedValue({ - execute: jest.fn().mockResolvedValue({ - status: 'error', - message: 'Error message', - serviceMessage: 'Service error message', - }), - }), - } as unknown as ActionsPluginStart; + const actionsClient = actionsClientMock.create(); + actionsClient.execute.mockImplementationOnce( + jest.fn().mockResolvedValue({ + status: 'error', + message: 'Error message', + serviceMessage: 'Service error message', + }) + ); + testProps.actionsClient = actionsClient; await expect( executeAction({ ...testProps, - actions, + actionsClient, connectorId: '12345', }) ).rejects.toThrowError('Action result status is error: Error message - Service error message'); }); it('should throw an error if content of response data is not a string or streamable', async () => { - const actions = { - getActionsClientWithRequest: jest.fn().mockResolvedValue({ - execute: jest.fn().mockResolvedValue({ - status: 'ok', - data: { - message: 12345, - }, - }), - }), - } as unknown as ActionsPluginStart; + const actionsClient = actionsClientMock.create(); + actionsClient.execute.mockImplementationOnce( + jest.fn().mockResolvedValue({ + status: 'ok', + data: { + message: 12345, + }, + }) + ); + testProps.actionsClient = actionsClient; await expect( executeAction({ ...testProps, - actions, + actionsClient, connectorId: '12345', }) ).rejects.toThrowError('Action result status is error: result is not streamable'); diff --git a/x-pack/plugins/elastic_assistant/server/lib/executor.ts b/x-pack/plugins/elastic_assistant/server/lib/executor.ts index e7797805854ec..bd25a77808dbe 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/executor.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/executor.ts @@ -6,20 +6,18 @@ */ import { get } from 'lodash/fp'; -import { KibanaRequest } from '@kbn/core-http-server'; -import { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; +import { ActionsClient } from '@kbn/actions-plugin/server'; import { PassThrough, Readable } from 'stream'; -import { ExecuteConnectorRequestBody } from '@kbn/elastic-assistant-common'; import { Logger } from '@kbn/core/server'; +import { PublicMethodsOf } from '@kbn/utility-types'; import { handleStreamStorage } from './parse_stream'; export interface Props { onLlmResponse?: (content: string) => Promise; abortSignal?: AbortSignal; - actions: ActionsPluginStart; + actionsClient: PublicMethodsOf; connectorId: string; params: InvokeAIActionsParams; - request: KibanaRequest; actionTypeId: string; logger: Logger; } @@ -43,15 +41,13 @@ interface InvokeAIActionsParams { export const executeAction = async ({ onLlmResponse, - actions, + actionsClient, params, connectorId, actionTypeId, - request, logger, abortSignal, }: Props): Promise => { - const actionsClient = await actions.getActionsClientWithRequest(request); const actionResult = await actionsClient.execute({ actionId: connectorId, params: { diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.test.ts index e1e8cdc50eee0..6f05edbed007b 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.test.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.test.ts @@ -5,10 +5,11 @@ * 2.0. */ -import { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { coreMock } from '@kbn/core/server/mocks'; import { KibanaRequest } from '@kbn/core/server'; +import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client/actions_client.mock'; + import { loggerMock } from '@kbn/logging-mocks'; import { initializeAgentExecutorWithOptions } from 'langchain/agents'; @@ -84,7 +85,7 @@ const mockRequest: KibanaRequest = { body: {} } as K any // eslint-disable-line @typescript-eslint/no-explicit-any >; -const mockActions: ActionsPluginStart = {} as ActionsPluginStart; +const actionsClient = actionsClientMock.create(); const mockLogger = loggerMock.create(); const mockTelemetry = coreMock.createSetup().analytics; const esClientMock = elasticsearchServiceMock.createScopedClusterClient().asCurrentUser; @@ -95,7 +96,7 @@ const esStoreMock = new ElasticsearchStore( mockTelemetry ); const defaultProps: AgentExecutorParams = { - actions: mockActions, + actionsClient, isEnabledKnowledgeBase: true, connectorId: mockConnectorId, esClient: esClientMock, @@ -151,11 +152,10 @@ describe('callAgentExecutor', () => { await callAgentExecutor(defaultProps); expect(ActionsClientChatOpenAI).toHaveBeenCalledWith({ - actions: mockActions, + actionsClient, connectorId: mockConnectorId, logger: mockLogger, maxRetries: 0, - request: mockRequest, streaming: false, temperature: 0.2, llmType: 'openai', @@ -189,11 +189,10 @@ describe('callAgentExecutor', () => { await callAgentExecutor({ ...defaultProps, isStream: true }); expect(ActionsClientChatOpenAI).toHaveBeenCalledWith({ - actions: mockActions, + actionsClient, connectorId: mockConnectorId, logger: mockLogger, maxRetries: 0, - request: mockRequest, streaming: true, temperature: 0.2, llmType: 'openai', @@ -213,11 +212,10 @@ describe('callAgentExecutor', () => { await callAgentExecutor(bedrockProps); expect(ActionsClientSimpleChatModel).toHaveBeenCalledWith({ - actions: mockActions, + actionsClient, connectorId: mockConnectorId, logger: mockLogger, maxRetries: 0, - request: mockRequest, streaming: false, temperature: 0, llmType: 'bedrock', @@ -253,11 +251,10 @@ describe('callAgentExecutor', () => { await callAgentExecutor({ ...bedrockProps, isStream: true }); expect(ActionsClientSimpleChatModel).toHaveBeenCalledWith({ - actions: mockActions, + actionsClient, connectorId: mockConnectorId, logger: mockLogger, maxRetries: 0, - request: mockRequest, streaming: true, temperature: 0, llmType: 'bedrock', diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts index bcf39320f21cc..b6a624b368d82 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts @@ -18,6 +18,8 @@ import { ActionsClientSimpleChatModel, } from '@kbn/langchain/server'; import { MessagesPlaceholder } from '@langchain/core/prompts'; +import { EsAnonymizationFieldsSchema } from '../../../ai_assistant_data_clients/anonymization_fields/types'; +import { transformESSearchToAnonymizationFields } from '../../../ai_assistant_data_clients/anonymization_fields/helpers'; import { AgentExecutor } from '../executors/types'; import { APMTracer } from '../tracers/apm_tracer'; import { AssistantToolParams } from '../../../types'; @@ -31,9 +33,8 @@ export const DEFAULT_AGENT_EXECUTOR_ID = 'Elastic AI Assistant Agent Executor'; */ export const callAgentExecutor: AgentExecutor = async ({ abortSignal, - actions, + actionsClient, alertsIndexPattern, - anonymizationFields, isEnabledKnowledgeBase, assistantTools = [], connectorId, @@ -49,14 +50,14 @@ export const callAgentExecutor: AgentExecutor = async ({ request, size, traceOptions, + dataClients, }) => { const isOpenAI = llmType === 'openai'; const llmClass = isOpenAI ? ActionsClientChatOpenAI : ActionsClientSimpleChatModel; const llm = new llmClass({ - actions, + actionsClient, connectorId, - request, llmType, logger, // possible client model override, @@ -72,6 +73,16 @@ export const callAgentExecutor: AgentExecutor = async ({ maxRetries: 0, }); + const anonymizationFieldsRes = + await dataClients?.anonymizationFieldsDataClient?.findDocuments({ + perPage: 1000, + page: 1, + }); + + const anonymizationFields = anonymizationFieldsRes + ? transformESSearchToAnonymizationFields(anonymizationFieldsRes.data) + : undefined; + const pastMessages = langChainMessages.slice(0, -1); // all but the last message const latestMessage = langChainMessages.slice(-1); // the last message diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/openai_functions_executor.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/openai_functions_executor.ts index 62b1d7ac7814f..6aa1aa3ce7890 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/openai_functions_executor.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/openai_functions_executor.ts @@ -25,7 +25,7 @@ export const OPEN_AI_FUNCTIONS_AGENT_EXECUTOR_ID = * NOTE: This is not to be used in production as-is, and must be used with an OpenAI ConnectorId */ export const callOpenAIFunctionsExecutor: AgentExecutor = async ({ - actions, + actionsClient, connectorId, esClient, esStore, @@ -36,9 +36,8 @@ export const callOpenAIFunctionsExecutor: AgentExecutor = async ({ traceOptions, }) => { const llm = new ActionsClientLlm({ - actions, + actionsClient, connectorId, - request, llmType, logger, model: request.body.model, diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts index bd07099e312b3..7af0b459f4bc9 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; +import { ActionsClient } from '@kbn/actions-plugin/server'; import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { BaseMessage } from '@langchain/core/messages'; import { Logger } from '@kbn/logging'; @@ -13,7 +13,7 @@ import { KibanaRequest, KibanaResponseFactory, ResponseHeaders } from '@kbn/core import type { LangChainTracer } from '@langchain/core/tracers/tracer_langchain'; import { ExecuteConnectorRequestBody, Message, Replacements } from '@kbn/elastic-assistant-common'; import { StreamResponseWithHeaders } from '@kbn/ml-response-stream/server'; -import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; +import { PublicMethodsOf } from '@kbn/utility-types'; import { ResponseBody } from '../types'; import type { AssistantTool } from '../../../types'; import { ElasticsearchStore } from '../elasticsearch_store/elasticsearch_store'; @@ -36,8 +36,7 @@ export interface AssistantDataClients { export interface AgentExecutorParams { abortSignal?: AbortSignal; alertsIndexPattern?: string; - actions: ActionsPluginStart; - anonymizationFields?: AnonymizationFieldResponse[]; + actionsClient: PublicMethodsOf; isEnabledKnowledgeBase: boolean; assistantTools?: AssistantTool[]; connectorId: string; @@ -56,6 +55,7 @@ export interface AgentExecutorParams { response?: KibanaResponseFactory; size?: number; traceOptions?: TraceOptions; + responseLanguage?: string; } export interface StaticReturnType { diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts index 779bf20a61720..b16f7d9693e5f 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts @@ -14,11 +14,25 @@ import type { Logger } from '@kbn/logging'; import { BaseMessage } from '@langchain/core/messages'; import { BaseChatModel } from '@langchain/core/language_models/chat_models'; +import { ConversationResponse, Replacements } from '@kbn/elastic-assistant-common'; import { AgentState, NodeParamsBase } from './types'; import { AssistantDataClients } from '../../executors/types'; -import { shouldContinue } from './nodes/should_continue'; +import { + shouldContinue, + shouldContinueGenerateTitle, + shouldContinueGetConversation, +} from './nodes/should_continue'; import { AGENT_NODE, runAgent } from './nodes/run_agent'; import { executeTools, TOOLS_NODE } from './nodes/execute_tools'; +import { GENERATE_CHAT_TITLE_NODE, generateChatTitle } from './nodes/generate_chat_title'; +import { + GET_PERSISTED_CONVERSATION_NODE, + getPersistedConversation, +} from './nodes/get_persisted_conversation'; +import { + PERSIST_CONVERSATION_CHANGES_NODE, + persistConversationChanges, +} from './nodes/persist_conversation_changes'; export const DEFAULT_ASSISTANT_GRAPH_ID = 'Default Security Assistant Graph'; @@ -28,8 +42,9 @@ interface GetDefaultAssistantGraphParams { conversationId?: string; llm: BaseChatModel; logger: Logger; - messages: BaseMessage[]; tools: StructuredTool[]; + responseLanguage: string; + replacements: Replacements; } export type DefaultAssistantGraph = ReturnType; @@ -43,8 +58,9 @@ export const getDefaultAssistantGraph = ({ dataClients, llm, logger, - messages, + responseLanguage, tools, + replacements, }: GetDefaultAssistantGraphParams) => { try { // Default graph state @@ -66,7 +82,16 @@ export const getDefaultAssistantGraph = ({ }, messages: { value: (x: BaseMessage[], y: BaseMessage[]) => x.concat(y), - default: () => messages, + default: () => [], + }, + chatTitle: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + conversation: { + value: (x: ConversationResponse | undefined, y?: ConversationResponse | undefined) => + y ?? x, + default: () => undefined, }, }; @@ -94,19 +119,68 @@ export const getDefaultAssistantGraph = ({ state, tools, }); + const generateChatTitleNode = (state: AgentState) => + generateChatTitle({ + ...nodeParams, + state, + responseLanguage, + }); + + const getPersistedConversationNode = (state: AgentState) => + getPersistedConversation({ + ...nodeParams, + state, + conversationsDataClient: dataClients?.conversationsDataClient, + conversationId, + }); + + const persistConversationChangesNode = (state: AgentState) => + persistConversationChanges({ + ...nodeParams, + state, + conversationsDataClient: dataClients?.conversationsDataClient, + conversationId, + replacements, + }); const shouldContinueEdge = (state: AgentState) => shouldContinue({ ...nodeParams, state }); + const shouldContinueGenerateTitleEdge = (state: AgentState) => + shouldContinueGenerateTitle({ ...nodeParams, state }); + const shouldContinueGetConversationEdge = (state: AgentState) => + shouldContinueGetConversation({ ...nodeParams, state, conversationId }); // Put together a new graph using the nodes and default state from above - const graph = new StateGraph, '__start__' | 'agent' | 'tools'>({ + const graph = new StateGraph< + AgentState, + Partial, + | '__start__' + | 'agent' + | 'tools' + | 'generateChatTitle' + | 'getPersistedConversation' + | 'persistConversationChanges' + >({ channels: graphState, }); // Define the nodes to cycle between + graph.addNode(GET_PERSISTED_CONVERSATION_NODE, getPersistedConversationNode); + graph.addNode(GENERATE_CHAT_TITLE_NODE, generateChatTitleNode); + graph.addNode(PERSIST_CONVERSATION_CHANGES_NODE, persistConversationChangesNode); graph.addNode(AGENT_NODE, runAgentNode); graph.addNode(TOOLS_NODE, executeToolsNode); + + // Add edges, alternating between agent and action until finished + graph.addConditionalEdges(START, shouldContinueGetConversationEdge, { + continue: GET_PERSISTED_CONVERSATION_NODE, + end: AGENT_NODE, + }); + graph.addConditionalEdges(GET_PERSISTED_CONVERSATION_NODE, shouldContinueGenerateTitleEdge, { + continue: GENERATE_CHAT_TITLE_NODE, + end: PERSIST_CONVERSATION_CHANGES_NODE, + }); + graph.addEdge(GENERATE_CHAT_TITLE_NODE, PERSIST_CONVERSATION_CHANGES_NODE); + graph.addEdge(PERSIST_CONVERSATION_CHANGES_NODE, AGENT_NODE); // Add conditional edge for basic routing graph.addConditionalEdges(AGENT_NODE, shouldContinueEdge, { continue: TOOLS_NODE, end: END }); - // Add edges, alternating between agent and action until finished - graph.addEdge(START, AGENT_NODE); graph.addEdge(TOOLS_NODE, AGENT_NODE); // Compile the graph return graph.compile(); diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts index 383b3e9f5cee8..482c89c10e969 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts @@ -11,6 +11,7 @@ import { streamFactory, StreamResponseWithHeaders } from '@kbn/ml-response-strea import { transformError } from '@kbn/securitysolution-es-utils'; import type { KibanaRequest } from '@kbn/core-http-server'; import type { ExecuteConnectorRequestBody, TraceData } from '@kbn/elastic-assistant-common'; +import { AGENT_NODE_TAG } from './nodes/run_agent'; import { DEFAULT_ASSISTANT_GRAPH_ID, DefaultAssistantGraph } from './graph'; import type { OnLlmResponse, TraceOptions } from '../../executors/types'; import type { APMTracer } from '../../tracers/apm_tracer'; @@ -91,27 +92,35 @@ export const streamGraph = async ({ if (done) return; const event = value; - if (event.event === 'on_llm_stream') { - const chunk = event.data?.chunk; - // TODO: For Bedrock streaming support, override `handleLLMNewToken` in callbacks, - // TODO: or maybe we can update ActionsClientSimpleChatModel to handle this `on_llm_stream` event - if (event.name === 'ActionsClientChatOpenAI') { - const msg = chunk.message; - - if (msg.tool_call_chunks && msg.tool_call_chunks.length > 0) { - /* empty */ - } else if (!didEnd) { - if (msg.response_metadata.finish_reason === 'stop') { - handleStreamEnd(finalMessage); - } else { - push({ payload: msg.content, type: 'content' }); - finalMessage += msg.content; + // only process events that are part of the agent run + if ((event.tags || []).includes(AGENT_NODE_TAG)) { + if (event.event === 'on_llm_stream') { + const chunk = event.data?.chunk; + // TODO: For Bedrock streaming support, override `handleLLMNewToken` in callbacks, + // TODO: or maybe we can update ActionsClientSimpleChatModel to handle this `on_llm_stream` event + if (event.name === 'ActionsClientChatOpenAI') { + const msg = chunk.message; + + if (msg.tool_call_chunks && msg.tool_call_chunks.length > 0) { + /* empty */ + } else if (!didEnd) { + if (msg.response_metadata.finish_reason === 'stop') { + handleStreamEnd(finalMessage); + } else { + push({ payload: msg.content, type: 'content' }); + finalMessage += msg.content; + } } } + } else if (event.event === 'on_llm_end') { + const generations = event.data.output?.generations[0]; + if (generations && generations[0]?.generationInfo.finish_reason === 'stop') { + handleStreamEnd(finalMessage); + } } } - await processEvent(); + void processEvent(); } catch (err) { // if I throw an error here, it crashes the server. Not sure how to get around that. // If I put await on this function the error works properly, but when there is not an error @@ -129,7 +138,7 @@ export const streamGraph = async ({ }; // Start processing events, do not await! Return `responseWithHeaders` immediately - await processEvent(); + void processEvent(); return responseWithHeaders; }; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts index 1e40f6b2fe127..517ac10479461 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts @@ -13,21 +13,22 @@ import { ActionsClientSimpleChatModel, } from '@kbn/langchain/server'; import { createOpenAIFunctionsAgent, createStructuredChatAgent } from 'langchain/agents'; +import { EsAnonymizationFieldsSchema } from '../../../../ai_assistant_data_clients/anonymization_fields/types'; import { AssistantToolParams } from '../../../../types'; import { AgentExecutor } from '../../executors/types'; import { openAIFunctionAgentPrompt, structuredChatAgentPrompt } from './prompts'; import { APMTracer } from '../../tracers/apm_tracer'; import { getDefaultAssistantGraph } from './graph'; import { invokeGraph, streamGraph } from './helpers'; +import { transformESSearchToAnonymizationFields } from '../../../../ai_assistant_data_clients/anonymization_fields/helpers'; /** * Drop in replacement for the existing `callAgentExecutor` that uses LangGraph */ export const callAssistantGraph: AgentExecutor = async ({ abortSignal, - actions, + actionsClient, alertsIndexPattern, - anonymizationFields, isEnabledKnowledgeBase, assistantTools = [], connectorId, @@ -45,15 +46,15 @@ export const callAssistantGraph: AgentExecutor = async ({ request, size, traceOptions, + responseLanguage = 'English', }) => { const logger = parentLogger.get('defaultAssistantGraph'); const isOpenAI = llmType === 'openai'; const llmClass = isOpenAI ? ActionsClientChatOpenAI : ActionsClientSimpleChatModel; const llm = new llmClass({ - actions, + actionsClient, connectorId, - request, llmType, logger, // possible client model override, @@ -68,15 +69,23 @@ export const callAssistantGraph: AgentExecutor = async ({ // failure could be due to bad connector, we should deliver that result to the client asap maxRetries: 0, }); - const model = llm; - const messages = langChainMessages.slice(0, -1); // all but the last message + const anonymizationFieldsRes = + await dataClients?.anonymizationFieldsDataClient?.findDocuments({ + perPage: 1000, + page: 1, + }); + + const anonymizationFields = anonymizationFieldsRes + ? transformESSearchToAnonymizationFields(anonymizationFieldsRes.data) + : undefined; + const latestMessage = langChainMessages.slice(-1); // the last message const modelExists = await esStore.isModelInstalled(); // Create a chain that uses the ELSER backed ElasticsearchStore, override k=10 for esql query generation for now - const chain = RetrievalQAChain.fromLLM(model, esStore.asRetriever(10)); + const chain = RetrievalQAChain.fromLLM(llm, esStore.asRetriever(10)); // Fetch any applicable tools that the source plugin may have registered const assistantToolParams: AssistantToolParams = { @@ -86,7 +95,7 @@ export const callAssistantGraph: AgentExecutor = async ({ esClient, isEnabledKnowledgeBase, kbDataClient: dataClients?.kbDataClient, - llm: model, + llm, logger, modelExists, onNewReplacements, @@ -121,10 +130,11 @@ export const callAssistantGraph: AgentExecutor = async ({ dataClients, llm, logger, - messages, tools, + responseLanguage, + replacements, }); - const inputs = { input: latestMessage[0].content as string }; + const inputs = { input: latestMessage[0]?.content as string }; if (isStream) { return streamGraph({ apmTracer, assistantGraph, inputs, logger, onLlmResponse, request }); diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/generate_chat_title.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/generate_chat_title.ts index bcba25eab0b0d..d1d60e9bed9b4 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/generate_chat_title.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/generate_chat_title.ts @@ -8,46 +8,45 @@ import { StringOutputParser } from '@langchain/core/output_parsers'; import { ChatPromptTemplate } from '@langchain/core/prompts'; import { AgentState, NodeParamsBase } from '../types'; -import { AIAssistantConversationsDataClient } from '../../../../../ai_assistant_data_clients/conversations'; -export const GENERATE_CHAT_TITLE_PROMPT = ChatPromptTemplate.fromMessages([ - [ - 'system', - `You are a helpful assistant for Elastic Security. Assume the following user message is the start of a conversation between you and a user; give this conversation a title based on the content below. DO NOT UNDER ANY CIRCUMSTANCES wrap this title in single or double quotes. This title is shown in a list of conversations to the user, so title it for the user, not for you. As an example, for the given MESSAGE, this is the TITLE: +export const GENERATE_CHAT_TITLE_PROMPT = (responseLanguage: string) => + ChatPromptTemplate.fromMessages([ + [ + 'system', + `You are a helpful assistant for Elastic Security. Assume the following user message is the start of a conversation between you and a user; give this conversation a title based on the content below. DO NOT UNDER ANY CIRCUMSTANCES wrap this title in single or double quotes. This title is shown in a list of conversations to the user, so title it for the user, not for you. Please create the title in ${responseLanguage}. As an example, for the given MESSAGE, this is the TITLE: MESSAGE: I am having trouble with the Elastic Security app. TITLE: Troubleshooting Elastic Security app issues `, - ], - ['human', '{input}'], -]); + ], + ['human', '{input}'], + ]); export interface GenerateChatTitleParams extends NodeParamsBase { - conversationsDataClient?: AIAssistantConversationsDataClient; - conversationId?: string; + responseLanguage: string; state: AgentState; } export const GENERATE_CHAT_TITLE_NODE = 'generateChatTitle'; export const generateChatTitle = async ({ - conversationsDataClient, + responseLanguage, logger, model, state, }: GenerateChatTitleParams) => { logger.debug(`Node state:\n ${JSON.stringify(state, null, 2)}`); + if (state.messages.length !== 0) { logger.debug('No need to generate chat title, messages already exist'); - return; + return { chatTitle: '' }; } const outputParser = new StringOutputParser(); - const graph = GENERATE_CHAT_TITLE_PROMPT.pipe(model).pipe(outputParser); + const graph = GENERATE_CHAT_TITLE_PROMPT(responseLanguage).pipe(model).pipe(outputParser); const chatTitle = await graph.invoke({ input: JSON.stringify(state.input, null, 2), }); - logger.debug(`chatTitle: ${chatTitle}`); return { diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/get_persisted_conversation.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/get_persisted_conversation.ts new file mode 100644 index 0000000000000..6dbf284e462c4 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/get_persisted_conversation.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AgentState, NodeParamsBase } from '../types'; +import { AIAssistantConversationsDataClient } from '../../../../../ai_assistant_data_clients/conversations'; +import { getLangChainMessages } from '../../../helpers'; + +export interface GetPersistedConversationParams extends NodeParamsBase { + conversationsDataClient?: AIAssistantConversationsDataClient; + conversationId?: string; + state: AgentState; +} + +export const GET_PERSISTED_CONVERSATION_NODE = 'getPersistedConversation'; + +export const getPersistedConversation = async ({ + conversationsDataClient, + conversationId, + logger, + state, +}: GetPersistedConversationParams) => { + logger.debug(`Node state:\n ${JSON.stringify(state, null, 2)}`); + if (!conversationId) { + logger.debug('Cannot get conversation, because conversationId is undefined'); + return { + conversation: undefined, + messages: [], + chatTitle: '', + input: state.input, + }; + } + + const conversation = await conversationsDataClient?.getConversation({ id: conversationId }); + if (!conversation) { + logger.debug('Requested conversation, because conversation is undefined'); + return { + conversation: undefined, + messages: [], + chatTitle: '', + input: state.input, + }; + } + + logger.debug(`conversationId: ${conversationId}`); + + const messages = getLangChainMessages(conversation.messages ?? []); + return { + conversation, + messages, + chatTitle: conversation.title, + input: !state.input ? conversation.messages?.slice(-1)[0].content : state.input, + }; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/persist_conversation_changes.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/persist_conversation_changes.ts new file mode 100644 index 0000000000000..a86897e67adbf --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/persist_conversation_changes.ts @@ -0,0 +1,78 @@ +/* + * 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 { + Replacements, + replaceAnonymizedValuesWithOriginalValues, +} from '@kbn/elastic-assistant-common'; +import { AgentState, NodeParamsBase } from '../types'; +import { AIAssistantConversationsDataClient } from '../../../../../ai_assistant_data_clients/conversations'; +import { getLangChainMessages } from '../../../helpers'; + +export interface PersistConversationChangesParams extends NodeParamsBase { + conversationsDataClient?: AIAssistantConversationsDataClient; + conversationId?: string; + state: AgentState; + replacements?: Replacements; +} + +export const PERSIST_CONVERSATION_CHANGES_NODE = 'persistConversationChanges'; + +export const persistConversationChanges = async ({ + conversationsDataClient, + conversationId, + logger, + state, + replacements = {}, +}: PersistConversationChangesParams) => { + logger.debug(`Node state:\n ${JSON.stringify(state, null, 2)}`); + + if (!state.conversation || !conversationId) { + logger.debug('No need to generate chat title, conversationId is undefined'); + return { + conversation: undefined, + messages: [], + }; + } + + let conversation; + if (state.conversation?.title !== state.chatTitle) { + conversation = await conversationsDataClient?.updateConversation({ + conversationUpdateProps: { + id: conversationId, + title: state.chatTitle, + }, + }); + } + + const updatedConversation = await conversationsDataClient?.appendConversationMessages({ + existingConversation: conversation ? conversation : state.conversation, + messages: [ + { + content: replaceAnonymizedValuesWithOriginalValues({ + messageContent: state.input, + replacements, + }), + role: 'user', + timestamp: new Date().toISOString(), + }, + ], + }); + if (!updatedConversation) { + logger.debug('Not updated conversation'); + return { conversation: undefined, messages: [] }; + } + + logger.debug(`conversationId: ${conversationId}`); + const langChainMessages = getLangChainMessages(updatedConversation.messages ?? []); + const messages = langChainMessages.slice(0, -1); // all but the last message + + return { + conversation: updatedConversation, + messages, + }; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/run_agent.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/run_agent.ts index b0353bb5d8ec7..0a6ee3b79087c 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/run_agent.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/run_agent.ts @@ -19,6 +19,8 @@ export interface RunAgentParams extends NodeParamsBase { export const AGENT_NODE = 'agent'; +export const AGENT_NODE_TAG = 'agent_run'; + const NO_HISTORY = '[No existing knowledge history]'; /** * Node to run the agent @@ -44,11 +46,11 @@ export const runAgent = async ({ query: '', }); - const agentOutcome = await agentRunnable.invoke( + const agentOutcome = await agentRunnable.withConfig({ tags: [AGENT_NODE_TAG] }).invoke( { ...state, chat_history: state.messages, // TODO: Message de-dupe with ...state spread - knowledge_history: knowledgeHistory?.length ? knowledgeHistory : NO_HISTORY, + knowledge_history: JSON.stringify(knowledgeHistory?.length ? knowledgeHistory : NO_HISTORY), }, config ); diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/should_continue.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/should_continue.ts index 281963df363a8..046c4a86d4c7a 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/should_continue.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/should_continue.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { NEW_CHAT } from '../../../../../routes/helpers'; import { AgentState, NodeParamsBase } from '../types'; export interface ShouldContinueParams extends NodeParamsBase { @@ -26,3 +27,31 @@ export const shouldContinue = ({ logger, state }: ShouldContinueParams) => { return 'continue'; }; + +export const shouldContinueGenerateTitle = ({ logger, state }: ShouldContinueParams) => { + logger.debug(`Node state:\n${JSON.stringify(state, null, 2)}`); + + if (state.conversation?.title !== NEW_CHAT) { + return 'end'; + } + + return 'continue'; +}; + +export interface ShouldContinueGetConversation extends NodeParamsBase { + state: AgentState; + conversationId?: string; +} +export const shouldContinueGetConversation = ({ + logger, + state, + conversationId, +}: ShouldContinueGetConversation) => { + logger.debug(`Node state:\n${JSON.stringify(state, null, 2)}`); + + if (!conversationId) { + return 'end'; + } + + return 'continue'; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts index 1d19646fb6eb3..4ee4f1ba1b148 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts @@ -9,6 +9,7 @@ import { BaseMessage } from '@langchain/core/messages'; import { AgentAction, AgentFinish, AgentStep } from '@langchain/core/agents'; import { BaseChatModel } from '@langchain/core/language_models/chat_models'; import type { Logger } from '@kbn/logging'; +import { ConversationResponse } from '@kbn/elastic-assistant-common'; export interface AgentStateBase { agentOutcome?: AgentAction | AgentFinish; @@ -18,6 +19,8 @@ export interface AgentStateBase { export interface AgentState extends AgentStateBase { input: string; messages: BaseMessage[]; + chatTitle: string; + conversation: ConversationResponse | undefined; } export interface NodeParamsBase { diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts index bd8f7983921cb..15877e6727715 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts @@ -7,6 +7,8 @@ import { AuthenticatedUser } from '@kbn/core-security-common'; import moment from 'moment'; +import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client/actions_client.mock'; + import { REQUIRED_FOR_ATTACK_DISCOVERY, addGenerationInterval, @@ -21,7 +23,6 @@ import { import { ActionsClientLlm } from '@kbn/langchain/server'; import { AttackDiscoveryDataClient } from '../../ai_assistant_data_clients/attack_discovery'; import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants'; -import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { loggerMock } from '@kbn/logging-mocks'; import { KibanaRequest } from '@kbn/core-http-server'; @@ -90,7 +91,6 @@ const mockApiConfig = { const mockCurrentAd = transformESSearchToAttackDiscovery(getAttackDiscoverySearchEsMock())[0]; -const mockActions: ActionsPluginStart = {} as ActionsPluginStart; // eslint-disable-next-line @typescript-eslint/no-explicit-any const mockRequest: KibanaRequest = {} as unknown as KibanaRequest< unknown, @@ -117,14 +117,14 @@ describe('helpers', () => { describe('getAssistantToolParams', () => { const alertsIndexPattern = '.alerts-security.alerts-default'; const esClient = elasticsearchClientMock.createElasticsearchClient(); + const actionsClient = actionsClientMock.create(); const langChainTimeout = 1000; const latestReplacements = {}; const llm = new ActionsClientLlm({ - actions: mockActions, + actionsClient, connectorId: 'test-connecter-id', llmType: 'bedrock', logger: mockLogger, - request: mockRequest, temperature: 0, timeout: 580000, }); @@ -132,7 +132,7 @@ describe('helpers', () => { const size = 20; const mockParams = { - actions: {} as unknown as ActionsPluginStart, + actionsClient, alertsIndexPattern: 'alerts-*', anonymizationFields: [{ id: '1', field: 'field1', allowed: true, anonymized: true }], apiConfig: mockApiConfig, @@ -173,7 +173,7 @@ describe('helpers', () => { ]; const result = getAssistantToolParams({ - actions: mockParams.actions, + actionsClient, alertsIndexPattern, apiConfig: mockApiConfig, anonymizationFields, @@ -208,7 +208,7 @@ describe('helpers', () => { const anonymizationFields = undefined; const result = getAssistantToolParams({ - actions: mockParams.actions, + actionsClient, alertsIndexPattern, apiConfig: mockApiConfig, anonymizationFields, diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts index c3665d1583a3f..5f5eb8d0d8659 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts @@ -24,9 +24,10 @@ import { ActionsClientLlm } from '@kbn/langchain/server'; import { Moment } from 'moment'; import { transformError } from '@kbn/securitysolution-es-utils'; -import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; +import type { ActionsClient } from '@kbn/actions-plugin/server'; import moment from 'moment/moment'; import { uniq } from 'lodash/fp'; +import { PublicMethodsOf } from '@kbn/utility-types'; import { getLangSmithTracer } from '../evaluate/utils'; import { getLlmType } from '../utils'; import type { GetRegisteredTools } from '../../services/app_context'; @@ -53,7 +54,7 @@ export const REQUIRED_FOR_ATTACK_DISCOVERY: AnonymizationFieldResponse[] = [ ]; export const getAssistantToolParams = ({ - actions, + actionsClient, alertsIndexPattern, anonymizationFields, apiConfig, @@ -68,7 +69,7 @@ export const getAssistantToolParams = ({ request, size, }: { - actions: ActionsPluginStart; + actionsClient: PublicMethodsOf; alertsIndexPattern: string; anonymizationFields?: AnonymizationFieldResponse[]; apiConfig: ApiConfig; @@ -99,11 +100,10 @@ export const getAssistantToolParams = ({ }; const llm = new ActionsClientLlm({ - actions, + actionsClient, connectorId: apiConfig.connectorId, llmType: getLlmType(apiConfig.actionTypeId), logger, - request, temperature: 0, // zero temperature for attack discovery, because we want structured JSON output timeout: connectorTimeout, traceOptions, diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.test.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.test.ts index 9ecfb5c2af333..cbd3e6063fbd2 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.test.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.test.ts @@ -10,6 +10,7 @@ import { postAttackDiscoveryRoute } from './post_attack_discovery'; import { serverMock } from '../../__mocks__/server'; import { requestContextMock } from '../../__mocks__/request_context'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; import { AttackDiscoveryDataClient } from '../../ai_assistant_data_clients/attack_discovery'; import { transformESSearchToAttackDiscovery } from '../../ai_assistant_data_clients/attack_discovery/transforms'; import { getAttackDiscoverySearchEsMock } from '../../__mocks__/attack_discovery_schema.mock'; @@ -68,6 +69,7 @@ describe('postAttackDiscoveryRoute', () => { jest.clearAllMocks(); context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser); context.elasticAssistant.getAttackDiscoveryDataClient.mockResolvedValue(mockDataClient); + context.elasticAssistant.actions = actionsMock.createStart(); postAttackDiscoveryRoute(server.router); findAttackDiscoveryByConnectorId.mockResolvedValue(mockCurrentAd); (getAssistantTool as jest.Mock).mockReturnValue({ getTool: jest.fn() }); diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.ts index 8ff2cd72ee36c..b9c680dde3d1d 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.ts @@ -70,6 +70,7 @@ export const postAttackDiscoveryRoute = ( try { // get the actions plugin start contract from the request context: const actions = (await context.elasticAssistant).actions; + const actionsClient = await actions.getActionsClientWithRequest(request); const dataClient = await assistantContext.getAttackDiscoveryDataClient(); const authenticatedUser = assistantContext.getCurrentUser(); if (authenticatedUser == null) { @@ -120,7 +121,7 @@ export const postAttackDiscoveryRoute = ( } const assistantToolParams = getAssistantToolParams({ - actions, + actionsClient, alertsIndexPattern, anonymizationFields, apiConfig, diff --git a/x-pack/plugins/elastic_assistant/server/routes/chat/chat_complete_route.test.ts b/x-pack/plugins/elastic_assistant/server/routes/chat/chat_complete_route.test.ts new file mode 100644 index 0000000000000..a487e56019bd8 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/chat/chat_complete_route.test.ts @@ -0,0 +1,456 @@ +/* + * 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 { ElasticsearchClient, IRouter, KibanaRequest, Logger } from '@kbn/core/server'; +import type { ActionsClient } from '@kbn/actions-plugin/server'; +import { BaseMessage } from '@langchain/core/messages'; +import { NEVER } from 'rxjs'; +import { mockActionResponse } from '../../__mocks__/action_result_data'; +import { ElasticAssistantRequestHandlerContext } from '../../types'; +import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import { coreMock } from '@kbn/core/server/mocks'; +import { + INVOKE_ASSISTANT_ERROR_EVENT, + INVOKE_ASSISTANT_SUCCESS_EVENT, +} from '../../lib/telemetry/event_based_telemetry'; +import { PassThrough } from 'stream'; +import { getConversationResponseMock } from '../../ai_assistant_data_clients/conversations/update_conversation.test'; +import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client/actions_client.mock'; +import { getFindAnonymizationFieldsResultWithSingleHit } from '../../__mocks__/response'; +import { defaultAssistantFeatures } from '@kbn/elastic-assistant-common'; +import { chatCompleteRoute } from './chat_complete_route'; +import { PublicMethodsOf } from '@kbn/utility-types'; +import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; + +const license = licensingMock.createLicenseMock(); + +const actionsClient = actionsClientMock.create(); +jest.mock('../../lib/build_response', () => ({ + buildResponse: jest.fn().mockImplementation((x) => x), +})); +const mockStream = jest.fn().mockImplementation(() => new PassThrough()); +jest.mock('../../lib/langchain/execute_custom_llm_chain', () => ({ + callAgentExecutor: jest.fn().mockImplementation( + async ({ + connectorId, + isStream, + onLlmResponse, + }: { + onLlmResponse: ( + content: string, + replacements: Record, + isError: boolean + ) => Promise; + actionsClient: PublicMethodsOf; + connectorId: string; + esClient: ElasticsearchClient; + langChainMessages: BaseMessage[]; + logger: Logger; + isStream: boolean; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + request: KibanaRequest; + }) => { + if (!isStream && connectorId === 'mock-connector-id') { + return { + connector_id: 'mock-connector-id', + data: mockActionResponse, + status: 'ok', + }; + } else if (isStream && connectorId === 'mock-connector-id') { + return mockStream; + } else { + onLlmResponse('simulated error', {}, true).catch(() => {}); + throw new Error('simulated error'); + } + } + ), +})); +const existingConversation = getConversationResponseMock(); +const reportEvent = jest.fn(); +const appendConversationMessages = jest.fn(); +const mockContext = { + resolve: jest.fn().mockResolvedValue({ + elasticAssistant: { + actions: { + getActionsClientWithRequest: jest.fn().mockResolvedValue(actionsClient), + }, + getRegisteredTools: jest.fn(() => []), + getRegisteredFeatures: jest.fn(() => defaultAssistantFeatures), + logger: loggingSystemMock.createLogger(), + telemetry: { ...coreMock.createSetup().analytics, reportEvent }, + getCurrentUser: () => ({ + username: 'user', + email: 'email', + fullName: 'full name', + roles: ['user-role'], + enabled: true, + authentication_realm: { name: 'native1', type: 'native' }, + lookup_realm: { name: 'native1', type: 'native' }, + authentication_provider: { type: 'basic', name: 'basic1' }, + authentication_type: 'realm', + elastic_cloud_user: false, + metadata: { _reserved: false }, + }), + getAIAssistantConversationsDataClient: jest.fn().mockResolvedValue({ + getConversation: jest.fn().mockResolvedValue(existingConversation), + updateConversation: jest.fn().mockResolvedValue(existingConversation), + createConversation: jest.fn().mockResolvedValue(existingConversation), + appendConversationMessages: + appendConversationMessages.mockResolvedValue(existingConversation), + }), + getAIAssistantAnonymizationFieldsDataClient: jest.fn().mockResolvedValue({ + findDocuments: jest.fn().mockResolvedValue(getFindAnonymizationFieldsResultWithSingleHit()), + }), + }, + core: { + elasticsearch: { + client: elasticsearchServiceMock.createScopedClusterClient(), + }, + savedObjects: coreMock.createRequestHandlerContext().savedObjects, + }, + licensing: { + ...licensingMock.createRequestHandlerContext({ license }), + license, + }, + }), +}; + +const mockRequest = { + body: { + conversationId: 'mock-conversation-id', + connectorId: 'mock-connector-id', + persist: true, + isEnabledKnowledgeBase: true, + isEnabledRAGAlerts: false, + model: 'gpt-4', + messages: [ + { + role: 'user', + content: + "Evaluate the event from the context and format your output neatly in markdown syntax for my Elastic Security case.\nAdd your description, recommended actions and bulleted triage steps. Use the MITRE ATT&CK data provided to add more context and recommendations from MITRE, and hyperlink to the relevant pages on MITRE's website. Be sure to include the user and host risk score data from the context. Your response should include steps that point to Elastic Security specific features, including endpoint response actions, the Elastic Agent OSQuery manager integration (with example osquery queries), timelines and entity analytics and link to all the relevant Elastic Security documentation.", + data: { + 'event.category': 'process', + 'process.pid': 69516, + 'host.os.version': 14.5, + 'host.os.name': 'macOS', + 'host.name': 'Yuliias-MBP', + 'process.name': 'biomesyncd', + 'user.name': 'yuliianaumenko', + 'process.working_directory': '/', + 'event.module': 'system', + 'process.executable': '/usr/libexec/biomesyncd', + 'process.args': '/usr/libexec/biomesyncd', + }, + }, + ], + }, + events: { + aborted$: NEVER, + }, +}; + +const mockResponse = { + ok: jest.fn().mockImplementation((x) => x), + error: jest.fn().mockImplementation((x) => x), +}; + +describe('chatCompleteRoute', () => { + const mockGetElser = jest.fn().mockResolvedValue('.elser_model_2'); + + beforeEach(() => { + jest.clearAllMocks(); + license.hasAtLeast.mockReturnValue(true); + actionsClient.execute.mockImplementation( + jest.fn().mockResolvedValue(() => ({ + data: 'mockChatCompletion', + status: 'ok', + })) + ); + actionsClient.getBulk.mockResolvedValue([ + { + id: '1', + isPreconfigured: false, + isSystemAction: false, + isDeprecated: false, + name: 'my name', + actionTypeId: '.gen-ai', + isMissingSecrets: false, + config: { + a: true, + b: true, + c: true, + }, + }, + ]); + }); + + it('returns the expected response when using the existingConversation', async () => { + const mockRouter = { + versioned: { + post: jest.fn().mockImplementation(() => { + return { + addVersion: jest.fn().mockImplementation(async (_, handler) => { + const result = await handler( + mockContext, + { + ...mockRequest, + body: { + ...mockRequest.body, + conversationId: existingConversation.id, + }, + }, + mockResponse + ); + + expect(result).toEqual({ + connector_id: 'mock-connector-id', + data: mockActionResponse, + status: 'ok', + }); + }), + }; + }), + }, + }; + + chatCompleteRoute( + mockRouter as unknown as IRouter, + mockGetElser + ); + }); + + it('returns the expected error when executeCustomLlmChain fails', async () => { + const requestWithBadConnectorId = { + ...mockRequest, + body: { + ...mockRequest.body, + connectorId: 'bad-connector-id', + }, + }; + + const mockRouter = { + versioned: { + post: jest.fn().mockImplementation(() => { + return { + addVersion: jest.fn().mockImplementation(async (_, handler) => { + const result = await handler(mockContext, requestWithBadConnectorId, mockResponse); + + expect(result).toEqual({ + body: 'simulated error', + statusCode: 500, + }); + }), + }; + }), + }, + }; + + await chatCompleteRoute( + mockRouter as unknown as IRouter, + mockGetElser + ); + }); + + it('reports success events to telemetry - kb on, RAG alerts off', async () => { + const mockRouter = { + versioned: { + post: jest.fn().mockImplementation(() => { + return { + addVersion: jest.fn().mockImplementation(async (_, handler) => { + await handler(mockContext, mockRequest, mockResponse); + + expect(reportEvent).toHaveBeenCalledWith(INVOKE_ASSISTANT_SUCCESS_EVENT.eventType, { + isEnabledKnowledgeBase: true, + isEnabledRAGAlerts: false, + actionTypeId: '.gen-ai', + model: 'gpt-4', + assistantStreamingEnabled: false, + }); + }), + }; + }), + }, + }; + + await chatCompleteRoute( + mockRouter as unknown as IRouter, + mockGetElser + ); + }); + + it('reports success events to telemetry - kb on, RAG alerts on', async () => { + const ragRequest = { + ...mockRequest, + body: { + ...mockRequest.body, + isEnabledRAGAlerts: true, + anonymizationFields: [ + { id: '@timestamp', field: '@timestamp', allowed: true, anonymized: false }, + { id: 'host.name', field: 'host.name', allowed: true, anonymized: true }, + ], + }, + }; + + const mockRouter = { + versioned: { + post: jest.fn().mockImplementation(() => { + return { + addVersion: jest.fn().mockImplementation(async (_, handler) => { + await handler(mockContext, ragRequest, mockResponse); + + expect(reportEvent).toHaveBeenCalledWith(INVOKE_ASSISTANT_SUCCESS_EVENT.eventType, { + isEnabledKnowledgeBase: true, + isEnabledRAGAlerts: true, + actionTypeId: '.gen-ai', + model: 'gpt-4', + assistantStreamingEnabled: false, + }); + }), + }; + }), + }, + }; + + await chatCompleteRoute( + mockRouter as unknown as IRouter, + mockGetElser + ); + }); + + it('reports error events to telemetry - kb on, RAG alerts off', async () => { + const requestWithBadConnectorId = { + ...mockRequest, + body: { + ...mockRequest.body, + connectorId: 'bad-connector-id', + }, + }; + + const mockRouter = { + versioned: { + post: jest.fn().mockImplementation(() => { + return { + addVersion: jest.fn().mockImplementation(async (_, handler) => { + await handler(mockContext, requestWithBadConnectorId, mockResponse); + + expect(reportEvent).toHaveBeenCalledWith(INVOKE_ASSISTANT_ERROR_EVENT.eventType, { + errorMessage: 'simulated error', + isEnabledKnowledgeBase: true, + isEnabledRAGAlerts: true, + actionTypeId: '.gen-ai', + model: 'gpt-4', + assistantStreamingEnabled: false, + }); + }), + }; + }), + }, + }; + + await chatCompleteRoute( + mockRouter as unknown as IRouter, + mockGetElser + ); + }); + + it('Adds error to conversation history', async () => { + const badRequest = { + ...mockRequest, + body: { + ...mockRequest.body, + conversationId: '99999', + connectorId: 'bad-connector-id', + }, + }; + + const mockRouter = { + versioned: { + post: jest.fn().mockImplementation(() => { + return { + addVersion: jest.fn().mockImplementation(async (_, handler) => { + await handler(mockContext, badRequest, mockResponse); + expect(appendConversationMessages.mock.calls[1][0].messages[0]).toEqual( + expect.objectContaining({ + content: 'simulated error', + isError: true, + role: 'assistant', + }) + ); + }), + }; + }), + }, + }; + + await chatCompleteRoute( + mockRouter as unknown as IRouter, + mockGetElser + ); + }); + + it('returns the expected response when isStream=true and actionTypeId=.gen-ai', async () => { + const mockRouter = { + versioned: { + post: jest.fn().mockImplementation(() => { + return { + addVersion: jest.fn().mockImplementation(async (_, handler) => { + const result = await handler( + mockContext, + { + ...mockRequest, + body: { + ...mockRequest.body, + isStream: true, + }, + }, + mockResponse + ); + + expect(result).toEqual(mockStream); + }), + }; + }), + }, + }; + + await chatCompleteRoute( + mockRouter as unknown as IRouter, + mockGetElser + ); + }); + + it('returns the expected response when isStream=true and actionTypeId=.bedrock', async () => { + const mockRouter = { + versioned: { + post: jest.fn().mockImplementation(() => { + return { + addVersion: jest.fn().mockImplementation(async (_, handler) => { + const result = await handler( + mockContext, + { + ...mockRequest, + body: { + ...mockRequest.body, + isStream: true, + }, + }, + mockResponse + ); + + expect(result).toEqual(mockStream); + }), + }; + }), + }, + }; + await chatCompleteRoute( + mockRouter as unknown as IRouter, + mockGetElser + ); + }); +}); diff --git a/x-pack/plugins/elastic_assistant/server/routes/chat/chat_complete_route.ts b/x-pack/plugins/elastic_assistant/server/routes/chat/chat_complete_route.ts new file mode 100644 index 0000000000000..10da330a36c79 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/chat/chat_complete_route.ts @@ -0,0 +1,249 @@ +/* + * 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 { transformError } from '@kbn/securitysolution-es-utils'; +import { Logger } from '@kbn/core/server'; +import { + ELASTIC_AI_ASSISTANT_CHAT_COMPLETE_URL, + ChatCompleteProps, + API_VERSIONS, + Message, + Replacements, + transformRawData, + getAnonymizedValue, + ConversationResponse, +} from '@kbn/elastic-assistant-common'; +import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common'; +import { getRequestAbortedSignal } from '@kbn/data-plugin/server'; +import { INVOKE_ASSISTANT_ERROR_EVENT } from '../../lib/telemetry/event_based_telemetry'; +import { ElasticAssistantPluginRouter, GetElser } from '../../types'; +import { buildResponse } from '../../lib/build_response'; +import { + DEFAULT_PLUGIN_NAME, + appendAssistantMessageToConversation, + createOrUpdateConversationWithUserInput, + getPluginNameFromRequest, + langChainExecute, + performChecks, +} from '../helpers'; +import { transformESSearchToAnonymizationFields } from '../../ai_assistant_data_clients/anonymization_fields/helpers'; +import { EsAnonymizationFieldsSchema } from '../../ai_assistant_data_clients/anonymization_fields/types'; + +export const SYSTEM_PROMPT_CONTEXT_NON_I18N = (context: string) => { + return `CONTEXT:\n"""\n${context}\n"""`; +}; + +export const chatCompleteRoute = ( + router: ElasticAssistantPluginRouter, + getElser: GetElser +): void => { + router.versioned + .post({ + access: 'public', + path: ELASTIC_AI_ASSISTANT_CHAT_COMPLETE_URL, + + options: { + tags: ['access:elasticAssistant'], + }, + }) + .addVersion( + { + version: API_VERSIONS.public.v1, + validate: { + request: { + body: buildRouteValidationWithZod(ChatCompleteProps), + }, + }, + }, + async (context, request, response) => { + const abortSignal = getRequestAbortedSignal(request.events.aborted$); + const assistantResponse = buildResponse(response); + let telemetry; + let actionTypeId; + try { + const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']); + const logger: Logger = ctx.elasticAssistant.logger; + telemetry = ctx.elasticAssistant.telemetry; + + // Perform license and authenticated user checks + const checkResponse = performChecks({ + authenticatedUser: true, + context: ctx, + license: true, + request, + response, + }); + if (checkResponse) { + return checkResponse; + } + + const conversationsDataClient = + await ctx.elasticAssistant.getAIAssistantConversationsDataClient(); + + const anonymizationFieldsDataClient = + await ctx.elasticAssistant.getAIAssistantAnonymizationFieldsDataClient(); + + let messages; + const conversationId = request.body.conversationId; + const connectorId = request.body.connectorId; + + let latestReplacements: Replacements = {}; + const onNewReplacements = (newReplacements: Replacements) => { + latestReplacements = { ...latestReplacements, ...newReplacements }; + }; + + // get the actions plugin start contract from the request context: + const actions = ctx.elasticAssistant.actions; + const actionsClient = await actions.getActionsClientWithRequest(request); + const connectors = await actionsClient.getBulk({ ids: [connectorId] }); + actionTypeId = connectors.length > 0 ? connectors[0].actionTypeId : '.gen-ai'; + + // replacements + const anonymizationFieldsRes = + await anonymizationFieldsDataClient?.findDocuments({ + perPage: 1000, + page: 1, + }); + + let anonymizationFields = anonymizationFieldsRes + ? transformESSearchToAnonymizationFields(anonymizationFieldsRes.data) + : undefined; + + // anonymize messages before sending to LLM + messages = request.body.messages.map((m) => { + let content = m.content ?? ''; + if (m.data) { + // includes/anonymize fields from the messages data + if (m.fields_to_anonymize && m.fields_to_anonymize.length > 0) { + anonymizationFields = anonymizationFields?.map((a) => { + if (m.fields_to_anonymize?.includes(a.field)) { + return { + ...a, + allowed: true, + anonymized: true, + }; + } + return a; + }); + } + const anonymizedData = transformRawData({ + anonymizationFields, + currentReplacements: latestReplacements, + getAnonymizedValue, + onNewReplacements, + rawData: Object.keys(m.data).reduce( + (obj, key) => ({ ...obj, [key]: [m.data ? m.data[key] : ''] }), + {} + ), + }); + const wr = `${SYSTEM_PROMPT_CONTEXT_NON_I18N(anonymizedData)}\n`; + content = `${wr}\n${m.content}`; + } + const transformedMessage = { + role: m.role, + content, + }; + return transformedMessage; + }); + + let updatedConversation: ConversationResponse | undefined | null; + // Fetch any tools registered by the request's originating plugin + const pluginName = getPluginNameFromRequest({ + request, + defaultPluginName: DEFAULT_PLUGIN_NAME, + logger, + }); + const enableKnowledgeBaseByDefault = + ctx.elasticAssistant.getRegisteredFeatures(pluginName).assistantKnowledgeBaseByDefault; + // TODO: remove non-graph persistance when KB will be enabled by default + if ( + (!enableKnowledgeBaseByDefault || (enableKnowledgeBaseByDefault && !conversationId)) && + request.body.persist && + conversationsDataClient + ) { + updatedConversation = await createOrUpdateConversationWithUserInput({ + actionsClient, + actionTypeId, + connectorId, + conversationId, + conversationsDataClient, + promptId: request.body.promptId, + logger, + replacements: latestReplacements, + newMessages: messages, + model: request.body.model, + }); + if (updatedConversation == null) { + return assistantResponse.error({ + body: `conversation id: "${conversationId}" not updated`, + statusCode: 400, + }); + } + // messages are anonymized by conversationsDataClient + messages = updatedConversation?.messages?.map((c) => ({ + role: c.role, + content: c.content, + })); + } + + const onLlmResponse = async ( + content: string, + traceData: Message['traceData'] = {}, + isError = false + ): Promise => { + if (updatedConversation?.id && conversationsDataClient) { + await appendAssistantMessageToConversation({ + conversationId: updatedConversation?.id, + conversationsDataClient, + messageContent: content, + replacements: latestReplacements, + isError, + traceData, + }); + } + }; + + return await langChainExecute({ + abortSignal, + isEnabledKnowledgeBase: true, + isStream: request.body.isStream ?? false, + actionsClient, + actionTypeId, + connectorId, + conversationId, + context: ctx, + getElser, + logger, + messages: messages ?? [], + onLlmResponse, + onNewReplacements, + replacements: latestReplacements, + request, + response, + telemetry, + responseLanguage: request.body.responseLanguage, + }); + } catch (err) { + const error = transformError(err as Error); + telemetry?.reportEvent(INVOKE_ASSISTANT_ERROR_EVENT.eventType, { + actionTypeId: actionTypeId ?? '', + isEnabledKnowledgeBase: true, + isEnabledRAGAlerts: true, + model: request.body.model, + errorMessage: error.message, + // TODO rm actionTypeId check when llmClass for bedrock streaming is implemented + // tracked here: https://github.com/elastic/security-team/issues/7363 + assistantStreamingEnabled: request.body.isStream ?? false, + }); + return assistantResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts b/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts index ef1950b5e90ad..990417b799234 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts @@ -192,7 +192,7 @@ export const postEvaluateRoute = ( agents.push({ agentEvaluator: async (langChainMessages, exampleId) => { const evalResult = await AGENT_EXECUTOR_MAP[agentName]({ - actions, + actionsClient, isEnabledKnowledgeBase: true, assistantTools, connectorId, @@ -237,9 +237,8 @@ export const postEvaluateRoute = ( evalModel == null || evalModel === '' ? undefined : new ActionsClientLlm({ - actions, + actionsClient, connectorId: evalModel, - request: skeletonRequest, logger, model: skeletonRequest.body.model, }); diff --git a/x-pack/plugins/elastic_assistant/server/routes/helpers.ts b/x-pack/plugins/elastic_assistant/server/routes/helpers.ts index 243de14d67ed3..aa060e24bc5df 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/helpers.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/helpers.ts @@ -5,15 +5,46 @@ * 2.0. */ -import { IKibanaResponse, KibanaRequest, KibanaResponseFactory } from '@kbn/core-http-server'; -import { Logger } from '@kbn/core/server'; -import { Message, TraceData } from '@kbn/elastic-assistant-common'; +import { + AnalyticsServiceSetup, + IKibanaResponse, + KibanaRequest, + KibanaResponseFactory, + Logger, +} from '@kbn/core/server'; +import { StreamResponseWithHeaders } from '@kbn/ml-response-stream/server'; + +import { + TraceData, + ConversationResponse, + ExecuteConnectorRequestBody, + Message, + Replacements, + replaceAnonymizedValuesWithOriginalValues, +} from '@kbn/elastic-assistant-common'; import { ILicense } from '@kbn/licensing-plugin/server'; -import { AwaitedProperties } from '@kbn/utility-types'; +import { i18n } from '@kbn/i18n'; +import { AwaitedProperties, PublicMethodsOf } from '@kbn/utility-types'; +import { ActionsClient } from '@kbn/actions-plugin/server'; import { AssistantFeatureKey } from '@kbn/elastic-assistant-common/impl/capabilities'; import { MINIMUM_AI_ASSISTANT_LICENSE } from '../../common/constants'; -import { ElasticAssistantRequestHandlerContext } from '../types'; -import { buildResponse } from './utils'; +import { ESQL_RESOURCE, KNOWLEDGE_BASE_INDEX_PATTERN } from './knowledge_base/constants'; +import { callAgentExecutor } from '../lib/langchain/execute_custom_llm_chain'; +import { buildResponse, getLlmType } from './utils'; +import { + AgentExecutorParams, + AssistantDataClients, + StaticReturnType, +} from '../lib/langchain/executors/types'; +import { executeAction, StaticResponse } from '../lib/executor'; +import { getLangChainMessages } from '../lib/langchain/helpers'; + +import { getLangSmithTracer } from './evaluate/utils'; +import { ElasticsearchStore } from '../lib/langchain/elasticsearch_store/elasticsearch_store'; +import { AIAssistantConversationsDataClient } from '../ai_assistant_data_clients/conversations'; +import { INVOKE_ASSISTANT_SUCCESS_EVENT } from '../lib/telemetry/event_based_telemetry'; +import { ElasticAssistantRequestHandlerContext, GetElser } from '../types'; +import { callAssistantGraph } from '../lib/langchain/graphs/default_assistant_graph'; interface GetPluginNameFromRequestParams { request: KibanaRequest; @@ -23,6 +54,10 @@ interface GetPluginNameFromRequestParams { export const DEFAULT_PLUGIN_NAME = 'securitySolutionUI'; +export const NEW_CHAT = i18n.translate('xpack.elasticAssistantPlugin.server.newChat', { + defaultMessage: 'New chat', +}); + /** * Attempts to extract the plugin name the request originated from using the request headers. * @@ -92,6 +127,495 @@ export const hasAIAssistantLicense = (license: ILicense): boolean => export const UPGRADE_LICENSE_MESSAGE = 'Your license does not support AI Assistant. Please upgrade your license.'; +export interface GenerateTitleForNewChatConversationParams { + message: Pick; + model?: string; + actionTypeId: string; + connectorId: string; + logger: Logger; + actionsClient: PublicMethodsOf; + responseLanguage?: string; +} +export const generateTitleForNewChatConversation = async ({ + message, + model, + actionTypeId, + connectorId, + logger, + actionsClient, + responseLanguage = 'English', +}: GenerateTitleForNewChatConversationParams) => { + try { + const autoTitle = (await executeAction({ + actionsClient, + connectorId, + actionTypeId, + params: { + subAction: 'invokeAI', + subActionParams: { + model, + messages: [ + { + role: 'system', + content: `You are a helpful assistant for Elastic Security. Assume the following message is the start of a conversation between you and a user; give this conversation a title based on the content below. DO NOT UNDER ANY CIRCUMSTANCES wrap this title in single or double quotes. This title is shown in a list of conversations to the user, so title it for the user, not for you. Please create the title in ${responseLanguage}.`, + }, + { + role: message.role, + content: message.content, + }, + ], + ...(actionTypeId === '.gen-ai' + ? { n: 1, stop: null, temperature: 0.2 } + : { temperature: 0, stopSequences: [] }), + }, + }, + logger, + })) as unknown as StaticResponse; // TODO: Use function overloads in executeAction to avoid this cast when sending subAction: 'invokeAI', + if (autoTitle.status === 'ok') { + // This regular expression captures a string enclosed in single or double quotes. + // It extracts the string content without the quotes. + // Example matches: + // - "Hello, World!" => Captures: Hello, World! + // - 'Another Example' => Captures: Another Example + // - JustTextWithoutQuotes => Captures: JustTextWithoutQuotes + const match = autoTitle.data.match(/^["']?([^"']+)["']?$/); + const title = match ? match[1] : autoTitle.data; + return title; + } + } catch (e) { + /* empty */ + } +}; + +export interface AppendMessageToConversationParams { + conversationsDataClient: AIAssistantConversationsDataClient; + messages: Array>; + replacements: Replacements; + conversation: ConversationResponse; +} +export const appendMessageToConversation = async ({ + conversationsDataClient, + messages, + replacements, + conversation, +}: AppendMessageToConversationParams) => { + const updatedConversation = await conversationsDataClient?.appendConversationMessages({ + existingConversation: conversation, + messages: messages.map((m) => ({ + ...{ + content: replaceAnonymizedValuesWithOriginalValues({ + messageContent: m.content, + replacements, + }), + role: m.role ?? 'user', + }, + timestamp: new Date().toISOString(), + })), + }); + return updatedConversation; +}; + +export interface AppendAssistantMessageToConversationParams { + conversationsDataClient: AIAssistantConversationsDataClient; + messageContent: string; + replacements: Replacements; + conversationId: string; + isError?: boolean; + traceData?: Message['traceData']; +} +export const appendAssistantMessageToConversation = async ({ + conversationsDataClient, + messageContent, + replacements, + conversationId, + isError = false, + traceData = {}, +}: AppendAssistantMessageToConversationParams) => { + const conversation = await conversationsDataClient.getConversation({ id: conversationId }); + if (!conversation) { + return; + } + + await conversationsDataClient.appendConversationMessages({ + existingConversation: conversation, + messages: [ + getMessageFromRawResponse({ + rawContent: replaceAnonymizedValuesWithOriginalValues({ + messageContent, + replacements, + }), + traceData, + isError, + }), + ], + }); + if (Object.keys(replacements).length > 0) { + await conversationsDataClient?.updateConversation({ + conversationUpdateProps: { + id: conversation.id, + replacements, + }, + }); + } +}; + +export interface NonLangChainExecuteParams { + request: KibanaRequest; + messages: Array>; + abortSignal: AbortSignal; + actionTypeId: string; + connectorId: string; + logger: Logger; + actionsClient: PublicMethodsOf; + onLlmResponse?: ( + content: string, + traceData?: Message['traceData'], + isError?: boolean + ) => Promise; + response: KibanaResponseFactory; + telemetry: AnalyticsServiceSetup; +} +export const nonLangChainExecute = async ({ + messages, + abortSignal, + actionTypeId, + connectorId, + logger, + actionsClient, + onLlmResponse, + response, + request, + telemetry, +}: NonLangChainExecuteParams) => { + logger.debug('Executing via actions framework directly'); + const result = await executeAction({ + abortSignal, + onLlmResponse, + actionsClient, + connectorId, + actionTypeId, + params: { + subAction: request.body.subAction, + subActionParams: { + model: request.body.model, + messages, + ...(actionTypeId === '.gen-ai' + ? { n: 1, stop: null, temperature: 0.2 } + : { temperature: 0, stopSequences: [] }), + }, + }, + logger, + }); + + telemetry.reportEvent(INVOKE_ASSISTANT_SUCCESS_EVENT.eventType, { + actionTypeId, + isEnabledKnowledgeBase: request.body.isEnabledKnowledgeBase, + isEnabledRAGAlerts: request.body.isEnabledRAGAlerts, + model: request.body.model, + assistantStreamingEnabled: request.body.subAction !== 'invokeAI', + }); + return response.ok({ + body: result, + ...(request.body.subAction === 'invokeAI' + ? { headers: { 'content-type': 'application/json' } } + : {}), + }); +}; + +export interface LangChainExecuteParams { + messages: Array>; + replacements: Replacements; + isEnabledKnowledgeBase: boolean; + isStream?: boolean; + onNewReplacements: (newReplacements: Replacements) => void; + abortSignal: AbortSignal; + telemetry: AnalyticsServiceSetup; + actionTypeId: string; + connectorId: string; + conversationId?: string; + context: AwaitedProperties< + Pick + >; + actionsClient: PublicMethodsOf; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + request: KibanaRequest; + logger: Logger; + onLlmResponse?: ( + content: string, + traceData?: Message['traceData'], + isError?: boolean + ) => Promise; + getElser: GetElser; + response: KibanaResponseFactory; + responseLanguage?: string; +} +export const langChainExecute = async ({ + messages, + replacements, + onNewReplacements, + isEnabledKnowledgeBase, + abortSignal, + telemetry, + actionTypeId, + connectorId, + context, + actionsClient, + request, + logger, + conversationId, + onLlmResponse, + getElser, + response, + responseLanguage, + isStream = true, +}: LangChainExecuteParams) => { + // TODO: Add `traceId` to actions request when calling via langchain + logger.debug( + `Executing via langchain, isEnabledKnowledgeBase: ${isEnabledKnowledgeBase}, isEnabledRAGAlerts: ${request.body.isEnabledRAGAlerts}` + ); + // Fetch any tools registered by the request's originating plugin + const pluginName = getPluginNameFromRequest({ + request, + defaultPluginName: DEFAULT_PLUGIN_NAME, + logger, + }); + const assistantContext = context.elasticAssistant; + const assistantTools = assistantContext + .getRegisteredTools(pluginName) + .filter((x) => x.id !== 'attack-discovery'); // We don't (yet) support asking the assistant for NEW attack discoveries from a conversation + + // get a scoped esClient for assistant memory + const esClient = context.core.elasticsearch.client.asCurrentUser; + + // convert the assistant messages to LangChain messages: + const langChainMessages = getLangChainMessages(messages); + + const elserId = await getElser(); + + const anonymizationFieldsDataClient = + await assistantContext.getAIAssistantAnonymizationFieldsDataClient(); + const conversationsDataClient = await assistantContext.getAIAssistantConversationsDataClient(); + + // Create an ElasticsearchStore for KB interactions + // Setup with kbDataClient if `assistantKnowledgeBaseByDefault` FF is enabled + const enableKnowledgeBaseByDefault = + assistantContext.getRegisteredFeatures(pluginName).assistantKnowledgeBaseByDefault; + const kbDataClient = enableKnowledgeBaseByDefault + ? (await assistantContext.getAIAssistantKnowledgeBaseDataClient(false)) ?? undefined + : undefined; + const kbIndex = + enableKnowledgeBaseByDefault && kbDataClient != null + ? kbDataClient.indexTemplateAndPattern.alias + : KNOWLEDGE_BASE_INDEX_PATTERN; + const esStore = new ElasticsearchStore( + esClient, + kbIndex, + logger, + telemetry, + elserId, + ESQL_RESOURCE, + kbDataClient + ); + + const dataClients: AssistantDataClients = { + anonymizationFieldsDataClient: anonymizationFieldsDataClient ?? undefined, + conversationsDataClient: conversationsDataClient ?? undefined, + kbDataClient, + }; + + // Shared executor params + const executorParams: AgentExecutorParams = { + abortSignal, + dataClients, + alertsIndexPattern: request.body.alertsIndexPattern, + actionsClient, + isEnabledKnowledgeBase, + assistantTools, + conversationId, + connectorId, + esClient, + esStore, + isStream, + llmType: getLlmType(actionTypeId), + langChainMessages, + logger, + onNewReplacements, + onLlmResponse, + request, + replacements, + responseLanguage, + size: request.body.size, + traceOptions: { + projectName: request.body.langSmithProject, + tracers: getLangSmithTracer({ + apiKey: request.body.langSmithApiKey, + projectName: request.body.langSmithProject, + logger, + }), + }, + }; + + // New code path for LangGraph implementation, behind `assistantKnowledgeBaseByDefault` FF + let result: StreamResponseWithHeaders | StaticReturnType; + if (enableKnowledgeBaseByDefault && request.body.isEnabledKnowledgeBase) { + result = await callAssistantGraph(executorParams); + } else { + result = await callAgentExecutor(executorParams); + } + + telemetry.reportEvent(INVOKE_ASSISTANT_SUCCESS_EVENT.eventType, { + actionTypeId, + isEnabledKnowledgeBase, + isEnabledRAGAlerts: request.body.isEnabledRAGAlerts ?? true, + model: request.body.model, + // TODO rm actionTypeId check when llmClass for bedrock streaming is implemented + // tracked here: https://github.com/elastic/security-team/issues/7363 + assistantStreamingEnabled: isStream && actionTypeId === '.gen-ai', + }); + return response.ok(result); +}; + +export interface CreateOrUpdateConversationWithParams { + logger: Logger; + conversationsDataClient: AIAssistantConversationsDataClient; + replacements: Replacements; + conversationId?: string; + promptId?: string; + actionTypeId: string; + connectorId: string; + actionsClient: PublicMethodsOf; + newMessages?: Array>; + model?: string; + responseLanguage?: string; +} +export const createOrUpdateConversationWithUserInput = async ({ + logger, + conversationsDataClient, + replacements, + conversationId, + actionTypeId, + promptId, + connectorId, + actionsClient, + newMessages, + model, + responseLanguage, +}: CreateOrUpdateConversationWithParams) => { + if (!conversationId) { + if (newMessages && newMessages.length > 0) { + const title = await generateTitleForNewChatConversation({ + message: newMessages[0], + actionsClient, + actionTypeId, + connectorId, + logger, + model, + responseLanguage, + }); + if (title) { + return conversationsDataClient.createConversation({ + conversation: { + title, + messages: newMessages.map((m) => ({ + content: m.content, + role: m.role, + timestamp: new Date().toISOString(), + })), + replacements, + apiConfig: { + connectorId, + actionTypeId, + model, + defaultSystemPromptId: promptId, + }, + }, + }); + } + } + return; + } + return updateConversationWithUserInput({ + actionsClient, + actionTypeId, + connectorId, + conversationId, + conversationsDataClient, + logger, + replacements, + newMessages, + model, + }); +}; + +export interface UpdateConversationWithParams { + logger: Logger; + conversationsDataClient: AIAssistantConversationsDataClient; + replacements: Replacements; + conversationId: string; + actionTypeId: string; + connectorId: string; + actionsClient: PublicMethodsOf; + newMessages?: Array>; + model?: string; +} +export const updateConversationWithUserInput = async ({ + logger, + conversationsDataClient, + replacements, + conversationId, + actionTypeId, + connectorId, + actionsClient, + newMessages, + model, +}: UpdateConversationWithParams) => { + const conversation = await conversationsDataClient?.getConversation({ + id: conversationId, + }); + if (conversation == null) { + throw new Error(`conversation id: "${conversationId}" not found`); + } + let updatedConversation = conversation; + + const messages = updatedConversation?.messages?.map((c) => ({ + role: c.role, + content: c.content, + timestamp: c.timestamp, + })); + + const lastMessage = newMessages?.[0] ?? messages?.[0]; + + if (conversation?.title === NEW_CHAT && lastMessage) { + const title = await generateTitleForNewChatConversation({ + message: lastMessage, + actionsClient, + actionTypeId, + connectorId, + logger, + model, + }); + const res = await conversationsDataClient.updateConversation({ + conversationUpdateProps: { + id: conversationId, + title, + }, + }); + if (res) { + updatedConversation = res; + } + } + + if (newMessages) { + return appendMessageToConversation({ + conversation: updatedConversation, + conversationsDataClient, + messages: newMessages, + replacements, + }); + } + return updatedConversation; +}; + interface PerformChecksParams { authenticatedUser?: boolean; capability?: AssistantFeatureKey; diff --git a/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.test.ts b/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.test.ts index 5ee8d8e83c846..91c2cdf18aa9d 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.test.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.test.ts @@ -24,7 +24,9 @@ import { getConversationResponseMock } from '../ai_assistant_data_clients/conver import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client/actions_client.mock'; import { getFindAnonymizationFieldsResultWithSingleHit } from '../__mocks__/response'; import { defaultAssistantFeatures } from '@kbn/elastic-assistant-common'; +import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; +const license = licensingMock.createLicenseMock(); const actionsClient = actionsClientMock.create(); jest.mock('../lib/build_response', () => ({ buildResponse: jest.fn().mockImplementation((x) => x), @@ -88,43 +90,49 @@ const existingConversation = getConversationResponseMock(); const reportEvent = jest.fn(); const appendConversationMessages = jest.fn(); const mockContext = { - elasticAssistant: { - actions: { - getActionsClientWithRequest: jest.fn().mockResolvedValue(actionsClient), + resolve: jest.fn().mockResolvedValue({ + elasticAssistant: { + actions: { + getActionsClientWithRequest: jest.fn().mockResolvedValue(actionsClient), + }, + getRegisteredTools: jest.fn(() => []), + getRegisteredFeatures: jest.fn(() => defaultAssistantFeatures), + logger: loggingSystemMock.createLogger(), + telemetry: { ...coreMock.createSetup().analytics, reportEvent }, + getCurrentUser: () => ({ + username: 'user', + email: 'email', + fullName: 'full name', + roles: ['user-role'], + enabled: true, + authentication_realm: { name: 'native1', type: 'native' }, + lookup_realm: { name: 'native1', type: 'native' }, + authentication_provider: { type: 'basic', name: 'basic1' }, + authentication_type: 'realm', + elastic_cloud_user: false, + metadata: { _reserved: false }, + }), + getAIAssistantConversationsDataClient: jest.fn().mockResolvedValue({ + getConversation: jest.fn().mockResolvedValue(existingConversation), + updateConversation: jest.fn().mockResolvedValue(existingConversation), + appendConversationMessages: + appendConversationMessages.mockResolvedValue(existingConversation), + }), + getAIAssistantAnonymizationFieldsDataClient: jest.fn().mockResolvedValue({ + findDocuments: jest.fn().mockResolvedValue(getFindAnonymizationFieldsResultWithSingleHit()), + }), }, - getRegisteredTools: jest.fn(() => []), - getRegisteredFeatures: jest.fn(() => defaultAssistantFeatures), - logger: loggingSystemMock.createLogger(), - telemetry: { ...coreMock.createSetup().analytics, reportEvent }, - getCurrentUser: () => ({ - username: 'user', - email: 'email', - fullName: 'full name', - roles: ['user-role'], - enabled: true, - authentication_realm: { name: 'native1', type: 'native' }, - lookup_realm: { name: 'native1', type: 'native' }, - authentication_provider: { type: 'basic', name: 'basic1' }, - authentication_type: 'realm', - elastic_cloud_user: false, - metadata: { _reserved: false }, - }), - getAIAssistantConversationsDataClient: jest.fn().mockResolvedValue({ - getConversation: jest.fn().mockResolvedValue(existingConversation), - updateConversation: jest.fn().mockResolvedValue(existingConversation), - appendConversationMessages: - appendConversationMessages.mockResolvedValue(existingConversation), - }), - getAIAssistantAnonymizationFieldsDataClient: jest.fn().mockResolvedValue({ - findDocuments: jest.fn().mockResolvedValue(getFindAnonymizationFieldsResultWithSingleHit()), - }), - }, - core: { - elasticsearch: { - client: elasticsearchServiceMock.createScopedClusterClient(), + core: { + elasticsearch: { + client: elasticsearchServiceMock.createScopedClusterClient(), + }, + savedObjects: coreMock.createRequestHandlerContext().savedObjects, }, - savedObjects: coreMock.createRequestHandlerContext().savedObjects, - }, + licensing: { + ...licensingMock.createRequestHandlerContext({ license }), + license, + }, + }), }; const mockRequest = { @@ -153,6 +161,7 @@ describe('postActionsConnectorExecuteRoute', () => { beforeEach(() => { jest.clearAllMocks(); + license.hasAtLeast.mockReturnValue(true); actionsClient.getBulk.mockResolvedValue([ { id: '1', @@ -195,6 +204,7 @@ describe('postActionsConnectorExecuteRoute', () => { data: mockActionResponse, status: 'ok', }, + headers: { 'content-type': 'application/json' }, }); }), }; diff --git a/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts b/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts index 197479fc24dd5..af095dbb47343 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts @@ -8,7 +8,6 @@ import { IRouter, Logger } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; import { getRequestAbortedSignal } from '@kbn/data-plugin/server'; -import { StreamResponseWithHeaders } from '@kbn/ml-response-stream/server'; import { schema } from '@kbn/config-schema'; import { @@ -16,37 +15,20 @@ import { ExecuteConnectorRequestBody, Message, Replacements, - replaceAnonymizedValuesWithOriginalValues, } from '@kbn/elastic-assistant-common'; import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common'; -import { i18n } from '@kbn/i18n'; -import { getLlmType } from './utils'; -import { - AgentExecutorParams, - AssistantDataClients, - StaticReturnType, -} from '../lib/langchain/executors/types'; -import { - INVOKE_ASSISTANT_ERROR_EVENT, - INVOKE_ASSISTANT_SUCCESS_EVENT, -} from '../lib/telemetry/event_based_telemetry'; -import { executeAction, StaticResponse } from '../lib/executor'; +import { INVOKE_ASSISTANT_ERROR_EVENT } from '../lib/telemetry/event_based_telemetry'; import { POST_ACTIONS_CONNECTOR_EXECUTE } from '../../common/constants'; -import { getLangChainMessages } from '../lib/langchain/helpers'; import { buildResponse } from '../lib/build_response'; import { ElasticAssistantRequestHandlerContext, GetElser } from '../types'; -import { ESQL_RESOURCE, KNOWLEDGE_BASE_INDEX_PATTERN } from './knowledge_base/constants'; -import { callAgentExecutor } from '../lib/langchain/execute_custom_llm_chain'; import { DEFAULT_PLUGIN_NAME, - getMessageFromRawResponse, + appendAssistantMessageToConversation, getPluginNameFromRequest, + langChainExecute, + nonLangChainExecute, + updateConversationWithUserInput, } from './helpers'; -import { getLangSmithTracer } from './evaluate/utils'; -import { EsAnonymizationFieldsSchema } from '../ai_assistant_data_clients/anonymization_fields/types'; -import { transformESSearchToAnonymizationFields } from '../ai_assistant_data_clients/anonymization_fields/helpers'; -import { ElasticsearchStore } from '../lib/langchain/elasticsearch_store/elasticsearch_store'; -import { callAssistantGraph } from '../lib/langchain/graphs/default_assistant_graph'; export const postActionsConnectorExecuteRoute = ( router: IRouter, @@ -76,7 +58,8 @@ export const postActionsConnectorExecuteRoute = ( const abortSignal = getRequestAbortedSignal(request.events.aborted$); const resp = buildResponse(response); - const assistantContext = await context.elasticAssistant; + const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']); + const assistantContext = ctx.elasticAssistant; const logger: Logger = assistantContext.logger; const telemetry = assistantContext.telemetry; let onLlmResponse; @@ -88,23 +71,16 @@ export const postActionsConnectorExecuteRoute = ( body: `Authenticated user not found`, }); } - const conversationsDataClient = - await assistantContext.getAIAssistantConversationsDataClient(); - - const anonymizationFieldsDataClient = - await assistantContext.getAIAssistantAnonymizationFieldsDataClient(); - let latestReplacements: Replacements = request.body.replacements; const onNewReplacements = (newReplacements: Replacements) => { latestReplacements = { ...latestReplacements, ...newReplacements }; }; - let prevMessages; + let messages; let newMessage: Pick | undefined; const conversationId = request.body.conversationId; const actionTypeId = request.body.actionTypeId; - const langSmithProject = request.body.langSmithProject; - const langSmithApiKey = request.body.langSmithApiKey; + const connectorId = decodeURIComponent(request.params.connectorId); // if message is undefined, it means the user is regenerating a message from the stored conversation if (request.body.message) { @@ -114,303 +90,100 @@ export const postActionsConnectorExecuteRoute = ( }; } - const connectorId = decodeURIComponent(request.params.connectorId); - // get the actions plugin start contract from the request context: - const actions = (await context.elasticAssistant).actions; + const actions = ctx.elasticAssistant.actions; + const actionsClient = await actions.getActionsClientWithRequest(request); - if (conversationId) { - const conversation = await conversationsDataClient?.getConversation({ - id: conversationId, - authenticatedUser, + const conversationsDataClient = + await assistantContext.getAIAssistantConversationsDataClient(); + + // Fetch any tools registered by the request's originating plugin + const pluginName = getPluginNameFromRequest({ + request, + defaultPluginName: DEFAULT_PLUGIN_NAME, + logger, + }); + const isGraphAvailable = + assistantContext.getRegisteredFeatures(pluginName).assistantKnowledgeBaseByDefault && + request.body.isEnabledKnowledgeBase; + + // TODO: remove non-graph persistance when KB will be enabled by default + if (!isGraphAvailable && conversationId && conversationsDataClient) { + const updatedConversation = await updateConversationWithUserInput({ + actionsClient, + actionTypeId, + connectorId, + conversationId, + conversationsDataClient, + logger, + replacements: latestReplacements, + newMessages: newMessage ? [newMessage] : [], + model: request.body.model, }); - if (conversation == null) { - return response.notFound({ - body: `conversation id: "${conversationId}" not found`, + if (updatedConversation == null) { + return response.badRequest({ + body: `conversation id: "${conversationId}" not updated`, }); } - // messages are anonymized by conversationsDataClient - prevMessages = conversation?.messages?.map((c) => ({ + messages = updatedConversation?.messages?.map((c) => ({ role: c.role, content: c.content, })); + } - if (request.body.message) { - const res = await conversationsDataClient?.appendConversationMessages({ - existingConversation: conversation, - messages: [ - { - ...{ - content: replaceAnonymizedValuesWithOriginalValues({ - messageContent: request.body.message, - replacements: request.body.replacements, - }), - role: 'user', - }, - timestamp: new Date().toISOString(), - }, - ], - }); - - if (res == null) { - return response.badRequest({ - body: `conversation id: "${conversationId}" not updated`, - }); - } - } - const updatedConversation = await conversationsDataClient?.getConversation({ - id: conversationId, - authenticatedUser, - }); - - if (updatedConversation == null) { - return response.notFound({ - body: `conversation id: "${conversationId}" not found`, + onLlmResponse = async ( + content: string, + traceData: Message['traceData'] = {}, + isError = false + ): Promise => { + if (conversationsDataClient && conversationId) { + await appendAssistantMessageToConversation({ + conversationId, + conversationsDataClient, + messageContent: content, + replacements: latestReplacements, + isError, + traceData, }); } + }; - const NEW_CHAT = i18n.translate('xpack.elasticAssistantPlugin.server.newChat', { - defaultMessage: 'New chat', - }); - if (conversation?.title === NEW_CHAT && prevMessages) { - try { - const autoTitle = (await executeAction({ - actions, - request, - connectorId, - actionTypeId, - params: { - subAction: 'invokeAI', - subActionParams: { - model: request.body.model, - messages: [ - { - role: 'system', - content: i18n.translate( - 'xpack.elasticAssistantPlugin.server.autoTitlePromptDescription', - { - defaultMessage: - 'You are a helpful assistant for Elastic Security. Assume the following message is the start of a conversation between you and a user; give this conversation a title based on the content below. DO NOT UNDER ANY CIRCUMSTANCES wrap this title in single or double quotes. This title is shown in a list of conversations to the user, so title it for the user, not for you.', - } - ), - }, - newMessage ?? prevMessages?.[0], - ], - ...(actionTypeId === '.gen-ai' - ? { n: 1, stop: null, temperature: 0.2 } - : { temperature: 0, stopSequences: [] }), - }, - }, - logger, - })) as unknown as StaticResponse; // TODO: Use function overloads in executeAction to avoid this cast when sending subAction: 'invokeAI', - if (autoTitle.status === 'ok') { - try { - // This regular expression captures a string enclosed in single or double quotes. - // It extracts the string content without the quotes. - // Example matches: - // - "Hello, World!" => Captures: Hello, World! - // - 'Another Example' => Captures: Another Example - // - JustTextWithoutQuotes => Captures: JustTextWithoutQuotes - const match = autoTitle.data.match(/^["']?([^"']+)["']?$/); - const title = match ? match[1] : autoTitle.data; - - await conversationsDataClient?.updateConversation({ - conversationUpdateProps: { - id: conversationId, - title, - }, - }); - } catch (e) { - logger.warn(`Failed to update conversation with generated title: ${e.message}`); - } - } - } catch (e) { - /* empty */ - } - } - - onLlmResponse = async ( - content: string, - traceData: Message['traceData'] = {}, - isError = false - ): Promise => { - if (updatedConversation) { - await conversationsDataClient?.appendConversationMessages({ - existingConversation: updatedConversation, - messages: [ - getMessageFromRawResponse({ - rawContent: replaceAnonymizedValuesWithOriginalValues({ - messageContent: content, - replacements: latestReplacements, - }), - traceData, - isError, - }), - ], - }); - } - if (Object.keys(latestReplacements).length > 0) { - await conversationsDataClient?.updateConversation({ - conversationUpdateProps: { - id: conversationId, - replacements: latestReplacements, - }, - }); - } - }; - } - - // if not langchain, call execute action directly and return the response: if (!request.body.isEnabledKnowledgeBase && !request.body.isEnabledRAGAlerts) { - logger.debug('Executing via actions framework directly'); - - const result = await executeAction({ + // if not langchain, call execute action directly and return the response: + return await nonLangChainExecute({ abortSignal, - onLlmResponse, - actions, - request, - connectorId, + actionsClient, actionTypeId, - params: { - subAction: request.body.subAction, - subActionParams: { - model: request.body.model, - messages: [...(prevMessages ?? []), ...(newMessage ? [newMessage] : [])], - ...(actionTypeId === '.gen-ai' - ? { n: 1, stop: null, temperature: 0.2 } - : { temperature: 0, stopSequences: [] }), - }, - }, + connectorId, logger, - }); - - telemetry.reportEvent(INVOKE_ASSISTANT_SUCCESS_EVENT.eventType, { - actionTypeId, - isEnabledKnowledgeBase: request.body.isEnabledKnowledgeBase, - isEnabledRAGAlerts: request.body.isEnabledRAGAlerts, - model: request.body.model, - assistantStreamingEnabled: request.body.subAction !== 'invokeAI', - }); - return response.ok({ - body: result, + messages: messages ?? [], + onLlmResponse, + request, + response, + telemetry, }); } - // TODO: Add `traceId` to actions request when calling via langchain - logger.debug( - `Executing via langchain, isEnabledKnowledgeBase: ${request.body.isEnabledKnowledgeBase}, isEnabledRAGAlerts: ${request.body.isEnabledRAGAlerts}` - ); - - // Fetch any tools registered by the request's originating plugin - const pluginName = getPluginNameFromRequest({ - request, - defaultPluginName: DEFAULT_PLUGIN_NAME, - logger, - }); - const assistantTools = (await context.elasticAssistant) - .getRegisteredTools(pluginName) - .filter((x) => x.id !== 'attack-discovery'); // We don't (yet) support asking the assistant for NEW attack discoveries from a conversation - - // get a scoped esClient for assistant memory - const esClient = (await context.core).elasticsearch.client.asCurrentUser; - - // convert the assistant messages to LangChain messages: - const langChainMessages = getLangChainMessages( - ([...(prevMessages ?? []), ...(newMessage ? [newMessage] : [])] ?? - []) as unknown as Array> - ); - - const elserId = await getElser(); - - const anonymizationFieldsRes = - await anonymizationFieldsDataClient?.findDocuments({ - perPage: 1000, - page: 1, - }); - - // Create an ElasticsearchStore for KB interactions - // Setup with kbDataClient if `assistantKnowledgeBaseByDefault` FF is enabled - const enableKnowledgeBaseByDefault = - assistantContext.getRegisteredFeatures(pluginName).assistantKnowledgeBaseByDefault; - const kbDataClient = enableKnowledgeBaseByDefault - ? (await assistantContext.getAIAssistantKnowledgeBaseDataClient(false)) ?? undefined - : undefined; - const kbIndex = - enableKnowledgeBaseByDefault && kbDataClient != null - ? kbDataClient.indexTemplateAndPattern.alias - : KNOWLEDGE_BASE_INDEX_PATTERN; - const esStore = new ElasticsearchStore( - esClient, - kbIndex, - logger, - telemetry, - elserId, - ESQL_RESOURCE, - kbDataClient - ); - - const dataClients: AssistantDataClients = { - anonymizationFieldsDataClient: anonymizationFieldsDataClient ?? undefined, - conversationsDataClient: conversationsDataClient ?? undefined, - kbDataClient, - }; - - // Shared executor params - const executorParams: AgentExecutorParams = { + return await langChainExecute({ abortSignal, - alertsIndexPattern: request.body.alertsIndexPattern, - anonymizationFields: anonymizationFieldsRes - ? transformESSearchToAnonymizationFields(anonymizationFieldsRes.data) - : undefined, - actions, + isStream: request.body.subAction !== 'invokeAI', isEnabledKnowledgeBase: request.body.isEnabledKnowledgeBase ?? false, - assistantTools, + actionsClient, + actionTypeId, connectorId, conversationId, - dataClients, - esClient, - esStore, - isStream: request.body.subAction !== 'invokeAI', - llmType: getLlmType(actionTypeId), - langChainMessages, + context: ctx, + getElser, logger, - onNewReplacements, + messages: (isGraphAvailable && newMessage ? [newMessage] : messages) ?? [], onLlmResponse, + onNewReplacements, + replacements: latestReplacements, request, response, - replacements: request.body.replacements, - size: request.body.size, - traceOptions: { - projectName: langSmithProject, - tracers: getLangSmithTracer({ - apiKey: langSmithApiKey, - projectName: langSmithProject, - logger, - }), - }, - }; - - // New code path for LangGraph implementation, behind `assistantKnowledgeBaseByDefault` FF - let result: StreamResponseWithHeaders | StaticReturnType; - if (enableKnowledgeBaseByDefault) { - result = await callAssistantGraph(executorParams); - } else { - result = await callAgentExecutor(executorParams); - } - - telemetry.reportEvent(INVOKE_ASSISTANT_SUCCESS_EVENT.eventType, { - actionTypeId, - isEnabledKnowledgeBase: request.body.isEnabledKnowledgeBase, - isEnabledRAGAlerts: request.body.isEnabledRAGAlerts, - model: request.body.model, - // TODO rm actionTypeId check when llmClass for bedrock streaming is implemented - // tracked here: https://github.com/elastic/security-team/issues/7363 - assistantStreamingEnabled: - request.body.subAction !== 'invokeAI' && actionTypeId === '.gen-ai', + telemetry, }); - - return response.ok(result); } catch (err) { logger.error(err); const error = transformError(err); diff --git a/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts b/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts index f4da7f9f1803a..bab389d514b7e 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts @@ -23,12 +23,13 @@ import { getKnowledgeBaseStatusRoute } from './knowledge_base/get_knowledge_base import { postKnowledgeBaseRoute } from './knowledge_base/post_knowledge_base'; import { getEvaluateRoute } from './evaluate/get_evaluate'; import { postEvaluateRoute } from './evaluate/post_evaluate'; -import { postActionsConnectorExecuteRoute } from './post_actions_connector_execute'; import { getCapabilitiesRoute } from './capabilities/get_capabilities_route'; import { bulkPromptsRoute } from './prompts/bulk_actions_route'; import { findPromptsRoute } from './prompts/find_route'; import { bulkActionAnonymizationFieldsRoute } from './anonymization_fields/bulk_actions_route'; import { findAnonymizationFieldsRoute } from './anonymization_fields/find_route'; +import { chatCompleteRoute } from './chat/chat_complete_route'; +import { postActionsConnectorExecuteRoute } from './post_actions_connector_execute'; import { bulkActionKnowledgeBaseEntriesRoute } from './knowledge_base/entries/bulk_actions_route'; import { createKnowledgeBaseEntryRoute } from './knowledge_base/entries/create_route'; import { findKnowledgeBaseEntriesRoute } from './knowledge_base/entries/find_route'; @@ -38,6 +39,11 @@ export const registerRoutes = ( logger: Logger, getElserId: GetElser ) => { + /** PUBLIC */ + // Chat + chatCompleteRoute(router, getElserId); + + /** INTERNAL */ // Capabilities getCapabilitiesRoute(router); diff --git a/x-pack/plugins/elastic_assistant/server/routes/user_conversations/create_route.ts b/x-pack/plugins/elastic_assistant/server/routes/user_conversations/create_route.ts index e66c83f77510d..9c02586a60b88 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/user_conversations/create_route.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/user_conversations/create_route.ts @@ -16,7 +16,7 @@ import { import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common'; import { ElasticAssistantPluginRouter } from '../../types'; import { buildResponse } from '../utils'; -import { UPGRADE_LICENSE_MESSAGE, hasAIAssistantLicense } from '../helpers'; +import { performChecks } from '../helpers'; export const createConversationRoute = (router: ElasticAssistantPluginRouter): void => { router.versioned @@ -41,27 +41,25 @@ export const createConversationRoute = (router: ElasticAssistantPluginRouter): v const assistantResponse = buildResponse(response); try { const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']); - const license = ctx.licensing.license; - if (!hasAIAssistantLicense(license)) { - return response.forbidden({ - body: { - message: UPGRADE_LICENSE_MESSAGE, - }, - }); + // Perform license and authenticated user checks + const checkResponse = performChecks({ + authenticatedUser: true, + context: ctx, + license: true, + request, + response, + }); + if (checkResponse) { + return checkResponse; } const dataClient = await ctx.elasticAssistant.getAIAssistantConversationsDataClient(); - const authenticatedUser = ctx.elasticAssistant.getCurrentUser(); - if (authenticatedUser == null) { - return assistantResponse.error({ - body: `Authenticated user not found`, - statusCode: 401, - }); - } const result = await dataClient?.findDocuments({ perPage: 100, page: 1, - filter: `users:{ id: "${authenticatedUser?.profile_uid}" } AND title:${request.body.title}`, + filter: `users:{ id: "${ + ctx.elasticAssistant.getCurrentUser()?.profile_uid + }" } AND title:${request.body.title}`, fields: ['title'], }); if (result?.data != null && result.total > 0) { @@ -73,7 +71,6 @@ export const createConversationRoute = (router: ElasticAssistantPluginRouter): v const createdConversation = await dataClient?.createConversation({ conversation: request.body, - authenticatedUser, }); if (createdConversation == null) { diff --git a/x-pack/plugins/elastic_assistant/server/routes/user_conversations/update_route.ts b/x-pack/plugins/elastic_assistant/server/routes/user_conversations/update_route.ts index 4a7fd5a9d67cb..069c189609f23 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/user_conversations/update_route.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/user_conversations/update_route.ts @@ -19,7 +19,7 @@ import { UpdateConversationRequestParams } from '@kbn/elastic-assistant-common/i import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common'; import { ElasticAssistantPluginRouter } from '../../types'; import { buildResponse } from '../utils'; -import { UPGRADE_LICENSE_MESSAGE, hasAIAssistantLicense } from '../helpers'; +import { performChecks } from '../helpers'; export const updateConversationRoute = (router: ElasticAssistantPluginRouter) => { router.versioned @@ -45,23 +45,20 @@ export const updateConversationRoute = (router: ElasticAssistantPluginRouter) => const { id } = request.params; try { const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']); - const license = ctx.licensing.license; - if (!hasAIAssistantLicense(license)) { - return response.forbidden({ - body: { - message: UPGRADE_LICENSE_MESSAGE, - }, - }); + const authenticatedUser = ctx.elasticAssistant.getCurrentUser(); + // Perform license and authenticated user checks + const checkResponse = performChecks({ + authenticatedUser: true, + context: ctx, + license: true, + request, + response, + }); + if (checkResponse) { + return checkResponse; } const dataClient = await ctx.elasticAssistant.getAIAssistantConversationsDataClient(); - const authenticatedUser = ctx.elasticAssistant.getCurrentUser(); - if (authenticatedUser == null) { - return assistantResponse.error({ - body: `Authenticated user not found`, - statusCode: 401, - }); - } const existingConversation = await dataClient?.getConversation({ id, authenticatedUser }); if (existingConversation == null) { @@ -72,7 +69,6 @@ export const updateConversationRoute = (router: ElasticAssistantPluginRouter) => } const conversation = await dataClient?.updateConversation({ conversationUpdateProps: request.body, - authenticatedUser, }); if (conversation == null) { return assistantResponse.error({ diff --git a/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts b/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts index 772ee8f2527b8..2dbdc63210a59 100644 --- a/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts +++ b/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts @@ -65,9 +65,8 @@ export function registerCategorizationRoutes( const llmClass = isOpenAI ? ActionsClientChatOpenAI : ActionsClientSimpleChatModel; const model = new llmClass({ - actions: actionsPlugin, + actionsClient, connectorId: connector.id, - request: req, logger, llmType: isOpenAI ? 'openai' : 'bedrock', model: connector.config?.defaultModel, diff --git a/x-pack/plugins/integration_assistant/server/routes/ecs_routes.ts b/x-pack/plugins/integration_assistant/server/routes/ecs_routes.ts index 923e0a9de4c5d..d177aeb4b2cdf 100644 --- a/x-pack/plugins/integration_assistant/server/routes/ecs_routes.ts +++ b/x-pack/plugins/integration_assistant/server/routes/ecs_routes.ts @@ -56,9 +56,8 @@ export function registerEcsRoutes(router: IRouter { const actions = { getActionsClientWithRequest: jest.fn(() => Promise.resolve(mockActionsClient)), } as unknown as ActionsPluginStartContract; + const logger = jest.fn() as unknown as Logger; const request = jest.fn() as unknown as KibanaRequest; @@ -89,11 +90,10 @@ describe('getChatParams', () => { temperature: 0, llmType: 'bedrock', traceId: 'test-uuid', - request: expect.anything(), logger: expect.anything(), model: 'custom-model', connectorId: '2', - actions: expect.anything(), + actionsClient: expect.anything(), }); expect(result.chatPrompt).toContain('How does it work?'); }); diff --git a/x-pack/plugins/search_playground/server/lib/get_chat_params.ts b/x-pack/plugins/search_playground/server/lib/get_chat_params.ts index a481309b9277d..9740988c3e1e2 100644 --- a/x-pack/plugins/search_playground/server/lib/get_chat_params.ts +++ b/x-pack/plugins/search_playground/server/lib/get_chat_params.ts @@ -46,9 +46,8 @@ export const getChatParams = async ( switch (connector.actionTypeId) { case OPENAI_CONNECTOR_ID: chatModel = new ActionsClientChatOpenAI({ - actions, + actionsClient, logger, - request, connectorId, model, traceId: uuidv4(), @@ -67,9 +66,8 @@ export const getChatParams = async ( case BEDROCK_CONNECTOR_ID: const llmType = 'bedrock'; chatModel = new ActionsClientLlm({ - actions, + actionsClient, logger, - request, connectorId, model, traceId: uuidv4(), From de027b80b23c8e0b2eebbbd0d62131577863082a Mon Sep 17 00:00:00 2001 From: "Quynh Nguyen (Quinn)" <43350163+qn895@users.noreply.github.com> Date: Mon, 1 Jul 2024 23:09:44 -0500 Subject: [PATCH 049/128] [ML] Add Field statistics embeddable as panel in Dashboard (#184030) ## Summary This PR adds Field statistics embeddable as panel in Dashboard By default, it will enable the ES|QL editor for the field stats panel. It will allow for editing of the ES|QL query, and time range. https://github.com/elastic/kibana/assets/43350163/4b5438c7-051f-4627-aab1-b802c23ca652 https://github.com/elastic/kibana/assets/43350163/e9bae0e4-17cf-4a86-ad70-0da9d3667b53 If and only if ES|QL is disabled, it will show the data view picker as a fallback. ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Stratoula Kalafateli --- .../field_stats_table/field_stats_table.tsx | 6 +- .../components/field_stats_table/types.ts | 16 +- .../embeddable/saved_search_embeddable.tsx | 2 + .../src/build_base_filter_criteria.ts | 9 +- .../common/types/field_stats.ts | 8 +- .../common/utils/build_query_filters.ts | 24 ++ x-pack/plugins/data_visualizer/kibana.jsonc | 2 + .../common/hooks/use_document_count_stats.ts | 4 +- .../common/types/data_visualizer_plugin.ts | 2 + .../index_data_visualizer_view.tsx | 4 +- .../embeddables/field_stats/constants.ts | 9 + .../field_stats/field_stats_esql_editor.tsx | 62 +++ .../field_stats/field_stats_factory.tsx | 392 ++++++++++++++++++ .../field_stats/field_stats_initializer.tsx | 301 ++++++++++++++ .../field_stats_initializer_view_type.tsx | 69 +++ .../embeddables/field_stats/index.ts | 16 + .../initialize_field_stats_controls.ts | 89 ++++ .../resolve_field_stats_embeddable_input.tsx | 108 +++++ .../embeddables/field_stats/types.ts | 39 ++ .../embeddable_esql_field_stats_table.tsx | 17 +- .../embeddable_field_stats_table.tsx | 8 +- ...le_wrapper.tsx => field_stats_wrapper.tsx} | 55 ++- .../embeddables/grid_embeddable/types.ts | 33 +- .../esql/use_data_visualizer_esql_data.tsx | 36 +- .../hooks/use_data_visualizer_grid_data.ts | 31 +- .../hooks/use_field_stats.ts | 5 +- .../hooks/use_overall_stats.ts | 9 +- .../requests/get_document_stats.ts | 9 +- .../requests/get_field_examples.ts | 4 +- .../search_strategy/requests/overall_stats.ts | 21 +- .../ui_actions/create_field_stats_table.tsx | 187 +++++++++ .../index_data_visualizer/ui_actions/index.ts | 21 + .../utils/get_fields_with_subfields_utils.ts | 4 +- .../plugins/data_visualizer/public/plugin.ts | 16 +- x-pack/plugins/data_visualizer/tsconfig.json | 7 +- .../translations/translations/fr-FR.json | 2 - 36 files changed, 1533 insertions(+), 94 deletions(-) create mode 100644 x-pack/plugins/data_visualizer/common/utils/build_query_filters.ts create mode 100644 x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/constants.ts create mode 100644 x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_esql_editor.tsx create mode 100644 x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_factory.tsx create mode 100644 x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_initializer.tsx create mode 100644 x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_initializer_view_type.tsx create mode 100644 x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/index.ts create mode 100644 x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/initialize_field_stats_controls.ts create mode 100644 x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/resolve_field_stats_embeddable_input.tsx create mode 100644 x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/types.ts rename x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/{field_stats_embeddable_wrapper.tsx => field_stats_wrapper.tsx} (71%) create mode 100644 x-pack/plugins/data_visualizer/public/application/index_data_visualizer/ui_actions/create_field_stats_table.tsx create mode 100644 x-pack/plugins/data_visualizer/public/application/index_data_visualizer/ui_actions/index.ts diff --git a/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx b/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx index 5829bd42c68c9..bf48c0bb83cf4 100644 --- a/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx +++ b/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx @@ -20,6 +20,7 @@ import { SmartFieldFallbackTooltip, } from '@kbn/unified-field-list'; import type { DataVisualizerTableItem } from '@kbn/data-visualizer-plugin/public/application/common/components/stats_table/types'; +import { isOfAggregateQueryType } from '@kbn/es-query'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import { FIELD_STATISTICS_LOADED } from './constants'; @@ -51,6 +52,7 @@ export const FieldStatisticsTable = React.memo((props: FieldStatisticsTableProps trackUiMetric, searchSessionId, additionalFieldGroups, + timeRange, } = props; const visibleFields = useMemo( @@ -156,6 +158,7 @@ export const FieldStatisticsTable = React.memo((props: FieldStatisticsTableProps dataView={dataView} savedSearch={savedSearch} filters={filters} + esqlQuery={isEsqlMode && isOfAggregateQueryType(query) ? query : undefined} query={query} visibleFieldNames={visibleFields} sessionId={searchSessionId} @@ -166,8 +169,9 @@ export const FieldStatisticsTable = React.memo((props: FieldStatisticsTableProps showPreviewByDefault={showPreviewByDefault} onTableUpdate={updateState} renderFieldName={renderFieldName} - esql={isEsqlMode} + isEsqlMode={isEsqlMode} overridableServices={overridableServices} + timeRange={timeRange} />
      ); diff --git a/src/plugins/discover/public/application/main/components/field_stats_table/types.ts b/src/plugins/discover/public/application/main/components/field_stats_table/types.ts index ddd62285d044d..79b3bbea85551 100644 --- a/src/plugins/discover/public/application/main/components/field_stats_table/types.ts +++ b/src/plugins/discover/public/application/main/components/field_stats_table/types.ts @@ -8,8 +8,12 @@ import type { DataViewField, DataView } from '@kbn/data-views-plugin/public'; import { type UiCounterMetricType } from '@kbn/analytics'; -import type { Filter, Query, AggregateQuery } from '@kbn/es-query'; -import type { SerializedTitles } from '@kbn/presentation-publishing'; +import type { Filter, Query, AggregateQuery, TimeRange } from '@kbn/es-query'; +import type { + PublishesBlockingError, + PublishesDataLoading, + SerializedTitles, +} from '@kbn/presentation-publishing'; import type { SavedSearch } from '@kbn/saved-search-plugin/public'; import { type BehaviorSubject } from 'rxjs'; import { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public'; @@ -116,7 +120,9 @@ interface FieldStatisticsTableEmbeddableComponentApi { export type FieldStatisticsTableEmbeddableApi = DefaultEmbeddableApi & - FieldStatisticsTableEmbeddableComponentApi; + FieldStatisticsTableEmbeddableComponentApi & + PublishesDataLoading & + PublishesBlockingError; export interface FieldStatisticsTableProps { /** @@ -173,4 +179,8 @@ export interface FieldStatisticsTableProps { * If table should query using ES|QL */ isEsqlMode?: boolean; + /** + * Time range + */ + timeRange?: TimeRange; } diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx index 861a0d50eeba6..28c1599dbe3d0 100644 --- a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx @@ -664,6 +664,7 @@ export class SavedSearchEmbeddable isEsqlMode, }); + const timeRange = this.getTimeRange(); if ( this.services.uiSettings.get(SHOW_FIELD_STATISTICS) === true && viewMode === VIEW_MODE.AGGREGATED_LEVEL && @@ -683,6 +684,7 @@ export class SavedSearchEmbeddable onAddFilter={searchProps.onFilter} searchSessionId={this.input.searchSessionId} isEsqlMode={isEsqlMode} + timeRange={timeRange} /> , diff --git a/x-pack/packages/ml/query_utils/src/build_base_filter_criteria.ts b/x-pack/packages/ml/query_utils/src/build_base_filter_criteria.ts index 3dd079aab33d7..9ec8ba8dccc3c 100644 --- a/x-pack/packages/ml/query_utils/src/build_base_filter_criteria.ts +++ b/x-pack/packages/ml/query_utils/src/build_base_filter_criteria.ts @@ -23,9 +23,10 @@ import { isFilterBasedDefaultQuery } from './filter_based_default_query'; */ export function buildBaseFilterCriteria( timeFieldName?: string, - earliestMs?: number, - latestMs?: number, - query?: Query['query'] + earliestMs?: number | string, + latestMs?: number | string, + query?: Query['query'], + timeFormat = 'epoch_millis' ): estypes.QueryDslQueryContainer[] { const filterCriteria = []; @@ -35,7 +36,7 @@ export function buildBaseFilterCriteria( [timeFieldName]: { gte: earliestMs, lte: latestMs, - format: 'epoch_millis', + format: timeFormat, }, }, }); diff --git a/x-pack/plugins/data_visualizer/common/types/field_stats.ts b/x-pack/plugins/data_visualizer/common/types/field_stats.ts index 97a2739f34ae0..b2e5c7a906c78 100644 --- a/x-pack/plugins/data_visualizer/common/types/field_stats.ts +++ b/x-pack/plugins/data_visualizer/common/types/field_stats.ts @@ -211,8 +211,8 @@ export function isValidFieldStats(arg: unknown): arg is FieldStats { export interface FieldStatsCommonRequestParams { index: string; timeFieldName?: string; - earliestMs?: number | undefined; - latestMs?: number | undefined; + earliestMs?: number | string | undefined; + latestMs?: number | string | undefined; runtimeFieldMap?: estypes.MappingRuntimeFields; intervalMs?: number; query: estypes.QueryDslQueryContainer; @@ -227,8 +227,8 @@ export type SupportedAggs = Set; export interface OverallStatsSearchStrategyParams { sessionId?: string; - earliest?: number; - latest?: number; + earliest?: number | string; + latest?: number | string; aggInterval: TimeBucketsInterval; intervalMs?: number; searchQuery: Query['query']; diff --git a/x-pack/plugins/data_visualizer/common/utils/build_query_filters.ts b/x-pack/plugins/data_visualizer/common/utils/build_query_filters.ts new file mode 100644 index 0000000000000..7fb2e35346484 --- /dev/null +++ b/x-pack/plugins/data_visualizer/common/utils/build_query_filters.ts @@ -0,0 +1,24 @@ +/* + * 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 * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { Query } from '@kbn/es-query'; +import { buildBaseFilterCriteria } from '@kbn/ml-query-utils'; + +export const buildFilterCriteria = ( + timeFieldName?: string, + earliestMs?: number | string, + latestMs?: number | string, + query?: Query['query'] +): estypes.QueryDslQueryContainer[] => { + return buildBaseFilterCriteria( + timeFieldName, + earliestMs, + latestMs, + query, + 'epoch_millis||strict_date_optional_time' + ); +}; diff --git a/x-pack/plugins/data_visualizer/kibana.jsonc b/x-pack/plugins/data_visualizer/kibana.jsonc index e80ccf5d3bb9c..1ad88eaea4cb4 100644 --- a/x-pack/plugins/data_visualizer/kibana.jsonc +++ b/x-pack/plugins/data_visualizer/kibana.jsonc @@ -29,6 +29,7 @@ "cloud" ], "requiredBundles": [ + "dataViews", "kibanaReact", "kibanaUtils", "maps", @@ -37,6 +38,7 @@ "uiActions", "lens", "textBasedLanguages", + "visualizations" ] } } diff --git a/x-pack/plugins/data_visualizer/public/application/common/hooks/use_document_count_stats.ts b/x-pack/plugins/data_visualizer/public/application/common/hooks/use_document_count_stats.ts index 86b436b0a69ff..f345c85f57a42 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/hooks/use_document_count_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/hooks/use_document_count_stats.ts @@ -16,8 +16,8 @@ import { createRandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import { each, get } from 'lodash'; import { lastValueFrom } from 'rxjs'; -import { buildBaseFilterCriteria } from '@kbn/ml-query-utils'; import useObservable from 'react-use/lib/useObservable'; +import { buildFilterCriteria } from '../../../../common/utils/build_query_filters'; import { useDataVisualizerKibana } from '../../kibana_context'; import { displayError } from '../util/display_error'; @@ -70,7 +70,7 @@ export const getDocumentCountStatsRequest = ( } = params; const size = 0; - const filterCriteria = buildBaseFilterCriteria(timeFieldName, earliestMs, latestMs, searchQuery); + const filterCriteria = buildFilterCriteria(timeFieldName, earliestMs, latestMs, searchQuery); const rawAggs: Record = { eventRate: { diff --git a/x-pack/plugins/data_visualizer/public/application/common/types/data_visualizer_plugin.ts b/x-pack/plugins/data_visualizer/public/application/common/types/data_visualizer_plugin.ts index cb2e90f7d4bae..ee7e89f0779e7 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/types/data_visualizer_plugin.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/types/data_visualizer_plugin.ts @@ -20,6 +20,7 @@ import type { LensPublicStart } from '@kbn/lens-plugin/public'; import type { IndexPatternFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; export interface DataVisualizerSetupDependencies { home?: HomePublicPluginSetup; @@ -42,4 +43,5 @@ export interface DataVisualizerStartDependencies { uiActions?: UiActionsStart; cloud?: CloudStart; savedSearch: SavedSearchPublicPluginStart; + usageCollection?: UsageCollectionStart; } diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx index c6e66b4e291a0..e1aae26be3a9a 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx @@ -73,7 +73,7 @@ import { RANDOM_SAMPLER_OPTION, type RandomSamplerOption, } from '../../constants/random_sampler'; -import type { FieldStatisticsTableEmbeddableState } from '../../embeddables/grid_embeddable/types'; +import type { FieldStatisticTableEmbeddableProps } from '../../embeddables/grid_embeddable/types'; const defaultSearchQuery = { match_all: {}, @@ -217,7 +217,7 @@ export const IndexDataVisualizerView: FC = (dataVi }); }; - const input: Required = useMemo(() => { + const input: Required = useMemo(() => { return { dataView: currentDataView, savedSearch: currentSavedSearch, diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/constants.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/constants.ts new file mode 100644 index 0000000000000..790b0edde4a5c --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/constants.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const FIELD_STATS_EMBEDDABLE_TYPE = 'field_stats_table'; +export const FIELD_STATS_DATA_VIEW_REF_NAME = 'fieldStatsTableDataViewId'; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_esql_editor.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_esql_editor.tsx new file mode 100644 index 0000000000000..bdaee8c1a5ae1 --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_esql_editor.tsx @@ -0,0 +1,62 @@ +/* + * 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, { useRef, useState, useCallback } from 'react'; +import { TextBasedLangEditor } from '@kbn/text-based-languages/public'; +import { EuiFlexItem } from '@elastic/eui'; +import type { AggregateQuery } from '@kbn/es-query'; + +const expandCodeEditor = (status: boolean) => {}; + +interface FieldStatsESQLEditorProps { + canEditTextBasedQuery?: boolean; + query: AggregateQuery; + setQuery: (query: AggregateQuery) => void; + onQuerySubmit: (query: AggregateQuery, abortController: AbortController) => Promise; +} +export const FieldStatsESQLEditor = ({ + canEditTextBasedQuery = true, + query, + setQuery, + onQuerySubmit, +}: FieldStatsESQLEditorProps) => { + const prevQuery = useRef(query); + const [isVisualizationLoading, setIsVisualizationLoading] = useState(false); + + const onTextLangQuerySubmit = useCallback( + async (q, abortController) => { + if (q && onQuerySubmit) { + setIsVisualizationLoading(true); + await onQuerySubmit(q, abortController); + setIsVisualizationLoading(false); + } + }, + [onQuerySubmit] + ); + + if (!canEditTextBasedQuery) return null; + + return ( + + { + setQuery(q); + prevQuery.current = q; + }} + expandCodeEditor={expandCodeEditor} + isCodeEditorExpanded + hideMinimizeButton + editorIsInline + hideRunQueryText + onTextLangQuerySubmit={onTextLangQuerySubmit} + isDisabled={false} + allowQueryCancellation + isLoading={isVisualizationLoading} + /> + + ); +}; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_factory.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_factory.tsx new file mode 100644 index 0000000000000..8e800d5d61767 --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_factory.tsx @@ -0,0 +1,392 @@ +/* + * 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 { Reference } from '@kbn/content-management-utils'; +import type { StartServicesAccessor } from '@kbn/core-lifecycle-browser'; +import { + APPLY_FILTER_TRIGGER, + generateFilters, + type DataPublicPluginStart, +} from '@kbn/data-plugin/public'; +import type { DataViewField } from '@kbn/data-views-plugin/common'; +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; +import type { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { + apiHasExecutionContext, + fetch$, + initializeTimeRange, + initializeTitles, + useBatchedPublishingSubjects, + useFetchContext, +} from '@kbn/presentation-publishing'; +import { cloneDeep } from 'lodash'; +import React, { useEffect } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { + BehaviorSubject, + map, + skipWhile, + Subscription, + skip, + switchMap, + distinctUntilChanged, +} from 'rxjs'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import { dynamic } from '@kbn/shared-ux-utility'; +import { isDefined } from '@kbn/ml-is-defined'; +import { EuiFlexItem } from '@elastic/eui'; +import { css } from '@emotion/react'; +import type { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; +import type { Filter } from '@kbn/es-query'; +import { FilterStateStore } from '@kbn/es-query'; +import { getESQLAdHocDataview, getIndexPatternFromESQLQuery } from '@kbn/esql-utils'; +import { ACTION_GLOBAL_APPLY_FILTER } from '@kbn/unified-search-plugin/public'; +import type { DataVisualizerTableState } from '../../../../../common/types'; +import type { DataVisualizerPluginStart } from '../../../../plugin'; +import type { FieldStatisticsTableEmbeddableState } from '../grid_embeddable/types'; +import { FieldStatsInitializerViewType } from '../grid_embeddable/types'; +import { FIELD_STATS_EMBEDDABLE_TYPE, FIELD_STATS_DATA_VIEW_REF_NAME } from './constants'; +import { initializeFieldStatsControls } from './initialize_field_stats_controls'; +import type { DataVisualizerStartDependencies } from '../../../common/types/data_visualizer_plugin'; +import type { FieldStatisticsTableEmbeddableApi } from './types'; +import { isESQLQuery } from '../../search_strategy/requests/esql_utils'; + +export interface EmbeddableFieldStatsChartStartServices { + data: DataPublicPluginStart; +} +export type EmbeddableFieldStatsChartType = typeof FIELD_STATS_EMBEDDABLE_TYPE; + +const FieldStatisticsWrapper = dynamic(() => import('../grid_embeddable/field_stats_wrapper')); + +const ERROR_MSG = { + APPLY_FILTER_ERR: i18n.translate('xpack.dataVisualizer.fieldStats.errors.errorApplyingFilter', { + defaultMessage: 'Error applying filter', + }), + UPDATE_CONFIG_ERROR: i18n.translate( + 'xpack.dataVisualizer.fieldStats.errors.errorUpdatingConfig', + { + defaultMessage: 'Error updating settings for field statistics.', + } + ), +}; + +export const getDependencies = async ( + getStartServices: StartServicesAccessor< + DataVisualizerStartDependencies, + DataVisualizerPluginStart + > +) => { + const [ + { http, uiSettings, notifications, ...startServices }, + { lens, data, usageCollection, fieldFormats }, + ] = await getStartServices(); + + return { + http, + uiSettings, + data, + notifications, + lens, + usageCollection, + fieldFormats, + ...startServices, + }; +}; + +export const getFieldStatsChartEmbeddableFactory = ( + getStartServices: StartServicesAccessor< + DataVisualizerStartDependencies, + DataVisualizerPluginStart + > +) => { + const factory: ReactEmbeddableFactory< + FieldStatisticsTableEmbeddableState, + FieldStatisticsTableEmbeddableState, + FieldStatisticsTableEmbeddableApi + > = { + type: FIELD_STATS_EMBEDDABLE_TYPE, + deserializeState: (state) => { + const serializedState = cloneDeep(state.rawState); + // inject the reference + const dataViewIdRef = state.references?.find( + (ref) => ref.name === FIELD_STATS_DATA_VIEW_REF_NAME + ); + // if the serializedState already contains a dataViewId, we don't want to overwrite it. (Unsaved state can cause this) + if (dataViewIdRef && serializedState && !serializedState.dataViewId) { + serializedState.dataViewId = dataViewIdRef?.id; + } + return serializedState; + }, + buildEmbeddable: async (state, buildApi, uuid, parentApi) => { + const [coreStart, pluginStart] = await getStartServices(); + + const { http, uiSettings, notifications, ...startServices } = coreStart; + const { lens, data, usageCollection, fieldFormats } = pluginStart; + + const deps = { + http, + uiSettings, + data, + notifications, + lens, + usageCollection, + fieldFormats, + ...startServices, + }; + const { + api: timeRangeApi, + comparators: timeRangeComparators, + serialize: serializeTimeRange, + } = initializeTimeRange(state); + + const { titlesApi, titleComparators, serializeTitles } = initializeTitles(state); + + const { + fieldStatsControlsApi, + dataLoadingApi, + fieldStatsControlsComparators, + serializeFieldStatsChartState, + onFieldStatsTableDestroy, + resetData$, + } = initializeFieldStatsControls(state); + const { onError, dataLoading, blockingError } = dataLoadingApi; + + const defaultDataViewId = await deps.data.dataViews.getDefaultId(); + const validDataViewId: string = + isDefined(state.dataViewId) && state.dataViewId !== '' + ? state.dataViewId + : defaultDataViewId ?? ''; + let initialDataView: DataView[] | undefined; + try { + const dataView = isESQLQuery(state.query) + ? await getESQLAdHocDataview( + getIndexPatternFromESQLQuery(state.query.esql), + deps.data.dataViews + ) + : await deps.data.dataViews.get(validDataViewId); + initialDataView = [dataView]; + } catch (error) { + // Only need to publish blocking error if viewtype is data view, and no data view found + if (state.viewType === FieldStatsInitializerViewType.DATA_VIEW) { + onError(error); + } + } + + const dataViews$ = new BehaviorSubject(initialDataView); + + const subscriptions = new Subscription(); + if (fieldStatsControlsApi.dataViewId$) { + subscriptions.add( + fieldStatsControlsApi.dataViewId$ + .pipe( + skip(1), + skipWhile((dataViewId) => !dataViewId && !defaultDataViewId), + switchMap(async (dataViewId) => { + try { + return await deps.data.dataViews.get(dataViewId ?? defaultDataViewId); + } catch (error) { + return undefined; + } + }) + ) + .subscribe((nextSelectedDataView) => { + if (nextSelectedDataView) { + dataViews$.next([nextSelectedDataView]); + } + }) + ); + } + + const { toasts } = deps.notifications; + + const api = buildApi( + { + ...timeRangeApi, + ...titlesApi, + ...fieldStatsControlsApi, + // PublishesDataLoading + dataLoading, + // PublishesBlockingError + blockingError, + getTypeDisplayName: () => + i18n.translate('xpack.dataVisualizer.fieldStats.typeDisplayName', { + defaultMessage: 'field statistics', + }), + isEditingEnabled: () => true, + onEdit: async () => { + try { + const { resolveEmbeddableFieldStatsUserInput } = await import( + './resolve_field_stats_embeddable_input' + ); + const chartState = serializeFieldStatsChartState(); + const nextUpdate = await resolveEmbeddableFieldStatsUserInput( + coreStart, + pluginStart, + parentApi, + uuid, + false, + chartState, + fieldStatsControlsApi + ); + fieldStatsControlsApi.updateUserInput(nextUpdate); + } catch (e) { + toasts.addError(e, { title: ERROR_MSG.UPDATE_CONFIG_ERROR }); + } + }, + dataViews: dataViews$, + serializeState: () => { + const dataViewId = fieldStatsControlsApi.dataViewId$?.getValue(); + const references: Reference[] = dataViewId + ? [ + { + type: DATA_VIEW_SAVED_OBJECT_TYPE, + name: FIELD_STATS_DATA_VIEW_REF_NAME, + id: dataViewId, + }, + ] + : []; + return { + rawState: { + ...serializeTitles(), + ...serializeTimeRange(), + ...serializeFieldStatsChartState(), + }, + references, + }; + }, + }, + { + ...timeRangeComparators, + ...titleComparators, + ...fieldStatsControlsComparators, + } + ); + + const reload$ = fetch$(api).pipe( + skipWhile((fetchContext) => !fetchContext.isReload), + map(() => Date.now()) + ); + const reset$ = resetData$.pipe(skip(1), distinctUntilChanged()); + + const onTableUpdate = (changes: Partial) => { + if (isDefined(changes?.showDistributions)) { + fieldStatsControlsApi.showDistributions$.next(changes.showDistributions); + } + }; + + const addFilters = (filters: Filter[], actionId: string = ACTION_GLOBAL_APPLY_FILTER) => { + if (!pluginStart.uiActions) { + toasts.addWarning(ERROR_MSG.APPLY_FILTER_ERR); + return; + } + const trigger = pluginStart.uiActions.getTrigger(APPLY_FILTER_TRIGGER); + if (!trigger) { + toasts.addWarning(ERROR_MSG.APPLY_FILTER_ERR); + return; + } + const actionContext = { + embeddable: api, + trigger, + } as ActionExecutionContext; + + const executeContext = { + ...actionContext, + filters, + }; + try { + const action = pluginStart.uiActions.getAction(actionId); + action.execute(executeContext); + } catch (error) { + toasts.addWarning(ERROR_MSG.APPLY_FILTER_ERR); + } + }; + + const statsTableCss = css({ + width: '100%', + height: '100%', + overflowY: 'auto', + }); + + return { + api, + Component: () => { + if (!apiHasExecutionContext(parentApi)) { + onError(new Error('Parent API does not have execution context')); + } + + const { filters: globalFilters, query: globalQuery, timeRange } = useFetchContext(api); + const [dataViews, esqlQuery, viewType, showPreviewByDefault] = + useBatchedPublishingSubjects( + api.dataViews, + api.query$, + api.viewType$, + api.showDistributions$ + ); + const lastReloadRequestTime = useObservable(reload$, Date.now()); + + const isEsqlMode = viewType === FieldStatsInitializerViewType.ESQL; + + const dataView = + Array.isArray(dataViews) && dataViews.length > 0 ? dataViews[0] : undefined; + const onAddFilter = ( + field: string | DataViewField, + value: string, + operator: '+' | '-' + ) => { + if (!dataView || !pluginStart.data) { + toasts.addWarning(ERROR_MSG.APPLY_FILTER_ERR); + return; + } + + let filters = generateFilters( + pluginStart.data.query.filterManager, + field, + value, + operator, + dataView + ); + filters = filters.map((filter) => ({ + ...filter, + $state: { store: FilterStateStore.APP_STATE }, + })); + addFilters(filters); + }; + + // On destroy + useEffect(() => { + return () => { + subscriptions?.unsubscribe(); + onFieldStatsTableDestroy(); + }; + }, []); + + return ( + + + + ); + }, + }; + }, + }; + + return factory; +}; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_initializer.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_initializer.tsx new file mode 100644 index 0000000000000..049406bd55775 --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_initializer.tsx @@ -0,0 +1,301 @@ +/* + * 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 { + EuiButton, + EuiButtonEmpty, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiFormRow, + EuiTitle, + EuiSpacer, + EuiIconTip, + EuiCallOut, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { FC } from 'react'; +import { useEffect } from 'react'; +import React, { useMemo, useState, useCallback } from 'react'; +import { ENABLE_ESQL, getESQLAdHocDataview, getIndexPatternFromESQLQuery } from '@kbn/esql-utils'; +import type { AggregateQuery } from '@kbn/es-query'; +import { css } from '@emotion/react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { useDataVisualizerKibana } from '../../../kibana_context'; +import { FieldStatsESQLEditor } from './field_stats_esql_editor'; +import type { + FieldStatisticsTableEmbeddableState, + FieldStatsInitialState, +} from '../grid_embeddable/types'; +import { FieldStatsInitializerViewType } from '../grid_embeddable/types'; +import { isESQLQuery } from '../../search_strategy/requests/esql_utils'; +import { DataSourceTypeSelector } from './field_stats_initializer_view_type'; + +export interface FieldStatsInitializerProps { + initialInput?: Partial; + onCreate: (props: FieldStatsInitialState) => Promise; + onCancel: () => void; + onPreview: (update: Partial) => Promise; + isNewPanel: boolean; +} + +const defaultESQLQuery = { esql: '' }; +const defaultTitle = i18n.translate('xpack.dataVisualizer.fieldStatistics.displayName', { + defaultMessage: 'Field statistics', +}); + +const isScrollable = false; +export const FieldStatisticsInitializer: FC = ({ + initialInput, + onCreate, + onCancel, + onPreview, + isNewPanel, +}) => { + const { + data: { dataViews }, + unifiedSearch: { + ui: { IndexPatternSelect }, + }, + uiSettings, + } = useDataVisualizerKibana().services; + + const [dataViewId, setDataViewId] = useState(initialInput?.dataViewId ?? ''); + const [viewType, setViewType] = useState( + initialInput?.viewType ?? FieldStatsInitializerViewType.DATA_VIEW + ); + const [esqlQuery, setQuery] = useState(initialInput?.query ?? defaultESQLQuery); + const isEsqlEnabled = useMemo(() => uiSettings.get(ENABLE_ESQL), [uiSettings]); + + useEffect(() => { + if (initialInput?.viewType === undefined) { + // By default, if ES|QL is enabled, then use ES|QL + setViewType( + isEsqlEnabled ? FieldStatsInitializerViewType.ESQL : FieldStatsInitializerViewType.DATA_VIEW + ); + } + }, [isEsqlEnabled, initialInput?.viewType]); + + const isEsqlMode = viewType === FieldStatsInitializerViewType.ESQL; + const updatedProps = useMemo(() => { + return { + viewType, + title: initialInput?.title ?? defaultTitle, + dataViewId, + query: isEsqlMode ? esqlQuery : undefined, + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dataViewId, viewType, esqlQuery.esql, isEsqlMode]); + const onESQLQuerySubmit = useCallback( + async (query: AggregateQuery, abortController: AbortController) => { + const adhocDataView = await getESQLAdHocDataview( + getIndexPatternFromESQLQuery(query.esql), + dataViews + ); + if (adhocDataView && adhocDataView.id) { + setDataViewId(adhocDataView.id); + } + + await onPreview({ + viewType, + dataViewId: adhocDataView?.id, + query, + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [isEsqlMode] + ); + const isEsqlFormValid = isEsqlMode ? isEsqlEnabled && isESQLQuery(esqlQuery) : true; + const isDataViewFormValid = + viewType === FieldStatsInitializerViewType.DATA_VIEW ? dataViewId !== '' : true; + + return ( + <> + + + + +

      + {isNewPanel + ? i18n.translate( + 'xpack.dataVisualizer.fieldStatisticsDashboardPanel.config.createLable', + { + defaultMessage: 'Create field statistics', + } + ) + : i18n.translate( + 'xpack.dataVisualizer.fieldStatisticsDashboardPanel.config.editLabel', + { + defaultMessage: 'Edit field statistics', + } + )}{' '} + +

      +
      +
      +
      +
      + * { + pointer-events: auto; + } + } + .euiFlyoutBody__overflowContent { + padding: 0; + block-size: 100%; + } + border-bottom: 2px solid ${euiThemeVars.euiBorderColor}; + `} + > + + {isNewPanel ? ( + + } + /> + ) : null} + {initialInput?.viewType === FieldStatsInitializerViewType.ESQL && !isEsqlEnabled ? ( + <> + + + ) : null} + {viewType === FieldStatsInitializerViewType.ESQL && !isEsqlEnabled ? ( + <> + + {esqlQuery.esql} + + ) : null} + + {viewType === FieldStatsInitializerViewType.DATA_VIEW ? ( + + { + setDataViewId(newId ?? ''); + }} + /> + + ) : null} + {isEsqlMode && isEsqlEnabled ? ( + + ) : null} + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_initializer_view_type.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_initializer_view_type.tsx new file mode 100644 index 0000000000000..d061fa259ece3 --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_initializer_view_type.tsx @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { FC } from 'react'; +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiButtonGroup, EuiFormRow, type EuiButtonGroupOptionProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FieldStatsInitializerViewType } from '../grid_embeddable/types'; + +const viewTypeOptions: EuiButtonGroupOptionProps[] = [ + { + id: FieldStatsInitializerViewType.DATA_VIEW, + label: ( + + ), + iconType: 'visLine', + }, + { + id: FieldStatsInitializerViewType.ESQL, + label: ( + + ), + iconType: 'visTable', + }, +]; + +const dataSourceLabel = i18n.translate( + 'xpack.dataVisualizer.fieldStatsDashboardPanel.dataSourceLabel', + { + defaultMessage: 'Data source', + } +); + +const dataSourceAriaLabel = i18n.translate( + 'xpack.dataVisualizer.fieldStatsDashboardPanel.viewTypeLabel', + { + defaultMessage: 'Pick type of data source to use', + } +); + +export interface ViewTypeSelectorProps { + value: FieldStatsInitializerViewType; + onChange: (update: FieldStatsInitializerViewType) => void; +} + +export const DataSourceTypeSelector: FC = ({ value, onChange }) => { + return ( + + void} + legend={dataSourceAriaLabel} + /> + + ); +}; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/index.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/index.ts new file mode 100644 index 0000000000000..d976e89af2c26 --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { EmbeddableSetup } from '@kbn/embeddable-plugin/public'; +import type { DataVisualizerCoreSetup } from '../../../../plugin'; +import { FIELD_STATS_EMBEDDABLE_TYPE } from './constants'; + +export const registerEmbeddables = (embeddable: EmbeddableSetup, core: DataVisualizerCoreSetup) => { + embeddable.registerReactEmbeddableFactory(FIELD_STATS_EMBEDDABLE_TYPE, async () => { + const { getFieldStatsChartEmbeddableFactory } = await import('./field_stats_factory'); + return getFieldStatsChartEmbeddableFactory(core.getStartServices); + }); +}; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/initialize_field_stats_controls.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/initialize_field_stats_controls.ts new file mode 100644 index 0000000000000..c6709825f6696 --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/initialize_field_stats_controls.ts @@ -0,0 +1,89 @@ +/* + * 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 type { AggregateQuery } from '@kbn/es-query'; +import type { StateComparators } from '@kbn/presentation-publishing'; +import { BehaviorSubject } from 'rxjs'; +import fastIsEqual from 'fast-deep-equal'; +import { FieldStatsInitializerViewType } from '../grid_embeddable/types'; +import type { FieldStatsInitialState } from '../grid_embeddable/types'; +import type { FieldStatsControlsApi } from './types'; + +export const initializeFieldStatsControls = (rawState: FieldStatsInitialState) => { + const viewType$ = new BehaviorSubject( + rawState.viewType ?? FieldStatsInitializerViewType.ESQL + ); + const dataViewId$ = new BehaviorSubject(rawState.dataViewId); + const query$ = new BehaviorSubject(rawState.query); + const showDistributions$ = new BehaviorSubject(rawState.showDistributions); + const resetData$ = new BehaviorSubject(Date.now()); + + const dataLoading$ = new BehaviorSubject(true); + const blockingError = new BehaviorSubject(undefined); + + const updateUserInput = (update: FieldStatsInitialState, shouldResetData = false) => { + if (shouldResetData) { + resetData$.next(Date.now()); + } + viewType$.next(update.viewType); + dataViewId$.next(update.dataViewId); + query$.next(update.query); + }; + + const serializeFieldStatsChartState = (): FieldStatsInitialState => { + return { + viewType: viewType$.getValue(), + dataViewId: dataViewId$.getValue(), + query: query$.getValue(), + showDistributions: showDistributions$.getValue(), + }; + }; + + const fieldStatsControlsComparators: StateComparators = { + viewType: [viewType$, (arg) => viewType$.next(arg)], + dataViewId: [dataViewId$, (arg) => dataViewId$.next(arg)], + query: [query$, (arg) => query$.next(arg), fastIsEqual], + showDistributions: [showDistributions$, (arg) => showDistributions$.next(arg)], + }; + + const onRenderComplete = () => dataLoading$.next(false); + const onLoading = (v: boolean) => dataLoading$.next(v); + const onError = (error?: Error) => blockingError.next(error); + + return { + fieldStatsControlsApi: { + viewType$, + dataViewId$, + query$, + updateUserInput, + showDistributions$, + } as unknown as FieldStatsControlsApi, + dataLoadingApi: { + dataLoading: dataLoading$, + onRenderComplete, + onLoading, + onError, + blockingError, + }, + // Reset data is internal state management, so no need to expose this in api + resetData$, + serializeFieldStatsChartState, + fieldStatsControlsComparators, + onFieldStatsTableDestroy: () => { + viewType$.complete(); + dataViewId$.complete(); + query$.complete(); + resetData$.complete(); + }, + }; +}; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/resolve_field_stats_embeddable_input.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/resolve_field_stats_embeddable_input.tsx new file mode 100644 index 0000000000000..893e77c12e80b --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/resolve_field_stats_embeddable_input.tsx @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { CoreStart } from '@kbn/core/public'; +import { tracksOverlays } from '@kbn/presentation-containers'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import React from 'react'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { getIndexPatternFromESQLQuery } from '@kbn/esql-utils'; +import { isDefined } from '@kbn/ml-is-defined'; +import { FieldStatisticsInitializer } from './field_stats_initializer'; +import type { DataVisualizerStartDependencies } from '../../../common/types/data_visualizer_plugin'; +import type { + FieldStatisticsTableEmbeddableState, + FieldStatsInitialState, +} from '../grid_embeddable/types'; +import type { FieldStatsControlsApi } from './types'; +import { getOrCreateDataViewByIndexPattern } from '../../search_strategy/requests/get_data_view_by_index_pattern'; + +export async function resolveEmbeddableFieldStatsUserInput( + coreStart: CoreStart, + pluginStart: DataVisualizerStartDependencies, + parentApi: unknown, + focusedPanelId: string, + isNewPanel: boolean, + initialState?: FieldStatisticsTableEmbeddableState, + fieldStatsControlsApi?: FieldStatsControlsApi +): Promise { + const { overlays } = coreStart; + + const overlayTracker = tracksOverlays(parentApi) ? parentApi : undefined; + const services = { + ...coreStart, + ...pluginStart, + }; + + let hasChanged = false; + return new Promise(async (resolve, reject) => { + try { + const cancelChanges = () => { + // Reset to initialState in case user has changed the preview state + if (hasChanged && fieldStatsControlsApi && initialState) { + fieldStatsControlsApi.updateUserInput(initialState); + } + + flyoutSession.close(); + overlayTracker?.clearOverlays(); + }; + + const update = async (nextUpdate: FieldStatsInitialState) => { + const esqlQuery = nextUpdate?.query?.esql; + if (isDefined(esqlQuery)) { + const indexPatternFromQuery = getIndexPatternFromESQLQuery(esqlQuery); + const dv = await getOrCreateDataViewByIndexPattern( + pluginStart.data.dataViews, + indexPatternFromQuery, + undefined + ); + if (dv?.id && nextUpdate.dataViewId !== dv.id) { + nextUpdate.dataViewId = dv.id; + } + } + + resolve(nextUpdate); + flyoutSession.close(); + overlayTracker?.clearOverlays(); + }; + + const flyoutSession = overlays.openFlyout( + toMountPoint( + + { + if (fieldStatsControlsApi) { + fieldStatsControlsApi.updateUserInput(nextUpdate); + hasChanged = true; + } + }} + onCreate={update} + onCancel={cancelChanges} + isNewPanel={isNewPanel} + /> + , + coreStart + ), + { + ownFocus: true, + size: 's', + paddingSize: 'm', + hideCloseButton: true, + type: 'push', + 'data-test-subj': 'fieldStatisticsInitializerFlyout', + onClose: cancelChanges, + } + ); + + if (tracksOverlays(parentApi)) { + parentApi.openOverlay(flyoutSession, { focusedPanelId }); + } + } catch (error) { + reject(error); + } + }); +} diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/types.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/types.ts new file mode 100644 index 0000000000000..c56a2b280f1e2 --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/types.ts @@ -0,0 +1,39 @@ +/* + * 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 { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public'; +import type { AggregateQuery } from '@kbn/es-query'; +import type { + HasEditCapabilities, + PublishesBlockingError, + PublishesDataLoading, + PublishesDataViews, + PublishesTimeRange, +} from '@kbn/presentation-publishing'; +import type { BehaviorSubject } from 'rxjs'; +import type { + FieldStatisticsTableEmbeddableState, + FieldStatsInitializerViewType, + FieldStatsInitialState, +} from '../grid_embeddable/types'; + +export interface FieldStatsControlsApi { + viewType$: BehaviorSubject; + dataViewId$: BehaviorSubject; + query$: BehaviorSubject; + showDistributions$: BehaviorSubject; + updateUserInput: (update: Partial) => void; +} + +export type FieldStatisticsTableEmbeddableApi = + DefaultEmbeddableApi & + HasEditCapabilities & + PublishesDataViews & + PublishesTimeRange & + PublishesDataLoading & + PublishesBlockingError & + FieldStatsControlsApi; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/embeddable_esql_field_stats_table.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/embeddable_esql_field_stats_table.tsx index b4bcd3c0da5a9..3cf282e3da1a2 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/embeddable_esql_field_stats_table.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/embeddable_esql_field_stats_table.tsx @@ -23,7 +23,7 @@ const restorableDefaults = getDefaultESQLDataVisualizerListState(); const EmbeddableESQLFieldStatsTableWrapper = React.memo( (props: ESQLDataVisualizerGridEmbeddableState) => { - const { onTableUpdate } = props; + const { onTableUpdate, onRenderComplete } = props; const [dataVisualizerListState, setDataVisualizerListState] = useState>(restorableDefaults); @@ -44,15 +44,30 @@ const EmbeddableESQLFieldStatsTableWrapper = React.memo( overallStatsProgress, setLastRefresh, getItemIdToExpandedRowMap, + resetData, } = useESQLDataVisualizerData(props, dataVisualizerListState); useEffect(() => { setLastRefresh(Date.now()); }, [props?.lastReloadRequestTime, setLastRefresh]); + useEffect(() => { + const subscription = props.resetData$?.subscribe(() => { + resetData(); + }); + return () => subscription?.unsubscribe(); + }, [props.resetData$, resetData]); + + useEffect(() => { + if (progress === 100 && onRenderComplete) { + onRenderComplete(); + } + }, [progress, onRenderComplete]); + if (progress === 100 && configs.length === 0) { return ; } + return ( items={configs} diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/embeddable_field_stats_table.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/embeddable_field_stats_table.tsx index 5c40b9e9c94f4..916e21a8b8922 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/embeddable_field_stats_table.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/embeddable_field_stats_table.tsx @@ -23,7 +23,7 @@ const restorableDefaults = getDefaultDataVisualizerListState(); const EmbeddableFieldStatsTableWrapper = ( props: Required ) => { - const { onTableUpdate, onAddFilter } = props; + const { onTableUpdate, onAddFilter, onRenderComplete } = props; const [dataVisualizerListState, setDataVisualizerListState] = useState>(restorableDefaults); @@ -73,6 +73,12 @@ const EmbeddableFieldStatsTableWrapper = ( [props.dataView, searchQueryLanguage, searchString, props.totalDocuments, onAddFilter] ); + useEffect(() => { + if (progress === 100 && onRenderComplete) { + onRenderComplete(); + } + }, [progress, onRenderComplete]); + if (progress === 100 && configs.length === 0) { return ; } diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/field_stats_embeddable_wrapper.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/field_stats_wrapper.tsx similarity index 71% rename from x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/field_stats_embeddable_wrapper.tsx rename to x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/field_stats_wrapper.tsx index a7e57e1e550b6..9899c7f9251e0 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/field_stats_embeddable_wrapper.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/field_stats_wrapper.tsx @@ -30,21 +30,62 @@ const EmbeddableFieldStatsTableWrapper = dynamic(() => import('./embeddable_fiel function isESQLFieldStatisticTableEmbeddableState( input: FieldStatisticTableEmbeddableProps ): input is ESQLDataVisualizerGridEmbeddableState { - return isPopulatedObject(input, ['esql']) && input.esql === true; + return isPopulatedObject(input, ['isEsqlMode']) && input.isEsqlMode === true; } function isFieldStatisticTableEmbeddableState( input: FieldStatisticTableEmbeddableProps ): input is Required { - return isPopulatedObject(input, ['dataView']) && Boolean(input.esql) === false; + return isPopulatedObject(input, ['dataView']) && Boolean(input.isEsqlMode) === false; } const FieldStatisticsWrapperContent = (props: FieldStatisticTableEmbeddableProps) => { if (isESQLFieldStatisticTableEmbeddableState(props)) { - return ; + return ( + + ); } if (isFieldStatisticTableEmbeddableState(props)) { - return ; + return ( + + ); } else { return ( { { showPreviewByDefault={props.showPreviewByDefault} totalDocuments={props.totalDocuments} visibleFieldNames={props.visibleFieldNames} + resetData$={props.resetData$} + timeRange={props.timeRange} + onRenderComplete={props.onRenderComplete} /> diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/types.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/types.ts index a703c012a0575..06c40515f6ca2 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/types.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/types.ts @@ -5,13 +5,12 @@ * 2.0. */ -import type { AggregateQuery, Filter } from '@kbn/es-query'; +import type { AggregateQuery, Filter, TimeRange } from '@kbn/es-query'; import type { Query } from '@kbn/es-query'; import type { DataView, DataViewField } from '@kbn/data-views-plugin/public'; import type { SavedSearch } from '@kbn/saved-search-plugin/public'; -import type { BehaviorSubject } from 'rxjs'; -import type { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public'; -import type { SerializedTitles } from '@kbn/presentation-publishing'; +import type { BehaviorSubject, Observable } from 'rxjs'; +import type { SerializedTimeRange, SerializedTitles } from '@kbn/presentation-publishing'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataVisualizerTableState } from '../../../../../common/types'; import type { SamplingOption } from '../../../../../common/types/field_stats'; @@ -82,6 +81,8 @@ export interface FieldStatisticTableEmbeddableProps { * If esql:true, switch table to ES|QL mode */ esql?: boolean; + isEsqlMode?: boolean; + esqlQuery?: AggregateQuery; /** * If esql:true, the index pattern is used to validate time field */ @@ -98,6 +99,9 @@ export interface FieldStatisticTableEmbeddableProps { */ overridableServices?: { data: DataPublicPluginStart }; renderFieldName?: (fieldName: string, item: DataVisualizerTableItem) => JSX.Element; + resetData$?: Observable; + timeRange?: TimeRange; + onRenderComplete?: () => void; } export type ESQLDataVisualizerGridEmbeddableState = Omit< @@ -105,12 +109,21 @@ export type ESQLDataVisualizerGridEmbeddableState = Omit< 'query' > & { query?: ESQLQuery }; -export type FieldStatisticsTableEmbeddableState = FieldStatisticTableEmbeddableProps & - SerializedTitles; -interface FieldStatisticsTableEmbeddableComponentApi { - showDistributions$?: BehaviorSubject; +export enum FieldStatsInitializerViewType { + DATA_VIEW = 'dataview', + ESQL = 'esql', } +export interface FieldStatsInitialState { + dataViewId?: string; + viewType?: FieldStatsInitializerViewType; + query?: AggregateQuery; + showDistributions?: boolean; +} +export type FieldStatisticsTableEmbeddableState = FieldStatsInitialState & + SerializedTitles & + SerializedTimeRange & {}; + export type OnAddFilter = (field: DataViewField | string, value: string, type: '+' | '-') => void; export interface FieldStatisticsTableEmbeddableParentApi { executionContext?: { value: string }; @@ -119,10 +132,6 @@ export interface FieldStatisticsTableEmbeddableParentApi { onAddFilter?: OnAddFilter; } -export type FieldStatisticsTableEmbeddableApi = - DefaultEmbeddableApi & - FieldStatisticsTableEmbeddableComponentApi; - export type DataVisualizerGridEmbeddableApi = Partial; export type ESQLDefaultLimitSizeOption = '5000' | '10000' | '100000'; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_data_visualizer_esql_data.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_data_visualizer_esql_data.tsx index 8513df90d682b..5252e0082146e 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_data_visualizer_esql_data.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_data_visualizer_esql_data.tsx @@ -20,6 +20,7 @@ import { useExecutionContext } from '@kbn/kibana-react-plugin/public'; import type { AggregateQuery, Query } from '@kbn/es-query'; import { useTimeBuckets } from '@kbn/ml-time-buckets'; import { buildEsQuery } from '@kbn/es-query'; +import usePrevious from 'react-use/lib/usePrevious'; import type { FieldVisConfig } from '../../../../../common/types/field_vis_config'; import type { SupportedFieldType } from '../../../../../common/types/job_field_type'; import type { ItemIdToExpandedRowMap } from '../../../common/components/stats_table'; @@ -82,7 +83,7 @@ export const useESQLDataVisualizerData = ( ) => { const [lastRefresh, setLastRefresh] = useState(0); const { services } = useDataVisualizerKibana(); - const { uiSettings, fieldFormats, executionContext } = services; + const { uiSettings, executionContext, data } = services; const parentExecutionContext = useObservable(executionContext?.context$); @@ -107,17 +108,20 @@ export const useESQLDataVisualizerData = ( autoRefreshSelector: true, }); + const [delayedESQLQuery, setDelayedESQLQuery] = useState(input?.esqlQuery); + const previousQuery = usePrevious(delayedESQLQuery); + const { currentDataView, parentQuery, parentFilters, query, visibleFieldNames, indexPattern } = useMemo(() => { let q = FALLBACK_ESQL_QUERY; - if (input?.query && isESQLQuery(input?.query)) q = input.query; + if (delayedESQLQuery && isESQLQuery(delayedESQLQuery)) q = delayedESQLQuery; if (input?.savedSearch && isESQLQuery(input.savedSearch.searchSource.getField('query'))) { q = input.savedSearch.searchSource.getField('query') as ESQLQuery; } return { currentDataView: input.dataView, - query: q ?? FALLBACK_ESQL_QUERY, + query: q, // It's possible that in a dashboard setting, we will have additional filters and queries parentQuery: input?.query, parentFilters: input?.filters, @@ -131,6 +135,7 @@ export const useESQLDataVisualizerData = ( input?.filters, input?.visibleFieldNames, input?.indexPattern, + delayedESQLQuery, ]); const restorableDefaults = useMemo( @@ -181,6 +186,7 @@ export const useESQLDataVisualizerData = ( (Array.isArray(parentQuery) ? parentQuery : [parentQuery]) as AnyQuery | AnyQuery[], parentFilters ?? [] ); + const timeRange = input.timeRange ? input.timeRange : timefilter.getTime(); if (currentDataView?.timeFieldName) { if (Array.isArray(filter?.bool?.filter)) { @@ -188,8 +194,8 @@ export const useESQLDataVisualizerData = ( range: { [currentDataView.timeFieldName]: { format: 'strict_date_optional_time', - gte: timefilter.getTime().from, - lte: timefilter.getTime().to, + gte: timeRange.from, + lte: timeRange.to, }, }, }); @@ -202,8 +208,8 @@ export const useESQLDataVisualizerData = ( range: { [currentDataView.timeFieldName]: { format: 'strict_date_optional_time', - gte: timefilter.getTime().from, - lte: timefilter.getTime().to, + gte: timeRange.from, + lte: timeRange.to, }, }, }, @@ -239,6 +245,8 @@ export const useESQLDataVisualizerData = ( indexPattern, lastRefresh, limitSize, + input.timeRange?.from, + input.timeRange?.to, ] ); @@ -420,7 +428,7 @@ export const useESQLDataVisualizerData = ( ...field, ...fieldData, loading: fieldData?.existsInDocs ?? true, - fieldFormat: fieldFormats.deserialize({ id: field.secondaryType }), + fieldFormat: data.fieldFormats.deserialize({ id: field.secondaryType }), aggregatable: true, deletable: false, type: getFieldType(field) as SupportedFieldType, @@ -493,7 +501,7 @@ export const useESQLDataVisualizerData = ( secondaryType: getFieldType(field) as SupportedFieldType, loading: fieldData?.existsInDocs ?? true, deletable: false, - fieldFormat: fieldFormats.deserialize({ id: field.secondaryType }), + fieldFormat: data.fieldFormats.deserialize({ id: field.secondaryType }), }; // Map the field type from the Kibana index pattern to the field type @@ -598,12 +606,14 @@ export const useESQLDataVisualizerData = ( totalDocuments={totalCount} typeAccessor="secondaryType" timeFieldName={timeFieldName} + onAddFilter={input.onAddFilter} /> ); } return map; }, {} as ItemIdToExpandedRowMap); }, + // eslint-disable-next-line react-hooks/exhaustive-deps [currentDataView, totalCount, query.esql, timeFieldName] ); @@ -633,6 +643,14 @@ export const useESQLDataVisualizerData = ( [cancelFieldStatsRequest, cancelOverallStatsRequest] ); + useEffect(() => { + if (previousQuery?.esql !== input?.esqlQuery?.esql) { + resetData(); + setDelayedESQLQuery(input?.esqlQuery); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [input?.esqlQuery?.esql, resetData]); + return { totalCount, progress: combinedProgress, diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts index 6cc039fd08b42..7b26fa4fdc872 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts @@ -49,7 +49,7 @@ import { getDefaultPageState, } from '../constants/index_data_visualizer_viewer'; import { getFieldsWithSubFields } from '../utils/get_fields_with_subfields_utils'; -import type { FieldStatisticsTableEmbeddableState } from '../embeddables/grid_embeddable/types'; +import type { FieldStatisticTableEmbeddableProps } from '../embeddables/grid_embeddable/types'; const defaults = getDefaultPageState(); @@ -64,7 +64,7 @@ const DEFAULT_SAMPLING_OPTION: SamplingOption = { }; export const useDataVisualizerGridData = ( // Data view is required for non-ES|QL queries like kuery or lucene - input: Required, + input: Required, dataVisualizerListState: Required, savedRandomSamplerPreference?: RandomSamplerOption, onUpdate?: (params: Dictionary) => void @@ -207,17 +207,19 @@ export const useDataVisualizerGridData = ( const tf = timefilter; - if (!buckets || !tf || !currentDataView) return; + if (!buckets || !tf || !currentDataView || lastRefresh === 0) return; const activeBounds = tf.getActiveBounds(); - - let earliest: number | undefined; - let latest: number | undefined; + let earliest: number | string | undefined; + let latest: number | string | undefined; if (activeBounds !== undefined && currentDataView.timeFieldName !== undefined) { earliest = activeBounds.min?.valueOf(); latest = activeBounds.max?.valueOf(); } - + if (input.timeRange) { + earliest = input.timeRange.from; + latest = input.timeRange.to; + } const bounds = tf.getActiveBounds(); const barTarget = uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET) ?? DEFAULT_BAR_TARGET; buckets.setInterval('auto'); @@ -264,23 +266,19 @@ export const useDataVisualizerGridData = ( nonAggregatableFields, browserSessionSeed, samplingOption: { ...samplingOption, seed: browserSessionSeed.toString() }, - componentExecutionContext, + embeddableExecutionContext: componentExecutionContext, }; }, // eslint-disable-next-line react-hooks/exhaustive-deps [ - _timeBuckets, - timefilter, currentDataView.id, + lastRefresh, // eslint-disable-next-line react-hooks/exhaustive-deps - JSON.stringify(searchQuery), - // eslint-disable-next-line react-hooks/exhaustive-deps - JSON.stringify(samplingOption), + JSON.stringify({ searchQuery, samplingOption, fieldsToFetch }), searchSessionId, - lastRefresh, - fieldsToFetch, browserSessionSeed, - componentExecutionContext, + input.timeRange?.from, + input.timeRange?.to, ] ); @@ -335,7 +333,6 @@ export const useDataVisualizerGridData = ( () => overallStatsProgress.loaded * 0.2 + strategyResponse.progress.loaded * 0.8, [overallStatsProgress.loaded, strategyResponse.progress.loaded] ); - useEffect(() => { const timeUpdateSubscription = merge( timefilter.getTimeUpdate$(), diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_field_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_field_stats.ts index c237647970443..563de0ef235a2 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_field_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_field_stats.ts @@ -13,7 +13,8 @@ import { last, cloneDeep } from 'lodash'; import { mergeMap, switchMap } from 'rxjs'; import { Comparators } from '@elastic/eui'; import type { ISearchOptions } from '@kbn/search-types'; -import { buildBaseFilterCriteria, getSafeAggregationName } from '@kbn/ml-query-utils'; +import { getSafeAggregationName } from '@kbn/ml-query-utils'; +import { buildFilterCriteria } from '../../../../common/utils/build_query_filters'; import type { DataStatsFetchProgress, FieldStatsSearchStrategyReturnBase, @@ -146,7 +147,7 @@ export function useFieldStatsSearchStrategy( return; } - const filterCriteria = buildBaseFilterCriteria( + const filterCriteria = buildFilterCriteria( searchStrategyParams.timeFieldName, searchStrategyParams.earliest, searchStrategyParams.latest, diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_overall_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_overall_stats.ts index e70e3b810cdb8..6e3f4ae33fa3f 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_overall_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_overall_stats.ts @@ -16,7 +16,6 @@ import type { } from '@kbn/search-types'; import { extractErrorProperties } from '@kbn/ml-error-utils'; import { getProcessedFields } from '@kbn/ml-data-grid'; -import { buildBaseFilterCriteria } from '@kbn/ml-query-utils'; import { isDefined } from '@kbn/ml-is-defined'; import type { FieldSpec } from '@kbn/data-views-plugin/common'; import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; @@ -52,6 +51,7 @@ import { fetchDataWithTimeout, rateLimitingForkJoin, } from '../search_strategy/requests/fetch_utils'; +import { buildFilterCriteria } from '../../../../common/utils/build_query_filters'; const getPopulatedFieldsInIndex = ( populatedFieldsInIndexWithoutRuntimeFields: Set | undefined | null, @@ -119,12 +119,7 @@ export function useOverallStats { const { index, timeFieldName, earliestMs, latestMs, query, runtimeFieldMap, maxExamples } = @@ -35,7 +35,7 @@ export const getFieldExamplesRequest = (params: FieldStatsCommonRequestParams, f // Request at least 100 docs so that we have a chance of obtaining // 'maxExamples' of the field. const size = Math.max(100, maxExamples ?? MAX_EXAMPLES_DEFAULT); - const filterCriteria = buildBaseFilterCriteria(timeFieldName, earliestMs, latestMs, query); + const filterCriteria = buildFilterCriteria(timeFieldName, earliestMs, latestMs, query); // Use an exists filter to return examples of the field. if (Array.isArray(filterCriteria)) { diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/overall_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/overall_stats.ts index 46894014f3556..06030db87224e 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/overall_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/overall_stats.ts @@ -11,7 +11,7 @@ import type { Query } from '@kbn/es-query'; import type { IKibanaSearchResponse } from '@kbn/search-types'; import type { AggCardinality } from '@kbn/ml-agg-utils'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import { buildBaseFilterCriteria, getSafeAggregationName } from '@kbn/ml-query-utils'; +import { getSafeAggregationName } from '@kbn/ml-query-utils'; import { buildAggregationWithSamplingOption } from './build_random_sampler_agg'; import { getDatafeedAggregations } from '../../../../../common/utils/datafeed_utils'; import type { AggregatableField, NonAggregatableField } from '../../types/overall_stats'; @@ -20,6 +20,7 @@ import type { OverallStatsSearchStrategyParams, SamplingOption, } from '../../../../../common/types/field_stats'; +import { buildFilterCriteria } from '../../../../../common/utils/build_query_filters'; export const checkAggregatableFieldsExistRequest = ( dataViewTitle: string, @@ -27,14 +28,14 @@ export const checkAggregatableFieldsExistRequest = ( aggregatableFields: OverallStatsSearchStrategyParams['aggregatableFields'], samplingOption: SamplingOption, timeFieldName: string | undefined, - earliestMs?: number, - latestMs?: number, + earliestMs?: number | string, + latestMs?: number | string, datafeedConfig?: estypes.MlDatafeed, runtimeMappings?: estypes.MappingRuntimeFields ): estypes.SearchRequest => { const index = dataViewTitle; const size = 0; - const filterCriteria = buildBaseFilterCriteria(timeFieldName, earliestMs, latestMs, query); + const filterCriteria = buildFilterCriteria(timeFieldName, earliestMs, latestMs, query); const datafeedAggregations = getDatafeedAggregations(datafeedConfig); // Value count aggregation faster way of checking if field exists than using @@ -217,13 +218,13 @@ export const checkNonAggregatableFieldExistsRequest = ( query: Query['query'], field: string, timeFieldName: string | undefined, - earliestMs: number | undefined, - latestMs: number | undefined, + earliestMs: number | string | undefined, + latestMs: number | string | undefined, runtimeMappings?: estypes.MappingRuntimeFields ): estypes.SearchRequest => { const index = dataViewTitle; const size = 0; - const filterCriteria = buildBaseFilterCriteria(timeFieldName, earliestMs, latestMs, query); + const filterCriteria = buildFilterCriteria(timeFieldName, earliestMs, latestMs, query); if (Array.isArray(filterCriteria)) { filterCriteria.push({ exists: { field } }); @@ -256,12 +257,12 @@ export const getSampleOfDocumentsForNonAggregatableFields = ( dataViewTitle: string, query: Query['query'], timeFieldName: string | undefined, - earliestMs: number | undefined, - latestMs: number | undefined, + earliestMs: number | string | undefined, + latestMs: number | string | undefined, runtimeMappings?: estypes.MappingRuntimeFields ): estypes.SearchRequest => { const index = dataViewTitle; - const filterCriteria = buildBaseFilterCriteria(timeFieldName, earliestMs, latestMs, query); + const filterCriteria = buildFilterCriteria(timeFieldName, earliestMs, latestMs, query); return { index, diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/ui_actions/create_field_stats_table.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/ui_actions/create_field_stats_table.tsx new file mode 100644 index 0000000000000..441fbd4d6c5e9 --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/ui_actions/create_field_stats_table.tsx @@ -0,0 +1,187 @@ +/* + * 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'; +import type { PresentationContainer } from '@kbn/presentation-containers'; +import { tracksOverlays } from '@kbn/presentation-containers'; +import type { EmbeddableApiContext } from '@kbn/presentation-publishing'; +import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public'; +import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import type { CoreStart } from '@kbn/core-lifecycle-browser'; +import { getIndexPatternFromESQLQuery } from '@kbn/esql-utils'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import React from 'react'; +import { isDefined } from '@kbn/ml-is-defined'; +import { COMMON_VISUALIZATION_GROUPING } from '@kbn/visualizations-plugin/public'; +import { FIELD_STATS_EMBEDDABLE_TYPE } from '../embeddables/field_stats/constants'; +import type { DataVisualizerStartDependencies } from '../../common/types/data_visualizer_plugin'; +import type { + FieldStatisticsTableEmbeddableApi, + FieldStatsControlsApi, +} from '../embeddables/field_stats/types'; +import { FieldStatsInitializerViewType } from '../embeddables/grid_embeddable/types'; +import type { FieldStatsInitialState } from '../embeddables/grid_embeddable/types'; +import { getOrCreateDataViewByIndexPattern } from '../search_strategy/requests/get_data_view_by_index_pattern'; +import { FieldStatisticsInitializer } from '../embeddables/field_stats/field_stats_initializer'; + +const parentApiIsCompatible = async ( + parentApi: unknown +): Promise => { + const { apiIsPresentationContainer } = await import('@kbn/presentation-containers'); + // we cannot have an async type check, so return the casted parentApi rather than a boolean + return apiIsPresentationContainer(parentApi) ? (parentApi as PresentationContainer) : undefined; +}; + +interface FieldStatsActionContext extends EmbeddableApiContext { + embeddable: FieldStatisticsTableEmbeddableApi; +} + +async function updatePanelFromFlyoutEdits({ + api, + isNewPanel, + deletePanel, + coreStart, + pluginStart, + initialState, +}: { + api: FieldStatisticsTableEmbeddableApi; + isNewPanel: boolean; + deletePanel?: () => void; + coreStart: CoreStart; + pluginStart: DataVisualizerStartDependencies; + initialState: FieldStatsInitialState; + fieldStatsControlsApi?: FieldStatsControlsApi; +}) { + const parentApi = api.parentApi; + const overlayTracker = tracksOverlays(parentApi) ? parentApi : undefined; + const services = { + ...coreStart, + ...pluginStart, + }; + let hasChanged = false; + const cancelChanges = () => { + // Reset to initialState in case user has changed the preview state + if (hasChanged && api && initialState) { + api.updateUserInput(initialState); + } + + if (isNewPanel && deletePanel) { + deletePanel(); + } + flyoutSession.close(); + overlayTracker?.clearOverlays(); + }; + + const update = async (nextUpdate: FieldStatsInitialState) => { + const esqlQuery = nextUpdate?.query?.esql; + if (isDefined(esqlQuery)) { + const indexPatternFromQuery = getIndexPatternFromESQLQuery(esqlQuery); + const dv = await getOrCreateDataViewByIndexPattern( + pluginStart.data.dataViews, + indexPatternFromQuery, + undefined + ); + if (dv?.id && nextUpdate.dataViewId !== dv.id) { + nextUpdate.dataViewId = dv.id; + } + } + if (api) { + api.updateUserInput(nextUpdate); + } + + flyoutSession.close(); + overlayTracker?.clearOverlays(); + }; + const flyoutSession = services.overlays.openFlyout( + toMountPoint( + + { + if (api.updateUserInput) { + api.updateUserInput(nextUpdate); + hasChanged = true; + } + }} + onCreate={update} + onCancel={cancelChanges} + isNewPanel={isNewPanel} + /> + , + coreStart + ), + { + ownFocus: true, + size: 's', + paddingSize: 'm', + hideCloseButton: true, + type: 'push', + 'data-test-subj': 'fieldStatisticsInitializerFlyout', + onClose: cancelChanges, + } + ); + overlayTracker?.openOverlay(flyoutSession, { focusedPanelId: api.uuid }); +} + +export function createAddFieldStatsTableAction( + coreStart: CoreStart, + pluginStart: DataVisualizerStartDependencies +): UiActionsActionDefinition { + return { + id: 'create-field-stats-table', + grouping: COMMON_VISUALIZATION_GROUPING, + order: 10, + getIconType: () => 'inspect', + getDisplayName: () => + i18n.translate('xpack.dataVisualizer.fieldStatistics.displayName', { + defaultMessage: 'Field statistics', + }), + async isCompatible(context: EmbeddableApiContext) { + return Boolean(await parentApiIsCompatible(context.embeddable)); + }, + async execute(context) { + const presentationContainerParent = await parentApiIsCompatible(context.embeddable); + if (!presentationContainerParent) throw new IncompatibleActionError(); + + try { + const defaultIndexPattern = await pluginStart.data.dataViews.getDefault(); + const defaultInitialState: FieldStatsInitialState = { + viewType: FieldStatsInitializerViewType.ESQL, + query: { + // Initial default query + esql: `from ${defaultIndexPattern?.getIndexPattern()} | limit 10`, + }, + }; + const embeddable = await presentationContainerParent.addNewPanel< + object, + FieldStatisticsTableEmbeddableApi + >({ + panelType: FIELD_STATS_EMBEDDABLE_TYPE, + initialState: defaultInitialState, + }); + // open the flyout if embeddable has been created successfully + if (embeddable) { + const deletePanel = () => { + presentationContainerParent.removePanel(embeddable.uuid); + }; + + updatePanelFromFlyoutEdits({ + api: embeddable, + isNewPanel: true, + deletePanel, + coreStart, + pluginStart, + initialState: defaultInitialState, + }); + } + } catch (e) { + return Promise.reject(e); + } + }, + }; +} diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/ui_actions/index.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/ui_actions/index.ts new file mode 100644 index 0000000000000..faa8f34bdfbd7 --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/ui_actions/index.ts @@ -0,0 +1,21 @@ +/* + * 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 { CoreStart } from '@kbn/core-lifecycle-browser'; +import type { UiActionsSetup } from '@kbn/ui-actions-plugin/public'; +import type { DataVisualizerStartDependencies } from '../../common/types/data_visualizer_plugin'; + +export function registerDataVisualizerUiActions( + uiActions: UiActionsSetup, + coreStart: CoreStart, + pluginStart: DataVisualizerStartDependencies +) { + import('./create_field_stats_table').then(({ createAddFieldStatsTableAction }) => { + const addFieldStatsAction = createAddFieldStatsTableAction(coreStart, pluginStart); + uiActions.addTriggerAction('ADD_PANEL_TRIGGER', addFieldStatsAction); + }); +} diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/get_fields_with_subfields_utils.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/get_fields_with_subfields_utils.ts index 0c159878e8b2a..4c509fb212755 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/get_fields_with_subfields_utils.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/get_fields_with_subfields_utils.ts @@ -6,7 +6,7 @@ */ import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; import { isDefined } from '@kbn/ml-is-defined'; -import type { FieldStatisticsTableEmbeddableState } from '../embeddables/grid_embeddable/types'; +import type { FieldStatisticTableEmbeddableProps } from '../embeddables/grid_embeddable/types'; /** * Helper logic to add multi-fields to the table for embeddables outside of Index data visualizer @@ -19,7 +19,7 @@ export const getFieldsWithSubFields = ({ currentDataView, shouldGetSubfields = false, }: { - input: FieldStatisticsTableEmbeddableState; + input: FieldStatisticTableEmbeddableProps; currentDataView: DataView; shouldGetSubfields: boolean; }) => { diff --git a/x-pack/plugins/data_visualizer/public/plugin.ts b/x-pack/plugins/data_visualizer/public/plugin.ts index 15e1cb88eb1ba..deaf4bdc99678 100644 --- a/x-pack/plugins/data_visualizer/public/plugin.ts +++ b/x-pack/plugins/data_visualizer/public/plugin.ts @@ -20,6 +20,8 @@ import type { DataVisualizerSetupDependencies, DataVisualizerStartDependencies, } from './application/common/types/data_visualizer_plugin'; +import { registerEmbeddables } from './application/index_data_visualizer/embeddables/field_stats'; +import { registerDataVisualizerUiActions } from './application/index_data_visualizer/ui_actions'; export type DataVisualizerPluginSetup = ReturnType; export type DataVisualizerPluginStart = ReturnType; @@ -50,7 +52,17 @@ export class DataVisualizerPlugin } } - public setup(core: DataVisualizerCoreSetup, plugins: DataVisualizerSetupDependencies) { + public async setup(core: DataVisualizerCoreSetup, plugins: DataVisualizerSetupDependencies) { + if (plugins.embeddable) { + registerEmbeddables(plugins.embeddable, core); + } + + const [coreStart, pluginStart] = await core.getStartServices(); + + if (plugins.uiActions) { + registerDataVisualizerUiActions(plugins.uiActions, coreStart, pluginStart); + } + if (plugins.home) { registerHomeAddData(plugins.home, this.resultsLinks); registerHomeFeatureCatalogue(plugins.home); @@ -75,7 +87,7 @@ export class DataVisualizerPlugin FieldStatisticsTable: dynamic( async () => import( - './application/index_data_visualizer/embeddables/grid_embeddable/field_stats_embeddable_wrapper' + './application/index_data_visualizer/embeddables/grid_embeddable/field_stats_wrapper' ) ), }; diff --git a/x-pack/plugins/data_visualizer/tsconfig.json b/x-pack/plugins/data_visualizer/tsconfig.json index 5a166df52eed2..9616783094354 100644 --- a/x-pack/plugins/data_visualizer/tsconfig.json +++ b/x-pack/plugins/data_visualizer/tsconfig.json @@ -82,7 +82,12 @@ "@kbn/presentation-publishing", "@kbn/shared-ux-utility", "@kbn/search-types", - "@kbn/unified-field-list" + "@kbn/unified-field-list", + "@kbn/content-management-utils", + "@kbn/core-lifecycle-browser", + "@kbn/presentation-containers", + "@kbn/react-kibana-mount", + "@kbn/visualizations-plugin" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index a24528f47fcd1..53e56dc0c09c1 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -44225,8 +44225,6 @@ "uiActions.errors.incompatibleAction": "Action non compatible", "uiActions.triggers.rowClickkDescription": "Un clic sur une ligne de tableau", "uiActions.triggers.rowClickTitle": "Clic sur ligne de tableau", - "uiActions.triggers.dashboard.addPanelMenu.description": "Une nouvelle action apparaîtra dans le menu Ajouter un panneau du tableau de bord", - "uiActions.triggers.dashboard.addPanelMenu.title": "Menu Ajouter un panneau", "unsavedChangesBadge.contextMenu.openButton": "Afficher les actions disponibles", "unsavedChangesBadge.contextMenu.revertChangesButton": "Restaurer les modifications", "unsavedChangesBadge.contextMenu.revertingChangesButtonStatus": "Annuler les modifications", From 640baeeda302559d8d7cf3a2203ad808b1701f58 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 2 Jul 2024 07:01:34 +0200 Subject: [PATCH 050/128] [api-docs] 2024-07-02 Daily api_docs build (#187322) Generated by https://buildkite.com/elastic/kibana-api-docs-daily/builds/756 --- api_docs/actions.mdx | 2 +- api_docs/advanced_settings.mdx | 2 +- .../ai_assistant_management_selection.mdx | 2 +- api_docs/aiops.mdx | 2 +- api_docs/alerting.mdx | 2 +- api_docs/apm.mdx | 2 +- api_docs/apm_data_access.mdx | 2 +- api_docs/assets_data_access.mdx | 2 +- api_docs/banners.mdx | 2 +- api_docs/bfetch.mdx | 2 +- api_docs/canvas.mdx | 2 +- api_docs/cases.mdx | 2 +- api_docs/charts.mdx | 2 +- api_docs/cloud.mdx | 2 +- api_docs/cloud_data_migration.mdx | 2 +- api_docs/cloud_defend.mdx | 2 +- api_docs/cloud_experiments.mdx | 2 +- api_docs/cloud_security_posture.mdx | 2 +- api_docs/console.mdx | 2 +- api_docs/content_management.mdx | 2 +- api_docs/controls.mdx | 2 +- api_docs/custom_integrations.mdx | 2 +- api_docs/dashboard.mdx | 2 +- api_docs/dashboard_enhanced.mdx | 2 +- api_docs/data.mdx | 2 +- api_docs/data_quality.mdx | 2 +- api_docs/data_query.mdx | 2 +- api_docs/data_search.mdx | 2 +- api_docs/data_view_editor.mdx | 2 +- api_docs/data_view_field_editor.mdx | 2 +- api_docs/data_view_management.mdx | 2 +- api_docs/data_views.mdx | 2 +- api_docs/data_visualizer.mdx | 2 +- api_docs/dataset_quality.mdx | 2 +- api_docs/deprecations_by_api.mdx | 8 +- api_docs/deprecations_by_plugin.mdx | 17 +- api_docs/deprecations_by_team.mdx | 2 +- api_docs/dev_tools.mdx | 2 +- api_docs/discover.mdx | 2 +- api_docs/discover_enhanced.mdx | 2 +- api_docs/discover_shared.mdx | 2 +- api_docs/ecs_data_quality_dashboard.mdx | 2 +- api_docs/elastic_assistant.mdx | 2 +- api_docs/embeddable.mdx | 2 +- api_docs/embeddable_enhanced.mdx | 2 +- api_docs/encrypted_saved_objects.mdx | 2 +- api_docs/enterprise_search.mdx | 2 +- api_docs/entity_manager.mdx | 2 +- api_docs/es_ui_shared.mdx | 2 +- api_docs/esql_data_grid.mdx | 2 +- api_docs/event_annotation.mdx | 2 +- api_docs/event_annotation_listing.mdx | 2 +- api_docs/event_log.mdx | 2 +- api_docs/exploratory_view.mdx | 2 +- api_docs/expression_error.mdx | 2 +- api_docs/expression_gauge.mdx | 2 +- api_docs/expression_heatmap.mdx | 2 +- api_docs/expression_image.mdx | 2 +- api_docs/expression_legacy_metric_vis.mdx | 2 +- api_docs/expression_metric.mdx | 2 +- api_docs/expression_metric_vis.mdx | 2 +- api_docs/expression_partition_vis.mdx | 2 +- api_docs/expression_repeat_image.mdx | 2 +- api_docs/expression_reveal_image.mdx | 2 +- api_docs/expression_shape.mdx | 2 +- api_docs/expression_tagcloud.mdx | 2 +- api_docs/expression_x_y.mdx | 2 +- api_docs/expressions.mdx | 2 +- api_docs/features.mdx | 2 +- api_docs/field_formats.mdx | 2 +- api_docs/fields_metadata.mdx | 2 +- api_docs/file_upload.mdx | 2 +- api_docs/files.mdx | 2 +- api_docs/files_management.mdx | 2 +- api_docs/fleet.mdx | 2 +- api_docs/global_search.mdx | 2 +- api_docs/guided_onboarding.mdx | 2 +- api_docs/home.mdx | 2 +- api_docs/image_embeddable.mdx | 2 +- api_docs/index_lifecycle_management.mdx | 2 +- api_docs/index_management.mdx | 2 +- api_docs/infra.mdx | 2 +- api_docs/ingest_pipelines.mdx | 2 +- api_docs/inspector.mdx | 2 +- api_docs/integration_assistant.mdx | 2 +- api_docs/interactive_setup.mdx | 2 +- api_docs/investigate.mdx | 2 +- api_docs/kbn_ace.mdx | 2 +- api_docs/kbn_actions_types.mdx | 2 +- api_docs/kbn_aiops_components.mdx | 2 +- api_docs/kbn_aiops_log_pattern_analysis.mdx | 2 +- api_docs/kbn_aiops_log_rate_analysis.mdx | 2 +- .../kbn_alerting_api_integration_helpers.mdx | 2 +- api_docs/kbn_alerting_comparators.mdx | 2 +- api_docs/kbn_alerting_state_types.mdx | 2 +- api_docs/kbn_alerting_types.mdx | 2 +- api_docs/kbn_alerts_as_data_utils.mdx | 2 +- api_docs/kbn_alerts_ui_shared.mdx | 2 +- api_docs/kbn_analytics.mdx | 2 +- api_docs/kbn_analytics_collection_utils.mdx | 2 +- api_docs/kbn_apm_config_loader.mdx | 2 +- api_docs/kbn_apm_data_view.mdx | 2 +- api_docs/kbn_apm_synthtrace.mdx | 2 +- api_docs/kbn_apm_synthtrace_client.mdx | 2 +- api_docs/kbn_apm_utils.mdx | 2 +- api_docs/kbn_axe_config.mdx | 2 +- api_docs/kbn_bfetch_error.mdx | 2 +- api_docs/kbn_calculate_auto.mdx | 2 +- .../kbn_calculate_width_from_char_count.mdx | 2 +- api_docs/kbn_cases_components.mdx | 2 +- api_docs/kbn_cell_actions.mdx | 2 +- api_docs/kbn_chart_expressions_common.mdx | 2 +- api_docs/kbn_chart_icons.mdx | 2 +- api_docs/kbn_ci_stats_core.mdx | 2 +- api_docs/kbn_ci_stats_performance_metrics.mdx | 2 +- api_docs/kbn_ci_stats_reporter.mdx | 2 +- api_docs/kbn_cli_dev_mode.mdx | 2 +- api_docs/kbn_code_editor.mdx | 2 +- api_docs/kbn_code_editor_mock.mdx | 2 +- api_docs/kbn_code_owners.mdx | 2 +- api_docs/kbn_coloring.mdx | 2 +- api_docs/kbn_config.mdx | 2 +- api_docs/kbn_config_mocks.devdocs.json | 2 +- api_docs/kbn_config_mocks.mdx | 2 +- api_docs/kbn_config_schema.devdocs.json | 64 ++++- api_docs/kbn_config_schema.mdx | 4 +- .../kbn_content_management_content_editor.mdx | 2 +- ...tent_management_tabbed_table_list_view.mdx | 2 +- ...kbn_content_management_table_list_view.mdx | 2 +- ...tent_management_table_list_view_common.mdx | 2 +- ...ntent_management_table_list_view_table.mdx | 2 +- .../kbn_content_management_user_profiles.mdx | 2 +- api_docs/kbn_content_management_utils.mdx | 2 +- .../kbn_core_analytics_browser.devdocs.json | 56 ++-- api_docs/kbn_core_analytics_browser.mdx | 2 +- .../kbn_core_analytics_browser_internal.mdx | 2 +- api_docs/kbn_core_analytics_browser_mocks.mdx | 2 +- .../kbn_core_analytics_server.devdocs.json | 56 ++-- api_docs/kbn_core_analytics_server.mdx | 2 +- .../kbn_core_analytics_server_internal.mdx | 2 +- api_docs/kbn_core_analytics_server_mocks.mdx | 2 +- api_docs/kbn_core_application_browser.mdx | 2 +- .../kbn_core_application_browser_internal.mdx | 2 +- .../kbn_core_application_browser_mocks.mdx | 2 +- api_docs/kbn_core_application_common.mdx | 2 +- api_docs/kbn_core_apps_browser_internal.mdx | 2 +- api_docs/kbn_core_apps_browser_mocks.mdx | 2 +- api_docs/kbn_core_apps_server_internal.mdx | 2 +- api_docs/kbn_core_base_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_common.mdx | 2 +- api_docs/kbn_core_base_server_internal.mdx | 2 +- api_docs/kbn_core_base_server_mocks.mdx | 2 +- .../kbn_core_capabilities_browser_mocks.mdx | 2 +- api_docs/kbn_core_capabilities_common.mdx | 2 +- api_docs/kbn_core_capabilities_server.mdx | 2 +- .../kbn_core_capabilities_server_mocks.mdx | 2 +- api_docs/kbn_core_chrome_browser.mdx | 2 +- api_docs/kbn_core_chrome_browser_mocks.mdx | 2 +- ...n_core_config_server_internal.devdocs.json | 89 ++++++- api_docs/kbn_core_config_server_internal.mdx | 7 +- api_docs/kbn_core_custom_branding_browser.mdx | 2 +- ..._core_custom_branding_browser_internal.mdx | 2 +- ...kbn_core_custom_branding_browser_mocks.mdx | 2 +- api_docs/kbn_core_custom_branding_common.mdx | 2 +- api_docs/kbn_core_custom_branding_server.mdx | 2 +- ...n_core_custom_branding_server_internal.mdx | 2 +- .../kbn_core_custom_branding_server_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_browser.mdx | 2 +- ...kbn_core_deprecations_browser_internal.mdx | 2 +- .../kbn_core_deprecations_browser_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_common.mdx | 2 +- api_docs/kbn_core_deprecations_server.mdx | 2 +- .../kbn_core_deprecations_server_internal.mdx | 2 +- .../kbn_core_deprecations_server_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_browser.mdx | 2 +- api_docs/kbn_core_doc_links_browser_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_server.mdx | 2 +- api_docs/kbn_core_doc_links_server_mocks.mdx | 2 +- ...e_elasticsearch_client_server_internal.mdx | 2 +- ...core_elasticsearch_client_server_mocks.mdx | 2 +- api_docs/kbn_core_elasticsearch_server.mdx | 2 +- ...kbn_core_elasticsearch_server_internal.mdx | 2 +- .../kbn_core_elasticsearch_server_mocks.mdx | 2 +- .../kbn_core_environment_server_internal.mdx | 2 +- .../kbn_core_environment_server_mocks.mdx | 2 +- .../kbn_core_execution_context_browser.mdx | 2 +- ...ore_execution_context_browser_internal.mdx | 2 +- ...n_core_execution_context_browser_mocks.mdx | 2 +- .../kbn_core_execution_context_common.mdx | 2 +- .../kbn_core_execution_context_server.mdx | 2 +- ...core_execution_context_server_internal.mdx | 2 +- ...bn_core_execution_context_server_mocks.mdx | 2 +- api_docs/kbn_core_fatal_errors_browser.mdx | 2 +- .../kbn_core_fatal_errors_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_browser.mdx | 2 +- api_docs/kbn_core_http_browser_internal.mdx | 2 +- api_docs/kbn_core_http_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_common.mdx | 2 +- .../kbn_core_http_context_server_mocks.mdx | 2 +- ...re_http_request_handler_context_server.mdx | 2 +- api_docs/kbn_core_http_resources_server.mdx | 2 +- ...bn_core_http_resources_server_internal.mdx | 2 +- .../kbn_core_http_resources_server_mocks.mdx | 2 +- .../kbn_core_http_router_server_internal.mdx | 2 +- .../kbn_core_http_router_server_mocks.mdx | 2 +- api_docs/kbn_core_http_server.devdocs.json | 10 +- api_docs/kbn_core_http_server.mdx | 2 +- api_docs/kbn_core_http_server_internal.mdx | 2 +- api_docs/kbn_core_http_server_mocks.mdx | 2 +- api_docs/kbn_core_i18n_browser.mdx | 2 +- api_docs/kbn_core_i18n_browser_mocks.mdx | 2 +- api_docs/kbn_core_i18n_server.mdx | 2 +- api_docs/kbn_core_i18n_server_internal.mdx | 2 +- api_docs/kbn_core_i18n_server_mocks.mdx | 2 +- ...n_core_injected_metadata_browser_mocks.mdx | 2 +- ...kbn_core_integrations_browser_internal.mdx | 2 +- .../kbn_core_integrations_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_browser.mdx | 2 +- api_docs/kbn_core_lifecycle_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_server.mdx | 2 +- api_docs/kbn_core_lifecycle_server_mocks.mdx | 2 +- api_docs/kbn_core_logging_browser_mocks.mdx | 2 +- api_docs/kbn_core_logging_common_internal.mdx | 2 +- api_docs/kbn_core_logging_server.mdx | 2 +- api_docs/kbn_core_logging_server_internal.mdx | 2 +- api_docs/kbn_core_logging_server_mocks.mdx | 2 +- ...ore_metrics_collectors_server_internal.mdx | 2 +- ...n_core_metrics_collectors_server_mocks.mdx | 2 +- api_docs/kbn_core_metrics_server.mdx | 2 +- api_docs/kbn_core_metrics_server_internal.mdx | 2 +- api_docs/kbn_core_metrics_server_mocks.mdx | 2 +- api_docs/kbn_core_mount_utils_browser.mdx | 2 +- api_docs/kbn_core_node_server.mdx | 2 +- api_docs/kbn_core_node_server_internal.mdx | 2 +- api_docs/kbn_core_node_server_mocks.mdx | 2 +- api_docs/kbn_core_notifications_browser.mdx | 2 +- ...bn_core_notifications_browser_internal.mdx | 2 +- .../kbn_core_notifications_browser_mocks.mdx | 2 +- api_docs/kbn_core_overlays_browser.mdx | 2 +- .../kbn_core_overlays_browser_internal.mdx | 2 +- api_docs/kbn_core_overlays_browser_mocks.mdx | 2 +- api_docs/kbn_core_plugins_browser.mdx | 2 +- api_docs/kbn_core_plugins_browser_mocks.mdx | 2 +- .../kbn_core_plugins_contracts_browser.mdx | 2 +- .../kbn_core_plugins_contracts_server.mdx | 2 +- api_docs/kbn_core_plugins_server.mdx | 2 +- api_docs/kbn_core_plugins_server_mocks.mdx | 2 +- api_docs/kbn_core_preboot_server.mdx | 2 +- api_docs/kbn_core_preboot_server_mocks.mdx | 2 +- api_docs/kbn_core_rendering_browser_mocks.mdx | 2 +- .../kbn_core_rendering_server_internal.mdx | 2 +- api_docs/kbn_core_rendering_server_mocks.mdx | 2 +- api_docs/kbn_core_root_server_internal.mdx | 2 +- .../kbn_core_saved_objects_api_browser.mdx | 2 +- .../kbn_core_saved_objects_api_server.mdx | 2 +- ...bn_core_saved_objects_api_server_mocks.mdx | 2 +- ...ore_saved_objects_base_server_internal.mdx | 2 +- ...n_core_saved_objects_base_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_browser.mdx | 2 +- ...bn_core_saved_objects_browser_internal.mdx | 2 +- .../kbn_core_saved_objects_browser_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_common.mdx | 2 +- ..._objects_import_export_server_internal.mdx | 2 +- ...ved_objects_import_export_server_mocks.mdx | 2 +- ...aved_objects_migration_server_internal.mdx | 2 +- ...e_saved_objects_migration_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_server.mdx | 2 +- ...kbn_core_saved_objects_server_internal.mdx | 2 +- .../kbn_core_saved_objects_server_mocks.mdx | 2 +- .../kbn_core_saved_objects_utils_server.mdx | 2 +- api_docs/kbn_core_security_browser.mdx | 2 +- .../kbn_core_security_browser_internal.mdx | 2 +- api_docs/kbn_core_security_browser_mocks.mdx | 2 +- api_docs/kbn_core_security_common.mdx | 2 +- api_docs/kbn_core_security_server.mdx | 2 +- .../kbn_core_security_server_internal.mdx | 2 +- api_docs/kbn_core_security_server_mocks.mdx | 2 +- api_docs/kbn_core_status_common.mdx | 2 +- api_docs/kbn_core_status_common_internal.mdx | 2 +- api_docs/kbn_core_status_server.mdx | 2 +- api_docs/kbn_core_status_server_internal.mdx | 2 +- api_docs/kbn_core_status_server_mocks.mdx | 2 +- ...core_test_helpers_deprecations_getters.mdx | 2 +- ...n_core_test_helpers_http_setup_browser.mdx | 2 +- api_docs/kbn_core_test_helpers_kbn_server.mdx | 2 +- .../kbn_core_test_helpers_model_versions.mdx | 2 +- ...n_core_test_helpers_so_type_serializer.mdx | 2 +- api_docs/kbn_core_test_helpers_test_utils.mdx | 2 +- api_docs/kbn_core_theme_browser.mdx | 2 +- api_docs/kbn_core_theme_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_browser.mdx | 2 +- .../kbn_core_ui_settings_browser_internal.mdx | 2 +- .../kbn_core_ui_settings_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_common.mdx | 2 +- api_docs/kbn_core_ui_settings_server.mdx | 2 +- .../kbn_core_ui_settings_server_internal.mdx | 2 +- .../kbn_core_ui_settings_server_mocks.mdx | 2 +- api_docs/kbn_core_usage_data_server.mdx | 2 +- .../kbn_core_usage_data_server_internal.mdx | 2 +- api_docs/kbn_core_usage_data_server_mocks.mdx | 2 +- api_docs/kbn_core_user_profile_browser.mdx | 2 +- ...kbn_core_user_profile_browser_internal.mdx | 2 +- .../kbn_core_user_profile_browser_mocks.mdx | 2 +- api_docs/kbn_core_user_profile_common.mdx | 2 +- api_docs/kbn_core_user_profile_server.mdx | 2 +- .../kbn_core_user_profile_server_internal.mdx | 2 +- .../kbn_core_user_profile_server_mocks.mdx | 2 +- api_docs/kbn_core_user_settings_server.mdx | 2 +- .../kbn_core_user_settings_server_mocks.mdx | 2 +- api_docs/kbn_crypto.mdx | 2 +- api_docs/kbn_crypto_browser.mdx | 2 +- api_docs/kbn_custom_icons.mdx | 2 +- api_docs/kbn_custom_integrations.mdx | 2 +- api_docs/kbn_cypress_config.mdx | 2 +- api_docs/kbn_data_forge.mdx | 2 +- api_docs/kbn_data_service.mdx | 2 +- api_docs/kbn_data_stream_adapter.mdx | 2 +- api_docs/kbn_data_view_utils.mdx | 2 +- api_docs/kbn_datemath.mdx | 2 +- api_docs/kbn_deeplinks_analytics.mdx | 2 +- api_docs/kbn_deeplinks_devtools.mdx | 2 +- api_docs/kbn_deeplinks_fleet.mdx | 2 +- api_docs/kbn_deeplinks_management.mdx | 2 +- api_docs/kbn_deeplinks_ml.mdx | 2 +- api_docs/kbn_deeplinks_observability.mdx | 2 +- api_docs/kbn_deeplinks_search.mdx | 2 +- api_docs/kbn_deeplinks_security.mdx | 2 +- api_docs/kbn_deeplinks_shared.mdx | 2 +- api_docs/kbn_default_nav_analytics.mdx | 2 +- api_docs/kbn_default_nav_devtools.mdx | 2 +- api_docs/kbn_default_nav_management.mdx | 2 +- api_docs/kbn_default_nav_ml.mdx | 2 +- api_docs/kbn_dev_cli_errors.mdx | 2 +- api_docs/kbn_dev_cli_runner.mdx | 2 +- api_docs/kbn_dev_proc_runner.mdx | 2 +- api_docs/kbn_dev_utils.mdx | 2 +- api_docs/kbn_discover_utils.mdx | 2 +- api_docs/kbn_doc_links.mdx | 2 +- api_docs/kbn_docs_utils.mdx | 2 +- api_docs/kbn_dom_drag_drop.mdx | 2 +- api_docs/kbn_ebt.devdocs.json | 56 ++-- api_docs/kbn_ebt.mdx | 2 +- api_docs/kbn_ebt_tools.mdx | 2 +- api_docs/kbn_ecs_data_quality_dashboard.mdx | 2 +- api_docs/kbn_elastic_agent_utils.mdx | 2 +- api_docs/kbn_elastic_assistant.mdx | 2 +- .../kbn_elastic_assistant_common.devdocs.json | 243 +++++++++++++++++- api_docs/kbn_elastic_assistant_common.mdx | 4 +- api_docs/kbn_entities_schema.mdx | 2 +- api_docs/kbn_es.mdx | 2 +- api_docs/kbn_es_archiver.mdx | 2 +- api_docs/kbn_es_errors.mdx | 2 +- api_docs/kbn_es_query.mdx | 2 +- api_docs/kbn_es_types.mdx | 2 +- api_docs/kbn_eslint_plugin_imports.mdx | 2 +- api_docs/kbn_esql_ast.mdx | 2 +- api_docs/kbn_esql_utils.mdx | 2 +- api_docs/kbn_esql_validation_autocomplete.mdx | 2 +- api_docs/kbn_event_annotation_common.mdx | 2 +- api_docs/kbn_event_annotation_components.mdx | 2 +- api_docs/kbn_expandable_flyout.mdx | 2 +- api_docs/kbn_field_types.mdx | 2 +- api_docs/kbn_field_utils.mdx | 2 +- api_docs/kbn_find_used_node_modules.mdx | 2 +- api_docs/kbn_formatters.mdx | 2 +- .../kbn_ftr_common_functional_services.mdx | 2 +- .../kbn_ftr_common_functional_ui_services.mdx | 2 +- api_docs/kbn_generate.mdx | 2 +- api_docs/kbn_generate_console_definitions.mdx | 2 +- api_docs/kbn_generate_csv.mdx | 2 +- api_docs/kbn_grouping.mdx | 2 +- api_docs/kbn_guided_onboarding.mdx | 2 +- api_docs/kbn_handlebars.mdx | 2 +- api_docs/kbn_hapi_mocks.mdx | 2 +- api_docs/kbn_health_gateway_server.mdx | 2 +- api_docs/kbn_home_sample_data_card.mdx | 2 +- api_docs/kbn_home_sample_data_tab.mdx | 2 +- api_docs/kbn_i18n.mdx | 2 +- api_docs/kbn_i18n_react.mdx | 2 +- api_docs/kbn_import_resolver.mdx | 2 +- api_docs/kbn_index_management.mdx | 2 +- api_docs/kbn_inference_integration_flyout.mdx | 2 +- api_docs/kbn_infra_forge.mdx | 2 +- api_docs/kbn_interpreter.mdx | 2 +- api_docs/kbn_io_ts_utils.mdx | 2 +- api_docs/kbn_ipynb.mdx | 2 +- api_docs/kbn_jest_serializers.mdx | 2 +- api_docs/kbn_journeys.mdx | 2 +- api_docs/kbn_json_ast.mdx | 2 +- api_docs/kbn_json_schemas.mdx | 2 +- api_docs/kbn_kibana_manifest_schema.mdx | 2 +- .../kbn_language_documentation_popover.mdx | 2 +- api_docs/kbn_lens_embeddable_utils.mdx | 2 +- api_docs/kbn_lens_formula_docs.mdx | 2 +- api_docs/kbn_logging.mdx | 2 +- api_docs/kbn_logging_mocks.mdx | 2 +- api_docs/kbn_managed_content_badge.mdx | 2 +- api_docs/kbn_managed_vscode_config.mdx | 2 +- api_docs/kbn_management_cards_navigation.mdx | 2 +- .../kbn_management_settings_application.mdx | 2 +- ...ent_settings_components_field_category.mdx | 2 +- ...gement_settings_components_field_input.mdx | 2 +- ...nagement_settings_components_field_row.mdx | 2 +- ...bn_management_settings_components_form.mdx | 2 +- ...n_management_settings_field_definition.mdx | 2 +- api_docs/kbn_management_settings_ids.mdx | 2 +- ...n_management_settings_section_registry.mdx | 2 +- api_docs/kbn_management_settings_types.mdx | 2 +- .../kbn_management_settings_utilities.mdx | 2 +- api_docs/kbn_management_storybook_config.mdx | 2 +- api_docs/kbn_mapbox_gl.mdx | 2 +- api_docs/kbn_maps_vector_tile_utils.mdx | 2 +- api_docs/kbn_ml_agg_utils.mdx | 2 +- api_docs/kbn_ml_anomaly_utils.mdx | 2 +- api_docs/kbn_ml_cancellable_search.mdx | 2 +- api_docs/kbn_ml_category_validator.mdx | 2 +- api_docs/kbn_ml_chi2test.mdx | 2 +- .../kbn_ml_data_frame_analytics_utils.mdx | 2 +- api_docs/kbn_ml_data_grid.mdx | 2 +- api_docs/kbn_ml_date_picker.mdx | 2 +- api_docs/kbn_ml_date_utils.mdx | 2 +- api_docs/kbn_ml_error_utils.mdx | 2 +- api_docs/kbn_ml_in_memory_table.mdx | 2 +- api_docs/kbn_ml_is_defined.mdx | 2 +- api_docs/kbn_ml_is_populated_object.mdx | 2 +- api_docs/kbn_ml_kibana_theme.mdx | 2 +- api_docs/kbn_ml_local_storage.mdx | 2 +- api_docs/kbn_ml_nested_property.mdx | 2 +- api_docs/kbn_ml_number_utils.mdx | 2 +- api_docs/kbn_ml_query_utils.mdx | 2 +- api_docs/kbn_ml_random_sampler_utils.mdx | 2 +- api_docs/kbn_ml_route_utils.mdx | 2 +- api_docs/kbn_ml_runtime_field_utils.mdx | 2 +- api_docs/kbn_ml_string_hash.mdx | 2 +- api_docs/kbn_ml_time_buckets.mdx | 2 +- api_docs/kbn_ml_trained_models_utils.mdx | 2 +- api_docs/kbn_ml_ui_actions.mdx | 2 +- api_docs/kbn_ml_url_state.mdx | 2 +- api_docs/kbn_mock_idp_utils.mdx | 2 +- api_docs/kbn_monaco.mdx | 2 +- api_docs/kbn_object_versioning.mdx | 2 +- api_docs/kbn_observability_alert_details.mdx | 2 +- .../kbn_observability_alerting_test_data.mdx | 2 +- ...ility_get_padded_alert_time_range_util.mdx | 2 +- api_docs/kbn_openapi_bundler.mdx | 2 +- api_docs/kbn_openapi_generator.mdx | 2 +- api_docs/kbn_optimizer.mdx | 2 +- api_docs/kbn_optimizer_webpack_helpers.mdx | 2 +- api_docs/kbn_osquery_io_ts_types.mdx | 2 +- api_docs/kbn_panel_loader.mdx | 2 +- ..._performance_testing_dataset_extractor.mdx | 2 +- api_docs/kbn_plugin_check.mdx | 2 +- api_docs/kbn_plugin_generator.mdx | 2 +- api_docs/kbn_plugin_helpers.mdx | 2 +- api_docs/kbn_presentation_containers.mdx | 2 +- api_docs/kbn_presentation_publishing.mdx | 2 +- api_docs/kbn_profiling_utils.mdx | 2 +- api_docs/kbn_random_sampling.mdx | 2 +- api_docs/kbn_react_field.mdx | 2 +- api_docs/kbn_react_hooks.mdx | 2 +- api_docs/kbn_react_kibana_context_common.mdx | 2 +- api_docs/kbn_react_kibana_context_render.mdx | 2 +- api_docs/kbn_react_kibana_context_root.mdx | 2 +- api_docs/kbn_react_kibana_context_styled.mdx | 2 +- api_docs/kbn_react_kibana_context_theme.mdx | 2 +- api_docs/kbn_react_kibana_mount.mdx | 2 +- api_docs/kbn_repo_file_maps.mdx | 2 +- api_docs/kbn_repo_linter.mdx | 2 +- api_docs/kbn_repo_path.mdx | 2 +- api_docs/kbn_repo_source_classifier.mdx | 2 +- api_docs/kbn_reporting_common.mdx | 2 +- api_docs/kbn_reporting_csv_share_panel.mdx | 2 +- api_docs/kbn_reporting_export_types_csv.mdx | 2 +- .../kbn_reporting_export_types_csv_common.mdx | 2 +- api_docs/kbn_reporting_export_types_pdf.mdx | 2 +- .../kbn_reporting_export_types_pdf_common.mdx | 2 +- api_docs/kbn_reporting_export_types_png.mdx | 2 +- .../kbn_reporting_export_types_png_common.mdx | 2 +- api_docs/kbn_reporting_mocks_server.mdx | 2 +- api_docs/kbn_reporting_public.mdx | 2 +- api_docs/kbn_reporting_server.mdx | 2 +- api_docs/kbn_resizable_layout.mdx | 2 +- .../kbn_response_ops_feature_flag_service.mdx | 2 +- api_docs/kbn_rison.mdx | 2 +- api_docs/kbn_rollup.mdx | 2 +- api_docs/kbn_router_to_openapispec.mdx | 2 +- api_docs/kbn_router_utils.mdx | 2 +- api_docs/kbn_rrule.mdx | 2 +- api_docs/kbn_rule_data_utils.mdx | 2 +- api_docs/kbn_saved_objects_settings.mdx | 2 +- api_docs/kbn_search_api_panels.mdx | 2 +- api_docs/kbn_search_connectors.mdx | 2 +- api_docs/kbn_search_errors.mdx | 2 +- api_docs/kbn_search_index_documents.mdx | 2 +- api_docs/kbn_search_response_warnings.mdx | 2 +- api_docs/kbn_search_types.mdx | 2 +- api_docs/kbn_security_api_key_management.mdx | 2 +- api_docs/kbn_security_form_components.mdx | 2 +- api_docs/kbn_security_hardening.mdx | 2 +- api_docs/kbn_security_plugin_types_common.mdx | 2 +- api_docs/kbn_security_plugin_types_public.mdx | 2 +- ..._security_plugin_types_server.devdocs.json | 20 -- api_docs/kbn_security_plugin_types_server.mdx | 2 +- api_docs/kbn_security_solution_features.mdx | 2 +- api_docs/kbn_security_solution_navigation.mdx | 2 +- api_docs/kbn_security_solution_side_nav.mdx | 2 +- ...kbn_security_solution_storybook_config.mdx | 2 +- .../kbn_securitysolution_autocomplete.mdx | 2 +- api_docs/kbn_securitysolution_data_table.mdx | 2 +- api_docs/kbn_securitysolution_ecs.mdx | 2 +- api_docs/kbn_securitysolution_es_utils.mdx | 2 +- ...ritysolution_exception_list_components.mdx | 2 +- api_docs/kbn_securitysolution_hook_utils.mdx | 2 +- ..._securitysolution_io_ts_alerting_types.mdx | 2 +- .../kbn_securitysolution_io_ts_list_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_utils.mdx | 2 +- api_docs/kbn_securitysolution_list_api.mdx | 2 +- ...curitysolution_list_constants.devdocs.json | 100 +++---- .../kbn_securitysolution_list_constants.mdx | 2 +- api_docs/kbn_securitysolution_list_hooks.mdx | 2 +- api_docs/kbn_securitysolution_list_utils.mdx | 2 +- api_docs/kbn_securitysolution_rules.mdx | 2 +- api_docs/kbn_securitysolution_t_grid.mdx | 2 +- api_docs/kbn_securitysolution_utils.mdx | 2 +- api_docs/kbn_server_http_tools.mdx | 2 +- api_docs/kbn_server_route_repository.mdx | 2 +- api_docs/kbn_serverless_common_settings.mdx | 2 +- .../kbn_serverless_observability_settings.mdx | 2 +- api_docs/kbn_serverless_project_switcher.mdx | 2 +- api_docs/kbn_serverless_search_settings.mdx | 2 +- api_docs/kbn_serverless_security_settings.mdx | 2 +- api_docs/kbn_serverless_storybook_config.mdx | 2 +- api_docs/kbn_shared_svg.mdx | 2 +- api_docs/kbn_shared_ux_avatar_solution.mdx | 2 +- .../kbn_shared_ux_button_exit_full_screen.mdx | 2 +- api_docs/kbn_shared_ux_button_toolbar.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_chrome_navigation.mdx | 2 +- api_docs/kbn_shared_ux_error_boundary.mdx | 2 +- api_docs/kbn_shared_ux_file_context.mdx | 2 +- api_docs/kbn_shared_ux_file_image.mdx | 2 +- api_docs/kbn_shared_ux_file_image_mocks.mdx | 2 +- api_docs/kbn_shared_ux_file_mocks.mdx | 2 +- api_docs/kbn_shared_ux_file_picker.mdx | 2 +- api_docs/kbn_shared_ux_file_types.mdx | 2 +- api_docs/kbn_shared_ux_file_upload.mdx | 2 +- api_docs/kbn_shared_ux_file_util.mdx | 2 +- api_docs/kbn_shared_ux_link_redirect_app.mdx | 2 +- .../kbn_shared_ux_link_redirect_app_mocks.mdx | 2 +- api_docs/kbn_shared_ux_markdown.mdx | 2 +- api_docs/kbn_shared_ux_markdown_mocks.mdx | 2 +- .../kbn_shared_ux_page_analytics_no_data.mdx | 2 +- ...shared_ux_page_analytics_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_no_data.mdx | 2 +- ...bn_shared_ux_page_kibana_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_template.mdx | 2 +- ...n_shared_ux_page_kibana_template_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data.mdx | 2 +- .../kbn_shared_ux_page_no_data_config.mdx | 2 +- ...bn_shared_ux_page_no_data_config_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_solution_nav.mdx | 2 +- .../kbn_shared_ux_prompt_no_data_views.mdx | 2 +- ...n_shared_ux_prompt_no_data_views_mocks.mdx | 2 +- api_docs/kbn_shared_ux_prompt_not_found.mdx | 2 +- api_docs/kbn_shared_ux_router.mdx | 2 +- api_docs/kbn_shared_ux_router_mocks.mdx | 2 +- api_docs/kbn_shared_ux_storybook_config.mdx | 2 +- api_docs/kbn_shared_ux_storybook_mock.mdx | 2 +- api_docs/kbn_shared_ux_tabbed_modal.mdx | 2 +- api_docs/kbn_shared_ux_utility.mdx | 2 +- api_docs/kbn_slo_schema.mdx | 2 +- api_docs/kbn_some_dev_log.mdx | 2 +- api_docs/kbn_sort_predicates.mdx | 2 +- api_docs/kbn_std.mdx | 2 +- api_docs/kbn_stdio_dev_helpers.mdx | 2 +- api_docs/kbn_storybook.mdx | 2 +- api_docs/kbn_telemetry_tools.mdx | 2 +- api_docs/kbn_test.mdx | 2 +- api_docs/kbn_test_eui_helpers.mdx | 2 +- api_docs/kbn_test_jest_helpers.mdx | 2 +- api_docs/kbn_test_subj_selector.mdx | 2 +- api_docs/kbn_text_based_editor.mdx | 2 +- api_docs/kbn_timerange.mdx | 2 +- api_docs/kbn_tooling_log.mdx | 2 +- api_docs/kbn_triggers_actions_ui_types.mdx | 2 +- api_docs/kbn_try_in_console.mdx | 2 +- api_docs/kbn_ts_projects.mdx | 2 +- api_docs/kbn_typed_react_router_config.mdx | 2 +- api_docs/kbn_ui_actions_browser.mdx | 2 +- api_docs/kbn_ui_shared_deps_src.mdx | 2 +- api_docs/kbn_ui_theme.mdx | 2 +- api_docs/kbn_unified_data_table.mdx | 2 +- api_docs/kbn_unified_doc_viewer.mdx | 2 +- api_docs/kbn_unified_field_list.mdx | 2 +- api_docs/kbn_unsaved_changes_badge.mdx | 2 +- api_docs/kbn_unsaved_changes_prompt.mdx | 2 +- api_docs/kbn_use_tracked_promise.mdx | 2 +- api_docs/kbn_user_profile_components.mdx | 2 +- api_docs/kbn_utility_types.mdx | 2 +- api_docs/kbn_utility_types_jest.mdx | 2 +- api_docs/kbn_utils.mdx | 2 +- api_docs/kbn_visualization_ui_components.mdx | 2 +- api_docs/kbn_visualization_utils.mdx | 2 +- api_docs/kbn_xstate_utils.mdx | 2 +- api_docs/kbn_yarn_lock_validator.mdx | 2 +- api_docs/kbn_zod_helpers.mdx | 2 +- api_docs/kibana_overview.mdx | 2 +- api_docs/kibana_react.mdx | 2 +- api_docs/kibana_utils.mdx | 2 +- api_docs/kubernetes_security.mdx | 2 +- api_docs/lens.mdx | 2 +- api_docs/license_api_guard.mdx | 2 +- api_docs/license_management.mdx | 2 +- api_docs/licensing.mdx | 2 +- api_docs/links.mdx | 2 +- api_docs/lists.mdx | 2 +- api_docs/logs_data_access.mdx | 2 +- api_docs/logs_explorer.mdx | 2 +- api_docs/logs_shared.mdx | 2 +- api_docs/management.mdx | 2 +- api_docs/maps.mdx | 2 +- api_docs/maps_ems.mdx | 2 +- api_docs/metrics_data_access.mdx | 2 +- api_docs/ml.mdx | 2 +- api_docs/mock_idp_plugin.mdx | 2 +- api_docs/monitoring.mdx | 2 +- api_docs/monitoring_collection.mdx | 2 +- api_docs/navigation.mdx | 2 +- api_docs/newsfeed.mdx | 2 +- api_docs/no_data_page.mdx | 2 +- api_docs/notifications.mdx | 2 +- api_docs/observability.mdx | 2 +- api_docs/observability_a_i_assistant.mdx | 2 +- api_docs/observability_a_i_assistant_app.mdx | 2 +- .../observability_ai_assistant_management.mdx | 2 +- api_docs/observability_logs_explorer.mdx | 2 +- api_docs/observability_onboarding.mdx | 2 +- api_docs/observability_shared.mdx | 2 +- api_docs/osquery.mdx | 2 +- api_docs/painless_lab.mdx | 2 +- api_docs/plugin_directory.mdx | 12 +- api_docs/presentation_panel.mdx | 2 +- api_docs/presentation_util.mdx | 2 +- api_docs/profiling.mdx | 2 +- api_docs/profiling_data_access.mdx | 2 +- api_docs/remote_clusters.mdx | 2 +- api_docs/reporting.mdx | 2 +- api_docs/rollup.mdx | 2 +- api_docs/rule_registry.mdx | 2 +- api_docs/runtime_fields.mdx | 2 +- api_docs/saved_objects.mdx | 2 +- api_docs/saved_objects_finder.mdx | 2 +- api_docs/saved_objects_management.mdx | 2 +- api_docs/saved_objects_tagging.mdx | 2 +- api_docs/saved_objects_tagging_oss.mdx | 2 +- api_docs/saved_search.mdx | 2 +- api_docs/screenshot_mode.mdx | 2 +- api_docs/screenshotting.mdx | 2 +- api_docs/search_connectors.mdx | 2 +- api_docs/search_homepage.mdx | 2 +- api_docs/search_inference_endpoints.mdx | 2 +- api_docs/search_notebooks.devdocs.json | 37 ++- api_docs/search_notebooks.mdx | 4 +- api_docs/search_playground.mdx | 2 +- api_docs/security.devdocs.json | 20 -- api_docs/security.mdx | 2 +- api_docs/security_solution.devdocs.json | 12 +- api_docs/security_solution.mdx | 2 +- api_docs/security_solution_ess.mdx | 2 +- api_docs/security_solution_serverless.mdx | 2 +- api_docs/serverless.mdx | 2 +- api_docs/serverless_observability.mdx | 2 +- api_docs/serverless_search.mdx | 2 +- api_docs/session_view.mdx | 2 +- api_docs/share.mdx | 2 +- api_docs/slo.mdx | 2 +- api_docs/snapshot_restore.mdx | 2 +- api_docs/spaces.mdx | 2 +- api_docs/stack_alerts.mdx | 2 +- api_docs/stack_connectors.mdx | 2 +- api_docs/task_manager.mdx | 2 +- api_docs/telemetry.mdx | 2 +- api_docs/telemetry_collection_manager.mdx | 2 +- api_docs/telemetry_collection_xpack.mdx | 2 +- api_docs/telemetry_management_section.mdx | 2 +- api_docs/text_based_languages.mdx | 2 +- api_docs/threat_intelligence.mdx | 2 +- api_docs/timelines.devdocs.json | 104 ++++---- api_docs/timelines.mdx | 2 +- api_docs/transform.mdx | 2 +- api_docs/triggers_actions_ui.mdx | 2 +- api_docs/ui_actions.mdx | 2 +- api_docs/ui_actions_enhanced.mdx | 2 +- api_docs/unified_doc_viewer.mdx | 2 +- api_docs/unified_histogram.mdx | 2 +- api_docs/unified_search.mdx | 2 +- api_docs/unified_search_autocomplete.mdx | 2 +- api_docs/uptime.mdx | 2 +- api_docs/url_forwarding.mdx | 2 +- api_docs/usage_collection.mdx | 2 +- api_docs/ux.mdx | 2 +- api_docs/vis_default_editor.mdx | 2 +- api_docs/vis_type_gauge.mdx | 2 +- api_docs/vis_type_heatmap.mdx | 2 +- api_docs/vis_type_pie.mdx | 2 +- api_docs/vis_type_table.mdx | 2 +- api_docs/vis_type_timelion.mdx | 2 +- api_docs/vis_type_timeseries.mdx | 2 +- api_docs/vis_type_vega.mdx | 2 +- api_docs/vis_type_vislib.mdx | 2 +- api_docs/vis_type_xy.mdx | 2 +- api_docs/visualizations.mdx | 2 +- 715 files changed, 1359 insertions(+), 954 deletions(-) diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 3ac069c1b984a..089bb205e7ff7 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index 5e963a9b0c035..4902e659d41cd 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/ai_assistant_management_selection.mdx b/api_docs/ai_assistant_management_selection.mdx index d52e08ef1be15..23c046dfe2d2c 100644 --- a/api_docs/ai_assistant_management_selection.mdx +++ b/api_docs/ai_assistant_management_selection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiAssistantManagementSelection title: "aiAssistantManagementSelection" image: https://source.unsplash.com/400x175/?github description: API docs for the aiAssistantManagementSelection plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiAssistantManagementSelection'] --- import aiAssistantManagementSelectionObj from './ai_assistant_management_selection.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 269f1a80032d5..30d8bb916e3ae 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 4ea6a20d9ae0c..8ef41508e6557 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 102fed2c28af3..161a9f7530a7b 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/apm_data_access.mdx b/api_docs/apm_data_access.mdx index e7c24c428be48..116881f2451e1 100644 --- a/api_docs/apm_data_access.mdx +++ b/api_docs/apm_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apmDataAccess title: "apmDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the apmDataAccess plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apmDataAccess'] --- import apmDataAccessObj from './apm_data_access.devdocs.json'; diff --git a/api_docs/assets_data_access.mdx b/api_docs/assets_data_access.mdx index f4f2d4ba9690e..1e5fe28083a13 100644 --- a/api_docs/assets_data_access.mdx +++ b/api_docs/assets_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/assetsDataAccess title: "assetsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the assetsDataAccess plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'assetsDataAccess'] --- import assetsDataAccessObj from './assets_data_access.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index f731da41ca03d..4a0898bb277bc 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index b61a7e6f6f87c..5d184cd9f744c 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index 4c563fdc591bb..cf8b182150a40 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 917432447179c..dbc9601ae025f 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index e604c881ec242..b1167ed25660e 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index 76446d2a779bc..5c8378a364d67 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_data_migration.mdx b/api_docs/cloud_data_migration.mdx index 8a209d0e01c02..be2e77e42670f 100644 --- a/api_docs/cloud_data_migration.mdx +++ b/api_docs/cloud_data_migration.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDataMigration title: "cloudDataMigration" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDataMigration plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDataMigration'] --- import cloudDataMigrationObj from './cloud_data_migration.devdocs.json'; diff --git a/api_docs/cloud_defend.mdx b/api_docs/cloud_defend.mdx index 1878fa665a718..9aa22ff4bb54b 100644 --- a/api_docs/cloud_defend.mdx +++ b/api_docs/cloud_defend.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDefend title: "cloudDefend" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDefend plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDefend'] --- import cloudDefendObj from './cloud_defend.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index 1ff538d276d3e..60020659deec0 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index c6d039e00fe45..957f69231c9bd 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index 4223612f0632a..0e682d7d30f8e 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/content_management.mdx b/api_docs/content_management.mdx index b06453f2ab3c4..5331b84b25b2c 100644 --- a/api_docs/content_management.mdx +++ b/api_docs/content_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/contentManagement title: "contentManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the contentManagement plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'contentManagement'] --- import contentManagementObj from './content_management.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 549435b4d899d..8d0daa844417f 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index e10a1ec12f215..8f060547b4a7a 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index c67808a638d60..7168a1f2c25f6 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index 3185563c83d40..b7c933abebdb1 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 7d39ca00d227d..cae9f9c76653a 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_quality.mdx b/api_docs/data_quality.mdx index 3c6d114f6978f..a7ac7b4b90cf8 100644 --- a/api_docs/data_quality.mdx +++ b/api_docs/data_quality.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataQuality title: "dataQuality" image: https://source.unsplash.com/400x175/?github description: API docs for the dataQuality plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataQuality'] --- import dataQualityObj from './data_quality.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index b9ce1c02694d7..4a729deaa1445 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 3262af66b08d8..e5a49c1a9e868 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 82451f44fc756..ca78f7fd66f7b 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index 6240d9f72769c..fe726b483fc0f 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index acd12fca3fa3c..872710dea197d 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 611e5e8b4e2b3..ddd63045decbf 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index f59ede7952396..7fe630d2c5413 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/dataset_quality.mdx b/api_docs/dataset_quality.mdx index a5f6be7deac5f..a854d7facde33 100644 --- a/api_docs/dataset_quality.mdx +++ b/api_docs/dataset_quality.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/datasetQuality title: "datasetQuality" image: https://source.unsplash.com/400x175/?github description: API docs for the datasetQuality plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'datasetQuality'] --- import datasetQualityObj from './dataset_quality.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index abe9b7608961d..8438260bdc43b 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -39,13 +39,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | securitySolution | - | | | cloudDefend, osquery, securitySolution, synthetics | - | | | cloudDefend, osquery, securitySolution, synthetics | - | -| | actions, alerting, observabilityAIAssistant, fleet, cloudDefend, cloudSecurityPosture, enterpriseSearch, lists, securitySolution, serverlessSearch, transform, upgradeAssistant, apm, entityManager, observabilityOnboarding, synthetics, security | - | +| | actions, alerting, observabilityAIAssistant, fleet, cloudSecurityPosture, enterpriseSearch, lists, securitySolution, serverlessSearch, transform, upgradeAssistant, apm, entityManager, observabilityOnboarding, synthetics, security | - | | | cases, securitySolution, security | - | | | @kbn/securitysolution-data-table, securitySolution | - | | | @kbn/securitysolution-data-table, securitySolution | - | | | securitySolution | - | -| | securitySolution, @kbn/securitysolution-data-table | - | -| | securitySolution, @kbn/securitysolution-data-table | - | +| | @kbn/securitysolution-data-table, securitySolution | - | +| | @kbn/securitysolution-data-table, securitySolution | - | | | securitySolution | - | | | securitySolution | - | | | @kbn/core-saved-objects-api-browser, @kbn/core-saved-objects-browser-internal, @kbn/core-saved-objects-api-server, @kbn/core, home, savedObjectsTagging, canvas, savedObjects, @kbn/core-saved-objects-browser-mocks, @kbn/core-saved-objects-import-export-server-internal, savedObjectsTaggingOss, lists, securitySolution, upgradeAssistant, savedObjectsManagement, @kbn/core-ui-settings-server-internal | - | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index ae56cb777456e..5084c1bf7de81 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -480,7 +480,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts#:~:text=fetch) | - | | | [plugin.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/plugin.test.ts#:~:text=getKibanaFeatures) | 8.8.0 | | | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/plugin.ts#:~:text=license%24), [license_state.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/lib/license_state.test.ts#:~:text=license%24), [license_state.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/lib/license_state.test.ts#:~:text=license%24) | 8.8.0 | -| | [rules_client_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/rules_client_factory.ts#:~:text=authc), [rules_client_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/rules_client_factory.ts#:~:text=authc), [rules_client_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/rules_client_factory.ts#:~:text=authc), [rules_settings_client_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/rules_settings_client_factory.ts#:~:text=authc), [maintenance_window_client_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/maintenance_window_client_factory.ts#:~:text=authc), [task.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/invalidate_pending_api_keys/task.ts#:~:text=authc), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/plugin.ts#:~:text=authc), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/plugin.ts#:~:text=authc), [rules_client_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/rules_client_factory.ts#:~:text=authc), [rules_client_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/rules_client_factory.ts#:~:text=authc)+ 6 more | - | +| | [rules_client_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/rules_client_factory.ts#:~:text=authc), [task.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/invalidate_pending_api_keys/task.ts#:~:text=authc), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/plugin.ts#:~:text=authc), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/plugin.ts#:~:text=authc), [rules_client_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/rules_client_factory.ts#:~:text=authc), [task.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/invalidate_pending_api_keys/task.ts#:~:text=authc), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/plugin.ts#:~:text=authc), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/plugin.ts#:~:text=authc) | - | | | [task.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/usage/task.ts#:~:text=index) | - | | | [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule_attributes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts#:~:text=SavedObjectAttributes), [rule_attributes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts#:~:text=SavedObjectAttributes), [rule_attributes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts#:~:text=SavedObjectAttributes), [rule_attributes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts#:~:text=SavedObjectAttributes), [rule_attributes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts#:~:text=SavedObjectAttributes), [inject_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/rules_client/common/inject_references.ts#:~:text=SavedObjectAttributes), [inject_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/rules_client/common/inject_references.ts#:~:text=SavedObjectAttributes), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/types.ts#:~:text=SavedObjectAttributes)+ 36 more | - | | | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/index.ts#:~:text=migrations) | - | @@ -567,7 +567,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | ---------------|-----------|-----------| | | [installation_stats_collector.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_defend/server/lib/telemetry/collectors/installation_stats_collector.ts#:~:text=policy_id), [mocks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_defend/public/test/mocks.ts#:~:text=policy_id), [mocks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_defend/public/test/mocks.ts#:~:text=policy_id) | - | | | [installation_stats_collector.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_defend/server/lib/telemetry/collectors/installation_stats_collector.ts#:~:text=policy_id), [mocks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_defend/public/test/mocks.ts#:~:text=policy_id), [mocks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_defend/public/test/mocks.ts#:~:text=policy_id) | - | -| | [setup_routes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_defend/server/routes/setup_routes.ts#:~:text=authc), [setup_routes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_defend/server/routes/setup_routes.ts#:~:text=authc) | - | @@ -1362,16 +1361,16 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [links.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/links.ts#:~:text=authc), [hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts#:~:text=authc) | - | | | [use_bulk_get_user_profiles.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/user_profiles/use_bulk_get_user_profiles.tsx#:~:text=userProfiles), [use_get_current_user_profile.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/user_profiles/use_get_current_user_profile.tsx#:~:text=userProfiles), [overlay.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/assistant/overlay.tsx#:~:text=userProfiles) | - | | | [request_context_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/request_context_factory.ts#:~:text=audit), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/plugin.ts#:~:text=audit) | - | -| | [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [trusted_app_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID)+ 25 more | - | +| | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [trusted_app_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID)+ 25 more | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_NAME), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_NAME) | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION) | - | -| | [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID)+ 24 more | - | -| | [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME) | - | -| | [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION) | - | -| | [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [exceptions_list_item_generator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/endpoint/data_generators/exceptions_list_item_generator.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID)+ 8 more | - | +| | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/utils.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/utils.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [service_actions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/service/service_actions.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [service_actions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/service/service_actions.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [service_actions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/service/service_actions.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID)+ 24 more | - | +| | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME) | - | +| | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION) | - | +| | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [exceptions_list_item_generator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/endpoint/data_generators/exceptions_list_item_generator.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID)+ 8 more | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_NAME), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/host_isolation_exceptions/index.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/host_isolation_exceptions/index.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_NAME) | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_DESCRIPTION), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/host_isolation_exceptions/index.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/host_isolation_exceptions/index.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_DESCRIPTION) | - | -| | [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklists_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklists_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklists_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklist_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID)+ 8 more | - | +| | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklists_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklists_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklists_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklist_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID)+ 8 more | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_NAME), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/blocklists/index.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/blocklists/index.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_NAME) | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/blocklists/index.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/blocklists/index.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION) | - | | | [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode), [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode), [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode), [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode), [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 259be3b5f4b6a..86813098ef475 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 94ab81dda7dc6..9bd9342e38d90 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index cae22d849f70f..86086518c5ec9 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 75f3ddfc64ae8..6dfd758a353b4 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/discover_shared.mdx b/api_docs/discover_shared.mdx index e9746fa185df9..09b4e32edeb64 100644 --- a/api_docs/discover_shared.mdx +++ b/api_docs/discover_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverShared title: "discoverShared" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverShared plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverShared'] --- import discoverSharedObj from './discover_shared.devdocs.json'; diff --git a/api_docs/ecs_data_quality_dashboard.mdx b/api_docs/ecs_data_quality_dashboard.mdx index 45124ba5473f4..395931a0f30e7 100644 --- a/api_docs/ecs_data_quality_dashboard.mdx +++ b/api_docs/ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ecsDataQualityDashboard title: "ecsDataQualityDashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the ecsDataQualityDashboard plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ecsDataQualityDashboard'] --- import ecsDataQualityDashboardObj from './ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/elastic_assistant.mdx b/api_docs/elastic_assistant.mdx index 0f973677b951c..b2e6a52f803cc 100644 --- a/api_docs/elastic_assistant.mdx +++ b/api_docs/elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/elasticAssistant title: "elasticAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the elasticAssistant plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'elasticAssistant'] --- import elasticAssistantObj from './elastic_assistant.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index c0045a14e6aaa..b15ad570394a6 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 87dbd50d3a039..2fdc2a508e6f6 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index 39b0d171a062e..ed4b96eaa3d95 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index 905d38204910a..a149c24ad3ee7 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/entity_manager.mdx b/api_docs/entity_manager.mdx index dfbe8a341020b..726544e187c60 100644 --- a/api_docs/entity_manager.mdx +++ b/api_docs/entity_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/entityManager title: "entityManager" image: https://source.unsplash.com/400x175/?github description: API docs for the entityManager plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'entityManager'] --- import entityManagerObj from './entity_manager.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index 505bf58a9ea99..bb2d047a2879a 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/esql_data_grid.mdx b/api_docs/esql_data_grid.mdx index 838b82d060adb..de2755078e9fd 100644 --- a/api_docs/esql_data_grid.mdx +++ b/api_docs/esql_data_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esqlDataGrid title: "esqlDataGrid" image: https://source.unsplash.com/400x175/?github description: API docs for the esqlDataGrid plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esqlDataGrid'] --- import esqlDataGridObj from './esql_data_grid.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index 108723661589e..e628071ee4934 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_annotation_listing.mdx b/api_docs/event_annotation_listing.mdx index b99ba4fdea88d..dc7887175e060 100644 --- a/api_docs/event_annotation_listing.mdx +++ b/api_docs/event_annotation_listing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotationListing title: "eventAnnotationListing" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotationListing plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotationListing'] --- import eventAnnotationListingObj from './event_annotation_listing.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 27ef25baf3155..07787d04cf659 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/exploratory_view.mdx b/api_docs/exploratory_view.mdx index 5ccf8e68f7984..6f26375c0a326 100644 --- a/api_docs/exploratory_view.mdx +++ b/api_docs/exploratory_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/exploratoryView title: "exploratoryView" image: https://source.unsplash.com/400x175/?github description: API docs for the exploratoryView plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'exploratoryView'] --- import exploratoryViewObj from './exploratory_view.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index 85f2920696311..217abb4a3da81 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index 905c292a47f3f..d34b47be8e4a0 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index 125fd8b22af3a..ded7ab8f7a0ae 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index c28665f5c0ee8..02c5a3a2384cc 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index 32a9079ec6529..21207676d742f 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index 65918f92a066b..2fd54ba779ba1 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index df5441052fa58..6ed2fadfcb832 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index 25438e77d5dd6..a70ec46e402c2 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index 3efdbbe0171b1..76c3a2293568f 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index c74c9458695d1..f4b9929d8d09d 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index 75a99fec9d042..347abfeba1577 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index 4b1830c954026..6462827c6d37e 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index d1a11d0a26b93..6d240631c7861 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index 1e5cc9df44d11..3762a30d9d937 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.mdx b/api_docs/features.mdx index b5f6ebbaccdb5..0ad7647b41c90 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index 5cba6d9ca3c49..0e60c5a5f4229 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/fields_metadata.mdx b/api_docs/fields_metadata.mdx index 380bd6ae46edb..d7e115d9ee8ef 100644 --- a/api_docs/fields_metadata.mdx +++ b/api_docs/fields_metadata.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldsMetadata title: "fieldsMetadata" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldsMetadata plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldsMetadata'] --- import fieldsMetadataObj from './fields_metadata.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 48fbd09c9df3a..e93ba838dbd9a 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 05bd9b6688fd7..b4cc4b7aaa36f 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/files_management.mdx b/api_docs/files_management.mdx index 49fcaa6454c6a..628ee241962c1 100644 --- a/api_docs/files_management.mdx +++ b/api_docs/files_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/filesManagement title: "filesManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the filesManagement plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'filesManagement'] --- import filesManagementObj from './files_management.devdocs.json'; diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 552ff0850fdce..992b932e619f1 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index cad2fc5665cfd..bc01cb373e227 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 1a16d767bd980..2da43333622b8 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 743470feef906..577f995206df3 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/image_embeddable.mdx b/api_docs/image_embeddable.mdx index d24ec2243abd7..f8d0091546d1d 100644 --- a/api_docs/image_embeddable.mdx +++ b/api_docs/image_embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/imageEmbeddable title: "imageEmbeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the imageEmbeddable plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'imageEmbeddable'] --- import imageEmbeddableObj from './image_embeddable.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index b423218f9903b..6b4da28e9c759 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 1cbdfd5285f47..945fdbaffa53e 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 35411b49bf328..0e04e24e16704 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/ingest_pipelines.mdx b/api_docs/ingest_pipelines.mdx index efbbfc40229d6..54fbadf2c9874 100644 --- a/api_docs/ingest_pipelines.mdx +++ b/api_docs/ingest_pipelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ingestPipelines title: "ingestPipelines" image: https://source.unsplash.com/400x175/?github description: API docs for the ingestPipelines plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ingestPipelines'] --- import ingestPipelinesObj from './ingest_pipelines.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 4a3f5b7f67cac..b31ae884f9566 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/integration_assistant.mdx b/api_docs/integration_assistant.mdx index 171ca4c1eeaa8..9b70ac4420d2d 100644 --- a/api_docs/integration_assistant.mdx +++ b/api_docs/integration_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/integrationAssistant title: "integrationAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the integrationAssistant plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'integrationAssistant'] --- import integrationAssistantObj from './integration_assistant.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 29a4dd1488451..29af3ddff2afb 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/investigate.mdx b/api_docs/investigate.mdx index a04438106c50a..ee8486b88a7e5 100644 --- a/api_docs/investigate.mdx +++ b/api_docs/investigate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/investigate title: "investigate" image: https://source.unsplash.com/400x175/?github description: API docs for the investigate plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'investigate'] --- import investigateObj from './investigate.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index 54644f76c7f87..23aee56bdac7b 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_actions_types.mdx b/api_docs/kbn_actions_types.mdx index a00b5ddceeecd..ff89c43715e85 100644 --- a/api_docs/kbn_actions_types.mdx +++ b/api_docs/kbn_actions_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-actions-types title: "@kbn/actions-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/actions-types plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/actions-types'] --- import kbnActionsTypesObj from './kbn_actions_types.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index abf110af0eb07..1a6385fb00a2c 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_log_pattern_analysis.mdx b/api_docs/kbn_aiops_log_pattern_analysis.mdx index 04a3d4666187e..1fc6bd2ce64c4 100644 --- a/api_docs/kbn_aiops_log_pattern_analysis.mdx +++ b/api_docs/kbn_aiops_log_pattern_analysis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-log-pattern-analysis title: "@kbn/aiops-log-pattern-analysis" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-log-pattern-analysis plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-log-pattern-analysis'] --- import kbnAiopsLogPatternAnalysisObj from './kbn_aiops_log_pattern_analysis.devdocs.json'; diff --git a/api_docs/kbn_aiops_log_rate_analysis.mdx b/api_docs/kbn_aiops_log_rate_analysis.mdx index 7a39393e10526..3aba3b3eb6001 100644 --- a/api_docs/kbn_aiops_log_rate_analysis.mdx +++ b/api_docs/kbn_aiops_log_rate_analysis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-log-rate-analysis title: "@kbn/aiops-log-rate-analysis" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-log-rate-analysis plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-log-rate-analysis'] --- import kbnAiopsLogRateAnalysisObj from './kbn_aiops_log_rate_analysis.devdocs.json'; diff --git a/api_docs/kbn_alerting_api_integration_helpers.mdx b/api_docs/kbn_alerting_api_integration_helpers.mdx index eeb195c21a371..13c04f60c0077 100644 --- a/api_docs/kbn_alerting_api_integration_helpers.mdx +++ b/api_docs/kbn_alerting_api_integration_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-api-integration-helpers title: "@kbn/alerting-api-integration-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-api-integration-helpers plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-api-integration-helpers'] --- import kbnAlertingApiIntegrationHelpersObj from './kbn_alerting_api_integration_helpers.devdocs.json'; diff --git a/api_docs/kbn_alerting_comparators.mdx b/api_docs/kbn_alerting_comparators.mdx index 867e10f4fb422..d17df9a9516dc 100644 --- a/api_docs/kbn_alerting_comparators.mdx +++ b/api_docs/kbn_alerting_comparators.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-comparators title: "@kbn/alerting-comparators" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-comparators plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-comparators'] --- import kbnAlertingComparatorsObj from './kbn_alerting_comparators.devdocs.json'; diff --git a/api_docs/kbn_alerting_state_types.mdx b/api_docs/kbn_alerting_state_types.mdx index eb974f2b2a5e3..72eacc2fc3ad7 100644 --- a/api_docs/kbn_alerting_state_types.mdx +++ b/api_docs/kbn_alerting_state_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-state-types title: "@kbn/alerting-state-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-state-types plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-state-types'] --- import kbnAlertingStateTypesObj from './kbn_alerting_state_types.devdocs.json'; diff --git a/api_docs/kbn_alerting_types.mdx b/api_docs/kbn_alerting_types.mdx index a8fe2f0cf6893..519d4c913868a 100644 --- a/api_docs/kbn_alerting_types.mdx +++ b/api_docs/kbn_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-types title: "@kbn/alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-types plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-types'] --- import kbnAlertingTypesObj from './kbn_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx index 7031533e507aa..8cfdb0378d47e 100644 --- a/api_docs/kbn_alerts_as_data_utils.mdx +++ b/api_docs/kbn_alerts_as_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-as-data-utils title: "@kbn/alerts-as-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-as-data-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-as-data-utils'] --- import kbnAlertsAsDataUtilsObj from './kbn_alerts_as_data_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts_ui_shared.mdx b/api_docs/kbn_alerts_ui_shared.mdx index 482c93d7458f4..9c3ce29931b64 100644 --- a/api_docs/kbn_alerts_ui_shared.mdx +++ b/api_docs/kbn_alerts_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-ui-shared title: "@kbn/alerts-ui-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-ui-shared plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-ui-shared'] --- import kbnAlertsUiSharedObj from './kbn_alerts_ui_shared.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index b79b2bd61fe5a..5ae2193a5fd82 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_collection_utils.mdx b/api_docs/kbn_analytics_collection_utils.mdx index b70a65772a74f..09b6d825d3a07 100644 --- a/api_docs/kbn_analytics_collection_utils.mdx +++ b/api_docs/kbn_analytics_collection_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-collection-utils title: "@kbn/analytics-collection-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-collection-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-collection-utils'] --- import kbnAnalyticsCollectionUtilsObj from './kbn_analytics_collection_utils.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 7ab060d69f891..63a63a5bd3027 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_data_view.mdx b/api_docs/kbn_apm_data_view.mdx index 0f5c199fe7f15..819f6843014cb 100644 --- a/api_docs/kbn_apm_data_view.mdx +++ b/api_docs/kbn_apm_data_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-data-view title: "@kbn/apm-data-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-data-view plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-data-view'] --- import kbnApmDataViewObj from './kbn_apm_data_view.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index ca7e9ec64141c..7e288744a97a5 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace_client.mdx b/api_docs/kbn_apm_synthtrace_client.mdx index 89426afbc987e..80b83bc500d2e 100644 --- a/api_docs/kbn_apm_synthtrace_client.mdx +++ b/api_docs/kbn_apm_synthtrace_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace-client title: "@kbn/apm-synthtrace-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace-client plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace-client'] --- import kbnApmSynthtraceClientObj from './kbn_apm_synthtrace_client.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index c7f68543cf56a..74bdfa13a5682 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index be5616dd139ed..1f9ab20cf8226 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_bfetch_error.mdx b/api_docs/kbn_bfetch_error.mdx index 19ec1c75ea292..2f5fd9ec6628b 100644 --- a/api_docs/kbn_bfetch_error.mdx +++ b/api_docs/kbn_bfetch_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-bfetch-error title: "@kbn/bfetch-error" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/bfetch-error plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/bfetch-error'] --- import kbnBfetchErrorObj from './kbn_bfetch_error.devdocs.json'; diff --git a/api_docs/kbn_calculate_auto.mdx b/api_docs/kbn_calculate_auto.mdx index 6246bf9cdcfa1..b24b89d5be35f 100644 --- a/api_docs/kbn_calculate_auto.mdx +++ b/api_docs/kbn_calculate_auto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-calculate-auto title: "@kbn/calculate-auto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/calculate-auto plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/calculate-auto'] --- import kbnCalculateAutoObj from './kbn_calculate_auto.devdocs.json'; diff --git a/api_docs/kbn_calculate_width_from_char_count.mdx b/api_docs/kbn_calculate_width_from_char_count.mdx index eba72f3e07e0c..ffe7f7f4263c0 100644 --- a/api_docs/kbn_calculate_width_from_char_count.mdx +++ b/api_docs/kbn_calculate_width_from_char_count.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-calculate-width-from-char-count title: "@kbn/calculate-width-from-char-count" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/calculate-width-from-char-count plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/calculate-width-from-char-count'] --- import kbnCalculateWidthFromCharCountObj from './kbn_calculate_width_from_char_count.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index 5f3fb239a8dbd..070381d998cb2 100644 --- a/api_docs/kbn_cases_components.mdx +++ b/api_docs/kbn_cases_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cases-components title: "@kbn/cases-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cases-components plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cases-components'] --- import kbnCasesComponentsObj from './kbn_cases_components.devdocs.json'; diff --git a/api_docs/kbn_cell_actions.mdx b/api_docs/kbn_cell_actions.mdx index 53a6d800d1fb5..5e516217151f0 100644 --- a/api_docs/kbn_cell_actions.mdx +++ b/api_docs/kbn_cell_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cell-actions title: "@kbn/cell-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cell-actions plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cell-actions'] --- import kbnCellActionsObj from './kbn_cell_actions.devdocs.json'; diff --git a/api_docs/kbn_chart_expressions_common.mdx b/api_docs/kbn_chart_expressions_common.mdx index 880bc371c704c..0fb0c476893f6 100644 --- a/api_docs/kbn_chart_expressions_common.mdx +++ b/api_docs/kbn_chart_expressions_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-expressions-common title: "@kbn/chart-expressions-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-expressions-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-expressions-common'] --- import kbnChartExpressionsCommonObj from './kbn_chart_expressions_common.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index 0f410f554b51f..e6dbef80fb6da 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index dce8026776b3e..41a535f805c72 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index a3a77ca1c90b6..b709dc243a81e 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index f342ef8fb9631..92b94fe7e7fc8 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index 00021f3246425..4fe7a06da17f0 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_code_editor.mdx b/api_docs/kbn_code_editor.mdx index 6b28bb1870898..ab37a405c4439 100644 --- a/api_docs/kbn_code_editor.mdx +++ b/api_docs/kbn_code_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor title: "@kbn/code-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor'] --- import kbnCodeEditorObj from './kbn_code_editor.devdocs.json'; diff --git a/api_docs/kbn_code_editor_mock.mdx b/api_docs/kbn_code_editor_mock.mdx index 716125e33e3ae..f23d63eaf87b9 100644 --- a/api_docs/kbn_code_editor_mock.mdx +++ b/api_docs/kbn_code_editor_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor-mock title: "@kbn/code-editor-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor-mock plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor-mock'] --- import kbnCodeEditorMockObj from './kbn_code_editor_mock.devdocs.json'; diff --git a/api_docs/kbn_code_owners.mdx b/api_docs/kbn_code_owners.mdx index 2bd5027f28d8c..56009b2104a5c 100644 --- a/api_docs/kbn_code_owners.mdx +++ b/api_docs/kbn_code_owners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-owners title: "@kbn/code-owners" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-owners plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-owners'] --- import kbnCodeOwnersObj from './kbn_code_owners.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index 9e895d74bf8ab..d6b40e956feb5 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index 6ff1ed4c17d61..ced3a27657dc3 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.devdocs.json b/api_docs/kbn_config_mocks.devdocs.json index c470bc5513b23..44c2f2ea91060 100644 --- a/api_docs/kbn_config_mocks.devdocs.json +++ b/api_docs/kbn_config_mocks.devdocs.json @@ -268,7 +268,7 @@ "Observable", "<", "Config", - ">, [], unknown>; setSchema: jest.MockInstance, [], unknown>; setGlobalStripUnknownKeys: jest.MockInstance; setSchema: jest.MockInstance, namespace?: string | undefined) => V" + "(value: unknown, context?: Record, namespace?: string | undefined, validationOptions?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.SchemaValidationOptions", + "text": "SchemaValidationOptions" + }, + " | undefined) => V" ], "path": "packages/kbn-config-schema/src/types/type.ts", "deprecated": false, @@ -1223,6 +1231,28 @@ "deprecated": false, "trackAdoption": false, "isRequired": false + }, + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.Type.validate.$4", + "type": "Object", + "tags": [], + "label": "validationOptions", + "description": [], + "signature": [ + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.SchemaValidationOptions", + "text": "SchemaValidationOptions" + }, + " | undefined" + ], + "path": "packages/kbn-config-schema/src/types/type.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], "returnComment": [] @@ -1612,6 +1642,38 @@ } ], "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.SchemaValidationOptions", + "type": "Interface", + "tags": [], + "label": "SchemaValidationOptions", + "description": [ + "\nGlobal validation Options to be provided when calling the `schema.validate()` method." + ], + "path": "packages/kbn-config-schema/src/types/type.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.SchemaValidationOptions.stripUnknownKeys", + "type": "CompoundType", + "tags": [], + "label": "stripUnknownKeys", + "description": [ + "\nRemove unknown config keys" + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-config-schema/src/types/type.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false } ], "enums": [], diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index 67cbfd9d7c068..737a3f575f51b 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 146 | 2 | 142 | 20 | +| 149 | 2 | 143 | 20 | ## Common diff --git a/api_docs/kbn_content_management_content_editor.mdx b/api_docs/kbn_content_management_content_editor.mdx index bc4b6d4be8349..59eede6a225e7 100644 --- a/api_docs/kbn_content_management_content_editor.mdx +++ b/api_docs/kbn_content_management_content_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-content-editor title: "@kbn/content-management-content-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-content-editor plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-content-editor'] --- import kbnContentManagementContentEditorObj from './kbn_content_management_content_editor.devdocs.json'; diff --git a/api_docs/kbn_content_management_tabbed_table_list_view.mdx b/api_docs/kbn_content_management_tabbed_table_list_view.mdx index f184668bbda1e..e3f154bf7e2e5 100644 --- a/api_docs/kbn_content_management_tabbed_table_list_view.mdx +++ b/api_docs/kbn_content_management_tabbed_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-tabbed-table-list-view title: "@kbn/content-management-tabbed-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-tabbed-table-list-view plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-tabbed-table-list-view'] --- import kbnContentManagementTabbedTableListViewObj from './kbn_content_management_tabbed_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view.mdx b/api_docs/kbn_content_management_table_list_view.mdx index d12e0d5c33478..341978fd43d6d 100644 --- a/api_docs/kbn_content_management_table_list_view.mdx +++ b/api_docs/kbn_content_management_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view title: "@kbn/content-management-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view'] --- import kbnContentManagementTableListViewObj from './kbn_content_management_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view_common.mdx b/api_docs/kbn_content_management_table_list_view_common.mdx index a2574062c5939..9eddd10872dc5 100644 --- a/api_docs/kbn_content_management_table_list_view_common.mdx +++ b/api_docs/kbn_content_management_table_list_view_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-common title: "@kbn/content-management-table-list-view-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-common'] --- import kbnContentManagementTableListViewCommonObj from './kbn_content_management_table_list_view_common.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view_table.mdx b/api_docs/kbn_content_management_table_list_view_table.mdx index 99032e0e797bd..5cdfc5b506f03 100644 --- a/api_docs/kbn_content_management_table_list_view_table.mdx +++ b/api_docs/kbn_content_management_table_list_view_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-table title: "@kbn/content-management-table-list-view-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-table plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-table'] --- import kbnContentManagementTableListViewTableObj from './kbn_content_management_table_list_view_table.devdocs.json'; diff --git a/api_docs/kbn_content_management_user_profiles.mdx b/api_docs/kbn_content_management_user_profiles.mdx index 07013c03e95ae..e9f46fcf553d5 100644 --- a/api_docs/kbn_content_management_user_profiles.mdx +++ b/api_docs/kbn_content_management_user_profiles.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-user-profiles title: "@kbn/content-management-user-profiles" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-user-profiles plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-user-profiles'] --- import kbnContentManagementUserProfilesObj from './kbn_content_management_user_profiles.devdocs.json'; diff --git a/api_docs/kbn_content_management_utils.mdx b/api_docs/kbn_content_management_utils.mdx index 5caca4fcf1162..ce03e7966d783 100644 --- a/api_docs/kbn_content_management_utils.mdx +++ b/api_docs/kbn_content_management_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-utils title: "@kbn/content-management-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-utils'] --- import kbnContentManagementUtilsObj from './kbn_content_management_utils.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.devdocs.json b/api_docs/kbn_core_analytics_browser.devdocs.json index 2bdcb21161c4a..492043475802f 100644 --- a/api_docs/kbn_core_analytics_browser.devdocs.json +++ b/api_docs/kbn_core_analytics_browser.devdocs.json @@ -755,6 +755,18 @@ "plugin": "elasticAssistant", "path": "x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts" }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/routes/helpers.ts" + }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/routes/helpers.ts" + }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts" + }, { "plugin": "globalSearchBar", "path": "x-pack/plugins/global_search_bar/public/telemetry/event_reporter.ts" @@ -795,33 +807,17 @@ "plugin": "infra", "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" }, - { - "plugin": "osquery", - "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" - }, - { - "plugin": "osquery", - "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" - }, - { - "plugin": "osquery", - "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" - }, - { - "plugin": "osquery", - "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" - }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" }, { "plugin": "securitySolution", @@ -919,17 +915,33 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" }, { "plugin": "securitySolution", diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 45f8b8ec8bbed..03ee16fedc58a 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 5665119cca26b..34185a9cc2cb4 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index 23be802b6cbc4..306ea6315aa11 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.devdocs.json b/api_docs/kbn_core_analytics_server.devdocs.json index d9a4cef589320..f0fb04c0b6745 100644 --- a/api_docs/kbn_core_analytics_server.devdocs.json +++ b/api_docs/kbn_core_analytics_server.devdocs.json @@ -755,6 +755,18 @@ "plugin": "elasticAssistant", "path": "x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts" }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/routes/helpers.ts" + }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/routes/helpers.ts" + }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts" + }, { "plugin": "globalSearchBar", "path": "x-pack/plugins/global_search_bar/public/telemetry/event_reporter.ts" @@ -795,33 +807,17 @@ "plugin": "infra", "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" }, - { - "plugin": "osquery", - "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" - }, - { - "plugin": "osquery", - "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" - }, - { - "plugin": "osquery", - "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" - }, - { - "plugin": "osquery", - "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" - }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" }, { "plugin": "securitySolution", @@ -919,17 +915,33 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" }, { "plugin": "securitySolution", diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index 6e0a665f9f0c1..dc02ae2d568f1 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index e37d08b09ec3f..f5fd5b00e6c49 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index 7469a7ad06fdb..434b2a84a2e5f 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index 607fef0224cf9..da86c2ab178f6 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index 5b3705bebc4c1..c23446e906462 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index d4f55f1e745b9..de33e071f48a3 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index 02675a24c5b78..fcd85266e96a3 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index 2b162c3f4e0e0..626b1565c76f6 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index 62aa9fea667e6..8990f7cabd04e 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_apps_server_internal.mdx b/api_docs/kbn_core_apps_server_internal.mdx index 89a07d1bab784..5af9e5d636075 100644 --- a/api_docs/kbn_core_apps_server_internal.mdx +++ b/api_docs/kbn_core_apps_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-server-internal title: "@kbn/core-apps-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-server-internal'] --- import kbnCoreAppsServerInternalObj from './kbn_core_apps_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index 013ded0fdf45b..77c77f578ca73 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 6f4da01d0fd96..326ccacf05452 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index ffea92a4a4027..1f8ffec2eb5c5 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index 2a5f969d24559..492721237e747 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index 17bc87e52f011..30ea1dcb4e469 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index a1d6e93965568..dfe1284977f16 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index 1245f249862fa..4d4ee98cd4cb1 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index 6e67e4e575e4e..c1a01ee841a6c 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index d2ca714abe84d..d7bde9c05efc9 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index 32ed1de922831..a9da9c467d88b 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.devdocs.json b/api_docs/kbn_core_config_server_internal.devdocs.json index 83a0b76b35fc0..e9d32490a9564 100644 --- a/api_docs/kbn_core_config_server_internal.devdocs.json +++ b/api_docs/kbn_core_config_server_internal.devdocs.json @@ -48,15 +48,25 @@ "parentPluginId": "@kbn/core-config-server-internal", "id": "def-common.ensureValidConfiguration", "type": "Function", - "tags": [], + "tags": [ + "private" + ], "label": "ensureValidConfiguration", - "description": [], + "description": [ + "\nValidate the entire Kibana configuration object, including the detection of extra keys." + ], "signature": [ "(configService: ", - "ConfigService", + "IConfigService", ", params: ", - "ConfigValidateParameters", - " | undefined) => Promise" + { + "pluginId": "@kbn/core-config-server-internal", + "scope": "common", + "docId": "kibKbnCoreConfigServerInternalPluginApi", + "section": "def-common.EnsureValidConfigurationParameters", + "text": "EnsureValidConfigurationParameters" + }, + ") => Promise" ], "path": "packages/core/config/core-config-server-internal/src/ensure_valid_configuration.ts", "deprecated": false, @@ -68,9 +78,11 @@ "type": "Object", "tags": [], "label": "configService", - "description": [], + "description": [ + "The {@link IConfigService } instance that has the raw configuration preloaded." + ], "signature": [ - "ConfigService" + "IConfigService" ], "path": "packages/core/config/core-config-server-internal/src/ensure_valid_configuration.ts", "deprecated": false, @@ -83,22 +95,75 @@ "type": "Object", "tags": [], "label": "params", - "description": [], + "description": [ + "{@link EnsureValidConfigurationParameters | Options} to enable/disable extra edge-cases." + ], "signature": [ - "ConfigValidateParameters", - " | undefined" + { + "pluginId": "@kbn/core-config-server-internal", + "scope": "common", + "docId": "kibKbnCoreConfigServerInternalPluginApi", + "section": "def-common.EnsureValidConfigurationParameters", + "text": "EnsureValidConfigurationParameters" + } ], "path": "packages/core/config/core-config-server-internal/src/ensure_valid_configuration.ts", "deprecated": false, "trackAdoption": false, - "isRequired": false + "isRequired": true } ], "returnComment": [], "initialIsOpen": false } ], - "interfaces": [], + "interfaces": [ + { + "parentPluginId": "@kbn/core-config-server-internal", + "id": "def-common.EnsureValidConfigurationParameters", + "type": "Interface", + "tags": [ + "private" + ], + "label": "EnsureValidConfigurationParameters", + "description": [ + "\nParameters for the helper {@link ensureValidConfiguration}\n" + ], + "signature": [ + { + "pluginId": "@kbn/core-config-server-internal", + "scope": "common", + "docId": "kibKbnCoreConfigServerInternalPluginApi", + "section": "def-common.EnsureValidConfigurationParameters", + "text": "EnsureValidConfigurationParameters" + }, + " extends ", + "ConfigValidateParameters" + ], + "path": "packages/core/config/core-config-server-internal/src/ensure_valid_configuration.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-config-server-internal", + "id": "def-common.EnsureValidConfigurationParameters.stripUnknownKeys", + "type": "CompoundType", + "tags": [], + "label": "stripUnknownKeys", + "description": [ + "\nSet to `true` to ignore any unknown keys and discard them from the final validated config object." + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/core/config/core-config-server-internal/src/ensure_valid_configuration.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], "enums": [], "misc": [], "objects": [] diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index 23998d6aadd9b..021494d595e56 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; @@ -21,10 +21,13 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 4 | 0 | 4 | 0 | +| 6 | 0 | 1 | 0 | ## Common ### Functions +### Interfaces + + diff --git a/api_docs/kbn_core_custom_branding_browser.mdx b/api_docs/kbn_core_custom_branding_browser.mdx index 5b7526d56a286..8e659c83b60da 100644 --- a/api_docs/kbn_core_custom_branding_browser.mdx +++ b/api_docs/kbn_core_custom_branding_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser title: "@kbn/core-custom-branding-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser'] --- import kbnCoreCustomBrandingBrowserObj from './kbn_core_custom_branding_browser.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_internal.mdx b/api_docs/kbn_core_custom_branding_browser_internal.mdx index c6c53153fed5b..b5bac2de8c813 100644 --- a/api_docs/kbn_core_custom_branding_browser_internal.mdx +++ b/api_docs/kbn_core_custom_branding_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-internal title: "@kbn/core-custom-branding-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-internal'] --- import kbnCoreCustomBrandingBrowserInternalObj from './kbn_core_custom_branding_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_mocks.mdx b/api_docs/kbn_core_custom_branding_browser_mocks.mdx index fe5b64b64ac93..6e9897235f13c 100644 --- a/api_docs/kbn_core_custom_branding_browser_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-mocks title: "@kbn/core-custom-branding-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-mocks'] --- import kbnCoreCustomBrandingBrowserMocksObj from './kbn_core_custom_branding_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_common.mdx b/api_docs/kbn_core_custom_branding_common.mdx index eb3c50a448b9d..978ba2c42a6d5 100644 --- a/api_docs/kbn_core_custom_branding_common.mdx +++ b/api_docs/kbn_core_custom_branding_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-common title: "@kbn/core-custom-branding-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-common'] --- import kbnCoreCustomBrandingCommonObj from './kbn_core_custom_branding_common.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server.mdx b/api_docs/kbn_core_custom_branding_server.mdx index c846813b6c4da..77e51df212644 100644 --- a/api_docs/kbn_core_custom_branding_server.mdx +++ b/api_docs/kbn_core_custom_branding_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server title: "@kbn/core-custom-branding-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server'] --- import kbnCoreCustomBrandingServerObj from './kbn_core_custom_branding_server.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_internal.mdx b/api_docs/kbn_core_custom_branding_server_internal.mdx index b667a01d17569..6fd209b592891 100644 --- a/api_docs/kbn_core_custom_branding_server_internal.mdx +++ b/api_docs/kbn_core_custom_branding_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-internal title: "@kbn/core-custom-branding-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-internal'] --- import kbnCoreCustomBrandingServerInternalObj from './kbn_core_custom_branding_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_mocks.mdx b/api_docs/kbn_core_custom_branding_server_mocks.mdx index 01027ca00fb31..e40d0a7e2c0f3 100644 --- a/api_docs/kbn_core_custom_branding_server_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-mocks title: "@kbn/core-custom-branding-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-mocks'] --- import kbnCoreCustomBrandingServerMocksObj from './kbn_core_custom_branding_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index ec9752b465e85..0f2c523863eff 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index 1ca2f94aca3fc..7cd4345becf38 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index 3706ba67ca651..b46e0b894a3be 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index aedd91d54e73b..01b20c36876a3 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index c51c0ff64b655..363adaa5a3d7a 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index 834d9bd9b9db9..5c23a887b50f5 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index 75882794f9f40..4fc3a83d19864 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index e55f122c76d48..fbcd18b0a90d6 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index bc20536cd96b8..6d465fad94575 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index 1aa437be9b6e8..4407e37301ff9 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index c5b91b9f5abe6..df79f07d92367 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index 2a9983c88ea7a..96ec115c3bc82 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index 74778900cbd39..2020232176410 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index d66135b1d9658..4866d8375cd2c 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index 7038857686e1d..965239b029808 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index 96ce852028560..43e30bab675bf 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index 433f2c8b504fa..36c5bcb2ff13c 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index aea64c35dc3c0..1e25d6531888a 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index ec92cec5a8666..385ad4bad9653 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index 4d21a5b327392..ed5796f04023b 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index d3d99b18c094e..460954788f0fb 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index 8f3ec845c140b..a81cda5291e45 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index 91d5a75bd2d5a..f5812eab86fa2 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index f2bc5f2ae5591..7b540d93e9e56 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index 2afdc0064ec07..788d274af073a 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index 8122aac361a52..0869dc22449d0 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index 7cf08334e70ca..b94ce1eb2ea1a 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index e365170421c4d..3867e3217d542 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index fa1a51035cf5d..97a363856a981 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index 17b12b6e40822..b41fc8727261a 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index fbd93f827a78f..c9ffdc50e99cf 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index f78e569dc0c19..9911f139cef74 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_request_handler_context_server.mdx b/api_docs/kbn_core_http_request_handler_context_server.mdx index bed4ad5469c2e..11880a41a5d36 100644 --- a/api_docs/kbn_core_http_request_handler_context_server.mdx +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server title: "@kbn/core-http-request-handler-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-request-handler-context-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-request-handler-context-server'] --- import kbnCoreHttpRequestHandlerContextServerObj from './kbn_core_http_request_handler_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server.mdx b/api_docs/kbn_core_http_resources_server.mdx index d5e5d7c5d0f6a..c7f389e8ff4fe 100644 --- a/api_docs/kbn_core_http_resources_server.mdx +++ b/api_docs/kbn_core_http_resources_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server title: "@kbn/core-http-resources-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server'] --- import kbnCoreHttpResourcesServerObj from './kbn_core_http_resources_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_internal.mdx b/api_docs/kbn_core_http_resources_server_internal.mdx index 5a236e99bb53f..02aa37a1d7449 100644 --- a/api_docs/kbn_core_http_resources_server_internal.mdx +++ b/api_docs/kbn_core_http_resources_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-internal title: "@kbn/core-http-resources-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-internal'] --- import kbnCoreHttpResourcesServerInternalObj from './kbn_core_http_resources_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_mocks.mdx b/api_docs/kbn_core_http_resources_server_mocks.mdx index c9f49f4be1342..b0b3694cd51a3 100644 --- a/api_docs/kbn_core_http_resources_server_mocks.mdx +++ b/api_docs/kbn_core_http_resources_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-mocks title: "@kbn/core-http-resources-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-mocks'] --- import kbnCoreHttpResourcesServerMocksObj from './kbn_core_http_resources_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index 690c67b27be50..148aa4d688fa9 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index 3ac841684d5ad..6900fea4b72fc 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.devdocs.json b/api_docs/kbn_core_http_server.devdocs.json index 3ba2c76647c3b..87a354908cae5 100644 --- a/api_docs/kbn_core_http_server.devdocs.json +++ b/api_docs/kbn_core_http_server.devdocs.json @@ -15922,15 +15922,19 @@ }, { "plugin": "elasticAssistant", - "path": "x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts" + "path": "x-pack/plugins/elastic_assistant/server/routes/prompts/bulk_actions_route.ts" }, { "plugin": "elasticAssistant", - "path": "x-pack/plugins/elastic_assistant/server/routes/prompts/bulk_actions_route.ts" + "path": "x-pack/plugins/elastic_assistant/server/routes/anonymization_fields/bulk_actions_route.ts" }, { "plugin": "elasticAssistant", - "path": "x-pack/plugins/elastic_assistant/server/routes/anonymization_fields/bulk_actions_route.ts" + "path": "x-pack/plugins/elastic_assistant/server/routes/chat/chat_complete_route.ts" + }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts" }, { "plugin": "elasticAssistant", diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index d923a90985177..2bdd0d7bc5fc2 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index 4d16d46cc8108..88b367ebdbf0b 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index 752f47421e763..f472ee37c8efb 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index c21661229f8f7..80a9adfc5e0ea 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index 3d109ac311ccd..dd6147798af32 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index ab35b6b8c50f1..b1d4de3280f10 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index c3e9fefff9e47..0d31538978c98 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index 2f0d31c847663..8584e2a95d93b 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index c1b246f06a7f0..94c18053976cf 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index 028fde1f6e529..3a4bf0b5fbe75 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index bc93d5ccbf538..32566769d2f76 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 4f3d732ac8d15..ed6dce5e83ee1 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index d32360e933d80..c0681954c4e0b 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server.mdx b/api_docs/kbn_core_lifecycle_server.mdx index a73f554152119..2552de14914ba 100644 --- a/api_docs/kbn_core_lifecycle_server.mdx +++ b/api_docs/kbn_core_lifecycle_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server title: "@kbn/core-lifecycle-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server'] --- import kbnCoreLifecycleServerObj from './kbn_core_lifecycle_server.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server_mocks.mdx b/api_docs/kbn_core_lifecycle_server_mocks.mdx index 541252dce2d30..a6881c247fc36 100644 --- a/api_docs/kbn_core_lifecycle_server_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server-mocks title: "@kbn/core-lifecycle-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server-mocks'] --- import kbnCoreLifecycleServerMocksObj from './kbn_core_lifecycle_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_browser_mocks.mdx b/api_docs/kbn_core_logging_browser_mocks.mdx index 6ec82aefa4b9c..43e26e804a442 100644 --- a/api_docs/kbn_core_logging_browser_mocks.mdx +++ b/api_docs/kbn_core_logging_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-browser-mocks title: "@kbn/core-logging-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-browser-mocks'] --- import kbnCoreLoggingBrowserMocksObj from './kbn_core_logging_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_common_internal.mdx b/api_docs/kbn_core_logging_common_internal.mdx index 02ed8c06d5de8..f38b1b3d8e95e 100644 --- a/api_docs/kbn_core_logging_common_internal.mdx +++ b/api_docs/kbn_core_logging_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-common-internal title: "@kbn/core-logging-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-common-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-common-internal'] --- import kbnCoreLoggingCommonInternalObj from './kbn_core_logging_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 23f339d6f4cf1..04e504cf2fb13 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index 7a7996f3ea76e..93994b09d5f5a 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index 472c9d1079d0e..639b06bdd6a09 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index 9292fc4d04694..0386850d25dc2 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index 4e765d5c27643..a66864ef65e14 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index ba0707c5dc40e..cba8c8ab6b62f 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_server.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index fd0a01857ae28..6f9b20081da9b 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index 89e7a6cffed70..5e54846e4b3c8 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index ce04fb6eaee47..fc8651fc901d7 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index c30f3e0d94148..0d6458897d020 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index b977075ae6099..54e3df0fd1702 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index 2d125bae00fa6..0daaa6a2e3ebe 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index 18ec802175cbf..ebd7346e2db50 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index 52f85dfdf864f..fec623e8cf016 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index 0bb1ccaf6fcfa..f3346fb04da1b 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index 2ac5312f91498..7d59c37e3dda9 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index 34a7580cc209f..e78eff678db60 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index 108cea5b5ecd2..9ac7df2e312db 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index 2063cd2063af4..454a63fdd8bcd 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index 1d9e184867c58..95d7700b82fc2 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_contracts_browser.mdx b/api_docs/kbn_core_plugins_contracts_browser.mdx index ed0a3429f32ec..049ca26dd1975 100644 --- a/api_docs/kbn_core_plugins_contracts_browser.mdx +++ b/api_docs/kbn_core_plugins_contracts_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-contracts-browser title: "@kbn/core-plugins-contracts-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-contracts-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-contracts-browser'] --- import kbnCorePluginsContractsBrowserObj from './kbn_core_plugins_contracts_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_contracts_server.mdx b/api_docs/kbn_core_plugins_contracts_server.mdx index dc1b07990ebc7..b8b0f967f1650 100644 --- a/api_docs/kbn_core_plugins_contracts_server.mdx +++ b/api_docs/kbn_core_plugins_contracts_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-contracts-server title: "@kbn/core-plugins-contracts-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-contracts-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-contracts-server'] --- import kbnCorePluginsContractsServerObj from './kbn_core_plugins_contracts_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server.mdx b/api_docs/kbn_core_plugins_server.mdx index 7817596f7854f..b40bd3812bc25 100644 --- a/api_docs/kbn_core_plugins_server.mdx +++ b/api_docs/kbn_core_plugins_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server title: "@kbn/core-plugins-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server'] --- import kbnCorePluginsServerObj from './kbn_core_plugins_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server_mocks.mdx b/api_docs/kbn_core_plugins_server_mocks.mdx index 2c34636fdff51..45a1050700436 100644 --- a/api_docs/kbn_core_plugins_server_mocks.mdx +++ b/api_docs/kbn_core_plugins_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server-mocks title: "@kbn/core-plugins-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server-mocks'] --- import kbnCorePluginsServerMocksObj from './kbn_core_plugins_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index 7c2318a6787fa..46dc60c4f4e5a 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index 8e28d84414693..49e8293655418 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index 7ecf4d673dd83..b46cdc42362be 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_internal.mdx b/api_docs/kbn_core_rendering_server_internal.mdx index 59ac25c06cdd9..99fb29cfdb435 100644 --- a/api_docs/kbn_core_rendering_server_internal.mdx +++ b/api_docs/kbn_core_rendering_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-internal title: "@kbn/core-rendering-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-internal'] --- import kbnCoreRenderingServerInternalObj from './kbn_core_rendering_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_mocks.mdx b/api_docs/kbn_core_rendering_server_mocks.mdx index 3609236ecfd6a..8ffb428e2dc79 100644 --- a/api_docs/kbn_core_rendering_server_mocks.mdx +++ b/api_docs/kbn_core_rendering_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-mocks title: "@kbn/core-rendering-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] --- import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_root_server_internal.mdx b/api_docs/kbn_core_root_server_internal.mdx index e1fe9c9fdf01c..45205f9146268 100644 --- a/api_docs/kbn_core_root_server_internal.mdx +++ b/api_docs/kbn_core_root_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-root-server-internal title: "@kbn/core-root-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-root-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-root-server-internal'] --- import kbnCoreRootServerInternalObj from './kbn_core_root_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index d2bbe676baf8c..bb33e6cd2d80a 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index 1807c0045bf89..757b619a41b2a 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index 92ec6f08d1e4e..ef1eb6fbcd115 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index 0c5c237885026..e2a233ada34ac 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index 1e2ac2682ba93..e0e385416ffef 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index 491cc6fde9fd6..dd02401a10728 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index 1eabec8b1ebc0..f338540b7f073 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index 3106979b1a1ae..050c72c726fb1 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 06ede73a52055..7a01b43774217 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index aa567a8c58865..dbb8270e39021 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index fcce0ec4a8e2e..a81d4a947b5a5 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index 756704f54b3e9..5e538565c87b3 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index 7b1a43b12fa8b..96097521f4ada 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index f24492f78d58f..4048bd7ee1372 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index 7602aa2eb9ad3..063816510db3f 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index 86b27f680132f..c97b9ad38c21b 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index 446045d6abfdc..d1b4591bcffb7 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser.mdx b/api_docs/kbn_core_security_browser.mdx index b5df9baed6248..4c9b434baf3eb 100644 --- a/api_docs/kbn_core_security_browser.mdx +++ b/api_docs/kbn_core_security_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser title: "@kbn/core-security-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser'] --- import kbnCoreSecurityBrowserObj from './kbn_core_security_browser.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser_internal.mdx b/api_docs/kbn_core_security_browser_internal.mdx index ab947bff85233..4c7da33fd03f2 100644 --- a/api_docs/kbn_core_security_browser_internal.mdx +++ b/api_docs/kbn_core_security_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser-internal title: "@kbn/core-security-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser-internal'] --- import kbnCoreSecurityBrowserInternalObj from './kbn_core_security_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser_mocks.mdx b/api_docs/kbn_core_security_browser_mocks.mdx index ede76dc7cc443..24ec8e9e1421e 100644 --- a/api_docs/kbn_core_security_browser_mocks.mdx +++ b/api_docs/kbn_core_security_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser-mocks title: "@kbn/core-security-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser-mocks'] --- import kbnCoreSecurityBrowserMocksObj from './kbn_core_security_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_security_common.mdx b/api_docs/kbn_core_security_common.mdx index eb4f2475d7983..31310d27ec273 100644 --- a/api_docs/kbn_core_security_common.mdx +++ b/api_docs/kbn_core_security_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-common title: "@kbn/core-security-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-common'] --- import kbnCoreSecurityCommonObj from './kbn_core_security_common.devdocs.json'; diff --git a/api_docs/kbn_core_security_server.mdx b/api_docs/kbn_core_security_server.mdx index a4eeb79742c95..1502df6476a25 100644 --- a/api_docs/kbn_core_security_server.mdx +++ b/api_docs/kbn_core_security_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server title: "@kbn/core-security-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server'] --- import kbnCoreSecurityServerObj from './kbn_core_security_server.devdocs.json'; diff --git a/api_docs/kbn_core_security_server_internal.mdx b/api_docs/kbn_core_security_server_internal.mdx index 874a2cf42b3be..1b4b7e7182b62 100644 --- a/api_docs/kbn_core_security_server_internal.mdx +++ b/api_docs/kbn_core_security_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server-internal title: "@kbn/core-security-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server-internal'] --- import kbnCoreSecurityServerInternalObj from './kbn_core_security_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_security_server_mocks.mdx b/api_docs/kbn_core_security_server_mocks.mdx index 9be525d5b1224..421aa38ab4d56 100644 --- a/api_docs/kbn_core_security_server_mocks.mdx +++ b/api_docs/kbn_core_security_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server-mocks title: "@kbn/core-security-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server-mocks'] --- import kbnCoreSecurityServerMocksObj from './kbn_core_security_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 712d795b10de2..5701cd4ad73db 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index c775560bf7f3a..7848e60b1a93d 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common-internal'] --- import kbnCoreStatusCommonInternalObj from './kbn_core_status_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index 8c792950f9fad..a12fdeccd2e23 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index a9847cbcf7379..7d7c5c4e92326 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index a15bff562a2b0..5ec2badcdf42c 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index 9d6fbfb4c5cae..db4db0bc9dc6c 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index 5509dc4220153..7a32af6927598 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_kbn_server.mdx b/api_docs/kbn_core_test_helpers_kbn_server.mdx index be6cbbbf24564..3e14fc547b7de 100644 --- a/api_docs/kbn_core_test_helpers_kbn_server.mdx +++ b/api_docs/kbn_core_test_helpers_kbn_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-kbn-server title: "@kbn/core-test-helpers-kbn-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-kbn-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-kbn-server'] --- import kbnCoreTestHelpersKbnServerObj from './kbn_core_test_helpers_kbn_server.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_model_versions.mdx b/api_docs/kbn_core_test_helpers_model_versions.mdx index 60bf975e40208..d0a79ba4052f0 100644 --- a/api_docs/kbn_core_test_helpers_model_versions.mdx +++ b/api_docs/kbn_core_test_helpers_model_versions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-model-versions title: "@kbn/core-test-helpers-model-versions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-model-versions plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-model-versions'] --- import kbnCoreTestHelpersModelVersionsObj from './kbn_core_test_helpers_model_versions.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx index 3b024cb4571ba..a9bff8bc4471f 100644 --- a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx +++ b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-so-type-serializer title: "@kbn/core-test-helpers-so-type-serializer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-so-type-serializer plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-so-type-serializer'] --- import kbnCoreTestHelpersSoTypeSerializerObj from './kbn_core_test_helpers_so_type_serializer.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_test_utils.mdx b/api_docs/kbn_core_test_helpers_test_utils.mdx index b6196d14312b8..695937598f37b 100644 --- a/api_docs/kbn_core_test_helpers_test_utils.mdx +++ b/api_docs/kbn_core_test_helpers_test_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-test-utils title: "@kbn/core-test-helpers-test-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-test-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-test-utils'] --- import kbnCoreTestHelpersTestUtilsObj from './kbn_core_test_helpers_test_utils.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index bee15b5e0bb71..632f7303ad781 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 14ad25571ee72..197c645165dd0 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index be59f63cee936..926b0430461f2 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index 580a72762c02d..1bcb7adf0ab21 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index 303f2a17d0e47..4a7cea0edd1dc 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index 51454951e2c1e..d3808cc8cc13f 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index d5463d9e4c25c..1e609efe7530f 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index 3ecf5c6014360..07c0c0aed7f81 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index fa749c62e45c9..021027fdff6dc 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index c4d67bc784ca1..0c1f2fd1e90ba 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index 02db19347d2b7..7d3ff0274ea2b 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index db73b3a4d7306..0552b4dfaeced 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser.mdx b/api_docs/kbn_core_user_profile_browser.mdx index 35460db2f8ff3..2c9ca40490b7c 100644 --- a/api_docs/kbn_core_user_profile_browser.mdx +++ b/api_docs/kbn_core_user_profile_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser title: "@kbn/core-user-profile-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser'] --- import kbnCoreUserProfileBrowserObj from './kbn_core_user_profile_browser.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser_internal.mdx b/api_docs/kbn_core_user_profile_browser_internal.mdx index 3489265b746b9..b83d1668cf9cc 100644 --- a/api_docs/kbn_core_user_profile_browser_internal.mdx +++ b/api_docs/kbn_core_user_profile_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser-internal title: "@kbn/core-user-profile-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser-internal'] --- import kbnCoreUserProfileBrowserInternalObj from './kbn_core_user_profile_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser_mocks.mdx b/api_docs/kbn_core_user_profile_browser_mocks.mdx index 8442be6b104f9..75a581cf9f779 100644 --- a/api_docs/kbn_core_user_profile_browser_mocks.mdx +++ b/api_docs/kbn_core_user_profile_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser-mocks title: "@kbn/core-user-profile-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser-mocks'] --- import kbnCoreUserProfileBrowserMocksObj from './kbn_core_user_profile_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_common.mdx b/api_docs/kbn_core_user_profile_common.mdx index ade7ab8000a6c..5f33dc71663c9 100644 --- a/api_docs/kbn_core_user_profile_common.mdx +++ b/api_docs/kbn_core_user_profile_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-common title: "@kbn/core-user-profile-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-common'] --- import kbnCoreUserProfileCommonObj from './kbn_core_user_profile_common.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server.mdx b/api_docs/kbn_core_user_profile_server.mdx index bccad07651dec..63a10c6a6de6f 100644 --- a/api_docs/kbn_core_user_profile_server.mdx +++ b/api_docs/kbn_core_user_profile_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server title: "@kbn/core-user-profile-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server'] --- import kbnCoreUserProfileServerObj from './kbn_core_user_profile_server.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server_internal.mdx b/api_docs/kbn_core_user_profile_server_internal.mdx index 13639f19d1b6e..bebfef766b07b 100644 --- a/api_docs/kbn_core_user_profile_server_internal.mdx +++ b/api_docs/kbn_core_user_profile_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server-internal title: "@kbn/core-user-profile-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server-internal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server-internal'] --- import kbnCoreUserProfileServerInternalObj from './kbn_core_user_profile_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server_mocks.mdx b/api_docs/kbn_core_user_profile_server_mocks.mdx index 13ebabf1037a0..261a00774f6da 100644 --- a/api_docs/kbn_core_user_profile_server_mocks.mdx +++ b/api_docs/kbn_core_user_profile_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server-mocks title: "@kbn/core-user-profile-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server-mocks'] --- import kbnCoreUserProfileServerMocksObj from './kbn_core_user_profile_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server.mdx b/api_docs/kbn_core_user_settings_server.mdx index 2b0710aa1cc9a..b4ae070885c6a 100644 --- a/api_docs/kbn_core_user_settings_server.mdx +++ b/api_docs/kbn_core_user_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server title: "@kbn/core-user-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server'] --- import kbnCoreUserSettingsServerObj from './kbn_core_user_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server_mocks.mdx b/api_docs/kbn_core_user_settings_server_mocks.mdx index 8f2d9f629570e..fd793595b44ab 100644 --- a/api_docs/kbn_core_user_settings_server_mocks.mdx +++ b/api_docs/kbn_core_user_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server-mocks title: "@kbn/core-user-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server-mocks'] --- import kbnCoreUserSettingsServerMocksObj from './kbn_core_user_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index 426a9839e9229..9b3da182ff894 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index ebb4367885075..e3d4ec803aaad 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_custom_icons.mdx b/api_docs/kbn_custom_icons.mdx index 1d8d47cd68a25..8c853c6a88d68 100644 --- a/api_docs/kbn_custom_icons.mdx +++ b/api_docs/kbn_custom_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-custom-icons title: "@kbn/custom-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/custom-icons plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-icons'] --- import kbnCustomIconsObj from './kbn_custom_icons.devdocs.json'; diff --git a/api_docs/kbn_custom_integrations.mdx b/api_docs/kbn_custom_integrations.mdx index acf1aa560b76e..0e1ae4a187848 100644 --- a/api_docs/kbn_custom_integrations.mdx +++ b/api_docs/kbn_custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-custom-integrations title: "@kbn/custom-integrations" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/custom-integrations plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-integrations'] --- import kbnCustomIntegrationsObj from './kbn_custom_integrations.devdocs.json'; diff --git a/api_docs/kbn_cypress_config.mdx b/api_docs/kbn_cypress_config.mdx index ed98166d02f35..d35ddb7d386c6 100644 --- a/api_docs/kbn_cypress_config.mdx +++ b/api_docs/kbn_cypress_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cypress-config title: "@kbn/cypress-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cypress-config plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_data_forge.mdx b/api_docs/kbn_data_forge.mdx index 7849b0627f432..01c196a135a8c 100644 --- a/api_docs/kbn_data_forge.mdx +++ b/api_docs/kbn_data_forge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-forge title: "@kbn/data-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-forge plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-forge'] --- import kbnDataForgeObj from './kbn_data_forge.devdocs.json'; diff --git a/api_docs/kbn_data_service.mdx b/api_docs/kbn_data_service.mdx index 7a5b8f43760af..c9ecea76a09e9 100644 --- a/api_docs/kbn_data_service.mdx +++ b/api_docs/kbn_data_service.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-service title: "@kbn/data-service" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-service plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-service'] --- import kbnDataServiceObj from './kbn_data_service.devdocs.json'; diff --git a/api_docs/kbn_data_stream_adapter.mdx b/api_docs/kbn_data_stream_adapter.mdx index ccdf9ac69eb22..efa1e09acc72b 100644 --- a/api_docs/kbn_data_stream_adapter.mdx +++ b/api_docs/kbn_data_stream_adapter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-stream-adapter title: "@kbn/data-stream-adapter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-stream-adapter plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-stream-adapter'] --- import kbnDataStreamAdapterObj from './kbn_data_stream_adapter.devdocs.json'; diff --git a/api_docs/kbn_data_view_utils.mdx b/api_docs/kbn_data_view_utils.mdx index 9bc3877cb7906..b17495952f3a2 100644 --- a/api_docs/kbn_data_view_utils.mdx +++ b/api_docs/kbn_data_view_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-view-utils title: "@kbn/data-view-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-view-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-view-utils'] --- import kbnDataViewUtilsObj from './kbn_data_view_utils.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 7471e1979f4dd..1f7090e39b0c5 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_analytics.mdx b/api_docs/kbn_deeplinks_analytics.mdx index 4231afcaf0a8a..7c8abba79d1a0 100644 --- a/api_docs/kbn_deeplinks_analytics.mdx +++ b/api_docs/kbn_deeplinks_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-analytics title: "@kbn/deeplinks-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-analytics plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-analytics'] --- import kbnDeeplinksAnalyticsObj from './kbn_deeplinks_analytics.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_devtools.mdx b/api_docs/kbn_deeplinks_devtools.mdx index e276d857435b7..c53b7b4b52246 100644 --- a/api_docs/kbn_deeplinks_devtools.mdx +++ b/api_docs/kbn_deeplinks_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-devtools title: "@kbn/deeplinks-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-devtools plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-devtools'] --- import kbnDeeplinksDevtoolsObj from './kbn_deeplinks_devtools.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_fleet.mdx b/api_docs/kbn_deeplinks_fleet.mdx index 97ea42fc7d3a6..49804a270ed55 100644 --- a/api_docs/kbn_deeplinks_fleet.mdx +++ b/api_docs/kbn_deeplinks_fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-fleet title: "@kbn/deeplinks-fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-fleet plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-fleet'] --- import kbnDeeplinksFleetObj from './kbn_deeplinks_fleet.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_management.mdx b/api_docs/kbn_deeplinks_management.mdx index 02126c5b3569f..5b0eaccabc916 100644 --- a/api_docs/kbn_deeplinks_management.mdx +++ b/api_docs/kbn_deeplinks_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-management title: "@kbn/deeplinks-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-management plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-management'] --- import kbnDeeplinksManagementObj from './kbn_deeplinks_management.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_ml.mdx b/api_docs/kbn_deeplinks_ml.mdx index 19f221ff0ada6..2e30cd3b1989f 100644 --- a/api_docs/kbn_deeplinks_ml.mdx +++ b/api_docs/kbn_deeplinks_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-ml title: "@kbn/deeplinks-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-ml plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-ml'] --- import kbnDeeplinksMlObj from './kbn_deeplinks_ml.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_observability.mdx b/api_docs/kbn_deeplinks_observability.mdx index 48076c41160e3..a0fe11fcdc764 100644 --- a/api_docs/kbn_deeplinks_observability.mdx +++ b/api_docs/kbn_deeplinks_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-observability title: "@kbn/deeplinks-observability" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-observability plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-observability'] --- import kbnDeeplinksObservabilityObj from './kbn_deeplinks_observability.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_search.mdx b/api_docs/kbn_deeplinks_search.mdx index 1b7a033c34152..55dd85df61cc3 100644 --- a/api_docs/kbn_deeplinks_search.mdx +++ b/api_docs/kbn_deeplinks_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-search title: "@kbn/deeplinks-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-search plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-search'] --- import kbnDeeplinksSearchObj from './kbn_deeplinks_search.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_security.mdx b/api_docs/kbn_deeplinks_security.mdx index e0bf6749b04bc..e5464429dd223 100644 --- a/api_docs/kbn_deeplinks_security.mdx +++ b/api_docs/kbn_deeplinks_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-security title: "@kbn/deeplinks-security" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-security plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-security'] --- import kbnDeeplinksSecurityObj from './kbn_deeplinks_security.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_shared.mdx b/api_docs/kbn_deeplinks_shared.mdx index c1bb7b3925bcb..476e131ac68ef 100644 --- a/api_docs/kbn_deeplinks_shared.mdx +++ b/api_docs/kbn_deeplinks_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-shared title: "@kbn/deeplinks-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-shared plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-shared'] --- import kbnDeeplinksSharedObj from './kbn_deeplinks_shared.devdocs.json'; diff --git a/api_docs/kbn_default_nav_analytics.mdx b/api_docs/kbn_default_nav_analytics.mdx index 00f3493b8daad..917357f58a7ca 100644 --- a/api_docs/kbn_default_nav_analytics.mdx +++ b/api_docs/kbn_default_nav_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-analytics title: "@kbn/default-nav-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-analytics plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-analytics'] --- import kbnDefaultNavAnalyticsObj from './kbn_default_nav_analytics.devdocs.json'; diff --git a/api_docs/kbn_default_nav_devtools.mdx b/api_docs/kbn_default_nav_devtools.mdx index dd44d50d21925..b736232e39c5d 100644 --- a/api_docs/kbn_default_nav_devtools.mdx +++ b/api_docs/kbn_default_nav_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-devtools title: "@kbn/default-nav-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-devtools plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-devtools'] --- import kbnDefaultNavDevtoolsObj from './kbn_default_nav_devtools.devdocs.json'; diff --git a/api_docs/kbn_default_nav_management.mdx b/api_docs/kbn_default_nav_management.mdx index 7bd6ad9040289..2a768bf4ccf9b 100644 --- a/api_docs/kbn_default_nav_management.mdx +++ b/api_docs/kbn_default_nav_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-management title: "@kbn/default-nav-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-management plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-management'] --- import kbnDefaultNavManagementObj from './kbn_default_nav_management.devdocs.json'; diff --git a/api_docs/kbn_default_nav_ml.mdx b/api_docs/kbn_default_nav_ml.mdx index f6b4a242c74fb..f33f42d6c0acb 100644 --- a/api_docs/kbn_default_nav_ml.mdx +++ b/api_docs/kbn_default_nav_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-ml title: "@kbn/default-nav-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-ml plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-ml'] --- import kbnDefaultNavMlObj from './kbn_default_nav_ml.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index 33db442759532..2f61c8101ca54 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 8ba7202cce516..84504d1b9f669 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index f6d4a0e91b520..7dada1d331cb9 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index a9915c7c93a30..092fe46b425bc 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_discover_utils.mdx b/api_docs/kbn_discover_utils.mdx index 2ca9e521bed5c..65eaa6d7c85f4 100644 --- a/api_docs/kbn_discover_utils.mdx +++ b/api_docs/kbn_discover_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-discover-utils title: "@kbn/discover-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/discover-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/discover-utils'] --- import kbnDiscoverUtilsObj from './kbn_discover_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 88517d25b3d6f..083f16be9db7b 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index 67a3a066a5b0a..73ee0f3d4ec83 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_dom_drag_drop.mdx b/api_docs/kbn_dom_drag_drop.mdx index 48e0631edbb2e..3dbef41ef957d 100644 --- a/api_docs/kbn_dom_drag_drop.mdx +++ b/api_docs/kbn_dom_drag_drop.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dom-drag-drop title: "@kbn/dom-drag-drop" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dom-drag-drop plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dom-drag-drop'] --- import kbnDomDragDropObj from './kbn_dom_drag_drop.devdocs.json'; diff --git a/api_docs/kbn_ebt.devdocs.json b/api_docs/kbn_ebt.devdocs.json index 6a608e785f53d..0822efe4727eb 100644 --- a/api_docs/kbn_ebt.devdocs.json +++ b/api_docs/kbn_ebt.devdocs.json @@ -1890,6 +1890,18 @@ "plugin": "elasticAssistant", "path": "x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts" }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/routes/helpers.ts" + }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/routes/helpers.ts" + }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts" + }, { "plugin": "globalSearchBar", "path": "x-pack/plugins/global_search_bar/public/telemetry/event_reporter.ts" @@ -1930,33 +1942,17 @@ "plugin": "infra", "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" }, - { - "plugin": "osquery", - "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" - }, - { - "plugin": "osquery", - "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" - }, - { - "plugin": "osquery", - "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" - }, - { - "plugin": "osquery", - "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" - }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" }, { "plugin": "securitySolution", @@ -2054,17 +2050,33 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" }, { "plugin": "securitySolution", diff --git a/api_docs/kbn_ebt.mdx b/api_docs/kbn_ebt.mdx index 50be524b2d803..c825c07983c0d 100644 --- a/api_docs/kbn_ebt.mdx +++ b/api_docs/kbn_ebt.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt title: "@kbn/ebt" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt'] --- import kbnEbtObj from './kbn_ebt.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index 9db6d478e8418..5504b223532df 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_ecs_data_quality_dashboard.mdx b/api_docs/kbn_ecs_data_quality_dashboard.mdx index ede8b00d6fefc..de4baa7563208 100644 --- a/api_docs/kbn_ecs_data_quality_dashboard.mdx +++ b/api_docs/kbn_ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs-data-quality-dashboard title: "@kbn/ecs-data-quality-dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs-data-quality-dashboard plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs-data-quality-dashboard'] --- import kbnEcsDataQualityDashboardObj from './kbn_ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/kbn_elastic_agent_utils.mdx b/api_docs/kbn_elastic_agent_utils.mdx index 01c06dfdf7b92..2b1e0961d56b1 100644 --- a/api_docs/kbn_elastic_agent_utils.mdx +++ b/api_docs/kbn_elastic_agent_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-agent-utils title: "@kbn/elastic-agent-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-agent-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-agent-utils'] --- import kbnElasticAgentUtilsObj from './kbn_elastic_agent_utils.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant.mdx b/api_docs/kbn_elastic_assistant.mdx index ba73b5122824d..1a6ee5cce6bf5 100644 --- a/api_docs/kbn_elastic_assistant.mdx +++ b/api_docs/kbn_elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant title: "@kbn/elastic-assistant" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant'] --- import kbnElasticAssistantObj from './kbn_elastic_assistant.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant_common.devdocs.json b/api_docs/kbn_elastic_assistant_common.devdocs.json index 346c991076389..1355cdaaf05af 100644 --- a/api_docs/kbn_elastic_assistant_common.devdocs.json +++ b/api_docs/kbn_elastic_assistant_common.devdocs.json @@ -1221,6 +1221,100 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.ChatCompleteProps", + "type": "Type", + "tags": [], + "label": "ChatCompleteProps", + "description": [], + "signature": [ + "{ connectorId: string; persist: boolean; messages: { role: \"user\" | \"system\" | \"assistant\"; content?: string | undefined; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; fields_to_anonymize?: string[] | undefined; }[]; conversationId?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; model?: string | undefined; }" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.ChatCompleteRequestBody", + "type": "Type", + "tags": [], + "label": "ChatCompleteRequestBody", + "description": [], + "signature": [ + "{ connectorId: string; persist: boolean; messages: { role: \"user\" | \"system\" | \"assistant\"; content?: string | undefined; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; fields_to_anonymize?: string[] | undefined; }[]; conversationId?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; model?: string | undefined; }" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.ChatCompleteRequestBodyInput", + "type": "Type", + "tags": [], + "label": "ChatCompleteRequestBodyInput", + "description": [], + "signature": [ + "{ connectorId: string; persist: boolean; messages: { role: \"user\" | \"system\" | \"assistant\"; content?: string | undefined; data?: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; fields_to_anonymize?: string[] | undefined; }[]; conversationId?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; model?: string | undefined; }" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.ChatMessage", + "type": "Type", + "tags": [], + "label": "ChatMessage", + "description": [ + "\nAI assistant message." + ], + "signature": [ + "{ role: \"user\" | \"system\" | \"assistant\"; content?: string | undefined; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; fields_to_anonymize?: string[] | undefined; }" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.ChatMessageRole", + "type": "Type", + "tags": [], + "label": "ChatMessageRole", + "description": [ + "\nMessage role." + ], + "signature": [ + "\"user\" | \"system\" | \"assistant\"" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.ChatMessageRoleEnum", + "type": "Type", + "tags": [], + "label": "ChatMessageRoleEnum", + "description": [], + "signature": [ + "{ user: \"user\"; system: \"system\"; assistant: \"assistant\"; }" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/elastic-assistant-common", "id": "def-common.ConversationCategory", @@ -1711,6 +1805,18 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.ELASTIC_AI_ASSISTANT_CHAT_COMPLETE_URL", + "type": "string", + "tags": [], + "label": "ELASTIC_AI_ASSISTANT_CHAT_COMPLETE_URL", + "description": [], + "path": "x-pack/packages/kbn-elastic-assistant-common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/elastic-assistant-common", "id": "def-common.ELASTIC_AI_ASSISTANT_CONVERSATIONS_URL", @@ -1893,7 +1999,7 @@ "label": "ELASTIC_AI_ASSISTANT_URL", "description": [], "signature": [ - "\"/api/elastic_assistant\"" + "\"/api/security_ai_assistant\"" ], "path": "x-pack/packages/kbn-elastic-assistant-common/constants.ts", "deprecated": false, @@ -2446,6 +2552,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.MessageData", + "type": "Type", + "tags": [], + "label": "MessageData", + "description": [], + "signature": [ + "{} & { [k: string]: unknown; }" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/elastic-assistant-common", "id": "def-common.MessageRole", @@ -2951,6 +3072,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.RootContext", + "type": "Type", + "tags": [], + "label": "RootContext", + "description": [], + "signature": [ + "\"security\"" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/elastic-assistant-common", "id": "def-common.SortOrder", @@ -3638,6 +3774,81 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.ChatCompleteProps", + "type": "Object", + "tags": [], + "label": "ChatCompleteProps", + "description": [], + "signature": [ + "Zod.ZodObject<{ conversationId: Zod.ZodOptional; promptId: Zod.ZodOptional; isStream: Zod.ZodOptional; responseLanguage: Zod.ZodOptional; langSmithProject: Zod.ZodOptional; langSmithApiKey: Zod.ZodOptional; connectorId: Zod.ZodString; model: Zod.ZodOptional; persist: Zod.ZodBoolean; messages: Zod.ZodArray; role: Zod.ZodEnum<[\"system\", \"user\", \"assistant\"]>; data: Zod.ZodOptional, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>>; fields_to_anonymize: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { role: \"user\" | \"system\" | \"assistant\"; content?: string | undefined; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; fields_to_anonymize?: string[] | undefined; }, { role: \"user\" | \"system\" | \"assistant\"; content?: string | undefined; data?: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; fields_to_anonymize?: string[] | undefined; }>, \"many\">; }, \"strip\", Zod.ZodTypeAny, { connectorId: string; persist: boolean; messages: { role: \"user\" | \"system\" | \"assistant\"; content?: string | undefined; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; fields_to_anonymize?: string[] | undefined; }[]; conversationId?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; model?: string | undefined; }, { connectorId: string; persist: boolean; messages: { role: \"user\" | \"system\" | \"assistant\"; content?: string | undefined; data?: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; fields_to_anonymize?: string[] | undefined; }[]; conversationId?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; model?: string | undefined; }>" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.ChatCompleteRequestBody", + "type": "Object", + "tags": [], + "label": "ChatCompleteRequestBody", + "description": [], + "signature": [ + "Zod.ZodObject<{ conversationId: Zod.ZodOptional; promptId: Zod.ZodOptional; isStream: Zod.ZodOptional; responseLanguage: Zod.ZodOptional; langSmithProject: Zod.ZodOptional; langSmithApiKey: Zod.ZodOptional; connectorId: Zod.ZodString; model: Zod.ZodOptional; persist: Zod.ZodBoolean; messages: Zod.ZodArray; role: Zod.ZodEnum<[\"system\", \"user\", \"assistant\"]>; data: Zod.ZodOptional, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>>; fields_to_anonymize: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { role: \"user\" | \"system\" | \"assistant\"; content?: string | undefined; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; fields_to_anonymize?: string[] | undefined; }, { role: \"user\" | \"system\" | \"assistant\"; content?: string | undefined; data?: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; fields_to_anonymize?: string[] | undefined; }>, \"many\">; }, \"strip\", Zod.ZodTypeAny, { connectorId: string; persist: boolean; messages: { role: \"user\" | \"system\" | \"assistant\"; content?: string | undefined; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; fields_to_anonymize?: string[] | undefined; }[]; conversationId?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; model?: string | undefined; }, { connectorId: string; persist: boolean; messages: { role: \"user\" | \"system\" | \"assistant\"; content?: string | undefined; data?: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; fields_to_anonymize?: string[] | undefined; }[]; conversationId?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; model?: string | undefined; }>" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.ChatMessage", + "type": "Object", + "tags": [], + "label": "ChatMessage", + "description": [], + "signature": [ + "Zod.ZodObject<{ content: Zod.ZodOptional; role: Zod.ZodEnum<[\"system\", \"user\", \"assistant\"]>; data: Zod.ZodOptional, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>>; fields_to_anonymize: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { role: \"user\" | \"system\" | \"assistant\"; content?: string | undefined; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; fields_to_anonymize?: string[] | undefined; }, { role: \"user\" | \"system\" | \"assistant\"; content?: string | undefined; data?: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; fields_to_anonymize?: string[] | undefined; }>" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.ChatMessageRole", + "type": "Object", + "tags": [], + "label": "ChatMessageRole", + "description": [], + "signature": [ + "Zod.ZodEnum<[\"system\", \"user\", \"assistant\"]>" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.ChatMessageRoleEnum", + "type": "Object", + "tags": [], + "label": "ChatMessageRoleEnum", + "description": [], + "signature": [ + "{ user: \"user\"; system: \"system\"; assistant: \"assistant\"; }" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/elastic-assistant-common", "id": "def-common.ConversationCategory", @@ -4465,6 +4676,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.MessageData", + "type": "Object", + "tags": [], + "label": "MessageData", + "description": [], + "signature": [ + "Zod.ZodObject<{}, \"strip\", Zod.ZodUnknown, Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\">, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/elastic-assistant-common", "id": "def-common.MessageRole", @@ -4840,6 +5066,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.RootContext", + "type": "Object", + "tags": [], + "label": "RootContext", + "description": [], + "signature": [ + "Zod.ZodLiteral<\"security\">" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/elastic-assistant-common", "id": "def-common.SortOrder", diff --git a/api_docs/kbn_elastic_assistant_common.mdx b/api_docs/kbn_elastic_assistant_common.mdx index 5bedfe584179c..1aac5995bd622 100644 --- a/api_docs/kbn_elastic_assistant_common.mdx +++ b/api_docs/kbn_elastic_assistant_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant-common title: "@kbn/elastic-assistant-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant-common'] --- import kbnElasticAssistantCommonObj from './kbn_elastic_assistant_common.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 333 | 0 | 309 | 0 | +| 349 | 0 | 323 | 0 | ## Common diff --git a/api_docs/kbn_entities_schema.mdx b/api_docs/kbn_entities_schema.mdx index 0b4d524003243..1aef01fbd070e 100644 --- a/api_docs/kbn_entities_schema.mdx +++ b/api_docs/kbn_entities_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-entities-schema title: "@kbn/entities-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/entities-schema plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/entities-schema'] --- import kbnEntitiesSchemaObj from './kbn_entities_schema.devdocs.json'; diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index 17888c59dd7c9..d32dbbea262be 100644 --- a/api_docs/kbn_es.mdx +++ b/api_docs/kbn_es.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es title: "@kbn/es" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es'] --- import kbnEsObj from './kbn_es.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index e96f2de2bd948..cdb919e0b9a1d 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index e0a2d28b77dc5..e510651bef95a 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index 17c1fd1409a8c..8d66d7c0288a0 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index c9351dbf52315..1a6d0a8a522d5 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index 5a8ef28337e42..e28a43619b47b 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_esql_ast.mdx b/api_docs/kbn_esql_ast.mdx index a79ad0eb34a18..413246f2fc803 100644 --- a/api_docs/kbn_esql_ast.mdx +++ b/api_docs/kbn_esql_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-ast title: "@kbn/esql-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-ast plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-ast'] --- import kbnEsqlAstObj from './kbn_esql_ast.devdocs.json'; diff --git a/api_docs/kbn_esql_utils.mdx b/api_docs/kbn_esql_utils.mdx index b8b5f53f6965a..e3448bd511852 100644 --- a/api_docs/kbn_esql_utils.mdx +++ b/api_docs/kbn_esql_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-utils title: "@kbn/esql-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-utils'] --- import kbnEsqlUtilsObj from './kbn_esql_utils.devdocs.json'; diff --git a/api_docs/kbn_esql_validation_autocomplete.mdx b/api_docs/kbn_esql_validation_autocomplete.mdx index 3bfdc591a31d3..175ac7c66c0b8 100644 --- a/api_docs/kbn_esql_validation_autocomplete.mdx +++ b/api_docs/kbn_esql_validation_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-validation-autocomplete title: "@kbn/esql-validation-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-validation-autocomplete plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-validation-autocomplete'] --- import kbnEsqlValidationAutocompleteObj from './kbn_esql_validation_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_event_annotation_common.mdx b/api_docs/kbn_event_annotation_common.mdx index 16ccf0a5bfae9..e9b4d444de5e4 100644 --- a/api_docs/kbn_event_annotation_common.mdx +++ b/api_docs/kbn_event_annotation_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-common title: "@kbn/event-annotation-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/event-annotation-common'] --- import kbnEventAnnotationCommonObj from './kbn_event_annotation_common.devdocs.json'; diff --git a/api_docs/kbn_event_annotation_components.mdx b/api_docs/kbn_event_annotation_components.mdx index 073fcd2ac4071..f60f605f36b1b 100644 --- a/api_docs/kbn_event_annotation_components.mdx +++ b/api_docs/kbn_event_annotation_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-components title: "@kbn/event-annotation-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-components plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/event-annotation-components'] --- import kbnEventAnnotationComponentsObj from './kbn_event_annotation_components.devdocs.json'; diff --git a/api_docs/kbn_expandable_flyout.mdx b/api_docs/kbn_expandable_flyout.mdx index bb25e04aa1be8..da5d8766bb8f7 100644 --- a/api_docs/kbn_expandable_flyout.mdx +++ b/api_docs/kbn_expandable_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-expandable-flyout title: "@kbn/expandable-flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/expandable-flyout plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/expandable-flyout'] --- import kbnExpandableFlyoutObj from './kbn_expandable_flyout.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 65bf4181332a4..e0440c6219bc0 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_field_utils.mdx b/api_docs/kbn_field_utils.mdx index a015a658c9cfb..130fa58f548d2 100644 --- a/api_docs/kbn_field_utils.mdx +++ b/api_docs/kbn_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-utils title: "@kbn/field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-utils'] --- import kbnFieldUtilsObj from './kbn_field_utils.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index 9e1c946edfa6e..2ffd0d4cd2b54 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_formatters.mdx b/api_docs/kbn_formatters.mdx index 045fab34cf6ae..7e9482ff7e888 100644 --- a/api_docs/kbn_formatters.mdx +++ b/api_docs/kbn_formatters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-formatters title: "@kbn/formatters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/formatters plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/formatters'] --- import kbnFormattersObj from './kbn_formatters.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index 3abd4d0ab1778..30042c2823130 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_ui_services.mdx b/api_docs/kbn_ftr_common_functional_ui_services.mdx index f35aad5d320ae..27f9ac1db1de1 100644 --- a/api_docs/kbn_ftr_common_functional_ui_services.mdx +++ b/api_docs/kbn_ftr_common_functional_ui_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-ui-services title: "@kbn/ftr-common-functional-ui-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-ui-services plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-ui-services'] --- import kbnFtrCommonFunctionalUiServicesObj from './kbn_ftr_common_functional_ui_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index 9570e6c38b84b..a4f5a818cc6ed 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_generate_console_definitions.mdx b/api_docs/kbn_generate_console_definitions.mdx index 8c2b20ffa06d0..f80a1ff1dde10 100644 --- a/api_docs/kbn_generate_console_definitions.mdx +++ b/api_docs/kbn_generate_console_definitions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-console-definitions title: "@kbn/generate-console-definitions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-console-definitions plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-console-definitions'] --- import kbnGenerateConsoleDefinitionsObj from './kbn_generate_console_definitions.devdocs.json'; diff --git a/api_docs/kbn_generate_csv.mdx b/api_docs/kbn_generate_csv.mdx index 94ff39595d22a..0bf31cb696b56 100644 --- a/api_docs/kbn_generate_csv.mdx +++ b/api_docs/kbn_generate_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv title: "@kbn/generate-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv'] --- import kbnGenerateCsvObj from './kbn_generate_csv.devdocs.json'; diff --git a/api_docs/kbn_grouping.mdx b/api_docs/kbn_grouping.mdx index 328d53deb75b1..9080467cb3f6b 100644 --- a/api_docs/kbn_grouping.mdx +++ b/api_docs/kbn_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-grouping title: "@kbn/grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/grouping plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/grouping'] --- import kbnGroupingObj from './kbn_grouping.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index 731876d8e45e6..3cf9467ccf87a 100644 --- a/api_docs/kbn_guided_onboarding.mdx +++ b/api_docs/kbn_guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-guided-onboarding title: "@kbn/guided-onboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/guided-onboarding plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/guided-onboarding'] --- import kbnGuidedOnboardingObj from './kbn_guided_onboarding.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index 4165dedb0bc47..2d92ab85fcf0e 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index 80e2e2a8d1883..6c488f9190c15 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_health_gateway_server.mdx b/api_docs/kbn_health_gateway_server.mdx index 11a415938d5ca..9806f4b584a84 100644 --- a/api_docs/kbn_health_gateway_server.mdx +++ b/api_docs/kbn_health_gateway_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-health-gateway-server title: "@kbn/health-gateway-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/health-gateway-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/health-gateway-server'] --- import kbnHealthGatewayServerObj from './kbn_health_gateway_server.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index cff647c2d1eba..5610a4f35abd1 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index 622470ad36fc7..3e0deaf4be6da 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index 7727b08ed543d..d2da516a8357d 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_i18n_react.mdx b/api_docs/kbn_i18n_react.mdx index 0023a45ebf5bb..a79d98659ec50 100644 --- a/api_docs/kbn_i18n_react.mdx +++ b/api_docs/kbn_i18n_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n-react title: "@kbn/i18n-react" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n-react plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n-react'] --- import kbnI18nReactObj from './kbn_i18n_react.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 9eebecc4be306..75d976070a87e 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_index_management.mdx b/api_docs/kbn_index_management.mdx index ee2e689b54984..5b31ba8815113 100644 --- a/api_docs/kbn_index_management.mdx +++ b/api_docs/kbn_index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-index-management title: "@kbn/index-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/index-management plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/index-management'] --- import kbnIndexManagementObj from './kbn_index_management.devdocs.json'; diff --git a/api_docs/kbn_inference_integration_flyout.mdx b/api_docs/kbn_inference_integration_flyout.mdx index c1fd535439dfc..cc8763aa93920 100644 --- a/api_docs/kbn_inference_integration_flyout.mdx +++ b/api_docs/kbn_inference_integration_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-inference_integration_flyout title: "@kbn/inference_integration_flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/inference_integration_flyout plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/inference_integration_flyout'] --- import kbnInferenceIntegrationFlyoutObj from './kbn_inference_integration_flyout.devdocs.json'; diff --git a/api_docs/kbn_infra_forge.mdx b/api_docs/kbn_infra_forge.mdx index 93afe59e78560..a5172f634ed86 100644 --- a/api_docs/kbn_infra_forge.mdx +++ b/api_docs/kbn_infra_forge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-infra-forge title: "@kbn/infra-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/infra-forge plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/infra-forge'] --- import kbnInfraForgeObj from './kbn_infra_forge.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 62959ccd79b78..d9a77cdcb4c54 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index deca7d0d8aff7..17e18c9d4824b 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_ipynb.mdx b/api_docs/kbn_ipynb.mdx index 87a266d9b9a14..dbe001af179d9 100644 --- a/api_docs/kbn_ipynb.mdx +++ b/api_docs/kbn_ipynb.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ipynb title: "@kbn/ipynb" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ipynb plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ipynb'] --- import kbnIpynbObj from './kbn_ipynb.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index b6e8e58d65a5b..d68639bcbf0fe 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index b8a18026004b8..1dda8eb0ac23a 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_json_ast.mdx b/api_docs/kbn_json_ast.mdx index 02fbb0b488130..f9e0a883fc286 100644 --- a/api_docs/kbn_json_ast.mdx +++ b/api_docs/kbn_json_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-ast title: "@kbn/json-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-ast plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-ast'] --- import kbnJsonAstObj from './kbn_json_ast.devdocs.json'; diff --git a/api_docs/kbn_json_schemas.mdx b/api_docs/kbn_json_schemas.mdx index eb01ef253ef6e..3f65818580acf 100644 --- a/api_docs/kbn_json_schemas.mdx +++ b/api_docs/kbn_json_schemas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-schemas title: "@kbn/json-schemas" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-schemas plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-schemas'] --- import kbnJsonSchemasObj from './kbn_json_schemas.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 34adbecb1dbdb..57f00cc72aa20 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_language_documentation_popover.mdx b/api_docs/kbn_language_documentation_popover.mdx index ee08ce2314c3b..eaede007ea7d6 100644 --- a/api_docs/kbn_language_documentation_popover.mdx +++ b/api_docs/kbn_language_documentation_popover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-language-documentation-popover title: "@kbn/language-documentation-popover" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/language-documentation-popover plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/language-documentation-popover'] --- import kbnLanguageDocumentationPopoverObj from './kbn_language_documentation_popover.devdocs.json'; diff --git a/api_docs/kbn_lens_embeddable_utils.mdx b/api_docs/kbn_lens_embeddable_utils.mdx index 6f11a7d367fdc..c5839e6b551c0 100644 --- a/api_docs/kbn_lens_embeddable_utils.mdx +++ b/api_docs/kbn_lens_embeddable_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-lens-embeddable-utils title: "@kbn/lens-embeddable-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/lens-embeddable-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-embeddable-utils'] --- import kbnLensEmbeddableUtilsObj from './kbn_lens_embeddable_utils.devdocs.json'; diff --git a/api_docs/kbn_lens_formula_docs.mdx b/api_docs/kbn_lens_formula_docs.mdx index b022a8b3bb5f3..5d4aa9f2cafa2 100644 --- a/api_docs/kbn_lens_formula_docs.mdx +++ b/api_docs/kbn_lens_formula_docs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-lens-formula-docs title: "@kbn/lens-formula-docs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/lens-formula-docs plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-formula-docs'] --- import kbnLensFormulaDocsObj from './kbn_lens_formula_docs.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 502f2b7c39ec6..634387ebe7287 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index cc74116040d53..1afc4a7c21596 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_content_badge.mdx b/api_docs/kbn_managed_content_badge.mdx index ba5b8e8cfb28f..0d8ca5b1db1a1 100644 --- a/api_docs/kbn_managed_content_badge.mdx +++ b/api_docs/kbn_managed_content_badge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-content-badge title: "@kbn/managed-content-badge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-content-badge plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-content-badge'] --- import kbnManagedContentBadgeObj from './kbn_managed_content_badge.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 230f9c36ffe55..786832ad85a12 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_management_cards_navigation.mdx b/api_docs/kbn_management_cards_navigation.mdx index e29ff4fc60e73..2ec607afdf49f 100644 --- a/api_docs/kbn_management_cards_navigation.mdx +++ b/api_docs/kbn_management_cards_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-cards-navigation title: "@kbn/management-cards-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-cards-navigation plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-cards-navigation'] --- import kbnManagementCardsNavigationObj from './kbn_management_cards_navigation.devdocs.json'; diff --git a/api_docs/kbn_management_settings_application.mdx b/api_docs/kbn_management_settings_application.mdx index fe19559e238c5..90f12142a0b1f 100644 --- a/api_docs/kbn_management_settings_application.mdx +++ b/api_docs/kbn_management_settings_application.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-application title: "@kbn/management-settings-application" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-application plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-application'] --- import kbnManagementSettingsApplicationObj from './kbn_management_settings_application.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_category.mdx b/api_docs/kbn_management_settings_components_field_category.mdx index c54984f72f55a..3ec5c7e8b450c 100644 --- a/api_docs/kbn_management_settings_components_field_category.mdx +++ b/api_docs/kbn_management_settings_components_field_category.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-category title: "@kbn/management-settings-components-field-category" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-category plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-category'] --- import kbnManagementSettingsComponentsFieldCategoryObj from './kbn_management_settings_components_field_category.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_input.mdx b/api_docs/kbn_management_settings_components_field_input.mdx index 7cc7caee9a8aa..f86bc492e4335 100644 --- a/api_docs/kbn_management_settings_components_field_input.mdx +++ b/api_docs/kbn_management_settings_components_field_input.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-input title: "@kbn/management-settings-components-field-input" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-input plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-input'] --- import kbnManagementSettingsComponentsFieldInputObj from './kbn_management_settings_components_field_input.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_row.mdx b/api_docs/kbn_management_settings_components_field_row.mdx index 5e746bc7b3907..f8cca3b521c56 100644 --- a/api_docs/kbn_management_settings_components_field_row.mdx +++ b/api_docs/kbn_management_settings_components_field_row.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-row title: "@kbn/management-settings-components-field-row" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-row plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-row'] --- import kbnManagementSettingsComponentsFieldRowObj from './kbn_management_settings_components_field_row.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_form.mdx b/api_docs/kbn_management_settings_components_form.mdx index f85c380a0cc0b..9dbe965a3f1ff 100644 --- a/api_docs/kbn_management_settings_components_form.mdx +++ b/api_docs/kbn_management_settings_components_form.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-form title: "@kbn/management-settings-components-form" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-form plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-form'] --- import kbnManagementSettingsComponentsFormObj from './kbn_management_settings_components_form.devdocs.json'; diff --git a/api_docs/kbn_management_settings_field_definition.mdx b/api_docs/kbn_management_settings_field_definition.mdx index 3abca702adbca..0ded710c70ec9 100644 --- a/api_docs/kbn_management_settings_field_definition.mdx +++ b/api_docs/kbn_management_settings_field_definition.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-field-definition title: "@kbn/management-settings-field-definition" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-field-definition plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-field-definition'] --- import kbnManagementSettingsFieldDefinitionObj from './kbn_management_settings_field_definition.devdocs.json'; diff --git a/api_docs/kbn_management_settings_ids.mdx b/api_docs/kbn_management_settings_ids.mdx index 8e8f5b15952ea..9d7a8baf693dc 100644 --- a/api_docs/kbn_management_settings_ids.mdx +++ b/api_docs/kbn_management_settings_ids.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-ids title: "@kbn/management-settings-ids" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-ids plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-ids'] --- import kbnManagementSettingsIdsObj from './kbn_management_settings_ids.devdocs.json'; diff --git a/api_docs/kbn_management_settings_section_registry.mdx b/api_docs/kbn_management_settings_section_registry.mdx index 07b3a9a81ef1a..e6abf44546f05 100644 --- a/api_docs/kbn_management_settings_section_registry.mdx +++ b/api_docs/kbn_management_settings_section_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-section-registry title: "@kbn/management-settings-section-registry" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-section-registry plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-section-registry'] --- import kbnManagementSettingsSectionRegistryObj from './kbn_management_settings_section_registry.devdocs.json'; diff --git a/api_docs/kbn_management_settings_types.mdx b/api_docs/kbn_management_settings_types.mdx index a6ee2ad22e9e9..4c990f8212268 100644 --- a/api_docs/kbn_management_settings_types.mdx +++ b/api_docs/kbn_management_settings_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-types title: "@kbn/management-settings-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-types plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-types'] --- import kbnManagementSettingsTypesObj from './kbn_management_settings_types.devdocs.json'; diff --git a/api_docs/kbn_management_settings_utilities.mdx b/api_docs/kbn_management_settings_utilities.mdx index e9998f34d99ad..91d6071b83de3 100644 --- a/api_docs/kbn_management_settings_utilities.mdx +++ b/api_docs/kbn_management_settings_utilities.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-utilities title: "@kbn/management-settings-utilities" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-utilities plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-utilities'] --- import kbnManagementSettingsUtilitiesObj from './kbn_management_settings_utilities.devdocs.json'; diff --git a/api_docs/kbn_management_storybook_config.mdx b/api_docs/kbn_management_storybook_config.mdx index f604f1a344dda..dad4f0ec9ce32 100644 --- a/api_docs/kbn_management_storybook_config.mdx +++ b/api_docs/kbn_management_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-storybook-config title: "@kbn/management-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-storybook-config plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-storybook-config'] --- import kbnManagementStorybookConfigObj from './kbn_management_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 0e5b134bd7431..6675a72e44489 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_maps_vector_tile_utils.mdx b/api_docs/kbn_maps_vector_tile_utils.mdx index aa79f7fb2872f..e2da8390c3b47 100644 --- a/api_docs/kbn_maps_vector_tile_utils.mdx +++ b/api_docs/kbn_maps_vector_tile_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-maps-vector-tile-utils title: "@kbn/maps-vector-tile-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/maps-vector-tile-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/maps-vector-tile-utils'] --- import kbnMapsVectorTileUtilsObj from './kbn_maps_vector_tile_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 2167365cc1b21..a6848336371dc 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_anomaly_utils.mdx b/api_docs/kbn_ml_anomaly_utils.mdx index f4ca6cde186dc..d45ed4e2673b8 100644 --- a/api_docs/kbn_ml_anomaly_utils.mdx +++ b/api_docs/kbn_ml_anomaly_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-anomaly-utils title: "@kbn/ml-anomaly-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-anomaly-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-anomaly-utils'] --- import kbnMlAnomalyUtilsObj from './kbn_ml_anomaly_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_cancellable_search.mdx b/api_docs/kbn_ml_cancellable_search.mdx index 0288e3bd89110..adc7f65b4813f 100644 --- a/api_docs/kbn_ml_cancellable_search.mdx +++ b/api_docs/kbn_ml_cancellable_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-cancellable-search title: "@kbn/ml-cancellable-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-cancellable-search plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-cancellable-search'] --- import kbnMlCancellableSearchObj from './kbn_ml_cancellable_search.devdocs.json'; diff --git a/api_docs/kbn_ml_category_validator.mdx b/api_docs/kbn_ml_category_validator.mdx index f1d469f6e47f4..e1a745510ebda 100644 --- a/api_docs/kbn_ml_category_validator.mdx +++ b/api_docs/kbn_ml_category_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-category-validator title: "@kbn/ml-category-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-category-validator plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-category-validator'] --- import kbnMlCategoryValidatorObj from './kbn_ml_category_validator.devdocs.json'; diff --git a/api_docs/kbn_ml_chi2test.mdx b/api_docs/kbn_ml_chi2test.mdx index 51d486a828b4e..0d8b7c7c15dfa 100644 --- a/api_docs/kbn_ml_chi2test.mdx +++ b/api_docs/kbn_ml_chi2test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-chi2test title: "@kbn/ml-chi2test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-chi2test plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-chi2test'] --- import kbnMlChi2testObj from './kbn_ml_chi2test.devdocs.json'; diff --git a/api_docs/kbn_ml_data_frame_analytics_utils.mdx b/api_docs/kbn_ml_data_frame_analytics_utils.mdx index 99dbaae6dbb1a..cd76f123b0064 100644 --- a/api_docs/kbn_ml_data_frame_analytics_utils.mdx +++ b/api_docs/kbn_ml_data_frame_analytics_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-frame-analytics-utils title: "@kbn/ml-data-frame-analytics-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-frame-analytics-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-data-frame-analytics-utils'] --- import kbnMlDataFrameAnalyticsUtilsObj from './kbn_ml_data_frame_analytics_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_data_grid.mdx b/api_docs/kbn_ml_data_grid.mdx index a82da9af2c1bb..98db095869699 100644 --- a/api_docs/kbn_ml_data_grid.mdx +++ b/api_docs/kbn_ml_data_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-grid title: "@kbn/ml-data-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-grid plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-data-grid'] --- import kbnMlDataGridObj from './kbn_ml_data_grid.devdocs.json'; diff --git a/api_docs/kbn_ml_date_picker.mdx b/api_docs/kbn_ml_date_picker.mdx index 6f0bbcfea8778..81e1b69e62da4 100644 --- a/api_docs/kbn_ml_date_picker.mdx +++ b/api_docs/kbn_ml_date_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-picker title: "@kbn/ml-date-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-picker plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-picker'] --- import kbnMlDatePickerObj from './kbn_ml_date_picker.devdocs.json'; diff --git a/api_docs/kbn_ml_date_utils.mdx b/api_docs/kbn_ml_date_utils.mdx index 503ee57e00123..b236b7d6c7ff1 100644 --- a/api_docs/kbn_ml_date_utils.mdx +++ b/api_docs/kbn_ml_date_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-utils title: "@kbn/ml-date-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-utils'] --- import kbnMlDateUtilsObj from './kbn_ml_date_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_error_utils.mdx b/api_docs/kbn_ml_error_utils.mdx index ce476f3e5de00..acdcf1800b08d 100644 --- a/api_docs/kbn_ml_error_utils.mdx +++ b/api_docs/kbn_ml_error_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-error-utils title: "@kbn/ml-error-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-error-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-error-utils'] --- import kbnMlErrorUtilsObj from './kbn_ml_error_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_in_memory_table.mdx b/api_docs/kbn_ml_in_memory_table.mdx index 2ff80ae3fddd8..a064a6cb2ffe9 100644 --- a/api_docs/kbn_ml_in_memory_table.mdx +++ b/api_docs/kbn_ml_in_memory_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-in-memory-table title: "@kbn/ml-in-memory-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-in-memory-table plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-in-memory-table'] --- import kbnMlInMemoryTableObj from './kbn_ml_in_memory_table.devdocs.json'; diff --git a/api_docs/kbn_ml_is_defined.mdx b/api_docs/kbn_ml_is_defined.mdx index d2a98acfe5360..7c01871684839 100644 --- a/api_docs/kbn_ml_is_defined.mdx +++ b/api_docs/kbn_ml_is_defined.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-defined title: "@kbn/ml-is-defined" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-defined plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-defined'] --- import kbnMlIsDefinedObj from './kbn_ml_is_defined.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 22e052a550feb..e0be61dff3c4c 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_kibana_theme.mdx b/api_docs/kbn_ml_kibana_theme.mdx index 28554f52db382..3e1829835be6b 100644 --- a/api_docs/kbn_ml_kibana_theme.mdx +++ b/api_docs/kbn_ml_kibana_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-kibana-theme title: "@kbn/ml-kibana-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-kibana-theme plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-kibana-theme'] --- import kbnMlKibanaThemeObj from './kbn_ml_kibana_theme.devdocs.json'; diff --git a/api_docs/kbn_ml_local_storage.mdx b/api_docs/kbn_ml_local_storage.mdx index 0213893a814af..d54023ab4ca2b 100644 --- a/api_docs/kbn_ml_local_storage.mdx +++ b/api_docs/kbn_ml_local_storage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-local-storage title: "@kbn/ml-local-storage" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-local-storage plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-local-storage'] --- import kbnMlLocalStorageObj from './kbn_ml_local_storage.devdocs.json'; diff --git a/api_docs/kbn_ml_nested_property.mdx b/api_docs/kbn_ml_nested_property.mdx index 135e64f1a9c0b..6215af6a958bf 100644 --- a/api_docs/kbn_ml_nested_property.mdx +++ b/api_docs/kbn_ml_nested_property.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-nested-property title: "@kbn/ml-nested-property" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-nested-property plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-nested-property'] --- import kbnMlNestedPropertyObj from './kbn_ml_nested_property.devdocs.json'; diff --git a/api_docs/kbn_ml_number_utils.mdx b/api_docs/kbn_ml_number_utils.mdx index 9d01e63ad802d..8613864038acd 100644 --- a/api_docs/kbn_ml_number_utils.mdx +++ b/api_docs/kbn_ml_number_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-number-utils title: "@kbn/ml-number-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-number-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-number-utils'] --- import kbnMlNumberUtilsObj from './kbn_ml_number_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_query_utils.mdx b/api_docs/kbn_ml_query_utils.mdx index e896a5d7ae1df..695aa09af2bfb 100644 --- a/api_docs/kbn_ml_query_utils.mdx +++ b/api_docs/kbn_ml_query_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-query-utils title: "@kbn/ml-query-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-query-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-query-utils'] --- import kbnMlQueryUtilsObj from './kbn_ml_query_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_random_sampler_utils.mdx b/api_docs/kbn_ml_random_sampler_utils.mdx index edf9b1658ce77..911b9481f6d05 100644 --- a/api_docs/kbn_ml_random_sampler_utils.mdx +++ b/api_docs/kbn_ml_random_sampler_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-random-sampler-utils title: "@kbn/ml-random-sampler-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-random-sampler-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-random-sampler-utils'] --- import kbnMlRandomSamplerUtilsObj from './kbn_ml_random_sampler_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_route_utils.mdx b/api_docs/kbn_ml_route_utils.mdx index 2d39f5bb08ac9..2a06d20c38a64 100644 --- a/api_docs/kbn_ml_route_utils.mdx +++ b/api_docs/kbn_ml_route_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-route-utils title: "@kbn/ml-route-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-route-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-route-utils'] --- import kbnMlRouteUtilsObj from './kbn_ml_route_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_runtime_field_utils.mdx b/api_docs/kbn_ml_runtime_field_utils.mdx index cdc25adb5f41c..58266a587504a 100644 --- a/api_docs/kbn_ml_runtime_field_utils.mdx +++ b/api_docs/kbn_ml_runtime_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-runtime-field-utils title: "@kbn/ml-runtime-field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-runtime-field-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-runtime-field-utils'] --- import kbnMlRuntimeFieldUtilsObj from './kbn_ml_runtime_field_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 0e4a948efd034..41ea694815055 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_ml_time_buckets.mdx b/api_docs/kbn_ml_time_buckets.mdx index 1e596764daa83..5342fc18f8440 100644 --- a/api_docs/kbn_ml_time_buckets.mdx +++ b/api_docs/kbn_ml_time_buckets.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-time-buckets title: "@kbn/ml-time-buckets" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-time-buckets plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-time-buckets'] --- import kbnMlTimeBucketsObj from './kbn_ml_time_buckets.devdocs.json'; diff --git a/api_docs/kbn_ml_trained_models_utils.mdx b/api_docs/kbn_ml_trained_models_utils.mdx index f8c21091576e5..bebd392e6322e 100644 --- a/api_docs/kbn_ml_trained_models_utils.mdx +++ b/api_docs/kbn_ml_trained_models_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-trained-models-utils title: "@kbn/ml-trained-models-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-trained-models-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-trained-models-utils'] --- import kbnMlTrainedModelsUtilsObj from './kbn_ml_trained_models_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_ui_actions.mdx b/api_docs/kbn_ml_ui_actions.mdx index 64954df4d1add..a4a65c5d900b4 100644 --- a/api_docs/kbn_ml_ui_actions.mdx +++ b/api_docs/kbn_ml_ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-ui-actions title: "@kbn/ml-ui-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-ui-actions plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-ui-actions'] --- import kbnMlUiActionsObj from './kbn_ml_ui_actions.devdocs.json'; diff --git a/api_docs/kbn_ml_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index 09558d2abdade..e081864aa7d7d 100644 --- a/api_docs/kbn_ml_url_state.mdx +++ b/api_docs/kbn_ml_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-url-state title: "@kbn/ml-url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-url-state plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-url-state'] --- import kbnMlUrlStateObj from './kbn_ml_url_state.devdocs.json'; diff --git a/api_docs/kbn_mock_idp_utils.mdx b/api_docs/kbn_mock_idp_utils.mdx index 69e8d3e030e60..e65f99cbbd927 100644 --- a/api_docs/kbn_mock_idp_utils.mdx +++ b/api_docs/kbn_mock_idp_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mock-idp-utils title: "@kbn/mock-idp-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mock-idp-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mock-idp-utils'] --- import kbnMockIdpUtilsObj from './kbn_mock_idp_utils.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 6921835681468..411cfb6ad8030 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_object_versioning.mdx b/api_docs/kbn_object_versioning.mdx index e84d112c4c30d..3c6d6fe84c463 100644 --- a/api_docs/kbn_object_versioning.mdx +++ b/api_docs/kbn_object_versioning.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-object-versioning title: "@kbn/object-versioning" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/object-versioning plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/object-versioning'] --- import kbnObjectVersioningObj from './kbn_object_versioning.devdocs.json'; diff --git a/api_docs/kbn_observability_alert_details.mdx b/api_docs/kbn_observability_alert_details.mdx index b3e86aee8d302..eb731fa78dba1 100644 --- a/api_docs/kbn_observability_alert_details.mdx +++ b/api_docs/kbn_observability_alert_details.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alert-details title: "@kbn/observability-alert-details" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alert-details plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alert-details'] --- import kbnObservabilityAlertDetailsObj from './kbn_observability_alert_details.devdocs.json'; diff --git a/api_docs/kbn_observability_alerting_test_data.mdx b/api_docs/kbn_observability_alerting_test_data.mdx index 73b57383c73d6..f21d5559fb680 100644 --- a/api_docs/kbn_observability_alerting_test_data.mdx +++ b/api_docs/kbn_observability_alerting_test_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alerting-test-data title: "@kbn/observability-alerting-test-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alerting-test-data plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alerting-test-data'] --- import kbnObservabilityAlertingTestDataObj from './kbn_observability_alerting_test_data.devdocs.json'; diff --git a/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx b/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx index 8f28e91503d14..69010ae222d23 100644 --- a/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx +++ b/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-get-padded-alert-time-range-util title: "@kbn/observability-get-padded-alert-time-range-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-get-padded-alert-time-range-util plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-get-padded-alert-time-range-util'] --- import kbnObservabilityGetPaddedAlertTimeRangeUtilObj from './kbn_observability_get_padded_alert_time_range_util.devdocs.json'; diff --git a/api_docs/kbn_openapi_bundler.mdx b/api_docs/kbn_openapi_bundler.mdx index e2793250162e5..4fca7d45fa0ed 100644 --- a/api_docs/kbn_openapi_bundler.mdx +++ b/api_docs/kbn_openapi_bundler.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-openapi-bundler title: "@kbn/openapi-bundler" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/openapi-bundler plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/openapi-bundler'] --- import kbnOpenapiBundlerObj from './kbn_openapi_bundler.devdocs.json'; diff --git a/api_docs/kbn_openapi_generator.mdx b/api_docs/kbn_openapi_generator.mdx index cc7682869dbb9..3d72748bc4f6a 100644 --- a/api_docs/kbn_openapi_generator.mdx +++ b/api_docs/kbn_openapi_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-openapi-generator title: "@kbn/openapi-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/openapi-generator plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/openapi-generator'] --- import kbnOpenapiGeneratorObj from './kbn_openapi_generator.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index d62fed6163b9c..db8c8ea35e19d 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index 42027de74f63e..3d9735b536e4b 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index d260f44d19b06..04a8610638c87 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_panel_loader.mdx b/api_docs/kbn_panel_loader.mdx index 4fe7f5ced096a..41830fcc2457a 100644 --- a/api_docs/kbn_panel_loader.mdx +++ b/api_docs/kbn_panel_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-panel-loader title: "@kbn/panel-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/panel-loader plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/panel-loader'] --- import kbnPanelLoaderObj from './kbn_panel_loader.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 882b59d74d914..ca830f34dc03c 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_check.mdx b/api_docs/kbn_plugin_check.mdx index 02a727684e830..1d9dfa5661bc1 100644 --- a/api_docs/kbn_plugin_check.mdx +++ b/api_docs/kbn_plugin_check.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-check title: "@kbn/plugin-check" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-check plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-check'] --- import kbnPluginCheckObj from './kbn_plugin_check.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 0f5d0174ae4e0..7cf78bbea75d3 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index d45353f8bc210..8b36f05605295 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_presentation_containers.mdx b/api_docs/kbn_presentation_containers.mdx index 1a9e32be1b5f2..8cec2143e40f6 100644 --- a/api_docs/kbn_presentation_containers.mdx +++ b/api_docs/kbn_presentation_containers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-presentation-containers title: "@kbn/presentation-containers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/presentation-containers plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-containers'] --- import kbnPresentationContainersObj from './kbn_presentation_containers.devdocs.json'; diff --git a/api_docs/kbn_presentation_publishing.mdx b/api_docs/kbn_presentation_publishing.mdx index 8ee1c4ba1ea1e..1c1af3d6c54bd 100644 --- a/api_docs/kbn_presentation_publishing.mdx +++ b/api_docs/kbn_presentation_publishing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-presentation-publishing title: "@kbn/presentation-publishing" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/presentation-publishing plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-publishing'] --- import kbnPresentationPublishingObj from './kbn_presentation_publishing.devdocs.json'; diff --git a/api_docs/kbn_profiling_utils.mdx b/api_docs/kbn_profiling_utils.mdx index b5eb5b118429e..f9261e8192f5c 100644 --- a/api_docs/kbn_profiling_utils.mdx +++ b/api_docs/kbn_profiling_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-profiling-utils title: "@kbn/profiling-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/profiling-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/profiling-utils'] --- import kbnProfilingUtilsObj from './kbn_profiling_utils.devdocs.json'; diff --git a/api_docs/kbn_random_sampling.mdx b/api_docs/kbn_random_sampling.mdx index 1b6384d28a0e4..34aadc7bb5f62 100644 --- a/api_docs/kbn_random_sampling.mdx +++ b/api_docs/kbn_random_sampling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-random-sampling title: "@kbn/random-sampling" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/random-sampling plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/random-sampling'] --- import kbnRandomSamplingObj from './kbn_random_sampling.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index aa2db43a50d84..8d9c4c1c67a01 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_react_hooks.mdx b/api_docs/kbn_react_hooks.mdx index 3b3c818606f4c..9e0d267563e7c 100644 --- a/api_docs/kbn_react_hooks.mdx +++ b/api_docs/kbn_react_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-hooks title: "@kbn/react-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-hooks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-hooks'] --- import kbnReactHooksObj from './kbn_react_hooks.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_common.mdx b/api_docs/kbn_react_kibana_context_common.mdx index 29dde5f1dba46..f522a514efaec 100644 --- a/api_docs/kbn_react_kibana_context_common.mdx +++ b/api_docs/kbn_react_kibana_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-common title: "@kbn/react-kibana-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-common'] --- import kbnReactKibanaContextCommonObj from './kbn_react_kibana_context_common.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_render.mdx b/api_docs/kbn_react_kibana_context_render.mdx index 7bf4a25dfe6e8..d4ce9e5890055 100644 --- a/api_docs/kbn_react_kibana_context_render.mdx +++ b/api_docs/kbn_react_kibana_context_render.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-render title: "@kbn/react-kibana-context-render" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-render plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-render'] --- import kbnReactKibanaContextRenderObj from './kbn_react_kibana_context_render.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_root.mdx b/api_docs/kbn_react_kibana_context_root.mdx index de1685a93a036..b8ef18f7f1668 100644 --- a/api_docs/kbn_react_kibana_context_root.mdx +++ b/api_docs/kbn_react_kibana_context_root.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-root title: "@kbn/react-kibana-context-root" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-root plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-root'] --- import kbnReactKibanaContextRootObj from './kbn_react_kibana_context_root.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_styled.mdx b/api_docs/kbn_react_kibana_context_styled.mdx index 14987e882950a..571d292c4bc01 100644 --- a/api_docs/kbn_react_kibana_context_styled.mdx +++ b/api_docs/kbn_react_kibana_context_styled.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-styled title: "@kbn/react-kibana-context-styled" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-styled plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-styled'] --- import kbnReactKibanaContextStyledObj from './kbn_react_kibana_context_styled.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_theme.mdx b/api_docs/kbn_react_kibana_context_theme.mdx index 41af25db1aac1..e2679bcdff842 100644 --- a/api_docs/kbn_react_kibana_context_theme.mdx +++ b/api_docs/kbn_react_kibana_context_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-theme title: "@kbn/react-kibana-context-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-theme plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-theme'] --- import kbnReactKibanaContextThemeObj from './kbn_react_kibana_context_theme.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_mount.mdx b/api_docs/kbn_react_kibana_mount.mdx index 219ef91e4fbae..ceb70a466aaeb 100644 --- a/api_docs/kbn_react_kibana_mount.mdx +++ b/api_docs/kbn_react_kibana_mount.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-mount title: "@kbn/react-kibana-mount" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-mount plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-mount'] --- import kbnReactKibanaMountObj from './kbn_react_kibana_mount.devdocs.json'; diff --git a/api_docs/kbn_repo_file_maps.mdx b/api_docs/kbn_repo_file_maps.mdx index a6ed7ddd3480f..6eba78e7e2946 100644 --- a/api_docs/kbn_repo_file_maps.mdx +++ b/api_docs/kbn_repo_file_maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-file-maps title: "@kbn/repo-file-maps" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-file-maps plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-file-maps'] --- import kbnRepoFileMapsObj from './kbn_repo_file_maps.devdocs.json'; diff --git a/api_docs/kbn_repo_linter.mdx b/api_docs/kbn_repo_linter.mdx index fb9ceb6f36def..310c814093ea2 100644 --- a/api_docs/kbn_repo_linter.mdx +++ b/api_docs/kbn_repo_linter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-linter title: "@kbn/repo-linter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-linter plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-linter'] --- import kbnRepoLinterObj from './kbn_repo_linter.devdocs.json'; diff --git a/api_docs/kbn_repo_path.mdx b/api_docs/kbn_repo_path.mdx index a24bfad6aecbc..b215725392c20 100644 --- a/api_docs/kbn_repo_path.mdx +++ b/api_docs/kbn_repo_path.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-path title: "@kbn/repo-path" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-path plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-path'] --- import kbnRepoPathObj from './kbn_repo_path.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index d37b1139ef143..05fcee61ee529 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_reporting_common.mdx b/api_docs/kbn_reporting_common.mdx index aee93339d3b7f..c7bf84cfcabe2 100644 --- a/api_docs/kbn_reporting_common.mdx +++ b/api_docs/kbn_reporting_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-common title: "@kbn/reporting-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-common'] --- import kbnReportingCommonObj from './kbn_reporting_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_csv_share_panel.mdx b/api_docs/kbn_reporting_csv_share_panel.mdx index 862f9c8ede562..cc7b5862185eb 100644 --- a/api_docs/kbn_reporting_csv_share_panel.mdx +++ b/api_docs/kbn_reporting_csv_share_panel.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-csv-share-panel title: "@kbn/reporting-csv-share-panel" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-csv-share-panel plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-csv-share-panel'] --- import kbnReportingCsvSharePanelObj from './kbn_reporting_csv_share_panel.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_csv.mdx b/api_docs/kbn_reporting_export_types_csv.mdx index 47444a4568d30..c8d800083c9f4 100644 --- a/api_docs/kbn_reporting_export_types_csv.mdx +++ b/api_docs/kbn_reporting_export_types_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-csv title: "@kbn/reporting-export-types-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-csv plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-csv'] --- import kbnReportingExportTypesCsvObj from './kbn_reporting_export_types_csv.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_csv_common.mdx b/api_docs/kbn_reporting_export_types_csv_common.mdx index 06ec94cc6e52d..a1b3a76a954bf 100644 --- a/api_docs/kbn_reporting_export_types_csv_common.mdx +++ b/api_docs/kbn_reporting_export_types_csv_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-csv-common title: "@kbn/reporting-export-types-csv-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-csv-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-csv-common'] --- import kbnReportingExportTypesCsvCommonObj from './kbn_reporting_export_types_csv_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_pdf.mdx b/api_docs/kbn_reporting_export_types_pdf.mdx index e3386f12456a1..e99dcb198d898 100644 --- a/api_docs/kbn_reporting_export_types_pdf.mdx +++ b/api_docs/kbn_reporting_export_types_pdf.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-pdf title: "@kbn/reporting-export-types-pdf" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-pdf plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-pdf'] --- import kbnReportingExportTypesPdfObj from './kbn_reporting_export_types_pdf.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_pdf_common.mdx b/api_docs/kbn_reporting_export_types_pdf_common.mdx index 95e92e092d1ed..01668db194698 100644 --- a/api_docs/kbn_reporting_export_types_pdf_common.mdx +++ b/api_docs/kbn_reporting_export_types_pdf_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-pdf-common title: "@kbn/reporting-export-types-pdf-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-pdf-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-pdf-common'] --- import kbnReportingExportTypesPdfCommonObj from './kbn_reporting_export_types_pdf_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_png.mdx b/api_docs/kbn_reporting_export_types_png.mdx index 4dcf081b6751c..7f6442264e231 100644 --- a/api_docs/kbn_reporting_export_types_png.mdx +++ b/api_docs/kbn_reporting_export_types_png.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-png title: "@kbn/reporting-export-types-png" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-png plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-png'] --- import kbnReportingExportTypesPngObj from './kbn_reporting_export_types_png.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_png_common.mdx b/api_docs/kbn_reporting_export_types_png_common.mdx index b20deffbb5ba4..a27cedc9dc97c 100644 --- a/api_docs/kbn_reporting_export_types_png_common.mdx +++ b/api_docs/kbn_reporting_export_types_png_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-png-common title: "@kbn/reporting-export-types-png-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-png-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-png-common'] --- import kbnReportingExportTypesPngCommonObj from './kbn_reporting_export_types_png_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_mocks_server.mdx b/api_docs/kbn_reporting_mocks_server.mdx index 37ca4c855c53c..b41b3b3fceefd 100644 --- a/api_docs/kbn_reporting_mocks_server.mdx +++ b/api_docs/kbn_reporting_mocks_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-mocks-server title: "@kbn/reporting-mocks-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-mocks-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-mocks-server'] --- import kbnReportingMocksServerObj from './kbn_reporting_mocks_server.devdocs.json'; diff --git a/api_docs/kbn_reporting_public.mdx b/api_docs/kbn_reporting_public.mdx index dc1d34a6b44a4..331c3bdc59355 100644 --- a/api_docs/kbn_reporting_public.mdx +++ b/api_docs/kbn_reporting_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-public title: "@kbn/reporting-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-public plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-public'] --- import kbnReportingPublicObj from './kbn_reporting_public.devdocs.json'; diff --git a/api_docs/kbn_reporting_server.mdx b/api_docs/kbn_reporting_server.mdx index f58338d54ee2b..bf71961fd72bd 100644 --- a/api_docs/kbn_reporting_server.mdx +++ b/api_docs/kbn_reporting_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-server title: "@kbn/reporting-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-server'] --- import kbnReportingServerObj from './kbn_reporting_server.devdocs.json'; diff --git a/api_docs/kbn_resizable_layout.mdx b/api_docs/kbn_resizable_layout.mdx index 5e43e6c32318f..8c62673244bfd 100644 --- a/api_docs/kbn_resizable_layout.mdx +++ b/api_docs/kbn_resizable_layout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-resizable-layout title: "@kbn/resizable-layout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/resizable-layout plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/resizable-layout'] --- import kbnResizableLayoutObj from './kbn_resizable_layout.devdocs.json'; diff --git a/api_docs/kbn_response_ops_feature_flag_service.mdx b/api_docs/kbn_response_ops_feature_flag_service.mdx index f2934a0d4c8c6..6a3cf64a706f0 100644 --- a/api_docs/kbn_response_ops_feature_flag_service.mdx +++ b/api_docs/kbn_response_ops_feature_flag_service.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-response-ops-feature-flag-service title: "@kbn/response-ops-feature-flag-service" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/response-ops-feature-flag-service plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/response-ops-feature-flag-service'] --- import kbnResponseOpsFeatureFlagServiceObj from './kbn_response_ops_feature_flag_service.devdocs.json'; diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index 8366fa9901bd4..598261b79f837 100644 --- a/api_docs/kbn_rison.mdx +++ b/api_docs/kbn_rison.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rison title: "@kbn/rison" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rison plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rollup.mdx b/api_docs/kbn_rollup.mdx index 831c4a46a4b31..b2df9fd449dfa 100644 --- a/api_docs/kbn_rollup.mdx +++ b/api_docs/kbn_rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rollup title: "@kbn/rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rollup plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rollup'] --- import kbnRollupObj from './kbn_rollup.devdocs.json'; diff --git a/api_docs/kbn_router_to_openapispec.mdx b/api_docs/kbn_router_to_openapispec.mdx index 98cce31db59df..2d2165346c3df 100644 --- a/api_docs/kbn_router_to_openapispec.mdx +++ b/api_docs/kbn_router_to_openapispec.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-router-to-openapispec title: "@kbn/router-to-openapispec" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/router-to-openapispec plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/router-to-openapispec'] --- import kbnRouterToOpenapispecObj from './kbn_router_to_openapispec.devdocs.json'; diff --git a/api_docs/kbn_router_utils.mdx b/api_docs/kbn_router_utils.mdx index c5d56d10ef97c..7a3e7b56f5f91 100644 --- a/api_docs/kbn_router_utils.mdx +++ b/api_docs/kbn_router_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-router-utils title: "@kbn/router-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/router-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/router-utils'] --- import kbnRouterUtilsObj from './kbn_router_utils.devdocs.json'; diff --git a/api_docs/kbn_rrule.mdx b/api_docs/kbn_rrule.mdx index 207d4e38f8574..c014f687e5888 100644 --- a/api_docs/kbn_rrule.mdx +++ b/api_docs/kbn_rrule.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rrule title: "@kbn/rrule" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rrule plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rrule'] --- import kbnRruleObj from './kbn_rrule.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index fc2e92ca6d5cc..71b55e87407f8 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_saved_objects_settings.mdx b/api_docs/kbn_saved_objects_settings.mdx index d2acf777896ce..f11343d16b399 100644 --- a/api_docs/kbn_saved_objects_settings.mdx +++ b/api_docs/kbn_saved_objects_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-saved-objects-settings title: "@kbn/saved-objects-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/saved-objects-settings plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/saved-objects-settings'] --- import kbnSavedObjectsSettingsObj from './kbn_saved_objects_settings.devdocs.json'; diff --git a/api_docs/kbn_search_api_panels.mdx b/api_docs/kbn_search_api_panels.mdx index 9ab91a9556692..b1161981b1c69 100644 --- a/api_docs/kbn_search_api_panels.mdx +++ b/api_docs/kbn_search_api_panels.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-api-panels title: "@kbn/search-api-panels" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-api-panels plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-api-panels'] --- import kbnSearchApiPanelsObj from './kbn_search_api_panels.devdocs.json'; diff --git a/api_docs/kbn_search_connectors.mdx b/api_docs/kbn_search_connectors.mdx index 85a231d535bd1..600b8201eb06f 100644 --- a/api_docs/kbn_search_connectors.mdx +++ b/api_docs/kbn_search_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-connectors title: "@kbn/search-connectors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-connectors plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-connectors'] --- import kbnSearchConnectorsObj from './kbn_search_connectors.devdocs.json'; diff --git a/api_docs/kbn_search_errors.mdx b/api_docs/kbn_search_errors.mdx index 26b9bea3667a8..41c4dff6928f4 100644 --- a/api_docs/kbn_search_errors.mdx +++ b/api_docs/kbn_search_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-errors title: "@kbn/search-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-errors plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-errors'] --- import kbnSearchErrorsObj from './kbn_search_errors.devdocs.json'; diff --git a/api_docs/kbn_search_index_documents.mdx b/api_docs/kbn_search_index_documents.mdx index ac22ee2209b86..3440d6365f165 100644 --- a/api_docs/kbn_search_index_documents.mdx +++ b/api_docs/kbn_search_index_documents.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-index-documents title: "@kbn/search-index-documents" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-index-documents plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-index-documents'] --- import kbnSearchIndexDocumentsObj from './kbn_search_index_documents.devdocs.json'; diff --git a/api_docs/kbn_search_response_warnings.mdx b/api_docs/kbn_search_response_warnings.mdx index 47915730e17c5..827729ecde166 100644 --- a/api_docs/kbn_search_response_warnings.mdx +++ b/api_docs/kbn_search_response_warnings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-response-warnings title: "@kbn/search-response-warnings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-response-warnings plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-response-warnings'] --- import kbnSearchResponseWarningsObj from './kbn_search_response_warnings.devdocs.json'; diff --git a/api_docs/kbn_search_types.mdx b/api_docs/kbn_search_types.mdx index 9cfb066c504e2..fe310c8364e78 100644 --- a/api_docs/kbn_search_types.mdx +++ b/api_docs/kbn_search_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-types title: "@kbn/search-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-types plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-types'] --- import kbnSearchTypesObj from './kbn_search_types.devdocs.json'; diff --git a/api_docs/kbn_security_api_key_management.mdx b/api_docs/kbn_security_api_key_management.mdx index 54ad93fae2244..3464c269a5d7e 100644 --- a/api_docs/kbn_security_api_key_management.mdx +++ b/api_docs/kbn_security_api_key_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-api-key-management title: "@kbn/security-api-key-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-api-key-management plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-api-key-management'] --- import kbnSecurityApiKeyManagementObj from './kbn_security_api_key_management.devdocs.json'; diff --git a/api_docs/kbn_security_form_components.mdx b/api_docs/kbn_security_form_components.mdx index 2cc84adcae9f6..e987fc5ad75eb 100644 --- a/api_docs/kbn_security_form_components.mdx +++ b/api_docs/kbn_security_form_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-form-components title: "@kbn/security-form-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-form-components plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-form-components'] --- import kbnSecurityFormComponentsObj from './kbn_security_form_components.devdocs.json'; diff --git a/api_docs/kbn_security_hardening.mdx b/api_docs/kbn_security_hardening.mdx index 2f6d259c8e092..1db9d3199e3c0 100644 --- a/api_docs/kbn_security_hardening.mdx +++ b/api_docs/kbn_security_hardening.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-hardening title: "@kbn/security-hardening" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-hardening plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-hardening'] --- import kbnSecurityHardeningObj from './kbn_security_hardening.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_common.mdx b/api_docs/kbn_security_plugin_types_common.mdx index 838090933be74..65bba991c5486 100644 --- a/api_docs/kbn_security_plugin_types_common.mdx +++ b/api_docs/kbn_security_plugin_types_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-common title: "@kbn/security-plugin-types-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-common plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-common'] --- import kbnSecurityPluginTypesCommonObj from './kbn_security_plugin_types_common.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_public.mdx b/api_docs/kbn_security_plugin_types_public.mdx index 900364abf89d4..80416ce94c0b1 100644 --- a/api_docs/kbn_security_plugin_types_public.mdx +++ b/api_docs/kbn_security_plugin_types_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-public title: "@kbn/security-plugin-types-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-public plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-public'] --- import kbnSecurityPluginTypesPublicObj from './kbn_security_plugin_types_public.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_server.devdocs.json b/api_docs/kbn_security_plugin_types_server.devdocs.json index 11afe5cb40dce..724814846da59 100644 --- a/api_docs/kbn_security_plugin_types_server.devdocs.json +++ b/api_docs/kbn_security_plugin_types_server.devdocs.json @@ -3240,22 +3240,6 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/rules_client_factory.ts" }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/rules_client_factory.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/rules_client_factory.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/rules_settings_client_factory.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/maintenance_window_client_factory.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/invalidate_pending_api_keys/task.ts" @@ -3288,10 +3272,6 @@ "plugin": "fleet", "path": "x-pack/plugins/fleet/server/routes/setup/handlers.ts" }, - { - "plugin": "cloudDefend", - "path": "x-pack/plugins/cloud_defend/server/routes/setup_routes.ts" - }, { "plugin": "cloudSecurityPosture", "path": "x-pack/plugins/cloud_security_posture/server/routes/setup_routes.ts" diff --git a/api_docs/kbn_security_plugin_types_server.mdx b/api_docs/kbn_security_plugin_types_server.mdx index 6029c81ec368e..f05bcb67cd468 100644 --- a/api_docs/kbn_security_plugin_types_server.mdx +++ b/api_docs/kbn_security_plugin_types_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-server title: "@kbn/security-plugin-types-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-server plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-server'] --- import kbnSecurityPluginTypesServerObj from './kbn_security_plugin_types_server.devdocs.json'; diff --git a/api_docs/kbn_security_solution_features.mdx b/api_docs/kbn_security_solution_features.mdx index a9a5c9dafc7ba..a1d800cef4edc 100644 --- a/api_docs/kbn_security_solution_features.mdx +++ b/api_docs/kbn_security_solution_features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-features title: "@kbn/security-solution-features" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-features plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-features'] --- import kbnSecuritySolutionFeaturesObj from './kbn_security_solution_features.devdocs.json'; diff --git a/api_docs/kbn_security_solution_navigation.mdx b/api_docs/kbn_security_solution_navigation.mdx index e6953d46f1961..1b1c587ee2cf1 100644 --- a/api_docs/kbn_security_solution_navigation.mdx +++ b/api_docs/kbn_security_solution_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-navigation title: "@kbn/security-solution-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-navigation plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-navigation'] --- import kbnSecuritySolutionNavigationObj from './kbn_security_solution_navigation.devdocs.json'; diff --git a/api_docs/kbn_security_solution_side_nav.mdx b/api_docs/kbn_security_solution_side_nav.mdx index 98f4db3262765..cdaecdd194ff5 100644 --- a/api_docs/kbn_security_solution_side_nav.mdx +++ b/api_docs/kbn_security_solution_side_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-side-nav title: "@kbn/security-solution-side-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-side-nav plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-side-nav'] --- import kbnSecuritySolutionSideNavObj from './kbn_security_solution_side_nav.devdocs.json'; diff --git a/api_docs/kbn_security_solution_storybook_config.mdx b/api_docs/kbn_security_solution_storybook_config.mdx index a9b3a4e379265..18ee9c6da6431 100644 --- a/api_docs/kbn_security_solution_storybook_config.mdx +++ b/api_docs/kbn_security_solution_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-storybook-config title: "@kbn/security-solution-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-storybook-config plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-storybook-config'] --- import kbnSecuritySolutionStorybookConfigObj from './kbn_security_solution_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index be2a9184de459..d8fd466416d3f 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_data_table.mdx b/api_docs/kbn_securitysolution_data_table.mdx index 036844c562285..17715c970c71c 100644 --- a/api_docs/kbn_securitysolution_data_table.mdx +++ b/api_docs/kbn_securitysolution_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-data-table title: "@kbn/securitysolution-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-data-table plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-data-table'] --- import kbnSecuritysolutionDataTableObj from './kbn_securitysolution_data_table.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_ecs.mdx b/api_docs/kbn_securitysolution_ecs.mdx index 43b4167de967a..056f5e5c86802 100644 --- a/api_docs/kbn_securitysolution_ecs.mdx +++ b/api_docs/kbn_securitysolution_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-ecs title: "@kbn/securitysolution-ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-ecs plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-ecs'] --- import kbnSecuritysolutionEcsObj from './kbn_securitysolution_ecs.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index 4d8bf98615bec..e5bc0d0869a3a 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index 64babe983ac7e..919547a1e8421 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.mdx +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components title: "@kbn/securitysolution-exception-list-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-exception-list-components plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 0c549e82880e8..abb89f2c33f94 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index 9bcca4c2afebc..758a3ef4d5be8 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 609ef4521a23d..7aff2873fb994 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index d42f6a2049389..ad6ed32459978 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index af842dcfc20aa..ec08d5c21eb34 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index b136699211a57..fab456836306e 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.devdocs.json b/api_docs/kbn_securitysolution_list_constants.devdocs.json index 889e28f5dc20c..f4fe485c20c72 100644 --- a/api_docs/kbn_securitysolution_list_constants.devdocs.json +++ b/api_docs/kbn_securitysolution_list_constants.devdocs.json @@ -67,14 +67,6 @@ "deprecated": true, "trackAdoption": false, "references": [ - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts" @@ -103,6 +95,14 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts" @@ -189,19 +189,19 @@ "references": [ { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts" + "path": "x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts" + "path": "x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts" }, { "plugin": "securitySolution", @@ -227,26 +227,6 @@ "deprecated": true, "trackAdoption": false, "references": [ - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts" @@ -307,6 +287,26 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts" @@ -381,19 +381,19 @@ "references": [ { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts" + "path": "x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts" + "path": "x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts" }, { "plugin": "securitySolution", @@ -453,19 +453,19 @@ "references": [ { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts" }, { "plugin": "securitySolution", @@ -473,11 +473,11 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts" + "path": "x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts" + "path": "x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts" }, { "plugin": "securitySolution", @@ -752,14 +752,6 @@ "plugin": "lists", "path": "x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts" @@ -788,6 +780,14 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts" diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index 52162af770cc7..3c88afc07a82b 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index 9c63a6588ec7f..478bee62116c4 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index ce7c749acfe50..354168a7829ea 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index 97e13a2d45ac9..cac01d08bc5f7 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index 59f7318f7338f..b48e0338fc24d 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index 8a9710d3594d3..5b0b418157eaa 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index 04a85f428b9aa..bbf0204f4b049 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index 790d83aa60999..92b1e3d21dece 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_serverless_common_settings.mdx b/api_docs/kbn_serverless_common_settings.mdx index b5512c2c6c475..d4aca879adf56 100644 --- a/api_docs/kbn_serverless_common_settings.mdx +++ b/api_docs/kbn_serverless_common_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-common-settings title: "@kbn/serverless-common-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-common-settings plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-common-settings'] --- import kbnServerlessCommonSettingsObj from './kbn_serverless_common_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_observability_settings.mdx b/api_docs/kbn_serverless_observability_settings.mdx index 866ea239779a9..810d5623c1b63 100644 --- a/api_docs/kbn_serverless_observability_settings.mdx +++ b/api_docs/kbn_serverless_observability_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-observability-settings title: "@kbn/serverless-observability-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-observability-settings plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-observability-settings'] --- import kbnServerlessObservabilitySettingsObj from './kbn_serverless_observability_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_project_switcher.mdx b/api_docs/kbn_serverless_project_switcher.mdx index 810463b36efcb..2b4c7d69f0e36 100644 --- a/api_docs/kbn_serverless_project_switcher.mdx +++ b/api_docs/kbn_serverless_project_switcher.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-project-switcher title: "@kbn/serverless-project-switcher" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-project-switcher plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-project-switcher'] --- import kbnServerlessProjectSwitcherObj from './kbn_serverless_project_switcher.devdocs.json'; diff --git a/api_docs/kbn_serverless_search_settings.mdx b/api_docs/kbn_serverless_search_settings.mdx index 3f52c7b9e9d1d..be11453ea43f1 100644 --- a/api_docs/kbn_serverless_search_settings.mdx +++ b/api_docs/kbn_serverless_search_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-search-settings title: "@kbn/serverless-search-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-search-settings plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-search-settings'] --- import kbnServerlessSearchSettingsObj from './kbn_serverless_search_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_security_settings.mdx b/api_docs/kbn_serverless_security_settings.mdx index 0b7627d04d0de..365a36814770a 100644 --- a/api_docs/kbn_serverless_security_settings.mdx +++ b/api_docs/kbn_serverless_security_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-security-settings title: "@kbn/serverless-security-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-security-settings plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-security-settings'] --- import kbnServerlessSecuritySettingsObj from './kbn_serverless_security_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_storybook_config.mdx b/api_docs/kbn_serverless_storybook_config.mdx index 441278297ed99..04a3b1a9bdf91 100644 --- a/api_docs/kbn_serverless_storybook_config.mdx +++ b/api_docs/kbn_serverless_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-storybook-config title: "@kbn/serverless-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-storybook-config plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-storybook-config'] --- import kbnServerlessStorybookConfigObj from './kbn_serverless_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index 05afafd9671f3..b4135a065c1ae 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_solution.mdx b/api_docs/kbn_shared_ux_avatar_solution.mdx index 65a21f2fcc501..2166bb29b5f22 100644 --- a/api_docs/kbn_shared_ux_avatar_solution.mdx +++ b/api_docs/kbn_shared_ux_avatar_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-solution title: "@kbn/shared-ux-avatar-solution" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-solution plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-solution'] --- import kbnSharedUxAvatarSolutionObj from './kbn_shared_ux_avatar_solution.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx index 642b5e1170cb8..bebb4766635c9 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen title: "@kbn/shared-ux-button-exit-full-screen" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen'] --- import kbnSharedUxButtonExitFullScreenObj from './kbn_shared_ux_button_exit_full_screen.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index ba6a03d4d1644..9e5e5ab07701d 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index 43ebdeee3e443..f79843c558f11 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index 430a12cc61fce..36a16afc5954e 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_chrome_navigation.mdx b/api_docs/kbn_shared_ux_chrome_navigation.mdx index 8242feae71a62..f12f1e82638bd 100644 --- a/api_docs/kbn_shared_ux_chrome_navigation.mdx +++ b/api_docs/kbn_shared_ux_chrome_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-chrome-navigation title: "@kbn/shared-ux-chrome-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-chrome-navigation plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-chrome-navigation'] --- import kbnSharedUxChromeNavigationObj from './kbn_shared_ux_chrome_navigation.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_error_boundary.mdx b/api_docs/kbn_shared_ux_error_boundary.mdx index 89469c92b08e1..2dc08494e8c79 100644 --- a/api_docs/kbn_shared_ux_error_boundary.mdx +++ b/api_docs/kbn_shared_ux_error_boundary.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-error-boundary title: "@kbn/shared-ux-error-boundary" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-error-boundary plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-error-boundary'] --- import kbnSharedUxErrorBoundaryObj from './kbn_shared_ux_error_boundary.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_context.mdx b/api_docs/kbn_shared_ux_file_context.mdx index 31f1c910af3c2..bb70deb67014b 100644 --- a/api_docs/kbn_shared_ux_file_context.mdx +++ b/api_docs/kbn_shared_ux_file_context.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-context title: "@kbn/shared-ux-file-context" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-context plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-context'] --- import kbnSharedUxFileContextObj from './kbn_shared_ux_file_context.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image.mdx b/api_docs/kbn_shared_ux_file_image.mdx index 3f758f564eb62..aa6dd14ac8c8b 100644 --- a/api_docs/kbn_shared_ux_file_image.mdx +++ b/api_docs/kbn_shared_ux_file_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image title: "@kbn/shared-ux-file-image" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image'] --- import kbnSharedUxFileImageObj from './kbn_shared_ux_file_image.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image_mocks.mdx b/api_docs/kbn_shared_ux_file_image_mocks.mdx index ba0cc5c2bc248..e57de7336fe84 100644 --- a/api_docs/kbn_shared_ux_file_image_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_image_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image-mocks title: "@kbn/shared-ux-file-image-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image-mocks'] --- import kbnSharedUxFileImageMocksObj from './kbn_shared_ux_file_image_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_mocks.mdx b/api_docs/kbn_shared_ux_file_mocks.mdx index 4fc51321e30c3..0c098cb8a8b96 100644 --- a/api_docs/kbn_shared_ux_file_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-mocks title: "@kbn/shared-ux-file-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-mocks'] --- import kbnSharedUxFileMocksObj from './kbn_shared_ux_file_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_picker.mdx b/api_docs/kbn_shared_ux_file_picker.mdx index 48d2a6e7cb3ca..a8849a571deb1 100644 --- a/api_docs/kbn_shared_ux_file_picker.mdx +++ b/api_docs/kbn_shared_ux_file_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-picker title: "@kbn/shared-ux-file-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-picker plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-picker'] --- import kbnSharedUxFilePickerObj from './kbn_shared_ux_file_picker.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_types.mdx b/api_docs/kbn_shared_ux_file_types.mdx index 17eb11e2c05fd..119b6b33331ad 100644 --- a/api_docs/kbn_shared_ux_file_types.mdx +++ b/api_docs/kbn_shared_ux_file_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-types title: "@kbn/shared-ux-file-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-types plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-types'] --- import kbnSharedUxFileTypesObj from './kbn_shared_ux_file_types.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_upload.mdx b/api_docs/kbn_shared_ux_file_upload.mdx index e73628fd9226d..48faf7f54088d 100644 --- a/api_docs/kbn_shared_ux_file_upload.mdx +++ b/api_docs/kbn_shared_ux_file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-upload title: "@kbn/shared-ux-file-upload" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-upload plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-upload'] --- import kbnSharedUxFileUploadObj from './kbn_shared_ux_file_upload.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_util.mdx b/api_docs/kbn_shared_ux_file_util.mdx index 79a941f92f584..77f99ae11af77 100644 --- a/api_docs/kbn_shared_ux_file_util.mdx +++ b/api_docs/kbn_shared_ux_file_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-util title: "@kbn/shared-ux-file-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-util plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-util'] --- import kbnSharedUxFileUtilObj from './kbn_shared_ux_file_util.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app.mdx b/api_docs/kbn_shared_ux_link_redirect_app.mdx index fa5ea1621dbe3..1cff93697fa9e 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app title: "@kbn/shared-ux-link-redirect-app" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app'] --- import kbnSharedUxLinkRedirectAppObj from './kbn_shared_ux_link_redirect_app.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 8fbe61ee4f3c4..6ba399fa3223e 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown.mdx b/api_docs/kbn_shared_ux_markdown.mdx index 84404d0a80a16..479e3a6359d8f 100644 --- a/api_docs/kbn_shared_ux_markdown.mdx +++ b/api_docs/kbn_shared_ux_markdown.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown title: "@kbn/shared-ux-markdown" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown'] --- import kbnSharedUxMarkdownObj from './kbn_shared_ux_markdown.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown_mocks.mdx b/api_docs/kbn_shared_ux_markdown_mocks.mdx index 62252f73ef8d0..bc577f62e1f87 100644 --- a/api_docs/kbn_shared_ux_markdown_mocks.mdx +++ b/api_docs/kbn_shared_ux_markdown_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown-mocks title: "@kbn/shared-ux-markdown-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown-mocks'] --- import kbnSharedUxMarkdownMocksObj from './kbn_shared_ux_markdown_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index 1310f572ceba7..a4f2327db9240 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index 923b2f3a66c9c..b3df36452f3b5 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index dc3612df6112b..7dc99348dc028 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index 8397786153a23..ac83e8dedf20e 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index 5937b1044fd07..90bb4c4474e81 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index 8d52acbf5040a..f7f6efaac9437 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index 10f3af8516bf3..3c3f1ca3b3dad 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index e5db6fc34d669..1c726ef14781d 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index dcfccbce574b2..89d469eaf08ab 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index 587ff6dc91d48..2912378d0a9bd 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index 43a0b60448441..009684e736646 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index a51e7e98159cf..0c32294a781a5 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index b9016aa9ffd22..4ef537cac3ada 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_not_found.mdx b/api_docs/kbn_shared_ux_prompt_not_found.mdx index 2fe07d9596a2c..9f8b2bf46816a 100644 --- a/api_docs/kbn_shared_ux_prompt_not_found.mdx +++ b/api_docs/kbn_shared_ux_prompt_not_found.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-not-found title: "@kbn/shared-ux-prompt-not-found" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-not-found plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-not-found'] --- import kbnSharedUxPromptNotFoundObj from './kbn_shared_ux_prompt_not_found.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index 07dc34a2ace38..1b50cab3cca41 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index 2d0f0fa55731e..7c755cb640f59 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index 0ef6471b360e5..7fab8aa2e6064 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index 062540743ab25..6e1619408dad3 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_tabbed_modal.mdx b/api_docs/kbn_shared_ux_tabbed_modal.mdx index 7ddf41f54776b..2987d9406865f 100644 --- a/api_docs/kbn_shared_ux_tabbed_modal.mdx +++ b/api_docs/kbn_shared_ux_tabbed_modal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-tabbed-modal title: "@kbn/shared-ux-tabbed-modal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-tabbed-modal plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-tabbed-modal'] --- import kbnSharedUxTabbedModalObj from './kbn_shared_ux_tabbed_modal.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index de4958f4af561..e777b94073328 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_slo_schema.mdx b/api_docs/kbn_slo_schema.mdx index 2406d3d260774..3c980d324b5ac 100644 --- a/api_docs/kbn_slo_schema.mdx +++ b/api_docs/kbn_slo_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-slo-schema title: "@kbn/slo-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/slo-schema plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/slo-schema'] --- import kbnSloSchemaObj from './kbn_slo_schema.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index 7f19c0069ed9a..e485a10146a5e 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_predicates.mdx b/api_docs/kbn_sort_predicates.mdx index 538c604ff6ad0..0f45ffb67da44 100644 --- a/api_docs/kbn_sort_predicates.mdx +++ b/api_docs/kbn_sort_predicates.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-predicates title: "@kbn/sort-predicates" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-predicates plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-predicates'] --- import kbnSortPredicatesObj from './kbn_sort_predicates.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index c630dda04f3a4..b1a0a81838f61 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index a42f68884d39a..16d85d7b597ac 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index 3de74afe59f63..4a2704f9ce58d 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index f06c67f6ca5b9..0617f321c4d75 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index b042d70c3711d..1c7879d687ec9 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_eui_helpers.mdx b/api_docs/kbn_test_eui_helpers.mdx index ec5062684c97b..ee8f509a15512 100644 --- a/api_docs/kbn_test_eui_helpers.mdx +++ b/api_docs/kbn_test_eui_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-eui-helpers title: "@kbn/test-eui-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-eui-helpers plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-eui-helpers'] --- import kbnTestEuiHelpersObj from './kbn_test_eui_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index 815901b8c623b..4d2b4a7aa3bcc 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index 3960bce2ae1cb..065f375399218 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_text_based_editor.mdx b/api_docs/kbn_text_based_editor.mdx index 0617ce75c7c52..a72b775e0521f 100644 --- a/api_docs/kbn_text_based_editor.mdx +++ b/api_docs/kbn_text_based_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-text-based-editor title: "@kbn/text-based-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/text-based-editor plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/text-based-editor'] --- import kbnTextBasedEditorObj from './kbn_text_based_editor.devdocs.json'; diff --git a/api_docs/kbn_timerange.mdx b/api_docs/kbn_timerange.mdx index 417fde8807576..658f724d84dbf 100644 --- a/api_docs/kbn_timerange.mdx +++ b/api_docs/kbn_timerange.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-timerange title: "@kbn/timerange" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/timerange plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/timerange'] --- import kbnTimerangeObj from './kbn_timerange.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 80fdf65e0898c..58e0db94145fa 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_triggers_actions_ui_types.mdx b/api_docs/kbn_triggers_actions_ui_types.mdx index 7f5ff2cc2265d..bbb0b8f2a2024 100644 --- a/api_docs/kbn_triggers_actions_ui_types.mdx +++ b/api_docs/kbn_triggers_actions_ui_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-triggers-actions-ui-types title: "@kbn/triggers-actions-ui-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/triggers-actions-ui-types plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/triggers-actions-ui-types'] --- import kbnTriggersActionsUiTypesObj from './kbn_triggers_actions_ui_types.devdocs.json'; diff --git a/api_docs/kbn_try_in_console.mdx b/api_docs/kbn_try_in_console.mdx index fd218b38a8bb7..fb0c895730ed8 100644 --- a/api_docs/kbn_try_in_console.mdx +++ b/api_docs/kbn_try_in_console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-try-in-console title: "@kbn/try-in-console" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/try-in-console plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/try-in-console'] --- import kbnTryInConsoleObj from './kbn_try_in_console.devdocs.json'; diff --git a/api_docs/kbn_ts_projects.mdx b/api_docs/kbn_ts_projects.mdx index d21d48abc5440..be3b688d70733 100644 --- a/api_docs/kbn_ts_projects.mdx +++ b/api_docs/kbn_ts_projects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ts-projects title: "@kbn/ts-projects" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ts-projects plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ts-projects'] --- import kbnTsProjectsObj from './kbn_ts_projects.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index e2c7d8f130913..9164dea77561d 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_actions_browser.mdx b/api_docs/kbn_ui_actions_browser.mdx index de8bd6dfa9b8f..eb03d2bc62386 100644 --- a/api_docs/kbn_ui_actions_browser.mdx +++ b/api_docs/kbn_ui_actions_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-actions-browser title: "@kbn/ui-actions-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-actions-browser plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-actions-browser'] --- import kbnUiActionsBrowserObj from './kbn_ui_actions_browser.devdocs.json'; diff --git a/api_docs/kbn_ui_shared_deps_src.mdx b/api_docs/kbn_ui_shared_deps_src.mdx index 1b198515e6659..eb1bef6033ab4 100644 --- a/api_docs/kbn_ui_shared_deps_src.mdx +++ b/api_docs/kbn_ui_shared_deps_src.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-shared-deps-src title: "@kbn/ui-shared-deps-src" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-shared-deps-src plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-shared-deps-src'] --- import kbnUiSharedDepsSrcObj from './kbn_ui_shared_deps_src.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index f87b0a967e737..c704533a9df98 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_unified_data_table.mdx b/api_docs/kbn_unified_data_table.mdx index 45e2d08bc66b5..c6e46db320d70 100644 --- a/api_docs/kbn_unified_data_table.mdx +++ b/api_docs/kbn_unified_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-data-table title: "@kbn/unified-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-data-table plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-data-table'] --- import kbnUnifiedDataTableObj from './kbn_unified_data_table.devdocs.json'; diff --git a/api_docs/kbn_unified_doc_viewer.mdx b/api_docs/kbn_unified_doc_viewer.mdx index 6d36e8b656dc3..59cda687f8cbb 100644 --- a/api_docs/kbn_unified_doc_viewer.mdx +++ b/api_docs/kbn_unified_doc_viewer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-doc-viewer title: "@kbn/unified-doc-viewer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-doc-viewer plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-doc-viewer'] --- import kbnUnifiedDocViewerObj from './kbn_unified_doc_viewer.devdocs.json'; diff --git a/api_docs/kbn_unified_field_list.mdx b/api_docs/kbn_unified_field_list.mdx index e6c88a3d4d217..c55282399b272 100644 --- a/api_docs/kbn_unified_field_list.mdx +++ b/api_docs/kbn_unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-field-list title: "@kbn/unified-field-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-field-list plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-field-list'] --- import kbnUnifiedFieldListObj from './kbn_unified_field_list.devdocs.json'; diff --git a/api_docs/kbn_unsaved_changes_badge.mdx b/api_docs/kbn_unsaved_changes_badge.mdx index 718c1b7569c9b..4dfd80d408092 100644 --- a/api_docs/kbn_unsaved_changes_badge.mdx +++ b/api_docs/kbn_unsaved_changes_badge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unsaved-changes-badge title: "@kbn/unsaved-changes-badge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unsaved-changes-badge plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unsaved-changes-badge'] --- import kbnUnsavedChangesBadgeObj from './kbn_unsaved_changes_badge.devdocs.json'; diff --git a/api_docs/kbn_unsaved_changes_prompt.mdx b/api_docs/kbn_unsaved_changes_prompt.mdx index 02fe85f1544ba..9450be4f2595e 100644 --- a/api_docs/kbn_unsaved_changes_prompt.mdx +++ b/api_docs/kbn_unsaved_changes_prompt.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unsaved-changes-prompt title: "@kbn/unsaved-changes-prompt" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unsaved-changes-prompt plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unsaved-changes-prompt'] --- import kbnUnsavedChangesPromptObj from './kbn_unsaved_changes_prompt.devdocs.json'; diff --git a/api_docs/kbn_use_tracked_promise.mdx b/api_docs/kbn_use_tracked_promise.mdx index 9414aa5ddc862..b95523811f1ca 100644 --- a/api_docs/kbn_use_tracked_promise.mdx +++ b/api_docs/kbn_use_tracked_promise.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-use-tracked-promise title: "@kbn/use-tracked-promise" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/use-tracked-promise plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/use-tracked-promise'] --- import kbnUseTrackedPromiseObj from './kbn_use_tracked_promise.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index a01a034244376..e0292a4700f38 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index c0dcacd69cba2..b236c38cd2090 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index 2a5ea20b6a311..3198415f11a50 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index d492f2bbbf6f3..805c4d2b13fdd 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_visualization_ui_components.mdx b/api_docs/kbn_visualization_ui_components.mdx index 5c5924b07a2d2..be730b28d1f2e 100644 --- a/api_docs/kbn_visualization_ui_components.mdx +++ b/api_docs/kbn_visualization_ui_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-visualization-ui-components title: "@kbn/visualization-ui-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/visualization-ui-components plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-ui-components'] --- import kbnVisualizationUiComponentsObj from './kbn_visualization_ui_components.devdocs.json'; diff --git a/api_docs/kbn_visualization_utils.mdx b/api_docs/kbn_visualization_utils.mdx index f686c0d763291..214ea1f50d745 100644 --- a/api_docs/kbn_visualization_utils.mdx +++ b/api_docs/kbn_visualization_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-visualization-utils title: "@kbn/visualization-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/visualization-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-utils'] --- import kbnVisualizationUtilsObj from './kbn_visualization_utils.devdocs.json'; diff --git a/api_docs/kbn_xstate_utils.mdx b/api_docs/kbn_xstate_utils.mdx index de0954d3f3f23..d2f03ffd1d84e 100644 --- a/api_docs/kbn_xstate_utils.mdx +++ b/api_docs/kbn_xstate_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-xstate-utils title: "@kbn/xstate-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/xstate-utils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/xstate-utils'] --- import kbnXstateUtilsObj from './kbn_xstate_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index ade55986cf2db..8bb01ba87a572 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kbn_zod_helpers.mdx b/api_docs/kbn_zod_helpers.mdx index 7fcba26eb3295..73e100a798b6c 100644 --- a/api_docs/kbn_zod_helpers.mdx +++ b/api_docs/kbn_zod_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-zod-helpers title: "@kbn/zod-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/zod-helpers plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/zod-helpers'] --- import kbnZodHelpersObj from './kbn_zod_helpers.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 87f2ed48a240b..34389774728a9 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index b913321e27dbc..4a58da6924bd5 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index c73d784f22c48..bbe1d21dc369e 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index 351f00f87cba0..ca65ac8aee662 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 84594ef2687a0..ebf11366e609e 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 866485eac5815..b177637d2c842 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index 3568b10d76a09..ab5f0bd64b8e2 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index 94264323f48f2..9b1d2bb043d25 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/links.mdx b/api_docs/links.mdx index 228de45ba3c27..8c1d3e9a37006 100644 --- a/api_docs/links.mdx +++ b/api_docs/links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/links title: "links" image: https://source.unsplash.com/400x175/?github description: API docs for the links plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'links'] --- import linksObj from './links.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index f0d4df4abd2ec..e0d216280fdeb 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/logs_data_access.mdx b/api_docs/logs_data_access.mdx index a42904bbdfd74..f39c8cab562ac 100644 --- a/api_docs/logs_data_access.mdx +++ b/api_docs/logs_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsDataAccess title: "logsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the logsDataAccess plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsDataAccess'] --- import logsDataAccessObj from './logs_data_access.devdocs.json'; diff --git a/api_docs/logs_explorer.mdx b/api_docs/logs_explorer.mdx index 6b8fba15ce551..c5e7fe9b11024 100644 --- a/api_docs/logs_explorer.mdx +++ b/api_docs/logs_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsExplorer title: "logsExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the logsExplorer plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsExplorer'] --- import logsExplorerObj from './logs_explorer.devdocs.json'; diff --git a/api_docs/logs_shared.mdx b/api_docs/logs_shared.mdx index b46e634bf3577..e97c57754dcaa 100644 --- a/api_docs/logs_shared.mdx +++ b/api_docs/logs_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsShared title: "logsShared" image: https://source.unsplash.com/400x175/?github description: API docs for the logsShared plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsShared'] --- import logsSharedObj from './logs_shared.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 4bf738fe5acaa..e1cf3a9b77f33 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 642c5a13a95b7..bbd1be2717c12 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index f6dbf835b7950..ba7751aec9780 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/metrics_data_access.mdx b/api_docs/metrics_data_access.mdx index f2e47393b6639..ccf4dae284259 100644 --- a/api_docs/metrics_data_access.mdx +++ b/api_docs/metrics_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/metricsDataAccess title: "metricsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the metricsDataAccess plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'metricsDataAccess'] --- import metricsDataAccessObj from './metrics_data_access.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index 51de1743ec845..d91dedaf0c778 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/mock_idp_plugin.mdx b/api_docs/mock_idp_plugin.mdx index 507d39f85f789..9db7f79b30787 100644 --- a/api_docs/mock_idp_plugin.mdx +++ b/api_docs/mock_idp_plugin.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mockIdpPlugin title: "mockIdpPlugin" image: https://source.unsplash.com/400x175/?github description: API docs for the mockIdpPlugin plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mockIdpPlugin'] --- import mockIdpPluginObj from './mock_idp_plugin.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 2b464c6063507..6e42e5733af3c 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index 9a84bcbb4cf79..39d1c1e4cd9b5 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index 1e43c314ceb7d..93a6fa9227c36 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index dd5a3637c8a2d..ecff332279934 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/no_data_page.mdx b/api_docs/no_data_page.mdx index de8609330a248..7cb8f468df8d4 100644 --- a/api_docs/no_data_page.mdx +++ b/api_docs/no_data_page.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/noDataPage title: "noDataPage" image: https://source.unsplash.com/400x175/?github description: API docs for the noDataPage plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'noDataPage'] --- import noDataPageObj from './no_data_page.devdocs.json'; diff --git a/api_docs/notifications.mdx b/api_docs/notifications.mdx index 6ec254e651be1..d08801c997816 100644 --- a/api_docs/notifications.mdx +++ b/api_docs/notifications.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/notifications title: "notifications" image: https://source.unsplash.com/400x175/?github description: API docs for the notifications plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'notifications'] --- import notificationsObj from './notifications.devdocs.json'; diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index e60c5ad48da4b..f7348d8a14f9a 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/observability_a_i_assistant.mdx b/api_docs/observability_a_i_assistant.mdx index 8532081a15967..c217894f55ed1 100644 --- a/api_docs/observability_a_i_assistant.mdx +++ b/api_docs/observability_a_i_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAIAssistant title: "observabilityAIAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAIAssistant plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistant'] --- import observabilityAIAssistantObj from './observability_a_i_assistant.devdocs.json'; diff --git a/api_docs/observability_a_i_assistant_app.mdx b/api_docs/observability_a_i_assistant_app.mdx index fdcdbaf5bca86..61b34dccc57f6 100644 --- a/api_docs/observability_a_i_assistant_app.mdx +++ b/api_docs/observability_a_i_assistant_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAIAssistantApp title: "observabilityAIAssistantApp" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAIAssistantApp plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistantApp'] --- import observabilityAIAssistantAppObj from './observability_a_i_assistant_app.devdocs.json'; diff --git a/api_docs/observability_ai_assistant_management.mdx b/api_docs/observability_ai_assistant_management.mdx index 139f2c05c4669..81baaa1f3c786 100644 --- a/api_docs/observability_ai_assistant_management.mdx +++ b/api_docs/observability_ai_assistant_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAiAssistantManagement title: "observabilityAiAssistantManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAiAssistantManagement plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAiAssistantManagement'] --- import observabilityAiAssistantManagementObj from './observability_ai_assistant_management.devdocs.json'; diff --git a/api_docs/observability_logs_explorer.mdx b/api_docs/observability_logs_explorer.mdx index 175d36ddfd4df..ff7748288ce12 100644 --- a/api_docs/observability_logs_explorer.mdx +++ b/api_docs/observability_logs_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityLogsExplorer title: "observabilityLogsExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityLogsExplorer plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityLogsExplorer'] --- import observabilityLogsExplorerObj from './observability_logs_explorer.devdocs.json'; diff --git a/api_docs/observability_onboarding.mdx b/api_docs/observability_onboarding.mdx index 2b6b96f32e079..6b70fb1fc1a6b 100644 --- a/api_docs/observability_onboarding.mdx +++ b/api_docs/observability_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityOnboarding title: "observabilityOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityOnboarding plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityOnboarding'] --- import observabilityOnboardingObj from './observability_onboarding.devdocs.json'; diff --git a/api_docs/observability_shared.mdx b/api_docs/observability_shared.mdx index aaee7258e2aa9..3661b3f8f07d0 100644 --- a/api_docs/observability_shared.mdx +++ b/api_docs/observability_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityShared title: "observabilityShared" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityShared plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityShared'] --- import observabilitySharedObj from './observability_shared.devdocs.json'; diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index 4d12385d01663..d5293d70426ac 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/painless_lab.mdx b/api_docs/painless_lab.mdx index 32c34c2a2f691..a1ba7b8a32f35 100644 --- a/api_docs/painless_lab.mdx +++ b/api_docs/painless_lab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/painlessLab title: "painlessLab" image: https://source.unsplash.com/400x175/?github description: API docs for the painlessLab plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'painlessLab'] --- import painlessLabObj from './painless_lab.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index 210ddae50410a..b326bfc17c2e4 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -21,7 +21,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 49602 | 238 | 37843 | 1887 | +| 49625 | 238 | 37857 | 1888 | ## Plugin Directory @@ -176,7 +176,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | Plugin hosting shared features for connectors | 19 | 0 | 19 | 3 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 18 | 0 | 10 | 0 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 10 | 0 | 6 | 1 | -| | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | Plugin to provide access to and rendering of python notebooks for use in the persistent developer console. | 6 | 0 | 6 | 0 | +| | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | Plugin to provide access to and rendering of python notebooks for use in the persistent developer console. | 8 | 0 | 8 | 1 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 18 | 0 | 10 | 1 | | searchprofiler | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 0 | 0 | 0 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user. | 411 | 0 | 204 | 1 | @@ -272,7 +272,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 217 | 0 | 180 | 9 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 79 | 0 | 50 | 9 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 24 | 0 | 24 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 146 | 2 | 142 | 20 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 149 | 2 | 143 | 20 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 10 | 0 | 8 | 4 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 8 | 0 | 8 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 3 | 0 | 3 | 0 | @@ -303,7 +303,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 6 | 0 | 6 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 207 | 0 | 100 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 3 | 0 | 3 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 4 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 6 | 0 | 1 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 8 | 0 | 8 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 5 | 0 | 5 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 4 | 0 | 4 | 0 | @@ -488,7 +488,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 13 | 0 | 5 | 0 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 35 | 0 | 34 | 0 | | | [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/security-generative-ai) | - | 166 | 0 | 139 | 9 | -| | [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/security-generative-ai) | - | 333 | 0 | 309 | 0 | +| | [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/security-generative-ai) | - | 349 | 0 | 323 | 0 | | | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 20 | 0 | 20 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 52 | 0 | 37 | 7 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 32 | 0 | 19 | 1 | diff --git a/api_docs/presentation_panel.mdx b/api_docs/presentation_panel.mdx index a3cb0410b2f54..c565b4a9f1b0c 100644 --- a/api_docs/presentation_panel.mdx +++ b/api_docs/presentation_panel.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationPanel title: "presentationPanel" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationPanel plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationPanel'] --- import presentationPanelObj from './presentation_panel.devdocs.json'; diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 98f5f54475ad8..b5d96c83c4709 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index 529b2ea6329a1..64683be34d421 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/profiling_data_access.mdx b/api_docs/profiling_data_access.mdx index 37e4a9556348a..b0c17a2b5b3ff 100644 --- a/api_docs/profiling_data_access.mdx +++ b/api_docs/profiling_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profilingDataAccess title: "profilingDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the profilingDataAccess plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profilingDataAccess'] --- import profilingDataAccessObj from './profiling_data_access.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 67c1fba9a3abc..991dba717c59e 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index 14bc69bab8977..29873d1c17699 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index 6eec93ed454fe..544426846e7a7 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index c198a170c584c..4915b2c0318a6 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index 370dea3505051..5c6cc2e90f977 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 0cab1b1bc831a..924252b2f4348 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index 05b325aba1952..463865509d373 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 30d315f4a96be..af36d0979c3d7 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index 1d5ea5fc5f2ca..344732767ff88 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index e8ae9f00b82a7..100c3f6a0e797 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index d10efc4c27372..bf731b7962377 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index a5b414e22f07b..51a9bda5eadae 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index 42e9f914559b4..9b8dc824ad355 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/search_connectors.mdx b/api_docs/search_connectors.mdx index f5d4c92fbf63b..542c330989c12 100644 --- a/api_docs/search_connectors.mdx +++ b/api_docs/search_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchConnectors title: "searchConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the searchConnectors plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchConnectors'] --- import searchConnectorsObj from './search_connectors.devdocs.json'; diff --git a/api_docs/search_homepage.mdx b/api_docs/search_homepage.mdx index e873770e1fec9..a1a70cd36de5d 100644 --- a/api_docs/search_homepage.mdx +++ b/api_docs/search_homepage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchHomepage title: "searchHomepage" image: https://source.unsplash.com/400x175/?github description: API docs for the searchHomepage plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchHomepage'] --- import searchHomepageObj from './search_homepage.devdocs.json'; diff --git a/api_docs/search_inference_endpoints.mdx b/api_docs/search_inference_endpoints.mdx index 607ce948ed198..3bf7dbc0ee318 100644 --- a/api_docs/search_inference_endpoints.mdx +++ b/api_docs/search_inference_endpoints.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchInferenceEndpoints title: "searchInferenceEndpoints" image: https://source.unsplash.com/400x175/?github description: API docs for the searchInferenceEndpoints plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchInferenceEndpoints'] --- import searchInferenceEndpointsObj from './search_inference_endpoints.devdocs.json'; diff --git a/api_docs/search_notebooks.devdocs.json b/api_docs/search_notebooks.devdocs.json index 88ebf531e43e9..ac5a01c9ceab0 100644 --- a/api_docs/search_notebooks.devdocs.json +++ b/api_docs/search_notebooks.devdocs.json @@ -31,7 +31,42 @@ "path": "x-pack/plugins/search_notebooks/public/types.ts", "deprecated": false, "trackAdoption": false, - "children": [], + "children": [ + { + "parentPluginId": "searchNotebooks", + "id": "def-public.SearchNotebooksPluginStart.setNotebookList", + "type": "Function", + "tags": [], + "label": "setNotebookList", + "description": [], + "signature": [ + "(value: ", + "NotebookListValue", + ") => void" + ], + "path": "x-pack/plugins/search_notebooks/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "searchNotebooks", + "id": "def-public.SearchNotebooksPluginStart.setNotebookList.$1", + "type": "CompoundType", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "NotebookListValue" + ], + "path": "x-pack/plugins/search_notebooks/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + } + ], "lifecycle": "start", "initialIsOpen": true } diff --git a/api_docs/search_notebooks.mdx b/api_docs/search_notebooks.mdx index c4533fcb3466d..90da558e7ee5e 100644 --- a/api_docs/search_notebooks.mdx +++ b/api_docs/search_notebooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchNotebooks title: "searchNotebooks" image: https://source.unsplash.com/400x175/?github description: API docs for the searchNotebooks plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchNotebooks'] --- import searchNotebooksObj from './search_notebooks.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-ki | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 6 | 0 | 6 | 0 | +| 8 | 0 | 8 | 1 | ## Client diff --git a/api_docs/search_playground.mdx b/api_docs/search_playground.mdx index f721339bac5c7..a4218a7a013ab 100644 --- a/api_docs/search_playground.mdx +++ b/api_docs/search_playground.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchPlayground title: "searchPlayground" image: https://source.unsplash.com/400x175/?github description: API docs for the searchPlayground plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchPlayground'] --- import searchPlaygroundObj from './search_playground.devdocs.json'; diff --git a/api_docs/security.devdocs.json b/api_docs/security.devdocs.json index 35017305bdb8a..e5a0c7debea31 100644 --- a/api_docs/security.devdocs.json +++ b/api_docs/security.devdocs.json @@ -5442,22 +5442,6 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/rules_client_factory.ts" }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/rules_client_factory.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/rules_client_factory.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/rules_settings_client_factory.ts" - }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/maintenance_window_client_factory.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/invalidate_pending_api_keys/task.ts" @@ -5490,10 +5474,6 @@ "plugin": "fleet", "path": "x-pack/plugins/fleet/server/routes/setup/handlers.ts" }, - { - "plugin": "cloudDefend", - "path": "x-pack/plugins/cloud_defend/server/routes/setup_routes.ts" - }, { "plugin": "cloudSecurityPosture", "path": "x-pack/plugins/cloud_security_posture/server/routes/setup_routes.ts" diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 652f77ed50717..2745c705d0d6e 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.devdocs.json b/api_docs/security_solution.devdocs.json index 053f30a5fd66f..292b7a1d793bc 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -485,7 +485,7 @@ "\nExperimental flag needed to enable the link" ], "signature": [ - "\"assistantKnowledgeBaseByDefault\" | \"assistantModelEvaluation\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"responseActionsSentinelOneV2Enabled\" | \"responseActionsSentinelOneGetFileEnabled\" | \"responseActionsCrowdstrikeManualHostIsolationEnabled\" | \"responseActionScanEnabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutDisabled\" | \"securitySolutionNotesEnabled\" | \"entityAlertPreviewEnabled\" | \"newUserDetailsFlyoutManagedUser\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | \"alertSuppressionForEsqlRuleEnabled\" | \"riskEnginePrivilegesRouteEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"crowdstrikeDataInAnalyzerEnabled\" | \"jamfDataInAnalyzerEnabled\" | \"timelineEsqlTabDisabled\" | \"unifiedComponentsInTimelineEnabled\" | \"analyzerDatePickersAndSourcererDisabled\" | \"prebuiltRulesCustomizationEnabled\" | \"malwareOnWriteScanOptionAvailable\" | \"unifiedManifestEnabled\" | \"aiAssistantFlyoutMode\" | \"valueListItemsModalEnabled\" | \"bulkCustomHighlightedFieldsEnabled\" | \"manualRuleRunEnabled\" | \"filterProcessDescendantsForEventFiltersEnabled\" | undefined" + "\"assistantKnowledgeBaseByDefault\" | \"assistantModelEvaluation\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"responseActionsSentinelOneV2Enabled\" | \"responseActionsSentinelOneGetFileEnabled\" | \"responseActionsCrowdstrikeManualHostIsolationEnabled\" | \"responseActionScanEnabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutDisabled\" | \"securitySolutionNotesEnabled\" | \"entityAlertPreviewEnabled\" | \"newUserDetailsFlyoutManagedUser\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"AIAssistantOnRuleCreationFormEnabled\" | \"disableTimelineSaveTour\" | \"alertSuppressionForEsqlRuleEnabled\" | \"riskEnginePrivilegesRouteEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"crowdstrikeDataInAnalyzerEnabled\" | \"jamfDataInAnalyzerEnabled\" | \"timelineEsqlTabDisabled\" | \"unifiedComponentsInTimelineEnabled\" | \"analyzerDatePickersAndSourcererDisabled\" | \"prebuiltRulesCustomizationEnabled\" | \"malwareOnWriteScanOptionAvailable\" | \"unifiedManifestEnabled\" | \"aiAssistantFlyoutMode\" | \"valueListItemsModalEnabled\" | \"bulkCustomHighlightedFieldsEnabled\" | \"manualRuleRunEnabled\" | \"filterProcessDescendantsForEventFiltersEnabled\" | undefined" ], "path": "x-pack/plugins/security_solution/public/common/links/types.ts", "deprecated": false, @@ -565,7 +565,7 @@ "\nExperimental flag needed to disable the link. Opposite of experimentalKey" ], "signature": [ - "\"assistantKnowledgeBaseByDefault\" | \"assistantModelEvaluation\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"responseActionsSentinelOneV2Enabled\" | \"responseActionsSentinelOneGetFileEnabled\" | \"responseActionsCrowdstrikeManualHostIsolationEnabled\" | \"responseActionScanEnabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutDisabled\" | \"securitySolutionNotesEnabled\" | \"entityAlertPreviewEnabled\" | \"newUserDetailsFlyoutManagedUser\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | \"alertSuppressionForEsqlRuleEnabled\" | \"riskEnginePrivilegesRouteEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"crowdstrikeDataInAnalyzerEnabled\" | \"jamfDataInAnalyzerEnabled\" | \"timelineEsqlTabDisabled\" | \"unifiedComponentsInTimelineEnabled\" | \"analyzerDatePickersAndSourcererDisabled\" | \"prebuiltRulesCustomizationEnabled\" | \"malwareOnWriteScanOptionAvailable\" | \"unifiedManifestEnabled\" | \"aiAssistantFlyoutMode\" | \"valueListItemsModalEnabled\" | \"bulkCustomHighlightedFieldsEnabled\" | \"manualRuleRunEnabled\" | \"filterProcessDescendantsForEventFiltersEnabled\" | undefined" + "\"assistantKnowledgeBaseByDefault\" | \"assistantModelEvaluation\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"responseActionsSentinelOneV2Enabled\" | \"responseActionsSentinelOneGetFileEnabled\" | \"responseActionsCrowdstrikeManualHostIsolationEnabled\" | \"responseActionScanEnabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutDisabled\" | \"securitySolutionNotesEnabled\" | \"entityAlertPreviewEnabled\" | \"newUserDetailsFlyoutManagedUser\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"AIAssistantOnRuleCreationFormEnabled\" | \"disableTimelineSaveTour\" | \"alertSuppressionForEsqlRuleEnabled\" | \"riskEnginePrivilegesRouteEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"crowdstrikeDataInAnalyzerEnabled\" | \"jamfDataInAnalyzerEnabled\" | \"timelineEsqlTabDisabled\" | \"unifiedComponentsInTimelineEnabled\" | \"analyzerDatePickersAndSourcererDisabled\" | \"prebuiltRulesCustomizationEnabled\" | \"malwareOnWriteScanOptionAvailable\" | \"unifiedManifestEnabled\" | \"aiAssistantFlyoutMode\" | \"valueListItemsModalEnabled\" | \"bulkCustomHighlightedFieldsEnabled\" | \"manualRuleRunEnabled\" | \"filterProcessDescendantsForEventFiltersEnabled\" | undefined" ], "path": "x-pack/plugins/security_solution/public/common/links/types.ts", "deprecated": false, @@ -1964,7 +1964,7 @@ "label": "experimentalFeatures", "description": [], "signature": [ - "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutDisabled: boolean; readonly securitySolutionNotesEnabled: boolean; readonly entityAlertPreviewEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly alertSuppressionForEsqlRuleEnabled: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineEnabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly prebuiltRulesCustomizationEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly aiAssistantFlyoutMode: boolean; readonly valueListItemsModalEnabled: boolean; readonly bulkCustomHighlightedFieldsEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; }" + "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutDisabled: boolean; readonly securitySolutionNotesEnabled: boolean; readonly entityAlertPreviewEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly AIAssistantOnRuleCreationFormEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly alertSuppressionForEsqlRuleEnabled: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineEnabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly prebuiltRulesCustomizationEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly aiAssistantFlyoutMode: boolean; readonly valueListItemsModalEnabled: boolean; readonly bulkCustomHighlightedFieldsEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/public/types.ts", "deprecated": false, @@ -3071,7 +3071,7 @@ "\nThe security solution generic experimental features" ], "signature": [ - "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutDisabled: boolean; readonly securitySolutionNotesEnabled: boolean; readonly entityAlertPreviewEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly alertSuppressionForEsqlRuleEnabled: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineEnabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly prebuiltRulesCustomizationEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly aiAssistantFlyoutMode: boolean; readonly valueListItemsModalEnabled: boolean; readonly bulkCustomHighlightedFieldsEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; }" + "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutDisabled: boolean; readonly securitySolutionNotesEnabled: boolean; readonly entityAlertPreviewEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly AIAssistantOnRuleCreationFormEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly alertSuppressionForEsqlRuleEnabled: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineEnabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly prebuiltRulesCustomizationEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly aiAssistantFlyoutMode: boolean; readonly valueListItemsModalEnabled: boolean; readonly bulkCustomHighlightedFieldsEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/server/plugin_contract.ts", "deprecated": false, @@ -3247,7 +3247,7 @@ "label": "ExperimentalFeatures", "description": [], "signature": [ - "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutDisabled: boolean; readonly securitySolutionNotesEnabled: boolean; readonly entityAlertPreviewEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly alertSuppressionForEsqlRuleEnabled: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineEnabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly prebuiltRulesCustomizationEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly aiAssistantFlyoutMode: boolean; readonly valueListItemsModalEnabled: boolean; readonly bulkCustomHighlightedFieldsEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; }" + "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutDisabled: boolean; readonly securitySolutionNotesEnabled: boolean; readonly entityAlertPreviewEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly AIAssistantOnRuleCreationFormEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly alertSuppressionForEsqlRuleEnabled: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineEnabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly prebuiltRulesCustomizationEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly aiAssistantFlyoutMode: boolean; readonly valueListItemsModalEnabled: boolean; readonly bulkCustomHighlightedFieldsEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/common/experimental_features.ts", "deprecated": false, @@ -3313,7 +3313,7 @@ "\nA list of allowed values that can be used in `xpack.securitySolution.enableExperimental`.\nThis object is then used to validate and parse the value entered." ], "signature": [ - "{ readonly excludePoliciesInFilterEnabled: false; readonly kubernetesEnabled: true; readonly donutChartEmbeddablesEnabled: false; readonly previewTelemetryUrlEnabled: false; readonly extendedRuleExecutionLoggingEnabled: false; readonly socTrendsEnabled: false; readonly responseActionsEnabled: true; readonly endpointResponseActionsEnabled: true; readonly responseActionUploadEnabled: true; readonly automatedProcessActionsEnabled: true; readonly responseActionsSentinelOneV1Enabled: true; readonly responseActionsSentinelOneV2Enabled: true; readonly responseActionsSentinelOneGetFileEnabled: true; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: true; readonly responseActionScanEnabled: false; readonly alertsPageChartsEnabled: true; readonly alertTypeEnabled: false; readonly expandableFlyoutDisabled: false; readonly securitySolutionNotesEnabled: false; readonly entityAlertPreviewEnabled: false; readonly assistantModelEvaluation: false; readonly assistantKnowledgeBaseByDefault: false; readonly newUserDetailsFlyoutManagedUser: false; readonly riskScoringPersistence: true; readonly riskScoringRoutesEnabled: true; readonly esqlRulesDisabled: false; readonly protectionUpdatesEnabled: true; readonly disableTimelineSaveTour: false; readonly alertSuppressionForEsqlRuleEnabled: false; readonly riskEnginePrivilegesRouteEnabled: true; readonly sentinelOneDataInAnalyzerEnabled: true; readonly sentinelOneManualHostActionsEnabled: true; readonly crowdstrikeDataInAnalyzerEnabled: true; readonly jamfDataInAnalyzerEnabled: false; readonly timelineEsqlTabDisabled: false; readonly unifiedComponentsInTimelineEnabled: false; readonly analyzerDatePickersAndSourcererDisabled: false; readonly prebuiltRulesCustomizationEnabled: false; readonly malwareOnWriteScanOptionAvailable: true; readonly unifiedManifestEnabled: false; readonly aiAssistantFlyoutMode: true; readonly valueListItemsModalEnabled: true; readonly bulkCustomHighlightedFieldsEnabled: false; readonly manualRuleRunEnabled: false; readonly filterProcessDescendantsForEventFiltersEnabled: false; }" + "{ readonly excludePoliciesInFilterEnabled: false; readonly kubernetesEnabled: true; readonly donutChartEmbeddablesEnabled: false; readonly previewTelemetryUrlEnabled: false; readonly extendedRuleExecutionLoggingEnabled: false; readonly socTrendsEnabled: false; readonly responseActionsEnabled: true; readonly endpointResponseActionsEnabled: true; readonly responseActionUploadEnabled: true; readonly automatedProcessActionsEnabled: true; readonly responseActionsSentinelOneV1Enabled: true; readonly responseActionsSentinelOneV2Enabled: true; readonly responseActionsSentinelOneGetFileEnabled: true; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: true; readonly responseActionScanEnabled: false; readonly alertsPageChartsEnabled: true; readonly alertTypeEnabled: false; readonly expandableFlyoutDisabled: false; readonly securitySolutionNotesEnabled: false; readonly entityAlertPreviewEnabled: false; readonly assistantModelEvaluation: false; readonly assistantKnowledgeBaseByDefault: false; readonly newUserDetailsFlyoutManagedUser: false; readonly riskScoringPersistence: true; readonly riskScoringRoutesEnabled: true; readonly esqlRulesDisabled: false; readonly protectionUpdatesEnabled: true; readonly AIAssistantOnRuleCreationFormEnabled: false; readonly disableTimelineSaveTour: false; readonly alertSuppressionForEsqlRuleEnabled: false; readonly riskEnginePrivilegesRouteEnabled: true; readonly sentinelOneDataInAnalyzerEnabled: true; readonly sentinelOneManualHostActionsEnabled: true; readonly crowdstrikeDataInAnalyzerEnabled: true; readonly jamfDataInAnalyzerEnabled: false; readonly timelineEsqlTabDisabled: false; readonly unifiedComponentsInTimelineEnabled: false; readonly analyzerDatePickersAndSourcererDisabled: false; readonly prebuiltRulesCustomizationEnabled: false; readonly malwareOnWriteScanOptionAvailable: true; readonly unifiedManifestEnabled: true; readonly aiAssistantFlyoutMode: true; readonly valueListItemsModalEnabled: true; readonly bulkCustomHighlightedFieldsEnabled: false; readonly manualRuleRunEnabled: false; readonly filterProcessDescendantsForEventFiltersEnabled: false; }" ], "path": "x-pack/plugins/security_solution/common/experimental_features.ts", "deprecated": false, diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index ab4c6424e8e24..c787d3dd3ae1a 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/security_solution_ess.mdx b/api_docs/security_solution_ess.mdx index 3b6aa069055a5..939fb812406ad 100644 --- a/api_docs/security_solution_ess.mdx +++ b/api_docs/security_solution_ess.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionEss title: "securitySolutionEss" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionEss plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolutionEss'] --- import securitySolutionEssObj from './security_solution_ess.devdocs.json'; diff --git a/api_docs/security_solution_serverless.mdx b/api_docs/security_solution_serverless.mdx index a37423119a556..b36e0aaedb6d8 100644 --- a/api_docs/security_solution_serverless.mdx +++ b/api_docs/security_solution_serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionServerless title: "securitySolutionServerless" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionServerless plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolutionServerless'] --- import securitySolutionServerlessObj from './security_solution_serverless.devdocs.json'; diff --git a/api_docs/serverless.mdx b/api_docs/serverless.mdx index f67451db51ca8..59451b72aad34 100644 --- a/api_docs/serverless.mdx +++ b/api_docs/serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverless title: "serverless" image: https://source.unsplash.com/400x175/?github description: API docs for the serverless plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverless'] --- import serverlessObj from './serverless.devdocs.json'; diff --git a/api_docs/serverless_observability.mdx b/api_docs/serverless_observability.mdx index be29967c889fe..8720cbb138cb0 100644 --- a/api_docs/serverless_observability.mdx +++ b/api_docs/serverless_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessObservability title: "serverlessObservability" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessObservability plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessObservability'] --- import serverlessObservabilityObj from './serverless_observability.devdocs.json'; diff --git a/api_docs/serverless_search.mdx b/api_docs/serverless_search.mdx index c191541c27396..603ceb886e750 100644 --- a/api_docs/serverless_search.mdx +++ b/api_docs/serverless_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessSearch title: "serverlessSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessSearch plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessSearch'] --- import serverlessSearchObj from './serverless_search.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index a5e379035d677..7ab8f0d7331a0 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index 7722bfd2df82d..96d9aead69b30 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/slo.mdx b/api_docs/slo.mdx index 58b76396f7ca8..1ee5c0b32a77a 100644 --- a/api_docs/slo.mdx +++ b/api_docs/slo.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/slo title: "slo" image: https://source.unsplash.com/400x175/?github description: API docs for the slo plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'slo'] --- import sloObj from './slo.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 4ab0efa209631..95ae6e9fbabd1 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index d71d47471b295..9c465c1c8c376 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index 97220a485cde0..1573ee8d57e73 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index 15fe7114a82e0..dae0e36e80fec 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index 44fa9dad02866..0c579cb1362ca 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 1eea3dc772fde..4a5f59c70a638 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index 69db22173e167..a32e11943a810 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index e62824d2b2c86..42c2dd2aef7ce 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] --- import telemetryCollectionXpackObj from './telemetry_collection_xpack.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index 7d1f7da9801be..b9ecf1338bf48 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/text_based_languages.mdx b/api_docs/text_based_languages.mdx index 515a3980cc868..ed28d15be5534 100644 --- a/api_docs/text_based_languages.mdx +++ b/api_docs/text_based_languages.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/textBasedLanguages title: "textBasedLanguages" image: https://source.unsplash.com/400x175/?github description: API docs for the textBasedLanguages plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'textBasedLanguages'] --- import textBasedLanguagesObj from './text_based_languages.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index 00b42a6e147b3..d8c1651b89472 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.devdocs.json b/api_docs/timelines.devdocs.json index c978c041d986f..49dc7a9ed7b5a 100644 --- a/api_docs/timelines.devdocs.json +++ b/api_docs/timelines.devdocs.json @@ -1480,6 +1480,30 @@ "deprecated": true, "trackAdoption": false, "references": [ + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts" @@ -1520,30 +1544,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_summary.tsx" }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx" @@ -3935,6 +3935,34 @@ "deprecated": true, "trackAdoption": false, "references": [ + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/index.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/index.tsx" + }, + { + "plugin": "@kbn/securitysolution-data-table", + "path": "x-pack/packages/security-solution/data_table/components/data_table/index.tsx" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts" @@ -4083,34 +4111,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/containers/source/mock.ts" }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/index.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/index.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/index.tsx" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/sourcerer/store/model.ts" diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 33373dd4691e5..7156045c0436e 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index 7fee997d41f96..e933c13c2622b 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index ea2a7b62cf454..b8ec0273fd277 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index a6d12caec1ff2..dc85949f758df 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index 36bf49e420a2e..1712adee943c4 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_doc_viewer.mdx b/api_docs/unified_doc_viewer.mdx index 93ed273ad78ef..ff7731e38354b 100644 --- a/api_docs/unified_doc_viewer.mdx +++ b/api_docs/unified_doc_viewer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedDocViewer title: "unifiedDocViewer" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedDocViewer plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedDocViewer'] --- import unifiedDocViewerObj from './unified_doc_viewer.devdocs.json'; diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index 0df8fd8154544..71725a9feac0c 100644 --- a/api_docs/unified_histogram.mdx +++ b/api_docs/unified_histogram.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedHistogram title: "unifiedHistogram" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedHistogram plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedHistogram'] --- import unifiedHistogramObj from './unified_histogram.devdocs.json'; diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 6c5182fb708c1..791f2544a4ff4 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 991d7ac2fbe77..abfaeced9aff6 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/uptime.mdx b/api_docs/uptime.mdx index 78fd42c02de10..1f2730c092539 100644 --- a/api_docs/uptime.mdx +++ b/api_docs/uptime.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uptime title: "uptime" image: https://source.unsplash.com/400x175/?github description: API docs for the uptime plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uptime'] --- import uptimeObj from './uptime.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 42a5bab939cc2..b541f5df3c3ba 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index d35eb84ee8aad..3dec965bd703f 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index 4a34a45f7d0c0..66f09ca821f94 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index a20658de54123..87fc77f3cd8fd 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index f1130396dc376..8bb52f28b89c3 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index c0ef69081465d..d473e7e86c68e 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index 623d861fd148e..829f998543e44 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index 84ef6a8f4f54b..2054d85d13760 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index 38d3f4ded1122..31eb23a29d730 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index 2cf0b885ba83a..b12148b9cadb9 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index b3ba492de6aba..07cad724acd81 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 0f1e4931aa4fc..b9115d3caab61 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 63b20260c928c..5985d38baab23 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 84089238b5ff0..8c822b962314e 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2024-07-01 +date: 2024-07-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; From 0da7bbe3186d9dd9b5b53bb0d9994aa3fcb61349 Mon Sep 17 00:00:00 2001 From: Liam Thompson <32779855+leemthompo@users.noreply.github.com> Date: Tue, 2 Jul 2024 09:27:09 +0200 Subject: [PATCH 051/128] [ESQL] Update inline docs for 8.15 (#186835) Aggregations, operators, and commands still need to be added manually - `::` is the only one so far landing in 8.15.0 --------- Co-authored-by: Stratoula Kalafateli --- .../src/esql_documentation_sections.tsx | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/kbn-text-based-editor/src/esql_documentation_sections.tsx b/packages/kbn-text-based-editor/src/esql_documentation_sections.tsx index 080151acbb829..df32875a33665 100644 --- a/packages/kbn-text-based-editor/src/esql_documentation_sections.tsx +++ b/packages/kbn-text-based-editor/src/esql_documentation_sections.tsx @@ -4456,6 +4456,33 @@ The following boolean operators are supported: /> ), }, + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.castOperator', + { + defaultMessage: 'Cast (::)', + } + ), + description: ( + \` type conversion functions. + +Example: +\`\`\` +ROW ver = CONCAT(("0"::INT + 1)::STRING, ".2.3")::VERSION +\`\`\` + `, + description: + 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', + } + )} + /> + ), + }, { label: i18n.translate( 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.inOperator', From 1fa094b7ac9b90e321338770aad9fed028533840 Mon Sep 17 00:00:00 2001 From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com> Date: Tue, 2 Jul 2024 03:51:28 -0400 Subject: [PATCH 052/128] [Security Solution][Timeline] Notes management table (#187214) --- .../link_to/redirect_to_timelines.tsx | 2 +- .../public/common/mock/global_state.ts | 23 +- .../left/components/notes_list.test.tsx | 26 +- .../left/components/notes_list.tsx | 12 +- .../public/management/links.ts | 20 - .../security_solution/public/notes/api/api.ts | 38 +- .../notes/components/delete_confirm_modal.tsx | 50 ++ .../public/notes/components/search_row.tsx | 64 +++ .../public/notes/components/translations.ts | 104 ++++ .../public/notes/components/utility_bar.tsx | 105 ++++ .../security_solution/public/notes/index.ts | 9 + .../notes/pages/note_management_page.tsx | 201 ++++++- .../public/notes/store/notes.slice.test.ts | 520 ++++++++++++++---- .../public/notes/store/notes.slice.ts | 163 +++++- .../edit_timeline_batch_actions.tsx | 2 +- .../components/open_timeline/index.tsx | 20 +- .../open_timeline/open_timeline.tsx | 159 +++--- .../open_timeline_modal_body.tsx | 8 +- .../timelines_table/actions_columns.tsx | 42 +- .../timelines_table/common_columns.tsx | 10 +- .../timelines_table/extended_columns.tsx | 4 +- .../timelines_table/icon_header_columns.tsx | 9 +- .../open_timeline/timelines_table/index.tsx | 92 ++-- .../open_timeline/timelines_table/mocks.ts | 1 + .../components/open_timeline/types.ts | 10 +- .../open_timeline/use_timeline_types.test.tsx | 31 +- .../open_timeline/use_timeline_types.tsx | 35 +- .../public/timelines/links.ts | 13 +- .../public/timelines/pages/index.tsx | 2 +- .../public/timelines/pages/timelines_page.tsx | 5 +- 30 files changed, 1412 insertions(+), 368 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/notes/components/delete_confirm_modal.tsx create mode 100644 x-pack/plugins/security_solution/public/notes/components/search_row.tsx create mode 100644 x-pack/plugins/security_solution/public/notes/components/translations.ts create mode 100644 x-pack/plugins/security_solution/public/notes/components/utility_bar.tsx create mode 100644 x-pack/plugins/security_solution/public/notes/index.ts diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx index 2423d3493d9eb..98f47334ceca7 100644 --- a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx +++ b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx @@ -9,7 +9,7 @@ import { isEmpty } from 'lodash/fp'; import type { TimelineTypeLiteral } from '../../../../common/api/timeline'; import { appendSearch } from './helpers'; -export const getTimelineTabsUrl = (tabName: TimelineTypeLiteral, search?: string) => +export const getTimelineTabsUrl = (tabName: TimelineTypeLiteral | 'notes', search?: string) => `/${tabName}${appendSearch(search)}`; export const getTimelineUrl = (id: string, graphEventId?: string) => diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index 6aa38d25806a8..0a8aebee35f55 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -504,10 +504,9 @@ export const mockGlobalState: State = { discover: getMockDiscoverInTimelineState(), dataViewPicker: dataViewPickerInitialState, notes: { - ids: ['1'], entities: { '1': { - eventId: 'event-id', + eventId: 'document-id-1', noteId: '1', note: 'note-1', timelineId: 'timeline-1', @@ -518,15 +517,31 @@ export const mockGlobalState: State = { version: 'version', }, }, + ids: ['1'], status: { fetchNotesByDocumentIds: ReqStatus.Idle, createNote: ReqStatus.Idle, - deleteNote: ReqStatus.Idle, + deleteNotes: ReqStatus.Idle, + fetchNotes: ReqStatus.Idle, }, error: { fetchNotesByDocumentIds: null, createNote: null, - deleteNote: null, + deleteNotes: null, + fetchNotes: null, + }, + pagination: { + page: 1, + perPage: 10, + total: 0, + }, + sort: { + field: 'created' as const, + direction: 'desc' as const, }, + filter: '', + search: '', + selectedIds: [], + pendingDeleteIds: [], }, }; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_list.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_list.test.tsx index 8491804e1a572..e35d71ec28d55 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_list.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_list.test.tsx @@ -41,7 +41,7 @@ jest.mock('react-redux', () => { const renderNotesList = () => render( - + ); @@ -69,7 +69,7 @@ describe('NotesList', () => { const { getByTestId } = render( - + ); @@ -115,7 +115,7 @@ describe('NotesList', () => { render( - + ); @@ -131,7 +131,7 @@ describe('NotesList', () => { ...mockGlobalState.notes, entities: { '1': { - eventId: 'event-id', + eventId: 'document-id-1', noteId: '1', note: 'note-1', timelineId: '', @@ -147,7 +147,7 @@ describe('NotesList', () => { const { getByTestId } = render( - + ); const { getByText } = within(getByTestId(`${NOTE_AVATAR_TEST_ID}-0`)); @@ -169,7 +169,7 @@ describe('NotesList', () => { const { getByTestId } = render( - + ); @@ -196,14 +196,14 @@ describe('NotesList', () => { ...mockGlobalState.notes, status: { ...mockGlobalState.notes.status, - deleteNote: ReqStatus.Loading, + deleteNotes: ReqStatus.Loading, }, }, }); const { getByTestId } = render( - + ); @@ -217,18 +217,18 @@ describe('NotesList', () => { ...mockGlobalState.notes, status: { ...mockGlobalState.notes.status, - deleteNote: ReqStatus.Failed, + deleteNotes: ReqStatus.Failed, }, error: { ...mockGlobalState.notes.error, - deleteNote: { type: 'http', status: 500 }, + deleteNotes: { type: 'http', status: 500 }, }, }, }); render( - + ); @@ -261,7 +261,7 @@ describe('NotesList', () => { ...mockGlobalState.notes, entities: { '1': { - eventId: 'event-id', + eventId: 'document-id-1', noteId: '1', note: 'note-1', timelineId: '', @@ -277,7 +277,7 @@ describe('NotesList', () => { const { queryByTestId } = render( - + ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_list.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_list.tsx index c27f8441c103a..51ee119499fd1 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_list.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_list.tsx @@ -30,11 +30,11 @@ import { import type { State } from '../../../../common/store'; import type { Note } from '../../../../../common/api/timeline'; import { - deleteNote, + deleteNotes, ReqStatus, selectCreateNoteStatus, - selectDeleteNoteError, - selectDeleteNoteStatus, + selectDeleteNotesError, + selectDeleteNotesStatus, selectFetchNotesByDocumentIdsError, selectFetchNotesByDocumentIdsStatus, selectNotesByDocumentId, @@ -91,14 +91,14 @@ export const NotesList = memo(({ eventId }: NotesListProps) => { const createStatus = useSelector((state: State) => selectCreateNoteStatus(state)); - const deleteStatus = useSelector((state: State) => selectDeleteNoteStatus(state)); - const deleteError = useSelector((state: State) => selectDeleteNoteError(state)); + const deleteStatus = useSelector((state: State) => selectDeleteNotesStatus(state)); + const deleteError = useSelector((state: State) => selectDeleteNotesError(state)); const [deletingNoteId, setDeletingNoteId] = useState(''); const deleteNoteFc = useCallback( (noteId: string) => { setDeletingNoteId(noteId); - dispatch(deleteNote({ id: noteId })); + dispatch(deleteNotes({ ids: [noteId] })); }, [dispatch] ); diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index 06d47e2936115..91bf4e958f6fb 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -22,7 +22,6 @@ import { EVENT_FILTERS_PATH, HOST_ISOLATION_EXCEPTIONS_PATH, MANAGE_PATH, - NOTES_MANAGEMENT_PATH, POLICIES_PATH, RESPONSE_ACTIONS_HISTORY_PATH, SecurityPageName, @@ -40,7 +39,6 @@ import { TRUSTED_APPLICATIONS, ENTITY_ANALYTICS_RISK_SCORE, ASSET_CRITICALITY, - NOTES, } from '../app/translations'; import { licenseService } from '../common/hooks/use_license'; import type { LinkItem } from '../common/links/types'; @@ -87,12 +85,6 @@ const categories = [ }), linkIds: [SecurityPageName.cloudDefendPolicies], }, - { - label: i18n.translate('xpack.securitySolution.appLinks.category.investigations', { - defaultMessage: 'Investigations', - }), - linkIds: [SecurityPageName.notesManagement], - }, ]; export const links: LinkItem = { @@ -223,18 +215,6 @@ export const links: LinkItem = { hideTimeline: true, }, cloudDefendLink, - { - id: SecurityPageName.notesManagement, - title: NOTES, - description: i18n.translate('xpack.securitySolution.appLinks.notesManagementDescription', { - defaultMessage: 'Visualize and delete notes.', - }), - landingIcon: IconTool, // TODO get new icon - path: NOTES_MANAGEMENT_PATH, - skipUrlState: true, - hideTimeline: true, - experimentalKey: 'securitySolutionNotesEnabled', - }, ], }; diff --git a/x-pack/plugins/security_solution/public/notes/api/api.ts b/x-pack/plugins/security_solution/public/notes/api/api.ts index 91455d71a8d17..4c9542458c304 100644 --- a/x-pack/plugins/security_solution/public/notes/api/api.ts +++ b/x-pack/plugins/security_solution/public/notes/api/api.ts @@ -29,6 +29,38 @@ export const createNote = async ({ note }: { note: BareNote }) => { } }; +export const fetchNotes = async ({ + page, + perPage, + sortField, + sortOrder, + filter, + search, +}: { + page: number; + perPage: number; + sortField: string; + sortOrder: string; + filter: string; + search: string; +}) => { + const response = await KibanaServices.get().http.get<{ totalCount: number; notes: Note[] }>( + NOTE_URL, + { + query: { + page, + perPage, + sortField, + sortOrder, + filter, + search, + }, + version: '2023-10-31', + } + ); + return response; +}; + /** * Fetches all the notes for an array of document ids */ @@ -44,11 +76,11 @@ export const fetchNotesByDocumentIds = async (documentIds: string[]) => { }; /** - * Deletes a note + * Deletes multiple notes */ -export const deleteNote = async (noteId: string) => { +export const deleteNotes = async (noteIds: string[]) => { const response = await KibanaServices.get().http.delete<{ data: unknown }>(NOTE_URL, { - body: JSON.stringify({ noteId }), + body: JSON.stringify({ noteIds }), version: '2023-10-31', }); return response; diff --git a/x-pack/plugins/security_solution/public/notes/components/delete_confirm_modal.tsx b/x-pack/plugins/security_solution/public/notes/components/delete_confirm_modal.tsx new file mode 100644 index 0000000000000..e4a37d6594e14 --- /dev/null +++ b/x-pack/plugins/security_solution/public/notes/components/delete_confirm_modal.tsx @@ -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. + */ +import React, { useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { EuiConfirmModal } from '@elastic/eui'; +import * as i18n from './translations'; +import { + deleteNotes, + userClosedDeleteModal, + selectNotesTablePendingDeleteIds, + selectDeleteNotesStatus, + ReqStatus, +} from '..'; + +export const DeleteConfirmModal = React.memo(() => { + const dispatch = useDispatch(); + const pendingDeleteIds = useSelector(selectNotesTablePendingDeleteIds); + const deleteNotesStatus = useSelector(selectDeleteNotesStatus); + const deleteLoading = deleteNotesStatus === ReqStatus.Loading; + + const onCancel = useCallback(() => { + dispatch(userClosedDeleteModal()); + }, [dispatch]); + + const onConfirm = useCallback(() => { + dispatch(deleteNotes({ ids: pendingDeleteIds })); + }, [dispatch, pendingDeleteIds]); + + return ( + + {i18n.DELETE_NOTES_CONFIRM(pendingDeleteIds.length)} + + ); +}); + +DeleteConfirmModal.displayName = 'DeleteConfirmModal'; diff --git a/x-pack/plugins/security_solution/public/notes/components/search_row.tsx b/x-pack/plugins/security_solution/public/notes/components/search_row.tsx new file mode 100644 index 0000000000000..1e88a47b2e2d2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/notes/components/search_row.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiSearchBar } from '@elastic/eui'; +import React, { useMemo, useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import styled from 'styled-components'; +import { userSearchedNotes, selectNotesTableSearch } from '..'; + +const SearchRowContainer = styled.div` + &:not(:last-child) { + margin-bottom: ${(props) => props.theme.eui.euiSizeL}; + } +`; + +SearchRowContainer.displayName = 'SearchRowContainer'; + +const SearchRowFlexGroup = styled(EuiFlexGroup)` + margin-bottom: ${(props) => props.theme.eui.euiSizeXS}; +`; + +SearchRowFlexGroup.displayName = 'SearchRowFlexGroup'; + +export const SearchRow = React.memo(() => { + const dispatch = useDispatch(); + const searchBox = useMemo( + () => ({ + placeholder: 'Search note contents', + incremental: false, + 'data-test-subj': 'notes-search-bar', + }), + [] + ); + + const notesSearch = useSelector(selectNotesTableSearch); + + const onQueryChange = useCallback( + ({ queryText }) => { + dispatch(userSearchedNotes(queryText.trim())); + }, + [dispatch] + ); + + return ( + + + + + + + + ); +}); + +SearchRow.displayName = 'SearchRow'; diff --git a/x-pack/plugins/security_solution/public/notes/components/translations.ts b/x-pack/plugins/security_solution/public/notes/components/translations.ts new file mode 100644 index 0000000000000..471c28cbc9d4c --- /dev/null +++ b/x-pack/plugins/security_solution/public/notes/components/translations.ts @@ -0,0 +1,104 @@ +/* + * 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 BATCH_ACTIONS = i18n.translate( + 'xpack.securitySolution.notes.management.batchActionsTitle', + { + defaultMessage: 'Bulk actions', + } +); + +export const CREATED_COLUMN = i18n.translate( + 'xpack.securitySolution.notes.management.createdColumnTitle', + { + defaultMessage: 'Created', + } +); + +export const CREATED_BY_COLUMN = i18n.translate( + 'xpack.securitySolution.notes.management.createdByColumnTitle', + { + defaultMessage: 'Created by', + } +); + +export const EVENT_ID_COLUMN = i18n.translate( + 'xpack.securitySolution.notes.management.eventIdColumnTitle', + { + defaultMessage: 'Document ID', + } +); + +export const TIMELINE_ID_COLUMN = i18n.translate( + 'xpack.securitySolution.notes.management.timelineIdColumnTitle', + { + defaultMessage: 'Timeline ID', + } +); + +export const NOTE_CONTENT_COLUMN = i18n.translate( + 'xpack.securitySolution.notes.management.noteContentColumnTitle', + { + defaultMessage: 'Note content', + } +); + +export const DELETE = i18n.translate('xpack.securitySolution.notes.management.deleteAction', { + defaultMessage: 'Delete', +}); + +export const DELETE_SINGLE_NOTE_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.notes.management.deleteDescription', + { + defaultMessage: 'Delete this note', + } +); + +export const NOTES_MANAGEMENT_TITLE = i18n.translate( + 'xpack.securitySolution.notes.management.pageTitle', + { + defaultMessage: 'Notes management', + } +); + +export const TABLE_ERROR = i18n.translate('xpack.securitySolution.notes.management.tableError', { + defaultMessage: 'Unable to load notes', +}); + +export const DELETE_NOTES_MODAL_TITLE = i18n.translate( + 'xpack.securitySolution.notes.management.deleteNotesModalTitle', + { + defaultMessage: 'Delete notes?', + } +); + +export const DELETE_NOTES_CONFIRM = (selectedNotes: number) => + i18n.translate('xpack.securitySolution.notes.management.deleteNotesConfirm', { + values: { selectedNotes }, + defaultMessage: + 'Are you sure you want to delete {selectedNotes} {selectedNotes, plural, one {note} other {notes}}?', + }); + +export const DELETE_NOTES_CANCEL = i18n.translate( + 'xpack.securitySolution.notes.management.deleteNotesCancel', + { + defaultMessage: 'Cancel', + } +); + +export const DELETE_SELECTED = i18n.translate( + 'xpack.securitySolution.notes.management.deleteSelected', + { + defaultMessage: 'Delete selected notes', + } +); + +export const REFRESH = i18n.translate('xpack.securitySolution.notes.management.refresh', { + defaultMessage: 'Refresh', +}); diff --git a/x-pack/plugins/security_solution/public/notes/components/utility_bar.tsx b/x-pack/plugins/security_solution/public/notes/components/utility_bar.tsx new file mode 100644 index 0000000000000..c6b54e473ae5c --- /dev/null +++ b/x-pack/plugins/security_solution/public/notes/components/utility_bar.tsx @@ -0,0 +1,105 @@ +/* + * 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, { useMemo, useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { EuiContextMenuItem } from '@elastic/eui'; +import { + UtilityBarGroup, + UtilityBarText, + UtilityBar, + UtilityBarSection, + UtilityBarAction, +} from '../../common/components/utility_bar'; +import { + selectNotesPagination, + selectNotesTableSort, + fetchNotes, + selectNotesTableSelectedIds, + selectNotesTableSearch, + userSelectedBulkDelete, +} from '..'; +import * as i18n from './translations'; + +export const NotesUtilityBar = React.memo(() => { + const dispatch = useDispatch(); + const pagination = useSelector(selectNotesPagination); + const sort = useSelector(selectNotesTableSort); + const totalItems = pagination.total ?? 0; + const selectedItems = useSelector(selectNotesTableSelectedIds); + const resultsCount = useMemo(() => { + const { perPage, page } = pagination; + const startOfCurrentPage = perPage * (page - 1) + 1; + const endOfCurrentPage = Math.min(perPage * page, totalItems); + return perPage === 0 ? 'All' : `${startOfCurrentPage}-${endOfCurrentPage} of ${totalItems}`; + }, [pagination, totalItems]); + const deleteSelectedNotes = useCallback(() => { + dispatch(userSelectedBulkDelete()); + }, [dispatch]); + const notesSearch = useSelector(selectNotesTableSearch); + + const BulkActionPopoverContent = useCallback(() => { + return ( + + {i18n.DELETE_SELECTED} + + ); + }, [deleteSelectedNotes, selectedItems.length]); + const refresh = useCallback(() => { + dispatch( + fetchNotes({ + page: pagination.page, + perPage: pagination.perPage, + sortField: sort.field, + sortOrder: sort.direction, + filter: '', + search: notesSearch, + }) + ); + }, [dispatch, pagination.page, pagination.perPage, sort.field, sort.direction, notesSearch]); + return ( + + + + + {`Showing: ${resultsCount}`} + + + + + {selectedItems.length > 0 ? `${selectedItems.length} selected` : ''} + + + + {i18n.BATCH_ACTIONS} + + + + {i18n.REFRESH} + + + + + ); +}); + +NotesUtilityBar.displayName = 'NotesUtilityBar'; diff --git a/x-pack/plugins/security_solution/public/notes/index.ts b/x-pack/plugins/security_solution/public/notes/index.ts new file mode 100644 index 0000000000000..2c8f3548cfb53 --- /dev/null +++ b/x-pack/plugins/security_solution/public/notes/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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { NoteManagementPage } from './pages/note_management_page'; +export * from './store/notes.slice'; diff --git a/x-pack/plugins/security_solution/public/notes/pages/note_management_page.tsx b/x-pack/plugins/security_solution/public/notes/pages/note_management_page.tsx index 1964fa65fd96f..dca13ce2eed7b 100644 --- a/x-pack/plugins/security_solution/public/notes/pages/note_management_page.tsx +++ b/x-pack/plugins/security_solution/public/notes/pages/note_management_page.tsx @@ -5,14 +5,207 @@ * 2.0. */ -import React from 'react'; +import React, { useCallback, useMemo, useEffect } from 'react'; +import type { DefaultItemAction, EuiBasicTableColumn } from '@elastic/eui'; +import { EuiBasicTable, EuiEmptyPrompt } from '@elastic/eui'; +import { useDispatch, useSelector } from 'react-redux'; +// TODO unify this type from the api with the one in public/common/lib/note +import type { Note } from '../../../common/api/timeline'; +import { FormattedRelativePreferenceDate } from '../../common/components/formatted_date'; +import { + userSelectedPage, + userSelectedPerPage, + userSelectedRow, + userSortedNotes, + selectAllNotes, + selectNotesPagination, + selectNotesTableSort, + fetchNotes, + selectNotesTableSearch, + selectFetchNotesStatus, + selectNotesTablePendingDeleteIds, + userSelectedRowForDeletion, + selectFetchNotesError, + ReqStatus, +} from '..'; +import type { NotesState } from '..'; +import { SearchRow } from '../components/search_row'; +import { NotesUtilityBar } from '../components/utility_bar'; +import { DeleteConfirmModal } from '../components/delete_confirm_modal'; +import * as i18n from '../components/translations'; + +const columns: Array> = [ + { + field: 'created', + name: i18n.CREATED_COLUMN, + sortable: true, + render: (created: Note['created']) => , + }, + { + field: 'createdBy', + name: i18n.CREATED_BY_COLUMN, + }, + { + field: 'eventId', + name: i18n.EVENT_ID_COLUMN, + sortable: true, + }, + { + field: 'timelineId', + name: i18n.TIMELINE_ID_COLUMN, + }, + { + field: 'note', + name: i18n.NOTE_CONTENT_COLUMN, + }, +]; + +const pageSizeOptions = [50, 25, 10, 0]; /** - * Page to allow users to manage notes. The page is accessible via the Investigations section within the Manage page. - * // TODO to be implemented + * Allows user to search and delete notes. + * This component uses the same slices of state as the notes functionality of the rest of the Security Solution applicaiton. + * Therefore, changes made in this page (like fetching or deleting notes) will have an impact everywhere. */ export const NoteManagementPage = () => { - return <>; + const dispatch = useDispatch(); + const notes = useSelector(selectAllNotes); + const pagination = useSelector(selectNotesPagination); + const sort = useSelector(selectNotesTableSort); + const totalItems = pagination.total ?? 0; + const notesSearch = useSelector(selectNotesTableSearch); + const pendingDeleteIds = useSelector(selectNotesTablePendingDeleteIds); + const isDeleteModalVisible = pendingDeleteIds.length > 0; + const fetchNotesStatus = useSelector(selectFetchNotesStatus); + const fetchLoading = fetchNotesStatus === ReqStatus.Loading; + const fetchError = fetchNotesStatus === ReqStatus.Failed; + const fetchErrorData = useSelector(selectFetchNotesError); + + const fetchData = useCallback(() => { + dispatch( + fetchNotes({ + page: pagination.page, + perPage: pagination.perPage, + sortField: sort.field, + sortOrder: sort.direction, + filter: '', + search: notesSearch, + }) + ); + }, [dispatch, pagination.page, pagination.perPage, sort.field, sort.direction, notesSearch]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + const onTableChange = useCallback( + ({ + page, + sort: newSort, + }: { + page?: { index: number; size: number }; + sort?: NotesState['sort']; + }) => { + if (page) { + dispatch(userSelectedPage(page.index + 1)); + dispatch(userSelectedPerPage(page.size)); + } + if (newSort) { + dispatch(userSortedNotes({ field: newSort.field, direction: newSort.direction })); + } + }, + [dispatch] + ); + + const selectRowForDeletion = useCallback( + (id: string) => { + dispatch(userSelectedRowForDeletion(id)); + }, + [dispatch] + ); + + const onSelectionChange = useCallback( + (selection: Note[]) => { + const rowIds = selection.map((item) => item.noteId); + dispatch(userSelectedRow(rowIds)); + }, + [dispatch] + ); + + const itemIdSelector = useCallback((item: Note) => { + return item.noteId; + }, []); + + const columnWithActions = useMemo(() => { + const actions: Array> = [ + { + name: i18n.DELETE, + description: i18n.DELETE_SINGLE_NOTE_DESCRIPTION, + color: 'primary', + icon: 'trash', + type: 'icon', + onClick: (note: Note) => selectRowForDeletion(note.noteId), + }, + ]; + return [ + ...columns, + { + name: 'actions', + actions, + }, + ]; + }, [selectRowForDeletion]); + + const currentPagination = useMemo(() => { + return { + pageIndex: pagination.page - 1, + pageSize: pagination.perPage, + totalItemCount: totalItems, + pageSizeOptions, + }; + }, [pagination, totalItems]); + + const selection = useMemo(() => { + return { + onSelectionChange, + selectable: () => true, + }; + }, [onSelectionChange]); + + const sorting: { sort: { field: keyof Note; direction: 'asc' | 'desc' } } = useMemo(() => { + return { + sort, + }; + }, [sort]); + + if (fetchError) { + return ( + {i18n.TABLE_ERROR}} + body={

      {fetchErrorData}

      } + /> + ); + } + + return ( + <> + + + + {isDeleteModalVisible && } + + ); }; NoteManagementPage.displayName = 'NoteManagementPage'; diff --git a/x-pack/plugins/security_solution/public/notes/store/notes.slice.test.ts b/x-pack/plugins/security_solution/public/notes/store/notes.slice.test.ts index 59f196b6a5af5..8290edb049e1e 100644 --- a/x-pack/plugins/security_solution/public/notes/store/notes.slice.test.ts +++ b/x-pack/plugins/security_solution/public/notes/store/notes.slice.test.ts @@ -5,113 +5,137 @@ * 2.0. */ import * as uuid from 'uuid'; +import { miniSerializeError } from '@reduxjs/toolkit'; +import type { SerializedError } from '@reduxjs/toolkit'; import { createNote, - deleteNote, + deleteNotes, fetchNotesByDocumentIds, + fetchNotes, initialNotesState, notesReducer, ReqStatus, selectAllNotes, selectCreateNoteError, selectCreateNoteStatus, - selectDeleteNoteError, - selectDeleteNoteStatus, + selectDeleteNotesError, + selectDeleteNotesStatus, selectFetchNotesByDocumentIdsError, selectFetchNotesByDocumentIdsStatus, + selectFetchNotesError, + selectFetchNotesStatus, selectNoteById, selectNoteIds, selectNotesByDocumentId, + selectNotesPagination, + selectNotesTablePendingDeleteIds, + selectNotesTableSearch, + selectNotesTableSelectedIds, + selectNotesTableSort, + userClosedDeleteModal, + userFilteredNotes, + userSearchedNotes, + userSelectedBulkDelete, + userSelectedPage, + userSelectedPerPage, + userSelectedRow, + userSelectedRowForDeletion, + userSortedNotes, } from './notes.slice'; +import type { NotesState } from './notes.slice'; import { mockGlobalState } from '../../common/mock'; +import type { Note } from '../../../common/api/timeline'; const initalEmptyState = initialNotesState; -export const generateNoteMock = (documentIds: string[]) => - documentIds.map((documentId: string) => ({ - noteId: uuid.v4(), - version: 'WzU1MDEsMV0=', - timelineId: '', - eventId: documentId, - note: 'This is a mocked note', - created: new Date().getTime(), - createdBy: 'elastic', - updated: new Date().getTime(), - updatedBy: 'elastic', - })); - -const mockNote = { ...generateNoteMock(['1'])[0] }; +const generateNoteMock = (documentId: string): Note => ({ + noteId: uuid.v4(), + version: 'WzU1MDEsMV0=', + timelineId: '', + eventId: documentId, + note: 'This is a mocked note', + created: new Date().getTime(), + createdBy: 'elastic', + updated: new Date().getTime(), + updatedBy: 'elastic', +}); + +const mockNote1 = generateNoteMock('1'); +const mockNote2 = generateNoteMock('2'); + const initialNonEmptyState = { entities: { - [mockNote.noteId]: mockNote, + [mockNote1.noteId]: mockNote1, + [mockNote2.noteId]: mockNote2, }, - ids: [mockNote.noteId], + ids: [mockNote1.noteId, mockNote2.noteId], status: { fetchNotesByDocumentIds: ReqStatus.Idle, createNote: ReqStatus.Idle, - deleteNote: ReqStatus.Idle, + deleteNotes: ReqStatus.Idle, + fetchNotes: ReqStatus.Idle, + }, + error: { fetchNotesByDocumentIds: null, createNote: null, deleteNotes: null, fetchNotes: null }, + pagination: { + page: 1, + perPage: 10, + total: 0, }, - error: { fetchNotesByDocumentIds: null, createNote: null, deleteNote: null }, + sort: { + field: 'created' as const, + direction: 'desc' as const, + }, + filter: '', + search: '', + selectedIds: [], + pendingDeleteIds: [], }; describe('notesSlice', () => { describe('notesReducer', () => { it('should handle an unknown action and return the initial state', () => { - expect(notesReducer(initalEmptyState, { type: 'unknown' })).toEqual({ - entities: {}, - ids: [], - status: { - fetchNotesByDocumentIds: ReqStatus.Idle, - createNote: ReqStatus.Idle, - deleteNote: ReqStatus.Idle, - }, - error: { fetchNotesByDocumentIds: null, createNote: null, deleteNote: null }, - }); + expect(notesReducer(initalEmptyState, { type: 'unknown' })).toEqual(initalEmptyState); }); describe('fetchNotesByDocumentIds', () => { - it('should set correct status state when fetching notes by document id', () => { + it('should set correct status state when fetching notes by document ids', () => { const action = { type: fetchNotesByDocumentIds.pending.type }; expect(notesReducer(initalEmptyState, action)).toEqual({ - entities: {}, - ids: [], + ...initalEmptyState, status: { + ...initalEmptyState.status, fetchNotesByDocumentIds: ReqStatus.Loading, - createNote: ReqStatus.Idle, - deleteNote: ReqStatus.Idle, }, - error: { fetchNotesByDocumentIds: null, createNote: null, deleteNote: null }, }); }); - it('should set correct state when success on fetch notes by document id on an empty state', () => { + it('should set correct state when success on fetch notes by document ids on an empty state', () => { const action = { type: fetchNotesByDocumentIds.fulfilled.type, payload: { entities: { notes: { - [mockNote.noteId]: mockNote, + [mockNote1.noteId]: mockNote1, }, }, - result: [mockNote.noteId], + result: [mockNote1.noteId], }, }; expect(notesReducer(initalEmptyState, action)).toEqual({ + ...initalEmptyState, entities: action.payload.entities.notes, ids: action.payload.result, status: { + ...initalEmptyState.status, fetchNotesByDocumentIds: ReqStatus.Succeeded, - createNote: ReqStatus.Idle, - deleteNote: ReqStatus.Idle, }, - error: { fetchNotesByDocumentIds: null, createNote: null, deleteNote: null }, }); }); - it('should replace notes when success on fetch notes by document id on a non-empty state', () => { - const newMockNote = { ...mockNote, timelineId: 'timelineId' }; + it('should replace notes when success on fetch notes by document ids on a non-empty state', () => { + const newMockNote = { ...mockNote1, timelineId: 'timelineId' }; const action = { type: fetchNotesByDocumentIds.fulfilled.type, payload: { @@ -125,173 +149,336 @@ describe('notesSlice', () => { }; expect(notesReducer(initialNonEmptyState, action)).toEqual({ - entities: action.payload.entities.notes, - ids: action.payload.result, + ...initalEmptyState, + entities: { + [newMockNote.noteId]: newMockNote, + [mockNote2.noteId]: mockNote2, + }, + ids: [newMockNote.noteId, mockNote2.noteId], status: { + ...initalEmptyState.status, fetchNotesByDocumentIds: ReqStatus.Succeeded, - createNote: ReqStatus.Idle, - deleteNote: ReqStatus.Idle, }, - error: { fetchNotesByDocumentIds: null, createNote: null, deleteNote: null }, }); }); - it('should set correct error state when failing to fetch notes by document id', () => { + it('should set correct error state when failing to fetch notes by document ids', () => { const action = { type: fetchNotesByDocumentIds.rejected.type, error: 'error' }; expect(notesReducer(initalEmptyState, action)).toEqual({ - entities: {}, - ids: [], + ...initalEmptyState, status: { + ...initalEmptyState.status, fetchNotesByDocumentIds: ReqStatus.Failed, - createNote: ReqStatus.Idle, - deleteNote: ReqStatus.Idle, }, error: { + ...initalEmptyState.error, fetchNotesByDocumentIds: 'error', - createNote: null, - deleteNote: null, }, }); }); }); describe('createNote', () => { - it('should set correct status state when creating a note by document id', () => { + it('should set correct status state when creating a note', () => { const action = { type: createNote.pending.type }; expect(notesReducer(initalEmptyState, action)).toEqual({ - entities: {}, - ids: [], + ...initalEmptyState, status: { - fetchNotesByDocumentIds: ReqStatus.Idle, + ...initalEmptyState.status, createNote: ReqStatus.Loading, - deleteNote: ReqStatus.Idle, }, - error: { fetchNotesByDocumentIds: null, createNote: null, deleteNote: null }, }); }); - it('should set correct state when success on create a note by document id on an empty state', () => { + it('should set correct state when success on create a note on an empty state', () => { const action = { type: createNote.fulfilled.type, payload: { entities: { notes: { - [mockNote.noteId]: mockNote, + [mockNote1.noteId]: mockNote1, }, }, - result: mockNote.noteId, + result: mockNote1.noteId, }, }; expect(notesReducer(initalEmptyState, action)).toEqual({ + ...initalEmptyState, entities: action.payload.entities.notes, ids: [action.payload.result], status: { - fetchNotesByDocumentIds: ReqStatus.Idle, + ...initalEmptyState.status, createNote: ReqStatus.Succeeded, - deleteNote: ReqStatus.Idle, }, - error: { fetchNotesByDocumentIds: null, createNote: null, deleteNote: null }, }); }); - it('should set correct error state when failing to create a note by document id', () => { + it('should set correct error state when failing to create a note', () => { const action = { type: createNote.rejected.type, error: 'error' }; expect(notesReducer(initalEmptyState, action)).toEqual({ - entities: {}, - ids: [], + ...initalEmptyState, status: { - fetchNotesByDocumentIds: ReqStatus.Idle, + ...initalEmptyState.status, createNote: ReqStatus.Failed, - deleteNote: ReqStatus.Idle, }, error: { - fetchNotesByDocumentIds: null, + ...initalEmptyState.error, createNote: 'error', - deleteNote: null, }, }); }); }); - describe('deleteNote', () => { - it('should set correct status state when deleting a note', () => { - const action = { type: deleteNote.pending.type }; + describe('deleteNotes', () => { + it('should set correct status state when deleting notes', () => { + const action = { type: deleteNotes.pending.type }; expect(notesReducer(initalEmptyState, action)).toEqual({ - entities: {}, - ids: [], + ...initalEmptyState, status: { - fetchNotesByDocumentIds: ReqStatus.Idle, - createNote: ReqStatus.Idle, - deleteNote: ReqStatus.Loading, + ...initalEmptyState.status, + deleteNotes: ReqStatus.Loading, }, - error: { fetchNotesByDocumentIds: null, createNote: null, deleteNote: null }, }); }); - it('should set correct state when success on deleting a note', () => { + it('should set correct state when success on deleting notes', () => { const action = { - type: deleteNote.fulfilled.type, - payload: mockNote.noteId, + type: deleteNotes.fulfilled.type, + payload: [mockNote1.noteId], + }; + const state = { + ...initialNonEmptyState, + pendingDeleteIds: [mockNote1.noteId], }; - expect(notesReducer(initialNonEmptyState, action)).toEqual({ - entities: {}, - ids: [], + expect(notesReducer(state, action)).toEqual({ + ...initialNonEmptyState, + entities: { + [mockNote2.noteId]: mockNote2, + }, + ids: [mockNote2.noteId], status: { - fetchNotesByDocumentIds: ReqStatus.Idle, - createNote: ReqStatus.Idle, - deleteNote: ReqStatus.Succeeded, + ...initialNonEmptyState.status, + deleteNotes: ReqStatus.Succeeded, }, - error: { fetchNotesByDocumentIds: null, createNote: null, deleteNote: null }, + pendingDeleteIds: [], }); }); - it('should set correct state when failing to create a note by document id', () => { - const action = { type: deleteNote.rejected.type, error: 'error' }; + it('should set correct state when failing to delete notes', () => { + const action = { type: deleteNotes.rejected.type, error: 'error' }; expect(notesReducer(initalEmptyState, action)).toEqual({ - entities: {}, - ids: [], + ...initalEmptyState, status: { - fetchNotesByDocumentIds: ReqStatus.Idle, - createNote: ReqStatus.Idle, - deleteNote: ReqStatus.Failed, + ...initalEmptyState.status, + deleteNotes: ReqStatus.Failed, }, error: { - fetchNotesByDocumentIds: null, - createNote: null, - deleteNote: 'error', + ...initalEmptyState.error, + deleteNotes: 'error', + }, + }); + }); + + it('should set correct status when fetching notes', () => { + const action = { type: fetchNotes.pending.type }; + expect(notesReducer(initialNotesState, action)).toEqual({ + ...initialNotesState, + status: { + ...initialNotesState.status, + fetchNotes: ReqStatus.Loading, + }, + }); + }); + + it('should set notes and update pagination when fetch is successful', () => { + const action = { + type: fetchNotes.fulfilled.type, + payload: { + entities: { + notes: { [mockNote2.noteId]: mockNote2, '2': { ...mockNote2, noteId: '2' } }, + }, + totalCount: 2, + }, + }; + const state = notesReducer(initialNotesState, action); + expect(state.entities).toEqual(action.payload.entities.notes); + expect(state.ids).toHaveLength(2); + expect(state.pagination.total).toBe(2); + expect(state.status.fetchNotes).toBe(ReqStatus.Succeeded); + }); + + it('should set error when fetch fails', () => { + const action = { type: fetchNotes.rejected.type, error: { message: 'Failed to fetch' } }; + const state = notesReducer(initialNotesState, action); + expect(state.status.fetchNotes).toBe(ReqStatus.Failed); + expect(state.error.fetchNotes).toEqual({ message: 'Failed to fetch' }); + }); + + it('should set correct status when deleting multiple notes', () => { + const action = { type: deleteNotes.pending.type }; + expect(notesReducer(initialNotesState, action)).toEqual({ + ...initialNotesState, + status: { + ...initialNotesState.status, + deleteNotes: ReqStatus.Loading, + }, + }); + }); + + it('should remove multiple notes when delete is successful', () => { + const initialState = { + ...initialNotesState, + entities: { '1': mockNote1, '2': { ...mockNote2, noteId: '2' } }, + ids: ['1', '2'], + }; + const action = { type: deleteNotes.fulfilled.type, payload: ['1', '2'] }; + const state = notesReducer(initialState, action); + expect(state.entities).toEqual({}); + expect(state.ids).toHaveLength(0); + expect(state.status.deleteNotes).toBe(ReqStatus.Succeeded); + }); + + it('should set error when delete fails', () => { + const action = { type: deleteNotes.rejected.type, error: { message: 'Failed to delete' } }; + const state = notesReducer(initialNotesState, action); + expect(state.status.deleteNotes).toBe(ReqStatus.Failed); + expect(state.error.deleteNotes).toEqual({ message: 'Failed to delete' }); + }); + }); + + describe('userSelectedPage', () => { + it('should set correct value for the selected page', () => { + const action = { type: userSelectedPage.type, payload: 2 }; + + expect(notesReducer(initalEmptyState, action)).toEqual({ + ...initalEmptyState, + pagination: { + ...initalEmptyState.pagination, + page: 2, + }, + }); + }); + }); + + describe('userSelectedPerPage', () => { + it('should set correct value for number of notes per page', () => { + const action = { type: userSelectedPerPage.type, payload: 25 }; + + expect(notesReducer(initalEmptyState, action)).toEqual({ + ...initalEmptyState, + pagination: { + ...initalEmptyState.pagination, + perPage: 25, + }, + }); + }); + }); + + describe('userSortedNotes', () => { + it('should set correct value for sorting notes', () => { + const action = { type: userSortedNotes.type, payload: { field: 'note', direction: 'asc' } }; + + expect(notesReducer(initalEmptyState, action)).toEqual({ + ...initalEmptyState, + sort: { + field: 'note', + direction: 'asc', }, }); }); }); + + describe('userFilteredNotes', () => { + it('should set correct value to filter notes', () => { + const action = { type: userFilteredNotes.type, payload: 'abc' }; + + expect(notesReducer(initalEmptyState, action)).toEqual({ + ...initalEmptyState, + filter: 'abc', + }); + }); + }); + + describe('userSearchedNotes', () => { + it('should set correct value to search notes', () => { + const action = { type: userSearchedNotes.type, payload: 'abc' }; + + expect(notesReducer(initalEmptyState, action)).toEqual({ + ...initalEmptyState, + search: 'abc', + }); + }); + }); + + describe('userSelectedRow', () => { + it('should set correct ids for selected rows', () => { + const action = { type: userSelectedRow.type, payload: ['1'] }; + + expect(notesReducer(initalEmptyState, action)).toEqual({ + ...initalEmptyState, + selectedIds: ['1'], + }); + }); + }); + + describe('userClosedDeleteModal', () => { + it('should reset pendingDeleteIds when closing modal', () => { + const action = { type: userClosedDeleteModal.type }; + + expect(notesReducer(initalEmptyState, action)).toEqual({ + ...initalEmptyState, + pendingDeleteIds: [], + }); + }); + }); + + describe('userSelectedRowForDeletion', () => { + it('should set correct id when user selects a row', () => { + const action = { type: userSelectedRowForDeletion.type, payload: '1' }; + + expect(notesReducer(initalEmptyState, action)).toEqual({ + ...initalEmptyState, + pendingDeleteIds: ['1'], + }); + }); + }); + + describe('userSelectedBulkDelete', () => { + it('should update pendingDeleteIds when user chooses bulk delete', () => { + const action = { type: userSelectedBulkDelete.type }; + const state = { + ...initalEmptyState, + selectedIds: ['1'], + }; + + expect(notesReducer(state, action)).toEqual({ + ...state, + pendingDeleteIds: ['1'], + }); + }); + }); }); describe('selectors', () => { it('should return all notes', () => { - const state = mockGlobalState; - state.notes.entities = initialNonEmptyState.entities; - state.notes.ids = initialNonEmptyState.ids; - expect(selectAllNotes(state)).toEqual([mockNote]); + expect(selectAllNotes(mockGlobalState)).toEqual( + Object.values(mockGlobalState.notes.entities) + ); }); it('should return note by id', () => { - const state = mockGlobalState; - state.notes.entities = initialNonEmptyState.entities; - state.notes.ids = initialNonEmptyState.ids; - expect(selectNoteById(state, mockNote.noteId)).toEqual(mockNote); + expect(selectNoteById(mockGlobalState, '1')).toEqual(mockGlobalState.notes.entities['1']); }); it('should return note ids', () => { - const state = mockGlobalState; - state.notes.entities = initialNonEmptyState.entities; - state.notes.ids = initialNonEmptyState.ids; - expect(selectNoteIds(state)).toEqual([mockNote.noteId]); + expect(selectNoteIds(mockGlobalState)).toEqual(['1']); }); it('should return fetch notes by document id status', () => { @@ -311,19 +498,110 @@ describe('notesSlice', () => { }); it('should return delete note status', () => { - expect(selectDeleteNoteStatus(mockGlobalState)).toEqual(ReqStatus.Idle); + expect(selectDeleteNotesStatus(mockGlobalState)).toEqual(ReqStatus.Idle); }); it('should return delete note error', () => { - expect(selectDeleteNoteError(mockGlobalState)).toEqual(null); + expect(selectDeleteNotesError(mockGlobalState)).toEqual(null); }); it('should return all notes for an existing document id', () => { - expect(selectNotesByDocumentId(mockGlobalState, '1')).toEqual([mockNote]); + expect(selectNotesByDocumentId(mockGlobalState, 'document-id-1')).toEqual([ + mockGlobalState.notes.entities['1'], + ]); }); it('should return no notes if document id does not exist', () => { - expect(selectNotesByDocumentId(mockGlobalState, '2')).toHaveLength(0); + expect(selectNotesByDocumentId(mockGlobalState, 'wrong-document-id')).toHaveLength(0); + }); + + it('should select notes pagination', () => { + const state = { + ...mockGlobalState, + notes: { ...initialNotesState, pagination: { page: 2, perPage: 20, total: 100 } }, + }; + expect(selectNotesPagination(state)).toEqual({ page: 2, perPage: 20, total: 100 }); + }); + + it('should select notes table sort', () => { + const notes: NotesState = { + ...initialNotesState, + sort: { field: 'created', direction: 'asc' }, + }; + const state = { + ...mockGlobalState, + notes, + }; + expect(selectNotesTableSort(state)).toEqual({ field: 'created', direction: 'asc' }); + }); + + it('should select notes table total items', () => { + const state = { + ...mockGlobalState, + notes: { + ...initialNotesState, + pagination: { ...initialNotesState.pagination, total: 100 }, + }, + }; + expect(selectNotesPagination(state).total).toBe(100); + }); + + it('should select notes table selected ids', () => { + const state = { + ...mockGlobalState, + notes: { ...initialNotesState, selectedIds: ['1', '2'] }, + }; + expect(selectNotesTableSelectedIds(state)).toEqual(['1', '2']); + }); + + it('should select notes table search', () => { + const state = { ...mockGlobalState, notes: { ...initialNotesState, search: 'test search' } }; + expect(selectNotesTableSearch(state)).toBe('test search'); + }); + + it('should select notes table pending delete ids', () => { + const state = { + ...mockGlobalState, + notes: { ...initialNotesState, pendingDeleteIds: ['1', '2'] }, + }; + expect(selectNotesTablePendingDeleteIds(state)).toEqual(['1', '2']); + }); + + it('should select delete notes status', () => { + const state = { + ...mockGlobalState, + notes: { + ...initialNotesState, + status: { ...initialNotesState.status, deleteNotes: ReqStatus.Loading }, + }, + }; + expect(selectDeleteNotesStatus(state)).toBe(ReqStatus.Loading); + }); + + it('should select fetch notes error', () => { + const error = new Error('Error fetching notes'); + const reudxToolKItError = miniSerializeError(error); + const notes: NotesState = { + ...initialNotesState, + error: { ...initialNotesState.error, fetchNotes: reudxToolKItError }, + }; + const state = { + ...mockGlobalState, + notes, + }; + const selectedError = selectFetchNotesError(state) as SerializedError; + expect(selectedError.message).toBe('Error fetching notes'); + }); + + it('should select fetch notes status', () => { + const state = { + ...mockGlobalState, + notes: { + ...initialNotesState, + status: { ...initialNotesState.status, fetchNotes: ReqStatus.Succeeded }, + }, + }; + expect(selectFetchNotesStatus(state)).toBe(ReqStatus.Succeeded); }); }); }); diff --git a/x-pack/plugins/security_solution/public/notes/store/notes.slice.ts b/x-pack/plugins/security_solution/public/notes/store/notes.slice.ts index 303313d180306..6b466d62f53b6 100644 --- a/x-pack/plugins/security_solution/public/notes/store/notes.slice.ts +++ b/x-pack/plugins/security_solution/public/notes/store/notes.slice.ts @@ -11,7 +11,8 @@ import { createSelector } from 'reselect'; import type { State } from '../../common/store'; import { createNote as createNoteApi, - deleteNote as deleteNoteApi, + deleteNotes as deleteNotesApi, + fetchNotes as fetchNotesApi, fetchNotesByDocumentIds as fetchNotesByDocumentIdsApi, } from '../api/api'; import type { NormalizedEntities, NormalizedEntity } from './normalize'; @@ -34,13 +35,28 @@ export interface NotesState extends EntityState { status: { fetchNotesByDocumentIds: ReqStatus; createNote: ReqStatus; - deleteNote: ReqStatus; + deleteNotes: ReqStatus; + fetchNotes: ReqStatus; }; error: { fetchNotesByDocumentIds: SerializedError | HttpError | null; createNote: SerializedError | HttpError | null; - deleteNote: SerializedError | HttpError | null; + deleteNotes: SerializedError | HttpError | null; + fetchNotes: SerializedError | HttpError | null; }; + pagination: { + page: number; + perPage: number; + total: number; + }; + sort: { + field: keyof Note; + direction: 'asc' | 'desc'; + }; + filter: string; + search: string; + selectedIds: string[]; + pendingDeleteIds: string[]; } const notesAdapter = createEntityAdapter({ @@ -51,13 +67,28 @@ export const initialNotesState: NotesState = notesAdapter.getInitialState({ status: { fetchNotesByDocumentIds: ReqStatus.Idle, createNote: ReqStatus.Idle, - deleteNote: ReqStatus.Idle, + deleteNotes: ReqStatus.Idle, + fetchNotes: ReqStatus.Idle, }, error: { fetchNotesByDocumentIds: null, createNote: null, - deleteNote: null, + deleteNotes: null, + fetchNotes: null, + }, + pagination: { + page: 1, + perPage: 10, + total: 0, }, + sort: { + field: 'created', + direction: 'desc', + }, + filter: '', + search: '', + selectedIds: [], + pendingDeleteIds: [], }); export const fetchNotesByDocumentIds = createAsyncThunk< @@ -70,6 +101,23 @@ export const fetchNotesByDocumentIds = createAsyncThunk< return normalizeEntities(res.notes); }); +export const fetchNotes = createAsyncThunk< + NormalizedEntities & { totalCount: number }, + { + page: number; + perPage: number; + sortField: string; + sortOrder: string; + filter: string; + search: string; + }, + {} +>('notes/fetchNotes', async (args) => { + const { page, perPage, sortField, sortOrder, filter, search } = args; + const res = await fetchNotesApi({ page, perPage, sortField, sortOrder, filter, search }); + return { ...normalizeEntities(res.notes), totalCount: res.totalCount }; +}); + export const createNote = createAsyncThunk, { note: BareNote }, {}>( 'notes/createNote', async (args) => { @@ -79,19 +127,50 @@ export const createNote = createAsyncThunk, { note: BareN } ); -export const deleteNote = createAsyncThunk( - 'notes/deleteNote', +export const deleteNotes = createAsyncThunk( + 'notes/deleteNotes', async (args) => { - const { id } = args; - await deleteNoteApi(id); - return id; + const { ids } = args; + await deleteNotesApi(ids); + return ids; } ); const notesSlice = createSlice({ name: 'notes', initialState: initialNotesState, - reducers: {}, + reducers: { + userSelectedPage: (state, action: { payload: number }) => { + state.pagination.page = action.payload; + }, + userSelectedPerPage: (state, action: { payload: number }) => { + state.pagination.perPage = action.payload; + }, + userSortedNotes: ( + state, + action: { payload: { field: keyof Note; direction: 'asc' | 'desc' } } + ) => { + state.sort = action.payload; + }, + userFilteredNotes: (state, action: { payload: string }) => { + state.filter = action.payload; + }, + userSearchedNotes: (state, action: { payload: string }) => { + state.search = action.payload; + }, + userSelectedRow: (state, action: { payload: string[] }) => { + state.selectedIds = action.payload; + }, + userClosedDeleteModal: (state) => { + state.pendingDeleteIds = []; + }, + userSelectedRowForDeletion: (state, action: { payload: string }) => { + state.pendingDeleteIds = [action.payload]; + }, + userSelectedBulkDelete: (state) => { + state.pendingDeleteIds = state.selectedIds; + }, + }, extraReducers(builder) { builder .addCase(fetchNotesByDocumentIds.pending, (state) => { @@ -116,16 +195,32 @@ const notesSlice = createSlice({ state.status.createNote = ReqStatus.Failed; state.error.createNote = action.payload ?? action.error; }) - .addCase(deleteNote.pending, (state) => { - state.status.deleteNote = ReqStatus.Loading; + .addCase(deleteNotes.pending, (state) => { + state.status.deleteNotes = ReqStatus.Loading; + }) + .addCase(deleteNotes.fulfilled, (state, action) => { + notesAdapter.removeMany(state, action.payload); + state.status.deleteNotes = ReqStatus.Succeeded; + state.pendingDeleteIds = state.pendingDeleteIds.filter( + (value) => !action.payload.includes(value) + ); + }) + .addCase(deleteNotes.rejected, (state, action) => { + state.status.deleteNotes = ReqStatus.Failed; + state.error.deleteNotes = action.payload ?? action.error; }) - .addCase(deleteNote.fulfilled, (state, action) => { - notesAdapter.removeOne(state, action.payload); - state.status.deleteNote = ReqStatus.Succeeded; + .addCase(fetchNotes.pending, (state) => { + state.status.fetchNotes = ReqStatus.Loading; }) - .addCase(deleteNote.rejected, (state, action) => { - state.status.deleteNote = ReqStatus.Failed; - state.error.deleteNote = action.payload ?? action.error; + .addCase(fetchNotes.fulfilled, (state, action) => { + notesAdapter.setAll(state, action.payload.entities.notes); + state.pagination.total = action.payload.totalCount; + state.status.fetchNotes = ReqStatus.Succeeded; + state.selectedIds = []; + }) + .addCase(fetchNotes.rejected, (state, action) => { + state.status.fetchNotes = ReqStatus.Failed; + state.error.fetchNotes = action.payload ?? action.error; }); }, }); @@ -148,11 +243,37 @@ export const selectCreateNoteStatus = (state: State) => state.notes.status.creat export const selectCreateNoteError = (state: State) => state.notes.error.createNote; -export const selectDeleteNoteStatus = (state: State) => state.notes.status.deleteNote; +export const selectDeleteNotesStatus = (state: State) => state.notes.status.deleteNotes; + +export const selectDeleteNotesError = (state: State) => state.notes.error.deleteNotes; + +export const selectNotesPagination = (state: State) => state.notes.pagination; + +export const selectNotesTableSort = (state: State) => state.notes.sort; + +export const selectNotesTableSelectedIds = (state: State) => state.notes.selectedIds; -export const selectDeleteNoteError = (state: State) => state.notes.error.deleteNote; +export const selectNotesTableSearch = (state: State) => state.notes.search; + +export const selectNotesTablePendingDeleteIds = (state: State) => state.notes.pendingDeleteIds; + +export const selectFetchNotesError = (state: State) => state.notes.error.fetchNotes; + +export const selectFetchNotesStatus = (state: State) => state.notes.status.fetchNotes; export const selectNotesByDocumentId = createSelector( [selectAllNotes, (state, documentId) => documentId], (notes, documentId) => notes.filter((note) => note.eventId === documentId) ); + +export const { + userSelectedPage, + userSelectedPerPage, + userSortedNotes, + userFilteredNotes, + userSearchedNotes, + userSelectedRow, + userClosedDeleteModal, + userSelectedRowForDeletion, + userSelectedBulkDelete, +} = notesSlice.actions; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/edit_timeline_batch_actions.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/edit_timeline_batch_actions.tsx index 67d0c5a9e4599..073e9c486ac6d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/edit_timeline_batch_actions.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/edit_timeline_batch_actions.tsx @@ -25,7 +25,7 @@ export const useEditTimelineBatchActions = ({ }: { deleteTimelines?: DeleteTimelines; selectedItems?: OpenTimelineResult[]; - tableRef: React.MutableRefObject | undefined>; + tableRef: React.MutableRefObject | null>; timelineType: TimelineType | null; }) => { const { diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx index aa80df5a33ba7..11e35ce4a800a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx @@ -69,7 +69,7 @@ interface OwnProps { export type OpenTimelineOwnProps = OwnProps & Pick< OpenTimelineProps, - 'defaultPageSize' | 'title' | 'importDataModalToggle' | 'setImportDataModalToggle' + 'defaultPageSize' | 'title' | 'importDataModalToggle' | 'setImportDataModalToggle' | 'tabName' >; /** Returns a collection of selected timeline ids */ @@ -130,6 +130,7 @@ export const StatefulOpenTimelineComponent = React.memo( importDataModalToggle, onOpenTimeline, setImportDataModalToggle, + tabName, title, }) => { const dispatch = useDispatch(); @@ -305,12 +306,16 @@ export const StatefulOpenTimelineComponent = React.memo( /** Invoked by the EUI table implementation when the user interacts with the table (i.e. to update sorting) */ const onTableChange: OnTableChange = useCallback(({ page, sort }: OnTableChangeParams) => { - const { index, size } = page; - const { field, direction } = sort; - setPageIndex(index); - setPageSize(size); - setSortDirection(direction); - setSortField(field); + if (page != null) { + const { index, size } = page; + setPageIndex(index); + setPageSize(size); + } + if (sort != null) { + const { field, direction } = sort; + setSortDirection(direction); + setSortField(field); + } }, []); /** Invoked when the user toggles the option to only view favorite timelines */ @@ -414,6 +419,7 @@ export const StatefulOpenTimelineComponent = React.memo( selectedItems={selectedItems} sortDirection={sortDirection} sortField={sortField} + tabName={tabName} templateTimelineFilter={templateTimelineFilter} timelineType={timelineType} timelineStatus={timelineStatus} diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx index 28e42b3aa2020..3dc686229e4fa 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { EuiBasicTable } from '@elastic/eui'; import React, { useCallback, useMemo, useRef } from 'react'; +import type { EuiBasicTable } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import styled from 'styled-components'; @@ -29,7 +29,8 @@ import { SearchRow } from './search_row'; import { TimelinesTable } from './timelines_table'; import * as i18n from './translations'; import { OPEN_TIMELINE_CLASS_NAME } from './helpers'; -import type { OpenTimelineProps, OpenTimelineResult, ActionTimelineToShow } from './types'; +import type { OpenTimelineProps, ActionTimelineToShow, OpenTimelineResult } from './types'; +import { NoteManagementPage } from '../../../notes'; const QueryText = styled.span` white-space: normal; @@ -63,13 +64,13 @@ export const OpenTimeline = React.memo( sortDirection, setImportDataModalToggle, sortField, + tabName, timelineType = TimelineType.default, timelineStatus, timelineFilter, templateTimelineFilter, totalSearchResultsCount, }) => { - const tableRef = useRef>(); const { actionItem, enableExportTimelineDownloader, @@ -78,7 +79,7 @@ export const OpenTimeline = React.memo( onOpenDeleteTimelineModal, onCompleteEditTimelineAction, } = useEditTimelineActions(); - + const tableRef = useRef | null>(null); const { kibanaSecuritySolutionsPrivileges } = useUserPrivileges(); const { getBatchItemsPopoverContent } = useEditTimelineBatchActions({ deleteTimelines: kibanaSecuritySolutionsPrivileges.crud ? deleteTimelines : undefined, @@ -227,84 +228,92 @@ export const OpenTimeline = React.memo(
      {!!timelineFilter && timelineFilter} - - {SearchRowContent} - + {tabName !== 'notes' ? ( + <> + + {SearchRowContent} + - - - - - <> - {i18n.SHOWING}{' '} - {timelineType === TimelineType.template ? nTemplates : nTimelines} - - - - - {timelineStatus !== TimelineStatus.immutable && ( - <> - - {timelineType === TimelineType.template - ? i18n.SELECTED_TEMPLATES(selectedItems.length) - : i18n.SELECTED_TIMELINES(selectedItems.length)} + + + + + <> + {i18n.SHOWING}{' '} + {timelineType === TimelineType.template ? nTemplates : nTimelines} + + + + {timelineStatus !== TimelineStatus.immutable && ( + <> + + {timelineType === TimelineType.template + ? i18n.SELECTED_TEMPLATES(selectedItems.length) + : i18n.SELECTED_TIMELINES(selectedItems.length)} + + + + {i18n.BATCH_ACTIONS} + + + + )} - {i18n.BATCH_ACTIONS} + {i18n.REFRESH} - - )} - - {i18n.REFRESH} - - - - + + + - + + + ) : ( + + )}
      ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.tsx index 6e012c65478c8..7eb3c65f427ac 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.tsx @@ -6,10 +6,11 @@ */ import { EuiModalBody, EuiModalHeader, EuiSpacer } from '@elastic/eui'; -import React, { Fragment, memo, useMemo } from 'react'; +import type { EuiBasicTable } from '@elastic/eui'; +import React, { Fragment, memo, useMemo, useRef } from 'react'; import styled from 'styled-components'; -import type { OpenTimelineProps, ActionTimelineToShow } from '../types'; +import type { OpenTimelineProps, ActionTimelineToShow, OpenTimelineResult } from '../types'; import { SearchRow } from '../search_row'; import { TimelinesTable } from '../timelines_table'; import { TitleRow } from '../title_row'; @@ -49,6 +50,8 @@ export const OpenTimelineModalBody = memo( title, totalSearchResultsCount, }) => { + const tableRef = useRef | null>(null); + const actionsToShow = useMemo(() => { const actions: ActionTimelineToShow[] = ['createFrom', 'duplicate']; @@ -118,6 +121,7 @@ export const OpenTimelineModalBody = memo( sortField={sortField} timelineType={timelineType} totalSearchResultsCount={totalSearchResultsCount} + tableRef={tableRef} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.tsx index 9c58b30fbfc53..a5eab754a7290 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import type { ICON_TYPES, EuiTableActionsColumnType } from '@elastic/eui'; import type { ActionTimelineToShow, DeleteTimelines, @@ -13,10 +14,11 @@ import type { OnOpenTimeline, OpenTimelineResult, OnOpenDeleteTimelineModal, - TimelineActionsOverflowColumns, } from '../types'; import * as i18n from '../translations'; import { TimelineStatus, TimelineType } from '../../../../../common/api/timeline'; + +type Action = EuiTableActionsColumnType['actions'][number]; /** * Returns the action columns (e.g. delete, open duplicate timeline) */ @@ -38,10 +40,10 @@ export const getActionsColumns = ({ onCreateRule?: OnCreateRuleFromTimeline; onCreateRuleFromEql?: OnCreateRuleFromTimeline; hasCrudAccess: boolean; -}): [TimelineActionsOverflowColumns] => { +}): Array> => { const createTimelineFromTemplate = { name: i18n.CREATE_TIMELINE_FROM_TEMPLATE, - icon: 'timeline', + icon: 'timeline' as typeof ICON_TYPES[number], onClick: ({ savedObjectId }: OpenTimelineResult) => { onOpenTimeline({ duplicate: true, @@ -56,11 +58,11 @@ export const getActionsColumns = ({ 'data-test-subj': 'create-from-template', available: (item: OpenTimelineResult) => item.timelineType === TimelineType.template && actionTimelineToShow.includes('createFrom'), - }; + } as Action; const createTemplateFromTimeline = { name: i18n.CREATE_TEMPLATE_FROM_TIMELINE, - icon: 'visText', + icon: 'visText' as typeof ICON_TYPES[number], onClick: ({ savedObjectId }: OpenTimelineResult) => { onOpenTimeline({ duplicate: true, @@ -75,11 +77,11 @@ export const getActionsColumns = ({ 'data-test-subj': 'create-template-from-timeline', available: (item: OpenTimelineResult) => item.timelineType !== TimelineType.template && actionTimelineToShow.includes('createFrom'), - }; + } as Action; const openAsDuplicateColumn = { name: i18n.OPEN_AS_DUPLICATE, - icon: 'copy', + icon: 'copy' as typeof ICON_TYPES[number], onClick: ({ savedObjectId }: OpenTimelineResult) => { onOpenTimeline({ duplicate: true, @@ -92,11 +94,11 @@ export const getActionsColumns = ({ 'data-test-subj': 'open-duplicate', available: (item: OpenTimelineResult) => item.timelineType !== TimelineType.template && actionTimelineToShow.includes('duplicate'), - }; + } as Action; const openAsDuplicateTemplateColumn = { name: i18n.OPEN_AS_DUPLICATE_TEMPLATE, - icon: 'copy', + icon: 'copy' as typeof ICON_TYPES[number], onClick: ({ savedObjectId }: OpenTimelineResult) => { onOpenTimeline({ duplicate: true, @@ -109,11 +111,12 @@ export const getActionsColumns = ({ 'data-test-subj': 'open-duplicate-template', available: (item: OpenTimelineResult) => item.timelineType === TimelineType.template && actionTimelineToShow.includes('duplicate'), - }; + } as Action; const exportTimelineAction = { name: i18n.EXPORT_SELECTED, - icon: 'exportAction', + icon: 'exportAction' as typeof ICON_TYPES[number], + type: 'icon', onClick: (selectedTimeline: OpenTimelineResult) => { if (enableExportTimelineDownloader != null) enableExportTimelineDownloader(selectedTimeline); }, @@ -123,11 +126,12 @@ export const getActionsColumns = ({ description: i18n.EXPORT_SELECTED, 'data-test-subj': 'export-timeline', available: () => actionTimelineToShow.includes('export'), - }; + } as Action; const deleteTimelineColumn = { name: i18n.DELETE_SELECTED, - icon: 'trash', + icon: 'trash' as typeof ICON_TYPES[number], + type: 'icon', onClick: (selectedTimeline: OpenTimelineResult) => { if (onOpenDeleteTimelineModal != null) onOpenDeleteTimelineModal(selectedTimeline); }, @@ -136,11 +140,12 @@ export const getActionsColumns = ({ description: i18n.DELETE_SELECTED, 'data-test-subj': 'delete-timeline', available: () => actionTimelineToShow.includes('delete') && deleteTimelines != null, - }; + } as Action; const createRuleFromTimeline = { name: i18n.CREATE_RULE_FROM_TIMELINE, - icon: 'indexEdit', + icon: 'indexEdit' as typeof ICON_TYPES[number], + type: 'icon', onClick: (selectedTimeline: OpenTimelineResult) => { if (onCreateRule != null && selectedTimeline.savedObjectId) onCreateRule(selectedTimeline.savedObjectId); @@ -156,11 +161,12 @@ export const getActionsColumns = ({ onCreateRule != null && queryType != null && queryType.hasQuery, - }; + } as Action; const createRuleFromTimelineCorrelation = { name: i18n.CREATE_RULE_FROM_TIMELINE_CORRELATION, - icon: 'indexEdit', + icon: 'indexEdit' as typeof ICON_TYPES[number], + type: 'icon', onClick: (selectedTimeline: OpenTimelineResult) => { if (onCreateRuleFromEql != null && selectedTimeline.savedObjectId) onCreateRuleFromEql(selectedTimeline.savedObjectId); @@ -176,7 +182,7 @@ export const getActionsColumns = ({ onCreateRuleFromEql != null && queryType != null && queryType.hasEql, - }; + } as Action; return [ { width: hasCrudAccess ? '80px' : '150px', diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.tsx index a040434f0a7df..7c1e0a419683e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.tsx @@ -6,6 +6,7 @@ */ import { EuiButtonIcon, EuiLink } from '@elastic/eui'; +import type { EuiBasicTableColumn, EuiTableDataType } from '@elastic/eui'; import { omit } from 'lodash/fp'; import React from 'react'; import styled from 'styled-components'; @@ -42,8 +43,9 @@ export const getCommonColumns = ({ onToggleShowNotes: OnToggleShowNotes; itemIdToExpandedNotesRowMap: Record; timelineType: TimelineType | null; -}) => [ +}): Array> => [ { + dataType: 'auto' as EuiTableDataType, isExpander: true, render: ({ notes, savedObjectId }: OpenTimelineResult) => notes != null && notes.length > 0 && savedObjectId != null ? ( @@ -64,7 +66,7 @@ export const getCommonColumns = ({ width: ACTION_COLUMN_WIDTH, }, { - dataType: 'string', + dataType: 'string' as EuiTableDataType, field: 'title', name: timelineType === TimelineType.default ? i18n.TIMELINE_NAME : i18n.TIMELINE_TEMPLATE_NAME, render: (title: string, timelineResult: OpenTimelineResult) => @@ -92,7 +94,7 @@ export const getCommonColumns = ({ sortable: false, }, { - dataType: 'string', + dataType: 'string' as EuiTableDataType, field: 'description', name: i18n.DESCRIPTION, render: (description: string) => ( @@ -103,7 +105,7 @@ export const getCommonColumns = ({ sortable: false, }, { - dataType: 'date', + dataType: 'date' as EuiTableDataType, field: 'updated', name: i18n.LAST_MODIFIED, render: (date: number, timelineResult: OpenTimelineResult) => ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/extended_columns.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/extended_columns.tsx index 454ecce7bf2af..3451d260da4f0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/extended_columns.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/extended_columns.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; - +import type { EuiTableDataType } from '@elastic/eui'; import { defaultToEmptyTag } from '../../../../common/components/empty_value'; import * as i18n from '../translations'; @@ -21,7 +21,7 @@ export const getExtendedColumns = (showExtendedColumns: boolean) => { return [ { - dataType: 'string', + dataType: 'string' as EuiTableDataType, field: 'updatedBy', name: i18n.MODIFIED_BY, render: (updatedBy: OpenTimelineResult['updatedBy']) => ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/icon_header_columns.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/icon_header_columns.tsx index f43a713315d1b..412ccd72c815c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/icon_header_columns.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/icon_header_columns.tsx @@ -6,6 +6,7 @@ */ import { EuiIcon, EuiToolTip } from '@elastic/eui'; +import type { EuiTableFieldDataColumnType, HorizontalAlignment } from '@elastic/eui'; import React from 'react'; import { ACTION_COLUMN_WIDTH } from './common_styles'; @@ -22,10 +23,10 @@ export const getIconHeaderColumns = ({ timelineType, }: { timelineType: TimelineTypeLiteralWithNull; -}) => { +}): Array> => { const columns = { note: { - align: 'center', + align: 'center' as HorizontalAlignment, field: 'eventIdToNoteIds', name: ( @@ -40,7 +41,7 @@ export const getIconHeaderColumns = ({ width: ACTION_COLUMN_WIDTH, }, pinnedEvent: { - align: 'center', + align: 'center' as HorizontalAlignment, field: 'pinnedEventIds', name: ( @@ -57,7 +58,7 @@ export const getIconHeaderColumns = ({ width: ACTION_COLUMN_WIDTH, }, favorite: { - align: 'center', + align: 'center' as HorizontalAlignment, field: 'favorite', name: ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.tsx index b4841b68810f7..1e49028326b5d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.tsx @@ -5,9 +5,10 @@ * 2.0. */ -import { EuiBasicTable as _EuiBasicTable } from '@elastic/eui'; +import { EuiBasicTable } from '@elastic/eui'; +import type { EuiBasicTableColumn } from '@elastic/eui'; import React, { useMemo } from 'react'; -import styled from 'styled-components'; +import { css } from '@emotion/react'; import * as i18n from '../translations'; import type { @@ -29,19 +30,6 @@ import { getIconHeaderColumns } from './icon_header_columns'; import type { TimelineTypeLiteralWithNull } from '../../../../../common/api/timeline'; import { TimelineStatus, TimelineType } from '../../../../../common/api/timeline'; import { useUserPrivileges } from '../../../../common/components/user_privileges'; -// there are a number of type mismatches across this file -const EuiBasicTable: any = _EuiBasicTable; // eslint-disable-line @typescript-eslint/no-explicit-any - -const BasicTable = styled(EuiBasicTable)` - .euiTableCellContent { - animation: none; /* Prevents applying max-height from animation */ - } - - .euiTableRow-isExpandedRow .euiTableCellContent__text { - width: 100%; /* Fixes collapsing nested flex content in IE11 */ - } -`; -BasicTable.displayName = 'BasicTable'; /** * Returns the column definitions (passed as the `columns` prop to @@ -77,7 +65,7 @@ export const getTimelinesTableColumns = ({ showExtendedColumns: boolean; timelineType: TimelineTypeLiteralWithNull; hasCrudAccess: boolean; -}) => { +}): Array> => { return [ ...getCommonColumns({ itemIdToExpandedNotesRowMap, @@ -123,8 +111,7 @@ export interface TimelinesTableProps { sortDirection: 'asc' | 'desc'; sortField: string; timelineType: TimelineTypeLiteralWithNull; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - tableRef?: React.MutableRefObject<_EuiBasicTable | undefined>; + tableRef: React.MutableRefObject | null>; totalSearchResultsCount: number; } @@ -157,33 +144,39 @@ export const TimelinesTable = React.memo( timelineType, totalSearchResultsCount, }) => { - const pagination = { - showPerPageOptions: showExtendedColumns, - pageIndex, - pageSize, - pageSizeOptions: [ - Math.floor(Math.max(defaultPageSize, 1) / 2), - defaultPageSize, - defaultPageSize * 2, - ], - totalItemCount: totalSearchResultsCount, - }; + const pagination = useMemo(() => { + return { + showPerPageOptions: showExtendedColumns, + pageIndex, + pageSize, + pageSizeOptions: [ + Math.floor(Math.max(defaultPageSize, 1) / 2), + defaultPageSize, + defaultPageSize * 2, + ], + totalItemCount: totalSearchResultsCount, + }; + }, [defaultPageSize, pageIndex, pageSize, showExtendedColumns, totalSearchResultsCount]); - const sorting = { - sort: { - field: sortField as keyof OpenTimelineResult, - direction: sortDirection, - }, - }; + const sorting = useMemo(() => { + return { + sort: { + field: sortField as keyof OpenTimelineResult, + direction: sortDirection, + }, + }; + }, [sortField, sortDirection]); - const selection = { - selectable: (timelineResult: OpenTimelineResult) => - timelineResult.savedObjectId != null && timelineResult.status !== TimelineStatus.immutable, - selectableMessage: (selectable: boolean) => - !selectable ? i18n.MISSING_SAVED_OBJECT_ID : undefined, - onSelectionChange, - }; - const basicTableProps = tableRef != null ? { ref: tableRef } : {}; + const selection = useMemo(() => { + return { + selectable: (timelineResult: OpenTimelineResult) => + timelineResult.savedObjectId != null && + timelineResult.status !== TimelineStatus.immutable, + selectableMessage: (selectable: boolean) => + !selectable ? i18n.MISSING_SAVED_OBJECT_ID : '', + onSelectionChange, + }; + }, [onSelectionChange]); const { kibanaSecuritySolutionsPrivileges } = useUserPrivileges(); const columns = useMemo( () => @@ -227,7 +220,7 @@ export const TimelinesTable = React.memo( : i18n.ZERO_TIMELINES_MATCH; return ( - ( pagination={pagination} selection={actionTimelineToShow.includes('selectable') ? selection : undefined} sorting={sorting} - {...basicTableProps} + css={css` + .euiTableCellContent { + animation: none; /* Prevents applying max-height from animation */ + } + + .euiTableRow-isExpandedRow .euiTableCellContent__text { + width: 100%; /* Fixes collapsing nested flex content in IE11 */ + } + `} + ref={tableRef} /> ); } diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/mocks.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/mocks.ts index 804d1625df842..075f4aca49f3f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/mocks.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/mocks.ts @@ -33,4 +33,5 @@ export const getMockTimelinesTableProps = ( sortField: DEFAULT_SORT_FIELD, timelineType: TimelineType.default, totalSearchResultsCount: mockOpenTimelineResults.length, + tableRef: { current: null }, }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts index 1373870c0b8aa..fd0fca18adc7f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts @@ -6,6 +6,7 @@ */ import type React from 'react'; +import type { IconType } from '@elastic/eui'; import type { TimelineModel } from '../../store/model'; import type { RowRendererId, @@ -39,11 +40,11 @@ export interface TimelineActionsOverflowColumns { width: string; actions: Array<{ name: string; - icon?: string; + icon: IconType; onClick?: (timeline: OpenTimelineResult) => void; description: string; render?: (timeline: OpenTimelineResult) => JSX.Element; - } | null>; + }>; } /** The results of the query run by the OpenTimeline component */ @@ -117,11 +118,11 @@ export type OnToggleShowNotes = (itemIdToExpandedNotesRowMap: Record { + const original = jest.requireActual('react-router-dom'); return { + ...original, useParams: jest.fn().mockReturnValue('default'), useHistory: jest.fn().mockReturnValue([]), }; @@ -50,7 +53,9 @@ describe('useTimelineTypes', () => { const { result, waitForNextUpdate } = renderHook< UseTimelineTypesArgs, UseTimelineTypesResult - >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 })); + >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), { + wrapper: TestProviders, + }); await waitForNextUpdate(); expect(result.current).toEqual({ timelineType: 'default', @@ -66,7 +71,9 @@ describe('useTimelineTypes', () => { const { result, waitForNextUpdate } = renderHook< UseTimelineTypesArgs, UseTimelineTypesResult - >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 })); + >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), { + wrapper: TestProviders, + }); await waitForNextUpdate(); const { container } = render(result.current.timelineTabs); @@ -84,7 +91,9 @@ describe('useTimelineTypes', () => { const { result, waitForNextUpdate } = renderHook< UseTimelineTypesArgs, UseTimelineTypesResult - >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 })); + >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), { + wrapper: TestProviders, + }); await waitForNextUpdate(); const { container } = render(result.current.timelineTabs); @@ -110,7 +119,9 @@ describe('useTimelineTypes', () => { const { result, waitForNextUpdate } = renderHook< UseTimelineTypesArgs, UseTimelineTypesResult - >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 })); + >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), { + wrapper: TestProviders, + }); await waitForNextUpdate(); const { container } = render(result.current.timelineTabs); @@ -138,7 +149,9 @@ describe('useTimelineTypes', () => { const { result, waitForNextUpdate } = renderHook< UseTimelineTypesArgs, UseTimelineTypesResult - >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 })); + >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), { + wrapper: TestProviders, + }); await waitForNextUpdate(); const { container } = render(<>{result.current.timelineFilters}); @@ -156,7 +169,9 @@ describe('useTimelineTypes', () => { const { result, waitForNextUpdate } = renderHook< UseTimelineTypesArgs, UseTimelineTypesResult - >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 })); + >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), { + wrapper: TestProviders, + }); await waitForNextUpdate(); const { container } = render(<>{result.current.timelineFilters}); @@ -182,7 +197,9 @@ describe('useTimelineTypes', () => { const { result, waitForNextUpdate } = renderHook< UseTimelineTypesArgs, UseTimelineTypesResult - >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 })); + >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), { + wrapper: TestProviders, + }); await waitForNextUpdate(); const { container } = render(<>{result.current.timelineFilters}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx index 5eefa23b0750a..d8943b0f674e7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx @@ -18,6 +18,7 @@ import * as i18n from './translations'; import type { TimelineTab } from './types'; import { TimelineTabsStyle } from './types'; import { useKibana } from '../../../common/lib/kibana'; +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; export interface UseTimelineTypesArgs { defaultTimelineCount?: number | null; templateTimelineCount?: number | null; @@ -42,8 +43,18 @@ export const useTimelineTypes = ({ : TimelineType.default ); - const timelineUrl = formatUrl(getTimelineTabsUrl(TimelineType.default, urlSearch)); - const templateUrl = formatUrl(getTimelineTabsUrl(TimelineType.template, urlSearch)); + const notesEnabled = useIsExperimentalFeatureEnabled('securitySolutionNotesEnabled'); + + const timelineUrl = useMemo(() => { + return formatUrl(getTimelineTabsUrl(TimelineType.default, urlSearch)); + }, [formatUrl, urlSearch]); + const templateUrl = useMemo(() => { + return formatUrl(getTimelineTabsUrl(TimelineType.template, urlSearch)); + }, [formatUrl, urlSearch]); + + const notesUrl = useMemo(() => { + return formatUrl(getTimelineTabsUrl('notes', urlSearch)); + }, [formatUrl, urlSearch]); const goToTimeline = useCallback( (ev) => { @@ -60,6 +71,15 @@ export const useTimelineTypes = ({ }, [navigateToUrl, templateUrl] ); + + const goToNotes = useCallback( + (ev) => { + ev.preventDefault(); + navigateToUrl(notesUrl); + }, + [navigateToUrl, notesUrl] + ); + const getFilterOrTabs: (timelineTabsStyle: TimelineTabsStyle) => TimelineTab[] = useCallback( (timelineTabsStyle: TimelineTabsStyle) => [ { @@ -113,6 +133,17 @@ export const useTimelineTypes = ({ {tab.name} ))} + {notesEnabled && ( + + {'Notes'} + + )} diff --git a/x-pack/plugins/security_solution/public/timelines/links.ts b/x-pack/plugins/security_solution/public/timelines/links.ts index 9315417d97646..97667c0ce8aa3 100644 --- a/x-pack/plugins/security_solution/public/timelines/links.ts +++ b/x-pack/plugins/security_solution/public/timelines/links.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { SecurityPageName, SERVER_APP_ID, TIMELINES_PATH } from '../../common/constants'; -import { TIMELINES } from '../app/translations'; +import { TIMELINES, NOTES } from '../app/translations'; import type { LinkItem } from '../common/links/types'; export const links: LinkItem = { @@ -30,5 +30,16 @@ export const links: LinkItem = { path: `${TIMELINES_PATH}/template`, sideNavDisabled: true, }, + { + id: SecurityPageName.notesManagement, + title: NOTES, + description: i18n.translate('xpack.securitySolution.appLinks.notesManagementDescription', { + defaultMessage: 'Visualize and delete notes.', + }), + path: `${TIMELINES_PATH}/notes`, + skipUrlState: true, + hideTimeline: true, + experimentalKey: 'securitySolutionNotesEnabled', + }, ], }; diff --git a/x-pack/plugins/security_solution/public/timelines/pages/index.tsx b/x-pack/plugins/security_solution/public/timelines/pages/index.tsx index 384a0b86ff62c..0fc2c87246a70 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/index.tsx @@ -17,7 +17,7 @@ import { appendSearch } from '../../common/components/link_to/helpers'; import { TIMELINES_PATH } from '../../../common/constants'; -const timelinesPagePath = `${TIMELINES_PATH}/:tabName(${TimelineType.default}|${TimelineType.template})`; +const timelinesPagePath = `${TIMELINES_PATH}/:tabName(${TimelineType.default}|${TimelineType.template}|notes)`; const timelinesDefaultPath = `${TIMELINES_PATH}/${TimelineType.default}`; export const Timelines = React.memo(() => ( diff --git a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx index 547bedf1caea3..459c37a4133f8 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx @@ -41,7 +41,7 @@ export const TimelinesPage = React.memo(() => { {indicesExist ? ( - {capabilitiesCanUserCRUD && ( + {capabilitiesCanUserCRUD && tabName !== 'notes' ? ( { - )} + ) : null} { setImportDataModalToggle={setImportDataModal} title={i18n.ALL_TIMELINES_PANEL_TITLE} data-test-subj="stateful-open-timeline" + tabName={tabName} /> ) : ( From e5c1b2596b0fe8c50c8ad9caebb4842dbea57d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Tue, 2 Jul 2024 10:00:23 +0200 Subject: [PATCH 053/128] [APM] Co-locate data fetcher and return format for AI Assistant Alert insights (#186971) This co-locates data fetchers with their return values. Before the all data fetching happens at once, then later joined with return values. This makes it easier to make changes and add new data fetchers. --- .../utils/flatten_object.test.ts | 0 .../utils/flatten_object.ts | 0 .../shared/key_value_table/index.tsx | 2 +- .../shared/stacktrace/variables.tsx | 2 +- .../get_log_categories/index.ts | 45 +-- .../index.ts | 292 +++++++++--------- .../observability/server/services/index.ts | 2 +- 7 files changed, 179 insertions(+), 164 deletions(-) rename x-pack/plugins/observability_solution/apm/{public => common}/utils/flatten_object.test.ts (100%) rename x-pack/plugins/observability_solution/apm/{public => common}/utils/flatten_object.ts (100%) diff --git a/x-pack/plugins/observability_solution/apm/public/utils/flatten_object.test.ts b/x-pack/plugins/observability_solution/apm/common/utils/flatten_object.test.ts similarity index 100% rename from x-pack/plugins/observability_solution/apm/public/utils/flatten_object.test.ts rename to x-pack/plugins/observability_solution/apm/common/utils/flatten_object.test.ts diff --git a/x-pack/plugins/observability_solution/apm/public/utils/flatten_object.ts b/x-pack/plugins/observability_solution/apm/common/utils/flatten_object.ts similarity index 100% rename from x-pack/plugins/observability_solution/apm/public/utils/flatten_object.ts rename to x-pack/plugins/observability_solution/apm/common/utils/flatten_object.ts diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/key_value_table/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/key_value_table/index.tsx index e5403397f8425..fdd993fab6c28 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/key_value_table/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/key_value_table/index.tsx @@ -8,7 +8,7 @@ import { castArray } from 'lodash'; import React, { TableHTMLAttributes } from 'react'; import { EuiTable, EuiTableProps, EuiTableBody, EuiTableRow, EuiTableRowCell } from '@elastic/eui'; import { FormattedValue } from './formatted_value'; -import { KeyValuePair } from '../../../utils/flatten_object'; +import { KeyValuePair } from '../../../../common/utils/flatten_object'; export function KeyValueTable({ keyValuePairs, diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/stacktrace/variables.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/stacktrace/variables.tsx index 81a7c2f8e18ff..5dc9a8a5073ba 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/stacktrace/variables.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/stacktrace/variables.tsx @@ -11,7 +11,7 @@ import React from 'react'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; import { KeyValueTable } from '../key_value_table'; -import { flattenObject } from '../../../utils/flatten_object'; +import { flattenObject } from '../../../../common/utils/flatten_object'; const VariablesContainer = euiStyled.div` background: ${({ theme }) => theme.eui.euiColorEmptyShade}; diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts index 9685cb920c17f..c55df1122a71e 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts @@ -9,15 +9,9 @@ import datemath from '@elastic/datemath'; import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { CoreRequestHandlerContext } from '@kbn/core/server'; import { aiAssistantLogsIndexPattern } from '@kbn/observability-ai-assistant-plugin/server'; +import { flattenObject, KeyValuePair } from '../../../../common/utils/flatten_object'; import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; -import { - SERVICE_NAME, - CONTAINER_ID, - HOST_NAME, - KUBERNETES_POD_NAME, - PROCESSOR_EVENT, - TRACE_ID, -} from '../../../../common/es_fields/apm'; +import { PROCESSOR_EVENT, TRACE_ID } from '../../../../common/es_fields/apm'; import { getTypedSearch } from '../../../utils/create_typed_es_client'; import { getDownstreamServiceResource } from '../get_observability_alert_details_context/get_downstream_dependency_name'; @@ -40,24 +34,25 @@ export async function getLogCategories({ arguments: { start: string; end: string; - 'service.name'?: string; - 'host.name'?: string; - 'container.id'?: string; - 'kubernetes.pod.name'?: string; + entities: { + 'service.name'?: string; + 'host.name'?: string; + 'container.id'?: string; + 'kubernetes.pod.name'?: string; + }; }; -}): Promise { +}): Promise<{ + logCategories: LogCategory[]; + entities: KeyValuePair[]; +}> { const start = datemath.parse(args.start)?.valueOf()!; const end = datemath.parse(args.end)?.valueOf()!; - const keyValueFilters = getShouldMatchOrNotExistFilter([ - { field: SERVICE_NAME, value: args[SERVICE_NAME] }, - { field: CONTAINER_ID, value: args[CONTAINER_ID] }, - { field: HOST_NAME, value: args[HOST_NAME] }, - { field: KUBERNETES_POD_NAME, value: args[KUBERNETES_POD_NAME] }, - ]); + const keyValueFilters = getShouldMatchOrNotExistFilter( + Object.entries(args.entities).map(([key, value]) => ({ field: key, value })) + ); const index = await coreContext.uiSettings.client.get(aiAssistantLogsIndexPattern); - const search = getTypedSearch(esClient); const query = { @@ -93,7 +88,8 @@ export async function getLogCategories({ const categorizedLogsRes = await search({ index, - size: 0, + size: 1, + _source: Object.keys(args.entities), track_total_hits: 0, query, aggs: { @@ -144,7 +140,12 @@ export async function getLogCategories({ } ); - return Promise.all(promises ?? []); + const sampleDoc = categorizedLogsRes.hits.hits?.[0]?._source as Record; + + return { + logCategories: await Promise.all(promises ?? []), + entities: flattenObject(sampleDoc), + }; } // field/value pairs should match, or the field should not exist diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/index.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/index.ts index c3a3eb5500869..4e9ae1b546aa7 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/index.ts @@ -6,9 +6,9 @@ */ import { Logger } from '@kbn/core/server'; -import { - AlertDetailsContextualInsightsHandlerQuery, - AlertDetailsContextualInsightsRequestContext, +import type { + AlertDetailsContextualInsight, + AlertDetailsContextualInsightsHandler, } from '@kbn/observability-plugin/server/services'; import moment from 'moment'; import { isEmpty } from 'lodash'; @@ -18,8 +18,11 @@ import { getApmEventClient } from '../../../lib/helpers/get_apm_event_client'; import { getMlClient } from '../../../lib/helpers/get_ml_client'; import { getRandomSampler } from '../../../lib/helpers/get_random_sampler'; import { getApmServiceSummary } from '../get_apm_service_summary'; -import { getAssistantDownstreamDependencies } from '../get_apm_downstream_dependencies'; -import { getLogCategories } from '../get_log_categories'; +import { + APMDownstreamDependency, + getAssistantDownstreamDependencies, +} from '../get_apm_downstream_dependencies'; +import { getLogCategories, LogCategory } from '../get_log_categories'; import { getAnomalies } from '../get_apm_service_summary/get_anomalies'; import { getServiceNameFromSignals } from './get_service_name_from_signals'; import { getContainerIdFromSignals } from './get_container_id_from_signals'; @@ -30,11 +33,8 @@ import { getApmErrors } from './get_apm_errors'; export const getAlertDetailsContextHandler = ( resourcePlugins: APMRouteHandlerResources['plugins'], logger: Logger -) => { - return async ( - requestContext: AlertDetailsContextualInsightsRequestContext, - query: AlertDetailsContextualInsightsHandlerQuery - ) => { +): AlertDetailsContextualInsightsHandler => { + return async (requestContext, query) => { const resources = { getApmIndices: async () => { const coreContext = await requestContext.core; @@ -91,6 +91,7 @@ export const getAlertDetailsContextHandler = ( const serviceEnvironment = query['service.environment']; const hostName = query['host.name']; const kubernetesPodName = query['kubernetes.pod.name']; + const [serviceName, containerId] = await Promise.all([ getServiceNameFromSignals({ query, @@ -106,169 +107,182 @@ export const getAlertDetailsContextHandler = ( }), ]); - async function handleError(cb: () => Promise): Promise { - try { - return await cb(); - } catch (error) { - logger.error('Error while fetching observability alert details context'); - logger.error(error); - return; - } - } - - const serviceSummaryPromise = serviceName - ? handleError(() => - getApmServiceSummary({ - apmEventClient, - annotationsClient, - esClient, - apmAlertsClient, - mlClient, - logger, - arguments: { - 'service.name': serviceName, - 'service.environment': serviceEnvironment, - start: moment(alertStartedAt).subtract(5, 'minute').toISOString(), - end: alertStartedAt, - }, - }) - ) - : undefined; - const downstreamDependenciesPromise = serviceName - ? handleError(() => - getAssistantDownstreamDependencies({ - apmEventClient, - arguments: { - 'service.name': serviceName, - 'service.environment': serviceEnvironment, - start: moment(alertStartedAt).subtract(24, 'hours').toISOString(), - end: alertStartedAt, - }, - randomSampler, - }) - ) + ? getAssistantDownstreamDependencies({ + apmEventClient, + arguments: { + 'service.name': serviceName, + 'service.environment': serviceEnvironment, + start: moment(alertStartedAt).subtract(24, 'hours').toISOString(), + end: alertStartedAt, + }, + randomSampler, + }) : undefined; - const logCategoriesPromise = handleError(() => - getLogCategories({ + const dataFetchers: Array<() => Promise> = []; + + // service summary + if (serviceName) { + dataFetchers.push(async () => { + const serviceSummary = await getApmServiceSummary({ + apmEventClient, + annotationsClient, + esClient, + apmAlertsClient, + mlClient, + logger, + arguments: { + 'service.name': serviceName, + 'service.environment': serviceEnvironment, + start: moment(alertStartedAt).subtract(5, 'minute').toISOString(), + end: alertStartedAt, + }, + }); + + return { + key: 'serviceSummary', + description: `Metadata for the service "${serviceName}" that produced the alert. The alert might be caused by an issue in the service itself or one of its dependencies.`, + data: serviceSummary, + }; + }); + } + + // downstream dependencies + if (serviceName) { + dataFetchers.push(async () => { + const downstreamDependencies = await downstreamDependenciesPromise; + return { + key: 'downstreamDependencies', + description: `Downstream dependencies from the service "${serviceName}". Problems in these services can negatively affect the performance of "${serviceName}"`, + data: downstreamDependencies, + }; + }); + } + + // log categories + dataFetchers.push(async () => { + const downstreamDependencies = await downstreamDependenciesPromise; + const { logCategories, entities } = await getLogCategories({ apmEventClient, esClient, coreContext, arguments: { start: moment(alertStartedAt).subtract(15, 'minute').toISOString(), end: alertStartedAt, - 'service.name': serviceName, - 'host.name': hostName, - 'container.id': containerId, - 'kubernetes.pod.name': kubernetesPodName, + entities: { + 'service.name': serviceName, + 'host.name': hostName, + 'container.id': containerId, + 'kubernetes.pod.name': kubernetesPodName, + }, }, - }) - ); + }); - const apmErrorsPromise = serviceName - ? handleError(() => - getApmErrors({ - apmEventClient, - start: moment(alertStartedAt).subtract(15, 'minute').toISOString(), - end: alertStartedAt, - serviceName, - serviceEnvironment, - }) - ) - : undefined; + const entitiesAsString = entities.map(({ key, value }) => `${key}:${value}`).join(', '); + + return { + key: 'logCategories', + description: `Log events occurring up to 15 minutes before the alert was triggered. Filtered by the entities: ${entitiesAsString}`, + data: logCategoriesWithDownstreamServiceName(logCategories, downstreamDependencies), + }; + }); + + // apm errors + if (serviceName) { + dataFetchers.push(async () => { + const apmErrors = await getApmErrors({ + apmEventClient, + start: moment(alertStartedAt).subtract(15, 'minute').toISOString(), + end: alertStartedAt, + serviceName, + serviceEnvironment, + }); + + const downstreamDependencies = await downstreamDependenciesPromise; + const errorsWithDownstreamServiceName = getApmErrorsWithDownstreamServiceName( + apmErrors, + downstreamDependencies + ); - const serviceChangePointsPromise = handleError(() => - getServiceChangePoints({ + return { + key: 'apmErrors', + description: `Exceptions (errors) thrown by the service "${serviceName}". If an error contains a downstream service name this could be a possible root cause. If relevant please describe what the error means and what it could be caused by.`, + data: errorsWithDownstreamServiceName, + }; + }); + } + + // exit span change points + dataFetchers.push(async () => { + const exitSpanChangePoints = await getExitSpanChangePoints({ apmEventClient, start: moment(alertStartedAt).subtract(6, 'hours').toISOString(), end: alertStartedAt, serviceName, serviceEnvironment, - transactionType: query['transaction.type'], - transactionName: query['transaction.name'], - }) - ); + }); + + return { + key: 'exitSpanChangePoints', + description: `Significant change points for the dependencies of "${serviceName}". Use this to spot dips or spikes in throughput, latency and failure rate for downstream dependencies`, + data: exitSpanChangePoints, + }; + }); - const exitSpanChangePointsPromise = handleError(() => - getExitSpanChangePoints({ + // service change points + dataFetchers.push(async () => { + const serviceChangePoints = await getServiceChangePoints({ apmEventClient, start: moment(alertStartedAt).subtract(6, 'hours').toISOString(), end: alertStartedAt, serviceName, serviceEnvironment, - }) - ); + transactionType: query['transaction.type'], + transactionName: query['transaction.name'], + }); + + return { + key: 'serviceChangePoints', + description: `Significant change points for "${serviceName}". Use this to spot dips and spikes in throughput, latency and failure rate`, + data: serviceChangePoints, + }; + }); - const anomaliesPromise = handleError(() => - getAnomalies({ + // Anomalies + dataFetchers.push(async () => { + const anomalies = await getAnomalies({ start: moment(alertStartedAt).subtract(1, 'hour').valueOf(), end: moment(alertStartedAt).valueOf(), environment: serviceEnvironment, mlClient, logger, - }) - ); - - const [ - serviceSummary, - downstreamDependencies, - logCategories, - apmErrors, - serviceChangePoints, - exitSpanChangePoints, - anomalies, - ] = await Promise.all([ - serviceSummaryPromise, - downstreamDependenciesPromise, - logCategoriesPromise, - apmErrorsPromise, - serviceChangePointsPromise, - exitSpanChangePointsPromise, - anomaliesPromise, - ]); + }); - return [ - { - key: 'serviceSummary', - description: `Metadata for the service "${serviceName}" that produced the alert. The alert might be caused by an issue in the service itself or one of its dependencies.`, - data: serviceSummary, - }, - { - key: 'downstreamDependencies', - description: `Downstream dependencies from the service "${serviceName}". Problems in these services can negatively affect the performance of "${serviceName}"`, - data: downstreamDependencies, - }, - { - key: 'serviceChangePoints', - description: `Significant change points for "${serviceName}". Use this to spot dips and spikes in throughput, latency and failure rate`, - data: serviceChangePoints, - }, - { - key: 'exitSpanChangePoints', - description: `Significant change points for the dependencies of "${serviceName}". Use this to spot dips or spikes in throughput, latency and failure rate for downstream dependencies`, - data: exitSpanChangePoints, - }, - { - key: 'logCategories', - description: `Related log events occurring shortly before the alert was triggered.`, - data: logCategoriesWithDownstreamServiceName(logCategories, downstreamDependencies), - }, - { - key: 'apmErrors', - description: `Exceptions for the service "${serviceName}". If a downstream service name is included this could be a possible root cause. If relevant please describe what the error means and what it could be caused by.`, - data: apmErrorsWithDownstreamServiceName(apmErrors, downstreamDependencies), - }, - { + return { key: 'anomalies', description: `Anomalies for services running in the environment "${serviceEnvironment}". Anomalies are detected using machine learning and can help you spot unusual patterns in your data.`, data: anomalies, - }, - ].filter(({ data }) => !isEmpty(data)); + }; + }); + + const items = await Promise.all( + dataFetchers.map(async (dataFetcher) => { + try { + return await dataFetcher(); + } catch (error) { + logger.error('Error while fetching observability alert details context'); + logger.error(error); + return; + } + }) + ); + + return items.filter((item) => item && !isEmpty(item.data)) as AlertDetailsContextualInsight[]; }; }; -function apmErrorsWithDownstreamServiceName( +function getApmErrorsWithDownstreamServiceName( apmErrors?: Awaited>, downstreamDependencies?: Awaited> ) { @@ -290,8 +304,8 @@ function apmErrorsWithDownstreamServiceName( } function logCategoriesWithDownstreamServiceName( - logCategories?: Awaited>, - downstreamDependencies?: Awaited> + logCategories?: LogCategory[], + downstreamDependencies?: APMDownstreamDependency[] ) { return logCategories?.map( ({ errorCategory, docCount, sampleMessage, downstreamServiceResource }) => { diff --git a/x-pack/plugins/observability_solution/observability/server/services/index.ts b/x-pack/plugins/observability_solution/observability/server/services/index.ts index 840bac95ee48b..3325a9d1dbfea 100644 --- a/x-pack/plugins/observability_solution/observability/server/services/index.ts +++ b/x-pack/plugins/observability_solution/observability/server/services/index.ts @@ -57,7 +57,7 @@ export interface AlertDetailsContextualInsightsRequestContext { }>; licensing: Promise; } -type AlertDetailsContextualInsightsHandler = ( +export type AlertDetailsContextualInsightsHandler = ( context: AlertDetailsContextualInsightsRequestContext, query: AlertDetailsContextualInsightsHandlerQuery ) => Promise; From 6b61af3fdebc79034458c3797fd9353734740150 Mon Sep 17 00:00:00 2001 From: Alex Szabo Date: Tue, 2 Jul 2024 10:16:07 +0200 Subject: [PATCH 054/128] [CI] Use elastic-images-prod everywhere (#185952) ## Summary Some references to `elastic-images-qa` were left in the code, probably as these pipelines were on a pending PR when the rest got changed. Optionally, we should remove all the `imageProject` fields, and everything we're setting defaults - it's just generating bloat. --- .buildkite/pipelines/artifacts.yml | 20 +++++++++---------- .../pipelines/artifacts_container_image.yml | 2 +- .buildkite/pipelines/artifacts_trigger.yml | 2 +- .../rewrite_buildkite_agent_rules.ts | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.buildkite/pipelines/artifacts.yml b/.buildkite/pipelines/artifacts.yml index 3f8a671a2d88a..8a4f407c9f225 100644 --- a/.buildkite/pipelines/artifacts.yml +++ b/.buildkite/pipelines/artifacts.yml @@ -3,7 +3,7 @@ steps: label: Build Kibana Artifacts agents: image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa + imageProject: elastic-images-prod provider: gcp machineType: c2-standard-16 timeout_in_minutes: 75 @@ -18,7 +18,7 @@ steps: label: Artifact Testing agents: image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa + imageProject: elastic-images-prod provider: gcp enableNestedVirtualization: true localSsds: 1 @@ -34,7 +34,7 @@ steps: label: Artifact Testing agents: image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa + imageProject: elastic-images-prod provider: gcp enableNestedVirtualization: true localSsds: 1 @@ -50,7 +50,7 @@ steps: label: Artifact Testing agents: image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa + imageProject: elastic-images-prod provider: gcp enableNestedVirtualization: true localSsds: 1 @@ -66,7 +66,7 @@ steps: label: 'Docker Context Verification' agents: image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa + imageProject: elastic-images-prod provider: gcp localSsds: 1 localSsdInterface: nvme @@ -81,7 +81,7 @@ steps: label: 'Docker Context Verification' agents: image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa + imageProject: elastic-images-prod provider: gcp localSsds: 1 localSsdInterface: nvme @@ -96,7 +96,7 @@ steps: label: 'Docker Context Verification' agents: image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa + imageProject: elastic-images-prod provider: gcp machineType: n2-standard-2 timeout_in_minutes: 30 @@ -109,7 +109,7 @@ steps: label: 'Docker Context Verification' agents: image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa + imageProject: elastic-images-prod provider: gcp localSsds: 1 localSsdInterface: nvme @@ -127,7 +127,7 @@ steps: - exit_status: -1 agents: image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa + imageProject: elastic-images-prod provider: gcp localSsds: 1 localSsdInterface: nvme @@ -154,7 +154,7 @@ steps: label: 'Publish Kibana Artifacts' agents: image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa + imageProject: elastic-images-prod provider: gcp localSsds: 1 localSsdInterface: nvme diff --git a/.buildkite/pipelines/artifacts_container_image.yml b/.buildkite/pipelines/artifacts_container_image.yml index 8f4436fb7db9e..4788625c142d2 100644 --- a/.buildkite/pipelines/artifacts_container_image.yml +++ b/.buildkite/pipelines/artifacts_container_image.yml @@ -3,7 +3,7 @@ steps: label: Build serverless container images agents: image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa + imageProject: elastic-images-prod provider: gcp machineType: n2-standard-16 timeout_in_minutes: 60 diff --git a/.buildkite/pipelines/artifacts_trigger.yml b/.buildkite/pipelines/artifacts_trigger.yml index 98851ddea31ad..760281dd4e584 100644 --- a/.buildkite/pipelines/artifacts_trigger.yml +++ b/.buildkite/pipelines/artifacts_trigger.yml @@ -3,7 +3,7 @@ steps: label: Trigger artifacts build agents: image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa + imageProject: elastic-images-prod provider: gcp machineType: n2-standard-2 timeout_in_minutes: 10 diff --git a/src/dev/buildkite_migration/rewrite_buildkite_agent_rules.ts b/src/dev/buildkite_migration/rewrite_buildkite_agent_rules.ts index 571e6de458365..6d33c3465c59f 100644 --- a/src/dev/buildkite_migration/rewrite_buildkite_agent_rules.ts +++ b/src/dev/buildkite_migration/rewrite_buildkite_agent_rules.ts @@ -210,7 +210,7 @@ function getFullAgentTargetingRule(queue: string): GobldGCPConfig { // Mapping based on expected fields in https://github.com/elastic/ci/blob/0df8430357109a19957dcfb1d867db9cfdd27937/docs/gobld/providers.mdx#L96 return removeNullish({ image: 'family/kibana-ubuntu-2004', - imageProject: 'elastic-images-qa', + imageProject: 'elastic-images-prod', provider: 'gcp', assignExternalIP: agent.disableExternalIp === true ? false : undefined, diskSizeGb: agent.diskSizeGb, From 2758dbbeca298f15000ab578d4be23e43008efc5 Mon Sep 17 00:00:00 2001 From: Jill Guyonnet Date: Tue, 2 Jul 2024 10:01:26 +0100 Subject: [PATCH 055/128] [Fleet] Use API key for standalone agent onboarding (#187133) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Closes https://github.com/elastic/kibana/issues/167218 This PR replaces the username and password with API key config in standalone agent onboarding. Note: the Observability Logs onboarding (at `app/observabilityOnboarding/systemLogs/?category=logs`) shows the encoded API key, I thought it would make more sense to show it in the Beats format (see https://www.elastic.co/guide/en/fleet/current/grant-access-to-elasticsearch.html). ### Testing Below are the steps for testing standalone agent install from the main Fleet UI (Agents table). I am not entirely sure how to test [the component used in CreatePackagePolicyPage](https://github.com/elastic/kibana/blob/main/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_standalone.tsx), so I'd appreciate a comment on that. 🙏 ⚠️ Ideally, this should also be tested in serverless config. 1. Open the Add agent flyout and select Standalone. Notice that the agent yaml config contains an `api_key` entry instead of `username` and `password`. 4. Click the new "Create API key" button. This should create a new API key and that can be copied (you can find this key under Stack Management -> Api keys, it should be called `standalone_agent-{randomString}`). 5. Check that the `{API_KEY}` placeholder in the agent yaml was updated with the API key value. 6. Download an Elastic Agent, e.g. on a VM. 7. Modify the `elastic-agent.yml` file of the agent to the yaml from the UI. If using a Multipass VM, you can for instance download it from the UI and copy it using `multipass transfer :./`. 8. Install the agent as standalone: `sudo ./elastic-agent install` (answer `n` when asked about enrolling in Fleet). 9. Check the agent status and logs with `sudo elastic-agent status` and `sudo elastic-agent logs`. 10. In the UI, go to Discover and search for the agent host name in the logs, it should appear. ### Screenshots Current behaviour (on `main`): Screenshot 2024-06-28 at 10 38 44 With this change: Screenshot 2024-06-28 at 10 31 35 After having clicked "Create API key": Screenshot 2024-06-28 at 10 31 58 ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: criamico Co-authored-by: Elastic Machine --- packages/kbn-doc-links/src/get_doc_links.ts | 1 + packages/kbn-doc-links/src/types.ts | 1 + .../plugins/fleet/common/constants/routes.ts | 3 +- .../services/full_agent_policy_to_yaml.ts | 16 ++- .../fleet/common/types/rest_spec/index.ts | 1 + .../rest_spec/standalone_agent_api_key.ts | 19 +++ .../install_agent_standalone.tsx | 63 ++------- .../agent_enrollment_flyout/hooks.tsx | 120 +++++++++++++++++- .../steps/compute_steps.tsx | 95 ++------------ .../steps/configure_standalone_agent_step.tsx | 92 ++++++++++++-- .../fleet/public/hooks/use_request/index.ts | 1 + .../use_request/standalone_agent_api_key.ts | 24 ++++ x-pack/plugins/fleet/public/types/index.ts | 2 + .../plugins/fleet/server/constants/index.ts | 1 + .../server/routes/agent_policy/handlers.ts | 4 +- x-pack/plugins/fleet/server/routes/index.ts | 2 + .../standalone_agent_api_key/handler.ts | 26 ++++ .../routes/standalone_agent_api_key/index.ts | 34 +++++ .../agent_policies/full_agent_policy.test.ts | 7 +- .../agent_policies/full_agent_policy.ts | 4 +- .../create_standalone_agent_api_key.ts | 31 +++++ .../fleet/server/services/api_keys/index.ts | 1 + .../fleet/server/types/rest_spec/index.ts | 1 + .../rest_spec/standalone_agent_api_key.ts | 14 ++ 24 files changed, 404 insertions(+), 159 deletions(-) create mode 100644 x-pack/plugins/fleet/common/types/rest_spec/standalone_agent_api_key.ts create mode 100644 x-pack/plugins/fleet/public/hooks/use_request/standalone_agent_api_key.ts create mode 100644 x-pack/plugins/fleet/server/routes/standalone_agent_api_key/handler.ts create mode 100644 x-pack/plugins/fleet/server/routes/standalone_agent_api_key/index.ts create mode 100644 x-pack/plugins/fleet/server/services/api_keys/create_standalone_agent_api_key.ts create mode 100644 x-pack/plugins/fleet/server/types/rest_spec/standalone_agent_api_key.ts diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index a135d8a58ae96..951bdbb531d60 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -848,6 +848,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D datastreamsDownsampling: `${ELASTICSEARCH_DOCS}downsampling.html`, installElasticAgent: `${FLEET_DOCS}install-fleet-managed-elastic-agent.html`, installElasticAgentStandalone: `${FLEET_DOCS}install-standalone-elastic-agent.html`, + grantESAccessToStandaloneAgents: `${FLEET_DOCS}grant-access-to-elasticsearch.html`, upgradeElasticAgent: `${FLEET_DOCS}upgrade-elastic-agent.html`, learnMoreBlog: `${ELASTIC_WEBSITE_URL}blog/elastic-agent-and-fleet-make-it-easier-to-integrate-your-systems-with-elastic`, apiKeysLearnMore: isServerless ? `${SERVERLESS_DOCS}api-keys` : `${KIBANA_DOCS}api-keys.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index 7970d7dadb4b9..7a296aac6d8ba 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -537,6 +537,7 @@ export interface DocLinks { datastreamsDownsampling: string; installElasticAgent: string; installElasticAgentStandalone: string; + grantESAccessToStandaloneAgents: string; packageSignatures: string; upgradeElasticAgent: string; learnMoreBlog: string; diff --git a/x-pack/plugins/fleet/common/constants/routes.ts b/x-pack/plugins/fleet/common/constants/routes.ts index 2f32d66f4ec74..0ff598fc0dd47 100644 --- a/x-pack/plugins/fleet/common/constants/routes.ts +++ b/x-pack/plugins/fleet/common/constants/routes.ts @@ -212,8 +212,9 @@ export const DOWNLOAD_SOURCE_API_ROUTES = { DELETE_PATTERN: `${API_ROOT}/agent_download_sources/{sourceId}`, }; -// Fleet debug routes +export const CREATE_STANDALONE_AGENT_API_KEY_ROUTE = `${INTERNAL_ROOT}/create_standalone_agent_api_key`; +// Fleet debug routes export const FLEET_DEBUG_ROUTES = { INDEX_PATTERN: `${INTERNAL_ROOT}/debug/index`, SAVED_OBJECTS_PATTERN: `${INTERNAL_ROOT}/debug/saved_objects`, diff --git a/x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts b/x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts index 18d995c96f2b8..4d464427a998e 100644 --- a/x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts +++ b/x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts @@ -28,15 +28,20 @@ const POLICY_KEYS_ORDER = [ 'signed', ]; -export const fullAgentPolicyToYaml = (policy: FullAgentPolicy, toYaml: typeof safeDump): string => { +export const fullAgentPolicyToYaml = ( + policy: FullAgentPolicy, + toYaml: typeof safeDump, + apiKey?: string +): string => { const yaml = toYaml(policy, { skipInvalid: true, sortKeys: _sortYamlKeys, }); + const formattedYml = apiKey ? replaceApiKey(yaml, apiKey) : yaml; - if (!policy?.secret_references?.length) return yaml; + if (!policy?.secret_references?.length) return formattedYml; - return _formatSecrets(policy.secret_references, yaml); + return _formatSecrets(policy.secret_references, formattedYml); }; export function _sortYamlKeys(keyA: string, keyB: string) { @@ -67,3 +72,8 @@ function _formatSecrets( return formattedText; } + +function replaceApiKey(ymlText: string, apiKey: string) { + const regex = new RegExp(/\'\${API_KEY}\'/, 'g'); + return ymlText.replace(regex, `'${apiKey}'`); +} diff --git a/x-pack/plugins/fleet/common/types/rest_spec/index.ts b/x-pack/plugins/fleet/common/types/rest_spec/index.ts index 34613da13f9d3..7aeaad859803b 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/index.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/index.ts @@ -20,3 +20,4 @@ export * from './package_policy'; export * from './settings'; export * from './health_check'; export * from './fleet_server_hosts'; +export * from './standalone_agent_api_key'; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/standalone_agent_api_key.ts b/x-pack/plugins/fleet/common/types/rest_spec/standalone_agent_api_key.ts new file mode 100644 index 0000000000000..3bb4e3f05ed64 --- /dev/null +++ b/x-pack/plugins/fleet/common/types/rest_spec/standalone_agent_api_key.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 { SecurityCreateApiKeyResponse } from '@elastic/elasticsearch/lib/api/types'; + +export interface PostStandaloneAgentAPIKeyRequest { + body: { + name: string; + }; +} + +export interface PostStandaloneAgentAPIKeyResponse { + action: string; + item: SecurityCreateApiKeyResponse; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_standalone.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_standalone.tsx index e2f92331759ac..1b0e791fbfd8c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_standalone.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_standalone.tsx @@ -5,79 +5,37 @@ * 2.0. */ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiSteps, EuiSpacer } from '@elastic/eui'; -import { safeDump } from 'js-yaml'; -import type { FullAgentPolicy } from '../../../../../../../../../../common/types/models/agent_policy'; -import { API_VERSIONS } from '../../../../../../../../../../common/constants'; import { getRootIntegrations } from '../../../../../../../../../../common/services'; import { AgentStandaloneBottomBar, StandaloneModeWarningCallout, NotObscuredByBottomBar, } from '../..'; -import { - fullAgentPolicyToYaml, - agentPolicyRouteService, -} from '../../../../../../../../../services'; import { Error as FleetError } from '../../../../../../../components'; -import { - useKibanaVersion, - useStartServices, - sendGetOneAgentPolicyFull, -} from '../../../../../../../../../hooks'; +import { useKibanaVersion } from '../../../../../../../../../hooks'; import { InstallStandaloneAgentStep, ConfigureStandaloneAgentStep, } from '../../../../../../../../../components/agent_enrollment_flyout/steps'; import { StandaloneInstructions } from '../../../../../../../../../components/enrollment_instructions'; +import { useFetchFullPolicy } from '../../../../../../../../../components/agent_enrollment_flyout/hooks'; + import type { InstallAgentPageProps } from './types'; export const InstallElasticAgentStandalonePageStep: React.FC = (props) => { const { setIsManaged, agentPolicy, cancelUrl, onNext, cancelClickHandler } = props; - const core = useStartServices(); + const kibanaVersion = useKibanaVersion(); - const [yaml, setYaml] = useState(''); const [commandCopied, setCommandCopied] = useState(false); const [policyCopied, setPolicyCopied] = useState(false); - const [fullAgentPolicy, setFullAgentPolicy] = useState(); - useEffect(() => { - async function fetchFullPolicy() { - try { - if (!agentPolicy?.id) { - return; - } - const query = { standalone: true, kubernetes: false }; - const res = await sendGetOneAgentPolicyFull(agentPolicy?.id, query); - if (res.error) { - throw res.error; - } - - if (!res.data) { - throw new Error('No data while fetching full agent policy'); - } - setFullAgentPolicy(res.data.item); - } catch (error) { - core.notifications.toasts.addError(error, { - title: 'Error', - }); - } - } - fetchFullPolicy(); - }, [core.http.basePath, agentPolicy?.id, core.notifications.toasts]); - - useEffect(() => { - if (!fullAgentPolicy) { - return; - } - - setYaml(fullAgentPolicyToYaml(fullAgentPolicy, safeDump)); - }, [fullAgentPolicy]); + const { yaml, onCreateApiKey, apiKey, downloadYaml } = useFetchFullPolicy(agentPolicy); if (!agentPolicy) { return ( @@ -95,16 +53,13 @@ export const InstallElasticAgentStandalonePageStep: React.FC setPolicyCopied(true), }), diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/hooks.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/hooks.tsx index 139268fbb2408..326a3c973ea00 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/hooks.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/hooks.tsx @@ -4,11 +4,20 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { useState, useEffect, useMemo } from 'react'; +import crypto from 'crypto'; + +import { useState, useEffect, useMemo, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; +import { safeDump } from 'js-yaml'; + import type { PackagePolicy, AgentPolicy } from '../../types'; -import { sendGetOneAgentPolicy, useGetPackageInfoByKeyQuery, useStartServices } from '../../hooks'; +import { + sendGetOneAgentPolicy, + sendGetOneAgentPolicyFull, + useGetPackageInfoByKeyQuery, + useStartServices, +} from '../../hooks'; import { FLEET_KUBERNETES_PACKAGE, FLEET_CLOUD_SECURITY_POSTURE_PACKAGE, @@ -23,6 +32,12 @@ import { SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG, } from '../cloud_security_posture/services'; +import { sendCreateStandaloneAgentAPIKey } from '../../hooks'; + +import type { FullAgentPolicy } from '../../../common'; + +import { fullAgentPolicyToYaml } from '../../services'; + import type { K8sMode, CloudSecurityIntegrationType, @@ -190,3 +205,104 @@ const getCloudSecurityPackagePolicyFromAgentPolicy = ( (input) => input.package?.name === FLEET_CLOUD_SECURITY_POSTURE_PACKAGE ); }; + +export function useGetCreateApiKey() { + const core = useStartServices(); + + const [apiKey, setApiKey] = useState(undefined); + const onCreateApiKey = useCallback(async () => { + try { + const res = await sendCreateStandaloneAgentAPIKey({ + name: crypto.randomBytes(16).toString('hex'), + }); + const newApiKey = `${res.data?.item.id}:${res.data?.item.api_key}`; + setApiKey(newApiKey); + } catch (err) { + core.notifications.toasts.addError(err, { + title: i18n.translate('xpack.fleet.standaloneAgentPage.errorCreatingAgentAPIKey', { + defaultMessage: 'Error creating Agent API Key', + }), + }); + } + }, [core.notifications.toasts]); + return { + apiKey, + onCreateApiKey, + }; +} + +export function useFetchFullPolicy(agentPolicy: AgentPolicy | undefined, isK8s?: K8sMode) { + const core = useStartServices(); + const [yaml, setYaml] = useState(''); + const [fullAgentPolicy, setFullAgentPolicy] = useState(); + const { apiKey, onCreateApiKey } = useGetCreateApiKey(); + + useEffect(() => { + async function fetchFullPolicy() { + try { + if (!agentPolicy?.id) { + return; + } + let query = { standalone: true, kubernetes: false }; + if (isK8s === 'IS_KUBERNETES') { + query = { standalone: true, kubernetes: true }; + } + const res = await sendGetOneAgentPolicyFull(agentPolicy?.id, query); + if (res.error) { + throw res.error; + } + + if (!res.data) { + throw new Error('No data while fetching full agent policy'); + } + setFullAgentPolicy(res.data.item); + } catch (error) { + core.notifications.toasts.addError(error, { + title: i18n.translate('xpack.fleet.standaloneAgentPage.errorFetchingFullAgentPolicy', { + defaultMessage: 'Error fetching full agent policy', + }), + }); + } + } + + if (isK8s === 'IS_NOT_KUBERNETES' || isK8s !== 'IS_LOADING') { + fetchFullPolicy(); + } + }, [core.http.basePath, agentPolicy?.id, core.notifications.toasts, apiKey, isK8s, agentPolicy]); + + useEffect(() => { + if (!fullAgentPolicy) { + return; + } + + if (isK8s === 'IS_KUBERNETES') { + if (typeof fullAgentPolicy === 'object') { + return; + } + setYaml(fullAgentPolicy); + } else { + if (typeof fullAgentPolicy === 'string') { + return; + } + setYaml(fullAgentPolicyToYaml(fullAgentPolicy, safeDump, apiKey)); + } + }, [apiKey, fullAgentPolicy, isK8s]); + + const downloadYaml = useMemo( + () => () => { + const link = document.createElement('a'); + link.href = `data:text/json;charset=utf-8,${yaml}`; + link.download = `elastic-agent.yaml`; + link.click(); + }, + [yaml] + ); + + return { + yaml, + onCreateApiKey, + fullAgentPolicy, + apiKey, + downloadYaml, + }; +} diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx index 615c6399b202f..bc4e7755044e7 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx @@ -5,28 +5,20 @@ * 2.0. */ -import React, { useState, useMemo, useEffect } from 'react'; +import React, { useState, useMemo } from 'react'; import { EuiSteps, EuiLoadingSpinner } from '@elastic/eui'; -import { safeDump } from 'js-yaml'; import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; -import type { FullAgentPolicy } from '../../../../common/types/models/agent_policy'; -import { API_VERSIONS } from '../../../../common/constants'; import { getRootIntegrations } from '../../../../common/services'; -import { fullAgentPolicyToYaml, agentPolicyRouteService } from '../../../services'; import { getGcpIntegrationDetailsFromAgentPolicy } from '../../cloud_security_posture/services'; import { StandaloneInstructions, ManualInstructions } from '../../enrollment_instructions'; -import { - useGetOneEnrollmentAPIKey, - useStartServices, - sendGetOneAgentPolicyFull, - useAgentVersion, -} from '../../../hooks'; +import { useGetOneEnrollmentAPIKey, useStartServices, useAgentVersion } from '../../../hooks'; +import { useFetchFullPolicy } from '../hooks'; import type { InstructionProps } from '../types'; import { usePollingAgentCount } from '../confirm_agent_enrollment'; @@ -62,74 +54,7 @@ export const StandaloneSteps: React.FunctionComponent = ({ isK8s, cloudSecurityIntegration, }) => { - const core = useStartServices(); - const { notifications } = core; - const [fullAgentPolicy, setFullAgentPolicy] = useState(); - const [yaml, setYaml] = useState(''); - - let downloadLink = ''; - - if (selectedPolicy?.id) { - downloadLink = - isK8s === 'IS_KUBERNETES' - ? core.http.basePath.prepend( - `${agentPolicyRouteService.getInfoFullDownloadPath( - selectedPolicy?.id - )}?kubernetes=true&standalone=true&apiVersion=${API_VERSIONS.public.v1}` - ) - : core.http.basePath.prepend( - `${agentPolicyRouteService.getInfoFullDownloadPath( - selectedPolicy?.id - )}?standalone=true&apiVersion=${API_VERSIONS.public.v1}` - ); - } - - useEffect(() => { - async function fetchFullPolicy() { - try { - if (!selectedPolicy?.id) { - return; - } - let query = { standalone: true, kubernetes: false }; - if (isK8s === 'IS_KUBERNETES') { - query = { standalone: true, kubernetes: true }; - } - const res = await sendGetOneAgentPolicyFull(selectedPolicy?.id, query); - if (res.error) { - throw res.error; - } - - if (!res.data) { - throw new Error('No data while fetching full agent policy'); - } - setFullAgentPolicy(res.data.item); - } catch (error) { - notifications.toasts.addError(error, { - title: 'Error', - }); - } - } - if (isK8s !== 'IS_LOADING') { - fetchFullPolicy(); - } - }, [selectedPolicy, notifications.toasts, isK8s, core.http.basePath]); - - useEffect(() => { - if (!fullAgentPolicy) { - return; - } - if (isK8s === 'IS_KUBERNETES') { - if (typeof fullAgentPolicy === 'object') { - return; - } - setYaml(fullAgentPolicy); - } else { - if (typeof fullAgentPolicy === 'string') { - return; - } - setYaml(fullAgentPolicyToYaml(fullAgentPolicy, safeDump)); - } - }, [fullAgentPolicy, isK8s]); + const { yaml, onCreateApiKey, apiKey, downloadYaml } = useFetchFullPolicy(selectedPolicy, isK8s); const agentVersion = useAgentVersion(); @@ -160,7 +85,9 @@ export const StandaloneSteps: React.FunctionComponent = ({ isK8s, selectedPolicyId: selectedPolicy?.id, yaml, - downloadLink, + downloadYaml, + apiKey, + onCreateApiKey, }) ); @@ -176,8 +103,6 @@ export const StandaloneSteps: React.FunctionComponent = ({ return steps; }, [ agentVersion, - isK8s, - cloudSecurityIntegration, agentPolicy, selectedPolicy, agentPolicies, @@ -186,8 +111,12 @@ export const StandaloneSteps: React.FunctionComponent = ({ setSelectedPolicyId, refreshAgentPolicies, selectionType, + isK8s, yaml, - downloadLink, + downloadYaml, + apiKey, + onCreateApiKey, + cloudSecurityIntegration, mode, setMode, ]); diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/configure_standalone_agent_step.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/configure_standalone_agent_step.tsx index 289c5b8ad8df2..30b08bf3a808e 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/configure_standalone_agent_step.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/configure_standalone_agent_step.tsx @@ -16,6 +16,9 @@ import { EuiCopy, EuiCodeBlock, EuiLink, + EuiCallOut, + EuiFieldText, + EuiButtonIcon, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; @@ -27,21 +30,25 @@ import { useStartServices } from '../../../hooks'; export const ConfigureStandaloneAgentStep = ({ isK8s, - selectedPolicyId, yaml, - downloadLink, + downloadYaml, + apiKey, + onCreateApiKey, isComplete, onCopy, }: { isK8s?: K8sMode; selectedPolicyId?: string; yaml: string; - downloadLink: string; + downloadYaml: () => void; + apiKey: string | undefined; + onCreateApiKey: () => void; isComplete?: boolean; onCopy?: () => void; }): EuiContainedStepProps => { const core = useStartServices(); const { docLinks } = core; + const policyMsg = isK8s === 'IS_KUBERNETES' ? ( elastic-agent.yml, - ESUsernameVariable: ES_USERNAME, - ESPasswordVariable: ES_PASSWORD, + apiKeyVariable: API_KEY, outputSection: outputs, + guideLink: ( + + + + ), }} /> ); @@ -89,6 +107,7 @@ export const ConfigureStandaloneAgentStep = ({ defaultMessage="Download Policy" /> ); + return { title: i18n.translate('xpack.fleet.agentEnrollment.stepConfigureAgentTitle', { defaultMessage: 'Configure the agent', @@ -99,7 +118,63 @@ export const ConfigureStandaloneAgentStep = ({ <>{policyMsg} + {apiKey && ( + +

      + {i18n.translate('xpack.fleet.agentEnrollment.apiKeyBanner.created.description', { + defaultMessage: + 'Remember to store this information in a safe place. It won’t be displayed anymore after you continue.', + })} +

      + + {(copy) => ( + svg.euiIcon': { + borderRadius: '0 !important', + }, + }} + aria-label={i18n.translate('xpack.fleet.apiKeyBanner.field.copyButton', { + defaultMessage: 'Copy to clipboard', + })} + /> + )} + + } + /> +
      + )} + + + + + + {(copy) => ( @@ -119,14 +194,13 @@ export const ConfigureStandaloneAgentStep = ({ - {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} { if (onCopy) onCopy(); + downloadYaml(); }} - isDisabled={!downloadLink} + isDisabled={!downloadYaml} > <>{downloadMsg} diff --git a/x-pack/plugins/fleet/public/hooks/use_request/index.ts b/x-pack/plugins/fleet/public/hooks/use_request/index.ts index 448b934bb3411..dc2f1292220cb 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/index.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/index.ts @@ -12,6 +12,7 @@ export * from './data_stream'; export * from './agents'; export * from './enrollment_api_keys'; export * from './epm'; +export * from './standalone_agent_api_key'; export * from './outputs'; export * from './settings'; export * from './setup'; diff --git a/x-pack/plugins/fleet/public/hooks/use_request/standalone_agent_api_key.ts b/x-pack/plugins/fleet/public/hooks/use_request/standalone_agent_api_key.ts new file mode 100644 index 0000000000000..3df53fd4f35f1 --- /dev/null +++ b/x-pack/plugins/fleet/public/hooks/use_request/standalone_agent_api_key.ts @@ -0,0 +1,24 @@ +/* + * 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 { + PostStandaloneAgentAPIKeyRequest, + PostStandaloneAgentAPIKeyResponse, +} from '../../types'; + +import { API_VERSIONS, CREATE_STANDALONE_AGENT_API_KEY_ROUTE } from '../../../common/constants'; + +import { sendRequest } from './use_request'; + +export function sendCreateStandaloneAgentAPIKey(body: PostStandaloneAgentAPIKeyRequest['body']) { + return sendRequest({ + method: 'post', + path: CREATE_STANDALONE_AGENT_API_KEY_ROUTE, + version: API_VERSIONS.internal.v1, + body, + }); +} diff --git a/x-pack/plugins/fleet/public/types/index.ts b/x-pack/plugins/fleet/public/types/index.ts index 82c13c6fc0e52..aeb6d302adaa8 100644 --- a/x-pack/plugins/fleet/public/types/index.ts +++ b/x-pack/plugins/fleet/public/types/index.ts @@ -80,6 +80,8 @@ export type { GetOneEnrollmentAPIKeyResponse, PostEnrollmentAPIKeyRequest, PostEnrollmentAPIKeyResponse, + PostStandaloneAgentAPIKeyRequest, + PostStandaloneAgentAPIKeyResponse, PostLogstashApiKeyResponse, GetOutputsResponse, GetCurrentUpgradesResponse, diff --git a/x-pack/plugins/fleet/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts index adb7858094865..d727cd30c6385 100644 --- a/x-pack/plugins/fleet/server/constants/index.ts +++ b/x-pack/plugins/fleet/server/constants/index.ts @@ -38,6 +38,7 @@ export { PRECONFIGURATION_API_ROUTES, DOWNLOAD_SOURCE_API_ROOT, DOWNLOAD_SOURCE_API_ROUTES, + CREATE_STANDALONE_AGENT_API_KEY_ROUTE, FLEET_DEBUG_ROUTES, // Saved Object indices INGEST_SAVED_OBJECT_INDEX, diff --git a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts index b6355bf0c7f78..8b7a93f6f332e 100644 --- a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts @@ -377,7 +377,9 @@ export const getFullAgentPolicy: FleetRequestHandler< const fullAgentPolicy = await agentPolicyService.getFullAgentPolicy( soClient, request.params.agentPolicyId, - { standalone: request.query.standalone === true } + { + standalone: request.query.standalone === true, + } ); if (fullAgentPolicy) { const body: GetFullAgentPolicyResponse = { diff --git a/x-pack/plugins/fleet/server/routes/index.ts b/x-pack/plugins/fleet/server/routes/index.ts index 77c4fa9eb4249..29efa03967ea0 100644 --- a/x-pack/plugins/fleet/server/routes/index.ts +++ b/x-pack/plugins/fleet/server/routes/index.ts @@ -26,6 +26,7 @@ import { registerRoutes as registerFleetServerHostRoutes } from './fleet_server_ import { registerRoutes as registerFleetProxiesRoutes } from './fleet_proxies'; import { registerRoutes as registerMessageSigningServiceRoutes } from './message_signing_service'; import { registerRoutes as registerUninstallTokenRoutes } from './uninstall_token'; +import { registerRoutes as registerStandaloneAgentApiKeyRoutes } from './standalone_agent_api_key'; import { registerRoutes as registerDebugRoutes } from './debug'; export function registerRoutes(fleetAuthzRouter: FleetAuthzRouter, config: FleetConfigType) { @@ -48,6 +49,7 @@ export function registerRoutes(fleetAuthzRouter: FleetAuthzRouter, config: Fleet registerHealthCheckRoutes(fleetAuthzRouter); registerMessageSigningServiceRoutes(fleetAuthzRouter); registerUninstallTokenRoutes(fleetAuthzRouter, config); + registerStandaloneAgentApiKeyRoutes(fleetAuthzRouter); registerDebugRoutes(fleetAuthzRouter); // Conditional config routes diff --git a/x-pack/plugins/fleet/server/routes/standalone_agent_api_key/handler.ts b/x-pack/plugins/fleet/server/routes/standalone_agent_api_key/handler.ts new file mode 100644 index 0000000000000..99c349899aaa6 --- /dev/null +++ b/x-pack/plugins/fleet/server/routes/standalone_agent_api_key/handler.ts @@ -0,0 +1,26 @@ +/* + * 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 { TypeOf } from '@kbn/config-schema'; + +import { createStandaloneAgentApiKey } from '../../services/api_keys'; +import type { FleetRequestHandler, PostStandaloneAgentAPIKeyRequestSchema } from '../../types'; + +export const createStandaloneAgentApiKeyHandler: FleetRequestHandler< + undefined, + undefined, + TypeOf +> = async (context, request, response) => { + const coreContext = await context.core; + const esClient = coreContext.elasticsearch.client.asCurrentUser; + const key = await createStandaloneAgentApiKey(esClient, request.body.name); + return response.ok({ + body: { + item: key, + }, + }); +}; diff --git a/x-pack/plugins/fleet/server/routes/standalone_agent_api_key/index.ts b/x-pack/plugins/fleet/server/routes/standalone_agent_api_key/index.ts new file mode 100644 index 0000000000000..9255f058aee46 --- /dev/null +++ b/x-pack/plugins/fleet/server/routes/standalone_agent_api_key/index.ts @@ -0,0 +1,34 @@ +/* + * 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 { FleetAuthzRouter } from '../../services/security'; + +import { API_VERSIONS } from '../../../common/constants'; + +import { CREATE_STANDALONE_AGENT_API_KEY_ROUTE } from '../../constants'; + +import { PostStandaloneAgentAPIKeyRequestSchema } from '../../types'; + +import { createStandaloneAgentApiKeyHandler } from './handler'; + +export const registerRoutes = (router: FleetAuthzRouter) => { + router.versioned + .post({ + path: CREATE_STANDALONE_AGENT_API_KEY_ROUTE, + access: 'internal', + fleetAuthz: { + fleet: { all: true }, + }, + }) + .addVersion( + { + version: API_VERSIONS.internal.v1, + validate: { request: PostStandaloneAgentAPIKeyRequestSchema }, + }, + createStandaloneAgentApiKeyHandler + ); +}; diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts index 5705f569d9fa4..5701a60b56d03 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts @@ -817,7 +817,7 @@ ssl.test: 123 `); }); - it('should return placeholder ES_USERNAME and ES_PASSWORD for elasticsearch output type in standalone ', () => { + it('should return placeholder API_KEY for elasticsearch output type in standalone ', () => { const policyOutput = transformOutputToFullPolicyOutput( { id: 'id123', @@ -833,18 +833,17 @@ ssl.test: 123 expect(policyOutput).toMatchInlineSnapshot(` Object { + "api_key": "\${API_KEY}", "hosts": Array [ "http://host.fr", ], - "password": "\${ES_PASSWORD}", "preset": "balanced", "type": "elasticsearch", - "username": "\${ES_USERNAME}", } `); }); - it('should not return placeholder ES_USERNAME and ES_PASSWORD for logstash output type in standalone ', () => { + it('should not return placeholder API_KEY for logstash output type in standalone ', () => { const policyOutput = transformOutputToFullPolicyOutput( { id: 'id123', diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts index b8e64be494651..efc3a732149d6 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts @@ -491,8 +491,8 @@ export function transformOutputToFullPolicyOutput( } if (output.type === outputType.Elasticsearch && standalone) { - newOutput.username = '${ES_USERNAME}'; - newOutput.password = '${ES_PASSWORD}'; + // adding a place_holder as API_KEY + newOutput.api_key = '${API_KEY}'; } if (output.type === outputType.RemoteElasticsearch) { diff --git a/x-pack/plugins/fleet/server/services/api_keys/create_standalone_agent_api_key.ts b/x-pack/plugins/fleet/server/services/api_keys/create_standalone_agent_api_key.ts new file mode 100644 index 0000000000000..011d8dfe8ec82 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/api_keys/create_standalone_agent_api_key.ts @@ -0,0 +1,31 @@ +/* + * 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 { ElasticsearchClient } from '@kbn/core/server'; + +export function createStandaloneAgentApiKey(esClient: ElasticsearchClient, name: string) { + // Based on https://www.elastic.co/guide/en/fleet/master/grant-access-to-elasticsearch.html#create-api-key-standalone-agent + return esClient.security.createApiKey({ + body: { + name: `standalone_agent-${name}`, + metadata: { + managed: true, + }, + role_descriptors: { + standalone_agent: { + cluster: ['monitor'], + indices: [ + { + names: ['logs-*-*', 'metrics-*-*', 'traces-*-*', 'synthetics-*-*'], + privileges: ['auto_configure', 'create_doc'], + }, + ], + }, + }, + }, + }); +} diff --git a/x-pack/plugins/fleet/server/services/api_keys/index.ts b/x-pack/plugins/fleet/server/services/api_keys/index.ts index 7b96d71c7ac9c..6421de567b742 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/index.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/index.ts @@ -8,3 +8,4 @@ export { invalidateAPIKeys } from './security'; export { generateLogstashApiKey, canCreateLogstashApiKey } from './logstash_api_keys'; export * from './enrollment_api_key'; +export { createStandaloneAgentApiKey } from './create_standalone_agent_api_key'; diff --git a/x-pack/plugins/fleet/server/types/rest_spec/index.ts b/x-pack/plugins/fleet/server/types/rest_spec/index.ts index ebdaa02902e37..04f9322354104 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/index.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/index.ts @@ -23,3 +23,4 @@ export * from './tags'; export * from './health_check'; export * from './message_signing_service'; export * from './app'; +export * from './standalone_agent_api_key'; diff --git a/x-pack/plugins/fleet/server/types/rest_spec/standalone_agent_api_key.ts b/x-pack/plugins/fleet/server/types/rest_spec/standalone_agent_api_key.ts new file mode 100644 index 0000000000000..f63db720a97cc --- /dev/null +++ b/x-pack/plugins/fleet/server/types/rest_spec/standalone_agent_api_key.ts @@ -0,0 +1,14 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +export const PostStandaloneAgentAPIKeyRequestSchema = { + body: schema.object({ + name: schema.string(), + }), +}; From d003ae302372d4140f33c48dc05463a78f7e8896 Mon Sep 17 00:00:00 2001 From: Abdul Wahab Zahid Date: Tue, 2 Jul 2024 11:26:26 +0200 Subject: [PATCH 056/128] [Dataset quality] EBT for Datasets table and Dataset Details (#187125) Implement the Event Based Telemetry for Datasets table (main page) and the Dataset details (the flyout at the time of writing). The following EBT events are reported: - `"Dataset Quality Navigated"` - `"Dataset Quality Dataset Details Opened"` - `"Dataset Quality Dataset Details Navigated"` The above allow to track the following: 1. Used query, available and chosen filters for "Integrations", "Namespaces" and "Qualities" when user clicks on the degraded documents percentage link (with `_ignored` filter) from main table's column or "Open" link from row actions. 2. Dataset health, percentage of degraded documents, duration to load and breakdown field when user opens the flyout. 3. All included in 2 plus whether `_ignored` filter is present in navigation, source and target of the navigation when user navigates away from the flyout. 4. All events also track selected date range and user's privileges state for the respective data stream. ### Main page - Datasets table Event Name: `"Dataset Quality Navigated"` This event is reported whenever a degraded percentage link or "Open" link is navigated to on the main datasets table. The following properties are tracked: ### Properties | Property | Type | Schema Type | Description | | --- | --- | --- | --- | | `index_name` | string | keyword | The name of the index e.g. `logs-apache.access-default` | | `data_stream` | object | object | Object containing [ECS Data Stream Fields](https://www.elastic.co/guide/en/ecs/current/ecs-data_stream.html) i.e. `dataset`, `namespace`, and `type` | | `data_stream_health` | object | keyword | Any of `"poor"`, `"degraded"` and `"good"` representing the health/quality of data stream | | `data_stream_aggregatable` | boolean | boolean | A boolean indicating whether the data stream is aggregatable for the `_ignored` field | | `from` | string | date | ISO start date string selected on datepicker | | `to` | string | date | ISO end date string selected on datepicker | | `degraded_percentage` | number | float | A number representing the percentage of degraded documents in the data stream | | `integration` | string (optional) | keyword | An optional string representing the integration name associated with the dataset | | `privileges` | object | object | An object representing the privileges. It includes `can_monitor_data_stream`, `can_view_integrations`, and an optional `can_view_dashboards`. All are boolean. | | `filters` | object | object | An object containing filter details. It includes `is_degraded`, `query_length`, `integrations`, `namespaces`, and `qualities`. See below for more details | The `filters` property is an object with the following sub-properties: |
      Sub-Property
      |
      Type
      |
      Schema Type
      | Description | | --- | --- | --- | --- | | `is_degraded` | boolean | boolean | A boolean indicating whether navigation included `ignored` filter | | `query_length` | number | short | The length of the query used | | `integrations` | object | object | An object including `total`, `included`, and `excluded` properties representing applied filters. | | `namespaces` | object | object | An object including `total`, `included`, and `excluded` properties representing applied filters | | `qualities` | object | object | An object including `total`, `included`, and `excluded` properties representing applied filters | ### Details page - Flyout Event `"Dataset Quality Dataset Details Opened"` is reported when flyout is opened whereas `"Dataset Quality Dataset Details Navigated"` is reported when a link is clicked on the flyout which navigates the user away from Dataset Quality page. Important properties are tracked which help analyse the state user had before the navigation e.g. breakdown field, selected date range and whether user clicked the degraded docs or all docs link. _Note that, the flyout is expected to be converted into a routed page, hence "Dataset Details" is used for event names instead of the flyout._ #### Properties `"Dataset Quality Dataset Details Opened"` only differs from [`"Dataset Quality Navigated"`](#dqn) by the following properties: |
      Property
      |
      Type
      |
      Schema Type
      | Description | | --- | --- | --- | --- | | `tracking_id` | string | keyword | Id to group flyout opening and navigation for funnel analysis | | `duration` | number | long | Time it took in milliseconds from opening the flyout until the data stream details are available | | `breakdown_field` | string (optional) | keyword | Fields used to break the chart down by | `"Dataset Quality Dataset Details Navigated"` only differs from [`"Dataset Quality Navigated"`](#dqn) by the following properties: |
      Property
      |
      Type
      |
      Schema Type
      | Description | | --- | --- | --- | --- | | `tracking_id` | string | keyword | Id to group flyout opening and navigation for funnel analysis | | `filters` | object | object | `{ "is_degraded": }` which represent whether the user is navigating with `_ignored` filter applied| | `breakdown_field` | string (optional) | keyword | Fields used to break the chart down by | | `target` | enum value | keyword | Action that user took to navigate away from the dataset details page. Possible values are `Exit`, `LogsExplorer`, `Discover`, `Lens`, `Integration`, `IndexTemplate`, `Dashboard`, `Hosts` and `Services` | | `source` | enum value | keyword | Section of dataset details page the action is originated from. Possible values are `"Header"`, `"Footer"`, `"Summary"`, `"Chart"`, `"Table"` and `"ActionMenu"` | --- .../components/dataset_quality/context.ts | 2 + .../dataset_quality/dataset_quality.tsx | 7 +- .../dataset_quality/table/columns.tsx | 8 +- .../table/degraded_docs_percentage_link.tsx | 10 +- .../public/components/flyout/flyout.tsx | 10 +- .../flyout_summary_kpi_item.tsx | 15 +- .../flyout_summary/flyout_summary_kpis.tsx | 19 +- .../flyout_summary/get_summary_kpis.test.ts | 21 +- .../flyout/flyout_summary/get_summary_kpis.ts | 56 ++- .../public/components/flyout/header.tsx | 11 +- .../dataset_quality/public/hooks/index.ts | 1 + .../public/hooks/use_degraded_docs_chart.tsx | 14 +- .../hooks/use_flyout_integration_actions.tsx | 56 ++- .../public/hooks/use_redirect_link.ts | 87 +++-- .../public/hooks/use_telemetry.tsx | 352 ++++++++++++++++++ .../dataset_quality/public/plugin.tsx | 8 +- .../public/services/telemetry/index.ts | 10 + .../services/telemetry/telemetry_client.ts | 58 +++ .../services/telemetry/telemetry_events.ts | 261 +++++++++++++ .../telemetry/telemetry_service.test.ts | 177 +++++++++ .../services/telemetry/telemetry_service.ts | 36 ++ .../public/services/telemetry/types.ts | 124 ++++++ .../dataset_quality/tsconfig.json | 2 + .../dataset_quality/dataset_quality_flyout.ts | 20 +- .../dataset_quality/dataset_quality_flyout.ts | 20 +- 25 files changed, 1262 insertions(+), 123 deletions(-) create mode 100644 x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_telemetry.tsx create mode 100644 x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/index.ts create mode 100644 x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_client.ts create mode 100644 x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_events.ts create mode 100644 x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts create mode 100644 x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.ts create mode 100644 x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/types.ts diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/context.ts b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/context.ts index 9d89c8522d7dc..460aad2f02476 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/context.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/context.ts @@ -6,9 +6,11 @@ */ import { createContext, useContext } from 'react'; import { DatasetQualityControllerStateService } from '../../state_machines/dataset_quality_controller'; +import { ITelemetryClient } from '../../services/telemetry'; export interface DatasetQualityContextValue { service: DatasetQualityControllerStateService; + telemetryClient: ITelemetryClient; } export const DatasetQualityContext = createContext({} as DatasetQualityContextValue); diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/dataset_quality.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/dataset_quality.tsx index 2a06fddf9f64d..8ae6f1f74bd2e 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/dataset_quality.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/dataset_quality.tsx @@ -13,7 +13,7 @@ import { DatasetQualityContext, DatasetQualityContextValue } from './context'; import { useKibanaContextForPluginProvider } from '../../utils'; import { DatasetQualityStartDeps } from '../../types'; import { DatasetQualityController } from '../../controller'; -import { IDataStreamsStatsClient } from '../../services/data_streams_stats'; +import { ITelemetryClient } from '../../services/telemetry'; export interface DatasetQualityProps { controller: DatasetQualityController; @@ -22,13 +22,13 @@ export interface DatasetQualityProps { export interface CreateDatasetQualityArgs { core: CoreStart; plugins: DatasetQualityStartDeps; - dataStreamStatsClient: IDataStreamsStatsClient; + telemetryClient: ITelemetryClient; } export const createDatasetQuality = ({ core, plugins, - dataStreamStatsClient, + telemetryClient, }: CreateDatasetQualityArgs) => { return ({ controller }: DatasetQualityProps) => { const SummaryPanelProvider = dynamic(() => import('../../hooks/use_summary_panel')); @@ -37,6 +37,7 @@ export const createDatasetQuality = ({ const datasetQualityProviderValue: DatasetQualityContextValue = useMemo( () => ({ service: controller.service, + telemetryClient, }), [controller.service] ); diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/table/columns.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/table/columns.tsx index 60f959176866b..515307df1a200 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/table/columns.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/table/columns.tsx @@ -31,6 +31,7 @@ import { BYTE_NUMBER_FORMAT, } from '../../../../common/constants'; import { DataStreamStat } from '../../../../common/data_streams_stats/data_stream_stat'; +import { NavigationSource } from '../../../services/telemetry'; import { DatasetQualityIndicator, QualityIndicator } from '../../quality_indicator'; import { PrivilegesWarningIconWrapper, IntegrationIcon } from '../../common'; import { useRedirectLink } from '../../../hooks'; @@ -352,10 +353,13 @@ const RedirectLink = ({ dataStreamStat: DataStreamStat; title: string; }) => { - const redirectLinkProps = useRedirectLink({ dataStreamStat }); + const redirectLinkProps = useRedirectLink({ + dataStreamStat, + telemetry: { page: 'main', navigationSource: NavigationSource.Table }, + }); return ( - + {title} ); diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/table/degraded_docs_percentage_link.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/table/degraded_docs_percentage_link.tsx index 66f61d6996bd3..9e8fb79168fd4 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/table/degraded_docs_percentage_link.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/table/degraded_docs_percentage_link.tsx @@ -8,6 +8,7 @@ import { EuiSkeletonRectangle, EuiFlexGroup, EuiLink } from '@elastic/eui'; import React from 'react'; import { _IGNORED } from '../../../../common/es_fields'; +import { NavigationSource } from '../../../services/telemetry'; import { useRedirectLink } from '../../../hooks'; import { QualityPercentageIndicator } from '../../quality_indicator'; import { DataStreamStat } from '../../../../common/data_streams_stats/data_stream_stat'; @@ -26,13 +27,20 @@ export const DegradedDocsPercentageLink = ({ const redirectLinkProps = useRedirectLink({ dataStreamStat, query: { language: 'kuery', query: `${_IGNORED}: *` }, + telemetry: { + page: 'main', + navigationSource: NavigationSource.Table, + }, }); return ( {percentage ? ( - + ) : ( diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout.tsx index 7543322a5dafc..1fb5134b0a92c 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment } from 'react'; +import React, { Fragment, useEffect } from 'react'; import { css } from '@emotion/react'; import { EuiButtonEmpty, @@ -20,7 +20,7 @@ import { EuiSkeletonRectangle, } from '@elastic/eui'; import { flyoutCancelText } from '../../../common/translations'; -import { useDatasetQualityFlyout } from '../../hooks'; +import { useDatasetQualityFlyout, useDatasetDetailsTelemetry } from '../../hooks'; import { DatasetSummary, DatasetSummaryLoading } from './dataset_summary'; import { Header } from './header'; import { IntegrationSummary } from './integration_summary'; @@ -41,6 +41,12 @@ export default function Flyout({ dataset, closeFlyout }: FlyoutProps) { flyoutLoading, } = useDatasetQualityFlyout(); + const { startTracking } = useDatasetDetailsTelemetry(); + + useEffect(() => { + startTracking(); + }, [startTracking]); + return ( [number] & { isLoading: boolean }) { const { euiTheme } = useEuiTheme(); return ( @@ -71,8 +63,7 @@ export function FlyoutSummaryKpiItem({ alignItems: 'center', width: 'fit-content', }} - href={link.href} - target="_blank" + {...link.props} > - {getSummaryKpis({}).map(({ title }) => ( + {getSummaryKpis({ telemetry }).map(({ title }) => ( diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/get_summary_kpis.test.ts b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/get_summary_kpis.test.ts index 42ac8bc5e7b28..5807c21a6a251 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/get_summary_kpis.test.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/get_summary_kpis.test.ts @@ -7,6 +7,7 @@ import { formatNumber } from '@elastic/eui'; import type { useKibanaContextForPlugin } from '../../../utils'; +import type { useDatasetDetailsTelemetry } from '../../../hooks'; import { TimeRangeConfig } from '../../../state_machines/dataset_quality_controller'; import { @@ -46,7 +47,11 @@ const timeRange: TimeRangeConfig = { to: 'now', }; -const degradedDocsHref = 'http://exploratory-view/degraded-docs'; +const degradedDocsLinkProps = { + linkProps: { href: 'http://exploratory-view/degraded-docs', onClick: () => {} }, + navigate: () => {}, + isLogsExplorerAvailable: true, +}; const hostsRedirectUrl = 'http://hosts/metric/'; const hostsLocator = { @@ -55,13 +60,18 @@ const hostsLocator = { typeof useKibanaContextForPlugin >['services']['observabilityShared']['locators']['infra']['hostsLocator']; +const telemetry = { + trackDetailsNavigated: () => {}, +} as unknown as ReturnType; + describe('getSummaryKpis', () => { it('should return the correct KPIs', () => { const result = getSummaryKpis({ dataStreamDetails, timeRange, - degradedDocsHref, + degradedDocsLinkProps, hostsLocator, + telemetry, }); expect(result).toEqual([ @@ -92,7 +102,7 @@ describe('getSummaryKpis', () => { value: '200', link: { label: flyoutShowAllText, - href: degradedDocsHref, + props: degradedDocsLinkProps.linkProps, }, userHasPrivilege: true, }, @@ -119,8 +129,9 @@ describe('getSummaryKpis', () => { const result = getSummaryKpis({ dataStreamDetails: detailsWithMaxPlusHosts, timeRange, - degradedDocsHref, + degradedDocsLinkProps, hostsLocator, + telemetry, }); expect(result).toEqual([ @@ -151,7 +162,7 @@ describe('getSummaryKpis', () => { value: '200', link: { label: flyoutShowAllText, - href: degradedDocsHref, + props: degradedDocsLinkProps.linkProps, }, userHasPrivilege: true, }, diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/get_summary_kpis.ts b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/get_summary_kpis.ts index 6dcf61838b834..563c7d06cea48 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/get_summary_kpis.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/get_summary_kpis.ts @@ -6,6 +6,7 @@ */ import { formatNumber } from '@elastic/eui'; +import { getRouterLinkProps, RouterLinkProps } from '@kbn/router-utils/src/get_router_link_props'; import { BYTE_NUMBER_FORMAT, DEFAULT_DATEPICKER_REFRESH, @@ -22,25 +23,29 @@ import { flyoutSizeText, } from '../../../../common/translations'; import { DataStreamDetails } from '../../../../common/api_types'; +import { NavigationTarget, NavigationSource } from '../../../services/telemetry'; import { useKibanaContextForPlugin } from '../../../utils'; +import type { useRedirectLink, useDatasetDetailsTelemetry } from '../../../hooks'; import { TimeRangeConfig } from '../../../state_machines/dataset_quality_controller'; export function getSummaryKpis({ dataStreamDetails, timeRange = { ...DEFAULT_TIME_RANGE, refresh: DEFAULT_DATEPICKER_REFRESH }, - degradedDocsHref, + degradedDocsLinkProps, hostsLocator, + telemetry, }: { dataStreamDetails?: DataStreamDetails; timeRange?: TimeRangeConfig; - degradedDocsHref?: string; + degradedDocsLinkProps?: ReturnType; hostsLocator?: ReturnType< typeof useKibanaContextForPlugin >['services']['observabilityShared']['locators']['infra']['hostsLocator']; + telemetry: ReturnType; }): Array<{ title: string; value: string; - link?: { label: string; href: string }; + link?: { label: string; props: RouterLinkProps }; userHasPrivilege: boolean; }> { const services = dataStreamDetails?.services ?? {}; @@ -48,14 +53,17 @@ export function getSummaryKpis({ const countOfServices = serviceKeys .map((key: string) => services[key].length) .reduce((a, b) => a + b, 0); - const servicesLink = undefined; // TODO: Add link to APM services page when possible - const degradedDocsLink = degradedDocsHref - ? { - label: flyoutShowAllText, - href: degradedDocsHref, - } - : undefined; + // @ts-ignore // TODO: Add link to APM services page when possible - https://github.com/elastic/kibana/issues/179904 + const servicesLink = { + label: flyoutShowAllText, + props: getRouterLinkProps({ + href: undefined, + onClick: () => { + telemetry.trackDetailsNavigated(NavigationTarget.Services, NavigationSource.Summary); + }, + }), + }; return [ { @@ -76,14 +84,20 @@ export function getSummaryKpis({ { title: flyoutServicesText, value: formatMetricValueForMax(countOfServices, MAX_HOSTS_METRIC_VALUE, NUMBER_FORMAT), - link: servicesLink, + link: undefined, userHasPrivilege: true, }, - getHostsKpi(dataStreamDetails?.hosts, timeRange, hostsLocator), + getHostsKpi(dataStreamDetails?.hosts, timeRange, telemetry, hostsLocator), { title: flyoutDegradedDocsText, value: formatNumber(dataStreamDetails?.degradedDocsCount ?? 0, NUMBER_FORMAT), - link: degradedDocsLink, + link: + degradedDocsLinkProps && degradedDocsLinkProps.linkProps.href + ? { + label: flyoutShowAllText, + props: degradedDocsLinkProps.linkProps, + } + : undefined, userHasPrivilege: true, }, ]; @@ -92,6 +106,7 @@ export function getSummaryKpis({ function getHostsKpi( dataStreamHosts: DataStreamDetails['hosts'], timeRange: TimeRangeConfig, + telemetry: ReturnType, hostsLocator?: ReturnType< typeof useKibanaContextForPlugin >['services']['observabilityShared']['locators']['infra']['hostsLocator'] @@ -120,12 +135,15 @@ function getHostsKpi( }); // @ts-ignore // TODO: Add link to Infra Hosts page when possible - const hostsLink = hostsUrl - ? { - label: flyoutShowAllText, - href: hostsUrl, - } - : undefined; + const hostsLink = { + label: flyoutShowAllText, + props: getRouterLinkProps({ + href: hostsUrl, + onClick: () => { + telemetry.trackDetailsNavigated(NavigationTarget.Hosts, NavigationSource.Summary); + }, + }), + }; return { title: flyoutHostsText, diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/header.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/header.tsx index 63fb761ce87f5..60d88992c979e 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/header.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/header.tsx @@ -20,6 +20,7 @@ import { flyoutOpenInDiscoverText, flyoutOpenInLogsExplorerText, } from '../../../common/translations'; +import { NavigationSource } from '../../services/telemetry'; import { useRedirectLink } from '../../hooks'; import { FlyoutDataset } from '../../state_machines/dataset_quality_controller'; import { IntegrationIcon } from '../common'; @@ -28,7 +29,13 @@ export function Header({ dataStreamStat }: { dataStreamStat: FlyoutDataset }) { const { integration, title } = dataStreamStat; const euiShadow = useEuiShadow('s'); const { euiTheme } = useEuiTheme(); - const redirectLinkProps = useRedirectLink({ dataStreamStat }); + const redirectLinkProps = useRedirectLink({ + dataStreamStat, + telemetry: { + page: 'details', + navigationSource: NavigationSource.Header, + }, + }); return ( @@ -61,7 +68,7 @@ export function Header({ dataStreamStat }: { dataStreamStat: FlyoutDataset }) { { services: { lens }, } = useKibanaContextForPlugin(); const { service } = useDatasetQualityContext(); + const { trackDetailsNavigated, navigationTargets, navigationSources } = + useDatasetDetailsTelemetry(); const { dataStreamStat, timeRange, breakdownField } = useDatasetQualityFlyout(); @@ -104,13 +107,14 @@ export const useDegradedDocsChart = ({ dataStream }: DegradedDocsChartDeps) => { const openInLensCallback = useCallback(() => { if (attributes) { + trackDetailsNavigated(navigationTargets.Lens, navigationSources.Chart); lens.navigateToPrefilledEditor({ id: '', timeRange, attributes, }); } - }, [lens, attributes, timeRange]); + }, [attributes, trackDetailsNavigated, navigationTargets, navigationSources, lens, timeRange]); const getOpenInLensAction = useMemo(() => { return { @@ -137,6 +141,10 @@ export const useDegradedDocsChart = ({ dataStream }: DegradedDocsChartDeps) => { query: { language: 'kuery', query: '_ignored:*' }, timeRangeConfig: timeRange, breakdownField: breakdownDataViewField?.name, + telemetry: { + page: 'details', + navigationSource: navigationSources.Chart, + }, }); const getOpenInLogsExplorerAction = useMemo(() => { @@ -149,10 +157,10 @@ export const useDegradedDocsChart = ({ dataStream }: DegradedDocsChartDeps) => { : exploreDataInDiscoverText; }, getHref: async () => { - return redirectLinkProps.href; + return redirectLinkProps.linkProps.href; }, getIconType(): string | undefined { - return 'popout'; + return 'visTable'; }, async isCompatible(): Promise { return true; diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_flyout_integration_actions.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_flyout_integration_actions.tsx index 29faaec2788ea..518b8f84aedd8 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_flyout_integration_actions.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_flyout_integration_actions.tsx @@ -12,6 +12,7 @@ import { MANAGEMENT_APP_LOCATOR } from '@kbn/deeplinks-management/constants'; import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics'; import { DashboardType } from '../../common/data_streams_stats'; import { useKibanaContextForPlugin } from '../utils'; +import { useDatasetDetailsTelemetry } from './use_telemetry'; export const useFlyoutIntegrationActions = () => { const { @@ -21,6 +22,8 @@ export const useFlyoutIntegrationActions = () => { share, }, } = useKibanaContextForPlugin(); + const { wrapLinkPropsForTelemetry, navigationSources, navigationTargets } = + useDatasetDetailsTelemetry(); const [isOpen, toggleIsOpen] = useToggle(false); @@ -43,28 +46,51 @@ export const useFlyoutIntegrationActions = () => { const getIntegrationOverviewLinkProps = useCallback( (name: string, version: string) => { const href = basePath.prepend(`/app/integrations/detail/${name}-${version}/overview`); - return getRouterLinkProps({ - href, - onClick: () => navigateToUrl(href), - }); + return wrapLinkPropsForTelemetry( + getRouterLinkProps({ + href, + onClick: () => { + return navigateToUrl(href); + }, + }), + navigationTargets.Integration, + navigationSources.ActionMenu + ); }, - [basePath, navigateToUrl] + [basePath, navigateToUrl, navigationSources, navigationTargets, wrapLinkPropsForTelemetry] ); const getIndexManagementLinkProps = useCallback( (params: { sectionId: string; appId: string }) => - getRouterLinkProps({ - href: indexManagementLocator?.getRedirectUrl(params), - onClick: () => indexManagementLocator?.navigate(params), - }), - [indexManagementLocator] + wrapLinkPropsForTelemetry( + getRouterLinkProps({ + href: indexManagementLocator?.getRedirectUrl(params), + onClick: () => { + return indexManagementLocator?.navigate(params); + }, + }), + navigationTargets.IndexTemplate, + navigationSources.ActionMenu + ), + [ + indexManagementLocator, + navigationSources.ActionMenu, + navigationTargets.IndexTemplate, + wrapLinkPropsForTelemetry, + ] ); const getDashboardLinkProps = useCallback( (dashboard: DashboardType) => - getRouterLinkProps({ - href: dashboardLocator?.getRedirectUrl({ dashboardId: dashboard?.id } || ''), - onClick: () => dashboardLocator?.navigate({ dashboardId: dashboard?.id } || ''), - }), - [dashboardLocator] + wrapLinkPropsForTelemetry( + getRouterLinkProps({ + href: dashboardLocator?.getRedirectUrl({ dashboardId: dashboard?.id } || ''), + onClick: () => { + return dashboardLocator?.navigate({ dashboardId: dashboard?.id } || ''); + }, + }), + navigationTargets.Dashboard, + navigationSources.ActionMenu + ), + [dashboardLocator, navigationSources, navigationTargets, wrapLinkPropsForTelemetry] ); return { diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_redirect_link.ts b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_redirect_link.ts index 751b7e14ebb79..5e2b39ba7a8b6 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_redirect_link.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_redirect_link.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { useMemo } from 'react'; import { SINGLE_DATASET_LOCATOR_ID, SingleDatasetLocatorParams, @@ -20,17 +21,20 @@ import { DataStreamStat } from '../../common/data_streams_stats/data_stream_stat import { useDatasetQualityContext } from '../components/dataset_quality/context'; import { FlyoutDataset, TimeRangeConfig } from '../state_machines/dataset_quality_controller'; import { useKibanaContextForPlugin } from '../utils'; +import { useRedirectLinkTelemetry } from './use_telemetry'; export const useRedirectLink = ({ dataStreamStat, query, timeRangeConfig, breakdownField, + telemetry, }: { dataStreamStat: DataStreamStat | FlyoutDataset; query?: Query | AggregateQuery; timeRangeConfig?: TimeRangeConfig; breakdownField?: string; + telemetry?: Parameters[0]['telemetry']; }) => { const { services: { share }, @@ -43,29 +47,66 @@ export const useRedirectLink = ({ const logsExplorerLocator = share.url.locators.get(SINGLE_DATASET_LOCATOR_ID); - const config = logsExplorerLocator - ? buildLogsExplorerConfig({ - locator: logsExplorerLocator, - dataStreamStat, - query, - from, - to, - breakdownField, - }) - : buildDiscoverConfig({ - locatorClient: share.url.locators, - dataStreamStat, - query, - from, - to, - breakdownField, - }); - - return { - ...config.routerLinkProps, - navigate: config.navigate, - isLogsExplorerAvailable: !!logsExplorerLocator, - }; + const { sendTelemetry } = useRedirectLinkTelemetry({ + rawName: dataStreamStat.rawName, + isLogsExplorer: !!logsExplorerLocator, + telemetry, + query, + }); + + return useMemo<{ + linkProps: RouterLinkProps; + navigate: () => void; + isLogsExplorerAvailable: boolean; + }>(() => { + const config = logsExplorerLocator + ? buildLogsExplorerConfig({ + locator: logsExplorerLocator, + dataStreamStat, + query, + from, + to, + breakdownField, + }) + : buildDiscoverConfig({ + locatorClient: share.url.locators, + dataStreamStat, + query, + from, + to, + breakdownField, + }); + + const onClickWithTelemetry = (event: Parameters[0]) => { + sendTelemetry(); + if (config.routerLinkProps.onClick) { + config.routerLinkProps.onClick(event); + } + }; + + const navigateWithTelemetry = () => { + sendTelemetry(); + config.navigate(); + }; + + return { + linkProps: { + ...config.routerLinkProps, + onClick: onClickWithTelemetry, + }, + navigate: navigateWithTelemetry, + isLogsExplorerAvailable: !!logsExplorerLocator, + }; + }, [ + breakdownField, + dataStreamStat, + from, + to, + logsExplorerLocator, + query, + sendTelemetry, + share.url.locators, + ]); }; const buildLogsExplorerConfig = ({ diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_telemetry.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_telemetry.tsx new file mode 100644 index 0000000000000..e5fc1088e7466 --- /dev/null +++ b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_telemetry.tsx @@ -0,0 +1,352 @@ +/* + * 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 { RouterLinkProps } from '@kbn/router-utils/src/get_router_link_props'; +import { useCallback, useEffect, useMemo } from 'react'; +import { useSelector } from '@xstate/react'; +import { getDateISORange } from '@kbn/timerange'; +import { AggregateQuery, Query } from '@kbn/es-query'; + +import { DataStreamStat } from '../../common/data_streams_stats'; +import { DataStreamDetails } from '../../common/api_types'; +import { mapPercentageToQuality } from '../../common/utils'; +import { + NavigationTarget, + NavigationSource, + DatasetDetailsEbtProps, + DatasetNavigatedEbtProps, + DatasetEbtProps, +} from '../services/telemetry'; +import { FlyoutDataset, TimeRangeConfig } from '../state_machines/dataset_quality_controller'; +import { useDatasetQualityContext } from '../components/dataset_quality/context'; +import { useDatasetQualityFilters } from './use_dataset_quality_filters'; + +export const useRedirectLinkTelemetry = ({ + rawName, + isLogsExplorer, + telemetry, + query, +}: { + rawName: string; + isLogsExplorer: boolean; + telemetry?: { + page: 'main' | 'details'; + navigationSource: NavigationSource; + }; + query?: Query | AggregateQuery; +}) => { + const { trackDatasetNavigated } = useDatasetTelemetry(); + const { trackDetailsNavigated, navigationTargets } = useDatasetDetailsTelemetry(); + + const sendTelemetry = useCallback(() => { + if (telemetry) { + const isIgnoredFilter = query ? JSON.stringify(query).includes('_ignored') : false; + if (telemetry.page === 'main') { + trackDatasetNavigated(rawName, isIgnoredFilter); + } else { + trackDetailsNavigated( + isLogsExplorer ? navigationTargets.LogsExplorer : navigationTargets.Discover, + telemetry.navigationSource, + isIgnoredFilter + ); + } + } + }, [ + isLogsExplorer, + trackDetailsNavigated, + navigationTargets, + query, + rawName, + telemetry, + trackDatasetNavigated, + ]); + + const wrapLinkPropsForTelemetry = useCallback( + (props: RouterLinkProps) => { + return { + ...props, + onClick: (event: Parameters[0]) => { + sendTelemetry(); + if (props.onClick) { + props.onClick(event); + } + }, + }; + }, + [sendTelemetry] + ); + + return { + wrapLinkPropsForTelemetry, + sendTelemetry, + }; +}; + +export const useDatasetTelemetry = () => { + const { service, telemetryClient } = useDatasetQualityContext(); + + // eslint-disable-next-line react-hooks/exhaustive-deps + const datasets = useSelector(service, (state) => state.context.datasets) ?? {}; + const nonAggregatableDatasets = useSelector( + service, + (state) => state.context.nonAggregatableDatasets + ); + const canUserViewIntegrations = useSelector( + service, + (state) => state.context.datasetUserPrivileges.canViewIntegrations + ); + const sort = useSelector(service, (state) => state.context.table.sort); + const appliedFilters = useDatasetQualityFilters(); + + const trackDatasetNavigated = useCallback<(rawName: string, isIgnoredFilter: boolean) => void>( + (rawName: string, isIgnoredFilter: boolean) => { + const foundDataset = datasets.find((dataset) => dataset.rawName === rawName); + if (foundDataset) { + const ebtProps = getDatasetEbtProps( + foundDataset, + sort, + appliedFilters, + nonAggregatableDatasets, + isIgnoredFilter, + canUserViewIntegrations + ); + telemetryClient.trackDatasetNavigated(ebtProps); + } else { + throw new Error( + `Cannot report dataset navigation telemetry for unknown dataset ${rawName}` + ); + } + }, + [ + sort, + appliedFilters, + canUserViewIntegrations, + datasets, + nonAggregatableDatasets, + telemetryClient, + ] + ); + + return { trackDatasetNavigated }; +}; + +export const useDatasetDetailsTelemetry = () => { + const { service, telemetryClient } = useDatasetQualityContext(); + + const { + dataset: dataStreamStat, + datasetDetails: dataStreamDetails, + insightsTimeRange, + breakdownField, + isNonAggregatable, + } = useSelector(service, (state) => state.context.flyout) ?? {}; + + const loadingState = useSelector(service, (state) => ({ + dataStreamDetailsLoading: state.matches('flyout.initializing.dataStreamDetails.fetching'), + })); + + const canUserAccessDashboards = useSelector( + service, + (state) => !state.matches('flyout.initializing.integrationDashboards.unauthorized') + ); + + const canUserViewIntegrations = useSelector( + service, + (state) => state.context.datasetUserPrivileges.canViewIntegrations + ); + + const ebtProps = useMemo(() => { + if ( + dataStreamDetails && + insightsTimeRange && + dataStreamStat && + !loadingState.dataStreamDetailsLoading + ) { + return getDatasetDetailsEbtProps( + insightsTimeRange, + dataStreamStat, + dataStreamDetails, + isNonAggregatable ?? false, + canUserViewIntegrations, + canUserAccessDashboards, + breakdownField + ); + } + + return undefined; + }, [ + insightsTimeRange, + dataStreamStat, + dataStreamDetails, + loadingState.dataStreamDetailsLoading, + isNonAggregatable, + canUserViewIntegrations, + canUserAccessDashboards, + breakdownField, + ]); + + const startTracking = useCallback(() => { + telemetryClient.startDatasetDetailsTracking(); + }, [telemetryClient]); + + // Report opening dataset details + useEffect(() => { + const datasetDetailsTrackingState = telemetryClient.getDatasetDetailsTrackingState(); + if (datasetDetailsTrackingState === 'started' && ebtProps) { + telemetryClient.trackDatasetDetailsOpened(ebtProps); + } + }, [ebtProps, telemetryClient]); + + const trackDetailsNavigated = useCallback( + (target: NavigationTarget, source: NavigationSource, isDegraded = false) => { + const datasetDetailsTrackingState = telemetryClient.getDatasetDetailsTrackingState(); + if ( + (datasetDetailsTrackingState === 'opened' || datasetDetailsTrackingState === 'navigated') && + ebtProps + ) { + telemetryClient.trackDatasetDetailsNavigated({ + ...ebtProps, + filters: { + is_degraded: isDegraded, + }, + target, + source, + }); + } else { + throw new Error( + 'Cannot report dataset details navigation telemetry without required data and state' + ); + } + }, + [ebtProps, telemetryClient] + ); + + const wrapLinkPropsForTelemetry = useCallback( + ( + props: RouterLinkProps, + target: NavigationTarget, + source: NavigationSource, + isDegraded = false + ) => { + return { + ...props, + onClick: (event: Parameters[0]) => { + trackDetailsNavigated(target, source, isDegraded); + if (props.onClick) { + props.onClick(event); + } + }, + }; + }, + [trackDetailsNavigated] + ); + + return { + startTracking, + trackDetailsNavigated, + wrapLinkPropsForTelemetry, + navigationTargets: NavigationTarget, + navigationSources: NavigationSource, + }; +}; + +function getDatasetEbtProps( + dataset: DataStreamStat, + sort: { field: string; direction: 'asc' | 'desc' }, + filters: ReturnType, + nonAggregatableDatasets: string[], + isIgnoredFilter: boolean, + canUserViewIntegrations: boolean +): DatasetNavigatedEbtProps { + const { startDate: from, endDate: to } = getDateISORange(filters.timeRange); + const datasetEbtProps: DatasetEbtProps = { + index_name: dataset.rawName, + data_stream: { + dataset: dataset.name, + namespace: dataset.namespace, + type: dataset.type, + }, + data_stream_health: dataset.degradedDocs.quality, + data_stream_aggregatable: nonAggregatableDatasets.some( + (indexName) => indexName === dataset.rawName + ), + from, + to, + degraded_percentage: dataset.degradedDocs.percentage, + integration: dataset.integration?.name, + privileges: { + can_monitor_data_stream: dataset.userPrivileges?.canMonitor ?? true, + can_view_integrations: canUserViewIntegrations, + }, + }; + + const ebtFilters: DatasetNavigatedEbtProps['filters'] = { + is_degraded: isIgnoredFilter, + query_length: filters.selectedQuery?.length ?? 0, + integrations: { + total: filters.integrations.filter((item) => item.name !== 'none').length, + included: filters.integrations.filter((item) => item?.checked === 'on').length, + excluded: filters.integrations.filter((item) => item?.checked === 'off').length, + }, + namespaces: { + total: filters.namespaces.length, + included: filters.namespaces.filter((item) => item?.checked === 'on').length, + excluded: filters.namespaces.filter((item) => item?.checked === 'off').length, + }, + qualities: { + total: filters.qualities.length, + included: filters.qualities.filter((item) => item?.checked === 'on').length, + excluded: filters.qualities.filter((item) => item?.checked === 'off').length, + }, + }; + + return { + ...datasetEbtProps, + sort, + filters: ebtFilters, + }; +} + +function getDatasetDetailsEbtProps( + insightsTimeRange: TimeRangeConfig, + flyoutDataset: FlyoutDataset, + details: DataStreamDetails, + isNonAggregatable: boolean, + canUserViewIntegrations: boolean, + canUserAccessDashboards: boolean, + breakdownField?: string +): DatasetDetailsEbtProps { + const indexName = flyoutDataset.rawName; + const dataStream = { + dataset: flyoutDataset.name, + namespace: flyoutDataset.namespace, + type: flyoutDataset.type, + }; + const degradedDocs = details?.degradedDocsCount ?? 0; + const totalDocs = details?.docsCount ?? 0; + const degradedPercentage = + totalDocs > 0 ? Number(((degradedDocs / totalDocs) * 100).toFixed(2)) : 0; + const health = mapPercentageToQuality(degradedPercentage); + const { startDate: from, endDate: to } = getDateISORange(insightsTimeRange); + + return { + index_name: indexName, + data_stream: dataStream, + privileges: { + can_monitor_data_stream: true, + can_view_integrations: canUserViewIntegrations, + can_view_dashboards: canUserAccessDashboards, + }, + data_stream_aggregatable: !isNonAggregatable, + data_stream_health: health, + from, + to, + degraded_percentage: degradedPercentage, + integration: flyoutDataset.integration?.name, + breakdown_field: breakdownField, + }; +} diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/plugin.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/plugin.tsx index 6ea2655450607..3e90347875ba8 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/plugin.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/plugin.tsx @@ -6,6 +6,7 @@ */ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; +import { TelemetryService } from './services/telemetry'; import { createDatasetQuality } from './components/dataset_quality'; import { createDatasetQualityControllerLazyFactory } from './controller/lazy_create_controller'; import { DataStreamsStatsService } from './services/data_streams_stats'; @@ -20,13 +21,18 @@ import { export class DatasetQualityPlugin implements Plugin { + private telemetry = new TelemetryService(); constructor(context: PluginInitializerContext) {} public setup(core: CoreSetup, plugins: DatasetQualitySetupDeps) { + this.telemetry.setup({ analytics: core.analytics }); + return {}; } public start(core: CoreStart, plugins: DatasetQualityStartDeps): DatasetQualityPluginStart { + const telemetryClient = this.telemetry.start(); + const dataStreamStatsClient = new DataStreamsStatsService().start({ http: core.http, }).client; @@ -38,7 +44,7 @@ export class DatasetQualityPlugin const DatasetQuality = createDatasetQuality({ core, plugins, - dataStreamStatsClient, + telemetryClient, }); const createDatasetQualityController = createDatasetQualityControllerLazyFactory({ diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/index.ts b/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/index.ts new file mode 100644 index 0000000000000..c7cc9eb577e38 --- /dev/null +++ b/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/index.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export * from './telemetry_client'; +export * from './telemetry_service'; +export * from './types'; diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_client.ts b/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_client.ts new file mode 100644 index 0000000000000..c0e93f13cd1b3 --- /dev/null +++ b/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_client.ts @@ -0,0 +1,58 @@ +/* + * 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 { v4 as uuidv4 } from 'uuid'; +import { AnalyticsServiceSetup } from '@kbn/core-analytics-server'; +import { + ITelemetryClient, + DatasetDetailsEbtProps, + DatasetQualityTelemetryEventTypes, + DatasetDetailsNavigatedEbtProps, + DatasetDetailsTrackingState, + DatasetNavigatedEbtProps, +} from './types'; + +export class TelemetryClient implements ITelemetryClient { + private datasetDetailsTrackingId = ''; + private startTime = 0; + private datasetDetailsState: DatasetDetailsTrackingState = 'initial'; + + constructor(private analytics: AnalyticsServiceSetup) {} + + public trackDatasetNavigated = (eventProps: DatasetNavigatedEbtProps) => { + this.analytics.reportEvent(DatasetQualityTelemetryEventTypes.NAVIGATED, eventProps); + }; + + public startDatasetDetailsTracking() { + this.datasetDetailsTrackingId = uuidv4(); + this.startTime = Date.now(); + this.datasetDetailsState = 'started'; + } + + public getDatasetDetailsTrackingState() { + return this.datasetDetailsState; + } + + public trackDatasetDetailsOpened = (eventProps: DatasetDetailsEbtProps) => { + const datasetDetailsLoadDuration = Date.now() - this.startTime; + + this.datasetDetailsState = 'opened'; + this.analytics.reportEvent(DatasetQualityTelemetryEventTypes.DETAILS_OPENED, { + ...eventProps, + tracking_id: this.datasetDetailsTrackingId, + duration: datasetDetailsLoadDuration, + }); + }; + + public trackDatasetDetailsNavigated = (eventProps: DatasetDetailsNavigatedEbtProps) => { + this.datasetDetailsState = 'navigated'; + this.analytics.reportEvent(DatasetQualityTelemetryEventTypes.DETAILS_NAVIGATED, { + ...eventProps, + tracking_id: this.datasetDetailsTrackingId, + }); + }; +} diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_events.ts b/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_events.ts new file mode 100644 index 0000000000000..a8244a6830ae6 --- /dev/null +++ b/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_events.ts @@ -0,0 +1,261 @@ +/* + * 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 { omit } from 'lodash'; +import { SchemaObject, SchemaValue } from '@kbn/ebt'; +import { + DatasetEbtFilter, + DatasetEbtProps, + DatasetNavigatedEbtProps, + DatasetQualityTelemetryEvent, + DatasetQualityTelemetryEventTypes, +} from './types'; + +const dataStreamSchema: SchemaObject = { + properties: { + dataset: { + type: 'keyword', + _meta: { + description: 'Data stream dataset name', + }, + }, + namespace: { + type: 'keyword', + _meta: { + description: 'Data stream namespace', + }, + }, + type: { + type: 'keyword', + _meta: { + description: 'Data stream type e.g. "logs", "metrics"', + }, + }, + }, +}; + +const privilegesSchema: SchemaObject = { + properties: { + can_monitor_data_stream: { + type: 'boolean', + _meta: { + description: 'Whether user can monitor the data stream', + }, + }, + can_view_integrations: { + type: 'boolean', + _meta: { + description: 'Whether user can view integrations', + }, + }, + can_view_dashboards: { + type: 'boolean', + _meta: { + description: 'Whether user can view dashboards', + optional: true, + }, + }, + }, +}; + +const ebtFilterObjectSchema: SchemaObject = { + properties: { + total: { + type: 'short', + _meta: { + description: 'Total number of values available to filter', + optional: false, + }, + }, + included: { + type: 'short', + _meta: { + description: 'Number of values selected to filter for', + optional: false, + }, + }, + excluded: { + type: 'short', + _meta: { + description: 'Number of values selected to filter out', + optional: false, + }, + }, + }, + _meta: { + description: 'Represents the multi select filters', + optional: false, + }, +}; + +const sortSchema: SchemaObject = { + properties: { + field: { + type: 'keyword', + _meta: { + description: 'Field used for sorting on the main table', + optional: false, + }, + }, + direction: { + type: 'keyword', + _meta: { + description: 'Sort direction', + optional: false, + }, + }, + }, + _meta: { + description: 'Represents the state of applied sorting on the dataset quality home page', + optional: false, + }, +}; + +const filtersSchema: SchemaObject = { + properties: { + is_degraded: { + type: 'boolean', + _meta: { + description: 'Whether _ignored filter is applied', + optional: false, + }, + }, + query_length: { + type: 'short', + _meta: { + description: 'Length of the query string', + optional: false, + }, + }, + integrations: ebtFilterObjectSchema, + namespaces: ebtFilterObjectSchema, + qualities: ebtFilterObjectSchema, + }, + _meta: { + description: 'Represents the state of applied filters on the dataset quality home page', + optional: false, + }, +}; + +const datasetCommonSchema = { + index_name: { + type: 'keyword', + _meta: { + description: 'Index name', + }, + } as SchemaValue, + data_stream: dataStreamSchema, + privileges: privilegesSchema, + data_stream_health: { + type: 'keyword', + _meta: { + description: 'Quality of the data stream e.g. "good", "degraded", "poor"', + }, + } as SchemaValue, + data_stream_aggregatable: { + type: 'boolean', + _meta: { + description: 'Whether data stream is aggregatable against _ignored field', + }, + } as SchemaValue, + degraded_percentage: { + type: 'float', + _meta: { + description: 'Percentage of degraded documents in the data stream', + }, + } as SchemaValue, + from: { + type: 'date', + _meta: { + description: 'Start of the time range ISO8601 formatted string', + }, + } as SchemaValue, + to: { + type: 'date', + _meta: { + description: 'End of the time range ISO8601 formatted string', + }, + } as SchemaValue, + integration: { + type: 'keyword', + _meta: { + description: 'Integration name, if any', + optional: true, + }, + } as SchemaValue, +}; + +const datasetNavigatedEventType: DatasetQualityTelemetryEvent = { + eventType: DatasetQualityTelemetryEventTypes.NAVIGATED, + schema: { + ...datasetCommonSchema, + sort: sortSchema, + filters: filtersSchema, + }, +}; + +const datasetDetailsOpenedEventType: DatasetQualityTelemetryEvent = { + eventType: DatasetQualityTelemetryEventTypes.DETAILS_OPENED, + schema: { + ...datasetCommonSchema, + tracking_id: { + type: 'keyword', + _meta: { + description: `Locally generated session tracking ID for funnel analysis`, + }, + }, + duration: { + type: 'long', + _meta: { + description: 'Duration in milliseconds to load the dataset details page', + }, + }, + breakdown_field: { + type: 'keyword', + _meta: { + description: 'Field used for chart breakdown, if any', + optional: true, + }, + }, + }, +}; + +const datasetDetailsNavigatedEventType: DatasetQualityTelemetryEvent = { + eventType: DatasetQualityTelemetryEventTypes.DETAILS_NAVIGATED, + schema: { + ...omit(datasetDetailsOpenedEventType.schema, 'duration'), + filters: { + properties: { + is_degraded: { + type: 'boolean', + _meta: { + description: 'Whether _ignored filter is applied to the link', + optional: false, + }, + }, + }, + }, + target: { + type: 'keyword', + _meta: { + description: 'Action that user took to navigate away from the dataset details page', + }, + }, + source: { + type: 'keyword', + _meta: { + description: + 'Section of dataset details page the action is originated from e.g. header, summary, chart or table etc.', + }, + }, + }, +}; + +export const datasetQualityEbtEvents = { + datasetNavigatedEventType, + datasetDetailsOpenedEventType, + datasetDetailsNavigatedEventType, +}; diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts b/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts new file mode 100644 index 0000000000000..dfd1bd4fb2b51 --- /dev/null +++ b/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts @@ -0,0 +1,177 @@ +/* + * 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 { coreMock } from '@kbn/core/server/mocks'; +import { datasetQualityEbtEvents } from './telemetry_events'; +import { TelemetryService } from './telemetry_service'; +import { + NavigationTarget, + NavigationSource, + DatasetDetailsNavigatedEbtProps, + DatasetDetailsEbtProps, + WithTrackingId, + WithDuration, + DatasetEbtProps, + DatasetNavigatedEbtProps, +} from './types'; + +// Mock uuidv4 +jest.mock('uuid', () => { + return { + v4: jest.fn(() => `mock-uuid-${Math.random()}`), + }; +}); + +describe('TelemetryService', () => { + const service = new TelemetryService(); + + const mockCoreStart = coreMock.createSetup(); + service.setup({ analytics: mockCoreStart.analytics }); + + const defaultEbtProps: DatasetEbtProps = { + index_name: 'logs-example-dataset-default', + data_stream: { + dataset: 'example-dataset', + namespace: 'default', + type: 'logs', + }, + privileges: { + can_monitor_data_stream: true, + can_view_integrations: true, + can_view_dashboards: true, + }, + data_stream_health: 'poor', + data_stream_aggregatable: true, + degraded_percentage: 0.5, + from: '2024-01-01T00:00:00.000Z', + to: '2024-01-02T00:00:00.000Z', + }; + + const defaultSort: DatasetNavigatedEbtProps['sort'] = { field: 'name', direction: 'asc' }; + + const defaultFilters: DatasetNavigatedEbtProps['filters'] = { + is_degraded: false, + query_length: 0, + integrations: { + total: 0, + included: 0, + excluded: 0, + }, + namespaces: { + total: 0, + included: 0, + excluded: 0, + }, + qualities: { + total: 0, + included: 0, + excluded: 0, + }, + }; + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should register all events', () => { + expect(mockCoreStart.analytics.registerEventType).toHaveBeenCalledTimes( + Object.keys(datasetQualityEbtEvents).length + ); + }); + + it('should report dataset navigated event', async () => { + const telemetry = service.start(); + const exampleEventData: DatasetNavigatedEbtProps = { + ...defaultEbtProps, + sort: defaultSort, + filters: defaultFilters, + }; + + telemetry.trackDatasetNavigated(exampleEventData); + + expect(mockCoreStart.analytics.reportEvent).toHaveBeenCalledTimes(1); + expect(mockCoreStart.analytics.reportEvent).toHaveBeenCalledWith( + datasetQualityEbtEvents.datasetNavigatedEventType.eventType, + expect.objectContaining(exampleEventData) + ); + }); + + it('should report opening dataset details with a tracking_id', async () => { + const telemetry = service.start(); + const exampleEventData: DatasetDetailsEbtProps = { + ...defaultEbtProps, + }; + + telemetry.startDatasetDetailsTracking(); + + // Increment jest's internal timer to simulate user interaction delay + jest.advanceTimersByTime(500); + + telemetry.trackDatasetDetailsOpened(exampleEventData); + + expect(mockCoreStart.analytics.reportEvent).toHaveBeenCalledTimes(1); + expect(mockCoreStart.analytics.reportEvent).toHaveBeenCalledWith( + datasetQualityEbtEvents.datasetDetailsOpenedEventType.eventType, + expect.objectContaining({ + ...exampleEventData, + tracking_id: expect.stringMatching(/\S/), + duration: expect.any(Number), + }) + ); + + // Expect the duration to be greater than the time mark + const args = mockCoreStart.analytics.reportEvent.mock.calls[0][1] as WithTrackingId & + WithDuration; + expect(args.duration).toBeGreaterThanOrEqual(500); + }); + + it('should report closing dataset details with the same tracking_id', async () => { + const telemetry = service.start(); + const exampleOpenEventData: DatasetDetailsEbtProps = { + ...defaultEbtProps, + }; + + const exampleNavigatedEventData: DatasetDetailsNavigatedEbtProps = { + ...exampleOpenEventData, + breakdown_field: 'example_field', + filters: { + is_degraded: false, + }, + target: NavigationTarget.Exit, + source: NavigationSource.Chart, + }; + + telemetry.startDatasetDetailsTracking(); + telemetry.trackDatasetDetailsOpened(exampleOpenEventData); + telemetry.trackDatasetDetailsNavigated(exampleNavigatedEventData); + + expect(mockCoreStart.analytics.reportEvent).toHaveBeenCalledTimes(2); + expect(mockCoreStart.analytics.reportEvent).toHaveBeenCalledWith( + datasetQualityEbtEvents.datasetDetailsNavigatedEventType.eventType, + expect.objectContaining({ + ...exampleNavigatedEventData, + tracking_id: expect.stringMatching(/\S/), + }) + ); + + // Make sure the tracking_id is the same for both events + const [firstCall, secondCall] = mockCoreStart.analytics.reportEvent.mock.calls; + expect((firstCall[1] as WithTrackingId).tracking_id).toEqual( + (secondCall[1] as WithTrackingId).tracking_id + ); + expect((secondCall[1] as DatasetDetailsNavigatedEbtProps).breakdown_field).toEqual( + 'example_field' + ); + }); +}); diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.ts b/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.ts new file mode 100644 index 0000000000000..f8f8c5322f556 --- /dev/null +++ b/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.ts @@ -0,0 +1,36 @@ +/* + * 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 { AnalyticsServiceSetup } from '@kbn/core-analytics-server'; +import { TelemetryServiceSetupParams, ITelemetryClient } from './types'; +import { datasetQualityEbtEvents } from './telemetry_events'; +import { TelemetryClient } from './telemetry_client'; + +/** + * Service that interacts with the Core's analytics module + */ +export class TelemetryService { + constructor(private analytics?: AnalyticsServiceSetup) {} + + public setup({ analytics }: TelemetryServiceSetupParams) { + this.analytics = analytics; + + analytics.registerEventType(datasetQualityEbtEvents.datasetNavigatedEventType); + analytics.registerEventType(datasetQualityEbtEvents.datasetDetailsOpenedEventType); + analytics.registerEventType(datasetQualityEbtEvents.datasetDetailsNavigatedEventType); + } + + public start(): ITelemetryClient { + if (!this.analytics) { + throw new Error( + 'The TelemetryService.setup() method has not been invoked, be sure to call it during the plugin setup.' + ); + } + + return new TelemetryClient(this.analytics); + } +} diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/types.ts b/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/types.ts new file mode 100644 index 0000000000000..2784f02187db1 --- /dev/null +++ b/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/types.ts @@ -0,0 +1,124 @@ +/* + * 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 { AnalyticsServiceSetup, RootSchema } from '@kbn/core/public'; +import { QualityIndicators } from '../../../common/types'; + +export interface TelemetryServiceSetupParams { + analytics: AnalyticsServiceSetup; +} + +export type DatasetDetailsTrackingState = 'initial' | 'started' | 'opened' | 'navigated'; + +export enum NavigationTarget { + Exit = 'exit', + LogsExplorer = 'logs_explorer', + Discover = 'discover', + Lens = 'lens', + Integration = 'integration', + IndexTemplate = 'index_template', + Dashboard = 'dashboard', + Hosts = 'hosts', + Services = 'services', +} + +/** + * Source UI component that triggered the navigation + */ +export enum NavigationSource { + Header = 'header', + Footer = 'footer', + Summary = 'summary', + Chart = 'chart', + Table = 'table', + ActionMenu = 'action_menu', +} + +export interface WithTrackingId { + tracking_id: string; // For funnel analysis and session tracking +} + +export interface WithDuration { + duration: number; // The time (in milliseconds) it took to reach the meaningful state +} + +export interface DatasetEbtProps { + index_name: string; + data_stream: { + dataset: string; + namespace: string; + type: string; + }; + data_stream_health: QualityIndicators; + data_stream_aggregatable: boolean; + from: string; + to: string; + degraded_percentage: number; + integration?: string; + privileges: { + can_monitor_data_stream: boolean; + can_view_integrations: boolean; + can_view_dashboards?: boolean; + }; +} + +export interface DatasetEbtFilter { + total: number; + included: number; + excluded: number; +} + +export interface DatasetNavigatedEbtProps extends DatasetEbtProps { + sort: { field: string; direction: 'asc' | 'desc' }; + filters: { + is_degraded: boolean; + query_length: number; + integrations: DatasetEbtFilter; + namespaces: DatasetEbtFilter; + qualities: DatasetEbtFilter; + }; +} + +export interface DatasetDetailsEbtProps extends DatasetEbtProps { + breakdown_field?: string; +} + +export interface DatasetDetailsNavigatedEbtProps extends DatasetDetailsEbtProps { + filters: { + is_degraded: boolean; + }; + target: NavigationTarget; + source: NavigationSource; +} + +export interface ITelemetryClient { + trackDatasetNavigated: (eventProps: DatasetNavigatedEbtProps) => void; + startDatasetDetailsTracking: () => void; + getDatasetDetailsTrackingState: () => DatasetDetailsTrackingState; + trackDatasetDetailsOpened: (eventProps: DatasetDetailsEbtProps) => void; + trackDatasetDetailsNavigated: (eventProps: DatasetDetailsNavigatedEbtProps) => void; +} + +export enum DatasetQualityTelemetryEventTypes { + NAVIGATED = 'Dataset Quality Navigated', + DETAILS_OPENED = 'Dataset Quality Dataset Details Opened', + DETAILS_NAVIGATED = 'Dataset Quality Dataset Details Navigated', +} + +export type DatasetQualityTelemetryEvent = + | { + eventType: DatasetQualityTelemetryEventTypes.NAVIGATED; + schema: RootSchema; + } + | { + eventType: DatasetQualityTelemetryEventTypes.DETAILS_OPENED; + schema: RootSchema; + } + | { + eventType: DatasetQualityTelemetryEventTypes.DETAILS_NAVIGATED; + schema: RootSchema; + }; diff --git a/x-pack/plugins/observability_solution/dataset_quality/tsconfig.json b/x-pack/plugins/observability_solution/dataset_quality/tsconfig.json index 8fcb39724d19e..13e698502a7a2 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/tsconfig.json +++ b/x-pack/plugins/observability_solution/dataset_quality/tsconfig.json @@ -50,6 +50,8 @@ "@kbn/calculate-auto", "@kbn/discover-plugin", "@kbn/shared-ux-prompt-no-data-views-types", + "@kbn/core-analytics-server", + "@kbn/ebt", "@kbn/ebt-tools" ], "exclude": [ diff --git a/x-pack/test/functional/apps/dataset_quality/dataset_quality_flyout.ts b/x-pack/test/functional/apps/dataset_quality/dataset_quality_flyout.ts index 59e11b225772a..6aa16cf806fe5 100644 --- a/x-pack/test/functional/apps/dataset_quality/dataset_quality_flyout.ts +++ b/x-pack/test/functional/apps/dataset_quality/dataset_quality_flyout.ts @@ -271,6 +271,11 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid }); describe('navigation', () => { + afterEach(async () => { + // Navigate back to dataset quality page after each test + await PageObjects.datasetQuality.navigateTo(); + }); + it('should go to log explorer page when the open in log explorer button is clicked', async () => { const testDatasetName = datasetNames[2]; await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName); @@ -283,9 +288,6 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid const datasetSelectorText = await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); expect(datasetSelectorText).to.eql(testDatasetName); - - // Should bring back the test to the dataset quality page - await PageObjects.datasetQuality.navigateTo(); }); it('should go log explorer for degraded docs when the show all button is clicked', async () => { @@ -293,17 +295,11 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid const degradedDocsShowAllSelector = `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutKpiLink}-${PageObjects.datasetQuality.texts.degradedDocs}`; await testSubjects.click(degradedDocsShowAllSelector); - await browser.switchTab(1); // Confirm dataset selector text in observability logs explorer const datasetSelectorText = await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); expect(datasetSelectorText).to.contain(apacheAccessDatasetName); - - await browser.closeCurrentWindow(); - await browser.switchTab(0); - - await PageObjects.datasetQuality.closeFlyout(); }); // Blocked by https://github.com/elastic/kibana/issues/181705 @@ -313,7 +309,6 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid const hostsShowAllSelector = `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutKpiLink}-${PageObjects.datasetQuality.texts.hosts}`; await testSubjects.click(hostsShowAllSelector); - await browser.switchTab(1); // Confirm url contains metrics/hosts await retry.tryForTime(5000, async () => { @@ -321,11 +316,6 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid const parsedUrl = new URL(currentUrl); expect(parsedUrl.pathname).to.contain('/app/metrics/hosts'); }); - - await browser.closeCurrentWindow(); - await browser.switchTab(0); - - await PageObjects.datasetQuality.closeFlyout(); }); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_flyout.ts b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_flyout.ts index 24f6011c78855..9d20aefcebd28 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_flyout.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_flyout.ts @@ -276,6 +276,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); describe('navigation', () => { + afterEach(async () => { + // Navigate back to dataset quality page after each test + await PageObjects.datasetQuality.navigateTo(); + }); + it('should go to log explorer page when the open in log explorer button is clicked', async () => { const testDatasetName = datasetNames[2]; await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName); @@ -288,9 +293,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const datasetSelectorText = await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); expect(datasetSelectorText).to.eql(testDatasetName); - - // Should bring back the test to the dataset quality page - await PageObjects.datasetQuality.navigateTo(); }); it('should go log explorer for degraded docs when the show all button is clicked', async () => { @@ -298,17 +300,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const degradedDocsShowAllSelector = `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutKpiLink}-${PageObjects.datasetQuality.texts.degradedDocs}`; await testSubjects.click(degradedDocsShowAllSelector); - await browser.switchTab(1); // Confirm dataset selector text in observability logs explorer const datasetSelectorText = await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); expect(datasetSelectorText).to.contain(apacheAccessDatasetName); - - await browser.closeCurrentWindow(); - await browser.switchTab(0); - - await PageObjects.datasetQuality.closeFlyout(); }); // Blocked by https://github.com/elastic/kibana/issues/181705 @@ -318,7 +314,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const hostsShowAllSelector = `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutKpiLink}-${PageObjects.datasetQuality.texts.hosts}`; await testSubjects.click(hostsShowAllSelector); - await browser.switchTab(1); // Confirm url contains metrics/hosts await retry.tryForTime(5000, async () => { @@ -326,11 +321,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const parsedUrl = new URL(currentUrl); expect(parsedUrl.pathname).to.contain('/app/metrics/hosts'); }); - - await browser.closeCurrentWindow(); - await browser.switchTab(0); - - await PageObjects.datasetQuality.closeFlyout(); }); }); From dcc3846e7850c8d38b763f91668d04bc2bbb0448 Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski Date: Tue, 2 Jul 2024 11:32:29 +0200 Subject: [PATCH 057/128] [EDR Workflows] Fix saved_queries flaky tests (#187302) --- x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts | 4 +--- x-pack/plugins/osquery/cypress/tasks/live_query.ts | 1 + x-pack/plugins/osquery/cypress/tasks/saved_queries.ts | 7 ++++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts index 27fe443eed062..8f04a30c57048 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts @@ -58,7 +58,6 @@ describe('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { it.skip('checks that user cant add a saved query with an ID that already exists', () => { cy.contains('Saved queries').click(); cy.contains('Add saved query').click(); - cy.get('input[name="id"]').type(`users_elastic{downArrow}{enter}`); cy.contains('ID must be unique').should('not.exist'); @@ -76,8 +75,7 @@ describe('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { }); }); - // FAILING ES SERVERLESS PROMOTION: https://github.com/elastic/kibana/issues/169787 - describe.skip('prebuilt', () => { + describe('prebuilt', () => { let packName: string; let packId: string; let savedQueryId: string; diff --git a/x-pack/plugins/osquery/cypress/tasks/live_query.ts b/x-pack/plugins/osquery/cypress/tasks/live_query.ts index cf6bf6020f903..4c05e2bbc98fe 100644 --- a/x-pack/plugins/osquery/cypress/tasks/live_query.ts +++ b/x-pack/plugins/osquery/cypress/tasks/live_query.ts @@ -14,6 +14,7 @@ export const DEFAULT_QUERY = 'select * from processes;'; export const BIG_QUERY = 'select * from processes, users limit 110;'; export const selectAllAgents = () => { + cy.getBySel('globalLoadingIndicator').should('not.exist'); cy.getBySel('agentSelection').find('input').should('not.be.disabled'); cy.getBySel('agentSelection').within(() => { cy.getBySel('comboBoxInput').click(); diff --git a/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts b/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts index bc006fae23ddb..ee752ace3c1de 100644 --- a/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts +++ b/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts @@ -21,8 +21,7 @@ import { import { navigateTo } from './navigation'; export const getSavedQueriesComplexTest = () => - // FLAKY: https://github.com/elastic/kibana/issues/169786 - describe.skip('Saved queries Complex Test', () => { + describe('Saved queries Complex Test', () => { const timeout = '601'; const suffix = generateRandomStringName(1)[0]; const savedQueryId = `Saved-Query-Id-${suffix}`; @@ -65,7 +64,9 @@ export const getSavedQueriesComplexTest = () => cy.getBySel(RESULTS_TABLE_COLUMNS_BUTTON).should('have.text', 'Columns32/35'); // change pagination - cy.getBySel('pagination-button-next').click().wait(500).click(); + cy.getBySel('pagination-button-next').click(); + cy.getBySel('globalLoadingIndicator').should('not.exist'); + cy.getBySel('pagination-button-next').click(); cy.getBySel(RESULTS_TABLE_COLUMNS_BUTTON).should('have.text', 'Columns32/35'); // enter fullscreen From d9b341ff1c7e9754e44a3517c60403a514e3e3f2 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 2 Jul 2024 11:33:08 +0200 Subject: [PATCH 058/128] [Discover][ES|QL] Correctly adds the limit to the field statistics queries (#186967) ## Summary Closes https://github.com/elastic/kibana/issues/186945 image ### Checklist - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../utils/get_esql_with_safe_limit.test.ts | 16 ++++++-- .../src/utils/get_esql_with_safe_limit.ts | 39 ++++++++++--------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/packages/kbn-esql-utils/src/utils/get_esql_with_safe_limit.test.ts b/packages/kbn-esql-utils/src/utils/get_esql_with_safe_limit.test.ts index e452127506b26..a18309d38dddd 100644 --- a/packages/kbn-esql-utils/src/utils/get_esql_with_safe_limit.test.ts +++ b/packages/kbn-esql-utils/src/utils/get_esql_with_safe_limit.test.ts @@ -17,15 +17,23 @@ describe('getESQLWithSafeLimit()', () => { }); it('should add the limit', () => { - expect(getESQLWithSafeLimit(' from logs', LIMIT)).toBe('from logs \n| LIMIT 10000'); + expect(getESQLWithSafeLimit('from logs', LIMIT)).toBe('from logs \n| LIMIT 10000'); expect(getESQLWithSafeLimit('FROM logs* | LIMIT 5', LIMIT)).toBe( - 'FROM logs* \n| LIMIT 10000| LIMIT 5' + 'FROM logs* \n| LIMIT 10000 | LIMIT 5' ); expect(getESQLWithSafeLimit('FROM logs* | SORT @timestamp | LIMIT 5', LIMIT)).toBe( - 'FROM logs* |SORT @timestamp \n| LIMIT 10000| LIMIT 5' + 'FROM logs* | SORT @timestamp \n| LIMIT 10000 | LIMIT 5' ); expect(getESQLWithSafeLimit('from logs* | STATS MIN(a) BY b', LIMIT)).toBe( - 'from logs* \n| LIMIT 10000| STATS MIN(a) BY b' + 'from logs* \n| LIMIT 10000 | STATS MIN(a) BY b' + ); + + expect(getESQLWithSafeLimit('from logs* | STATS MIN(a) BY b | SORT b', LIMIT)).toBe( + 'from logs* \n| LIMIT 10000 | STATS MIN(a) BY b | SORT b' + ); + + expect(getESQLWithSafeLimit('from logs* // | STATS MIN(a) BY b', LIMIT)).toBe( + 'from logs* \n| LIMIT 10000 // | STATS MIN(a) BY b' ); }); }); diff --git a/packages/kbn-esql-utils/src/utils/get_esql_with_safe_limit.ts b/packages/kbn-esql-utils/src/utils/get_esql_with_safe_limit.ts index 793292909c68f..21eef75b78800 100644 --- a/packages/kbn-esql-utils/src/utils/get_esql_with_safe_limit.ts +++ b/packages/kbn-esql-utils/src/utils/get_esql_with_safe_limit.ts @@ -5,30 +5,33 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ +import { getAstAndSyntaxErrors } from '@kbn/esql-ast'; export function getESQLWithSafeLimit(esql: string, limit: number): string { - if (!esql.trim().toLowerCase().startsWith('from')) { + const { ast } = getAstAndSyntaxErrors(esql); + const sourceCommand = ast.find(({ name }) => ['from', 'metrics'].includes(name)); + if (!sourceCommand) { return esql; } - const parts = esql.split('|'); - if (!parts.length) { - return esql; + let sortCommandIndex = -1; + const sortCommand = ast.find(({ name }, index) => { + sortCommandIndex = index; + return name === 'sort'; + }); + + if (!sortCommand || (sortCommand && sortCommandIndex !== 1)) { + const sourcePipeText = esql.substring( + sourceCommand.location.min, + sourceCommand.location.max + 1 + ); + return esql.replace(sourcePipeText, `${sourcePipeText} \n| LIMIT ${limit}`); } - const fromCommandIndex = 0; - const sortCommandIndex = 1; - const index = - parts.length > 1 && parts[1].trim().toLowerCase().startsWith('sort') - ? sortCommandIndex - : fromCommandIndex; + const sourceSortPipeText = esql.substring( + sourceCommand.location.min, + sortCommand.location.max + 1 + ); - return parts - .map((part, i) => { - if (i === index) { - return `${part.trim()} \n| LIMIT ${limit}`; - } - return part; - }) - .join('|'); + return esql.replace(sourceSortPipeText, `${sourceSortPipeText} \n| LIMIT ${limit}`); } From 320495b1ccbeb35d218e77cc23ca45f8af61d61f Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Tue, 2 Jul 2024 12:38:00 +0300 Subject: [PATCH 059/128] fix: Obs Applications > Transaction Detail][SCREEN READER]: Table rows need TH[scope="row"] for SR usability: 0011 (#186980) Closes: https://github.com/elastic/observability-dev/issues/3548 Closes: https://github.com/elastic/observability-dev/issues/3597 ## Summary 1. This PR incorporates the `rowHeader` attribute into the` Latency correlations` and `Failed transaction correlations ` tables 2. It also enhances accessibility by replacing `EuiTooltips` to `EuiIconTip`. --- .../app/correlations/correlations_table.tsx | 3 + .../failed_transactions_correlations.tsx | 133 +++++++++--------- .../app/correlations/latency_correlations.tsx | 33 +++-- 3 files changed, 85 insertions(+), 84 deletions(-) diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/correlations/correlations_table.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/correlations/correlations_table.tsx index d781e9cbf4e5d..670521bb6086c 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/correlations/correlations_table.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/correlations/correlations_table.tsx @@ -27,6 +27,7 @@ interface CorrelationsTableProps { selectedTerm?: FieldValuePair; onFilter?: () => void; columns: Array>; + rowHeader?: string; onTableChange: (c: Criteria) => void; sorting?: EuiTableSortingType; } @@ -40,6 +41,7 @@ export function CorrelationsTable({ selectedTerm, onTableChange, sorting, + rowHeader, }: CorrelationsTableProps) { const euiTheme = useTheme(); const trackApmEvent = useUiTracker({ app: 'apm' }); @@ -85,6 +87,7 @@ export function CorrelationsTable({ loading={status === FETCH_STATUS.LOADING} error={status === FETCH_STATUS.FAILURE ? errorMessage : ''} columns={columns} + rowHeader={rowHeader} rowProps={(term) => { return { onClick: () => { diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/correlations/failed_transactions_correlations.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/correlations/failed_transactions_correlations.tsx index d91f9e1935f76..9149be49e2986 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/correlations/failed_transactions_correlations.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/correlations/failed_transactions_correlations.tsx @@ -14,10 +14,8 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer, - EuiIcon, EuiTitle, EuiBadge, - EuiToolTip, EuiSwitch, EuiIconTip, } from '@elastic/eui'; @@ -108,30 +106,28 @@ export function FailedTransactionsCorrelations({ onFilter }: { onFilter: () => v width: '100px', field: 'pValue', name: ( - + {i18n.translate( + 'xpack.apm.correlations.failedTransactions.correlationsTable.pValueLabel', { - defaultMessage: - 'The chance of getting at least this amount of field name and value for failed transactions given its prevalence in successful transactions.', + defaultMessage: 'p-value', } )} - > - <> - {i18n.translate( - 'xpack.apm.correlations.failedTransactions.correlationsTable.pValueLabel', +   + - - + /> + ), render: (pValue: number) => pValue.toPrecision(3), @@ -141,29 +137,27 @@ export function FailedTransactionsCorrelations({ onFilter }: { onFilter: () => v width: '100px', field: 'failurePercentage', name: ( - + {i18n.translate( + 'xpack.apm.correlations.failedTransactions.correlationsTable.failurePercentageLabel', { - defaultMessage: 'Percentage of time the term appear in failed transactions.', + defaultMessage: 'Failure %', } )} - > - <> - {i18n.translate( - 'xpack.apm.correlations.failedTransactions.correlationsTable.failurePercentageLabel', +   + - - + /> + ), render: (_, { failurePercentage }) => asPercent(failurePercentage, 1), sortable: true, @@ -172,32 +166,29 @@ export function FailedTransactionsCorrelations({ onFilter }: { onFilter: () => v field: 'successPercentage', width: '100px', name: ( - + {i18n.translate( + 'xpack.apm.correlations.failedTransactions.correlationsTable.successPercentageLabel', { - defaultMessage: - 'Percentage of time the term appear in successful transactions.', + defaultMessage: 'Success %', } )} - > - <> - {i18n.translate( - 'xpack.apm.correlations.failedTransactions.correlationsTable.successPercentageLabel', +   + - - + /> + ), - render: (_, { successPercentage }) => asPercent(successPercentage, 1), sortable: true, }, @@ -208,25 +199,28 @@ export function FailedTransactionsCorrelations({ onFilter }: { onFilter: () => v width: '116px', field: 'normalizedScore', name: ( - + {i18n.translate( + 'xpack.apm.correlations.failedTransactions.correlationsTable.scoreLabel', { - defaultMessage: - 'The score [0-1] of an attribute; the greater the score, the more an attribute contributes to failed transactions.', + defaultMessage: 'Score', } )} - > - <> - {i18n.translate( - 'xpack.apm.correlations.failedTransactions.correlationsTable.scoreLabel', +   + - - + /> + ), render: (_, { normalizedScore }) => { return
      {asPreciseDecimal(normalizedScore, 2)}
      ; @@ -515,6 +509,7 @@ export function FailedTransactionsCorrelations({ onFilter }: { onFilter: () => v {showCorrelationsTable && ( columns={failedTransactionsCorrelationsColumns} + rowHeader="normalizedScore" significantTerms={correlationTerms} status={progress.isRunning ? FETCH_STATUS.LOADING : FETCH_STATUS.SUCCESS} setPinnedSignificantTerm={setPinnedSignificantTerm} diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/correlations/latency_correlations.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/correlations/latency_correlations.tsx index 33310928a398b..1183338b0f4bd 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/correlations/latency_correlations.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/correlations/latency_correlations.tsx @@ -10,14 +10,13 @@ import { useHistory } from 'react-router-dom'; import { orderBy } from 'lodash'; import { - EuiIcon, EuiBasicTableColumn, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle, - EuiToolTip, EuiBadge, + EuiIconTip, } from '@elastic/eui'; import { Direction } from '@elastic/eui/src/services/sort/sort_direction'; import { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types'; @@ -123,25 +122,28 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { width: '116px', field: 'correlation', name: ( - + {i18n.translate( + 'xpack.apm.correlations.latencyCorrelations.correlationsTable.correlationLabel', { - defaultMessage: - 'The correlation score [0-1] of an attribute; the greater the score, the more an attribute increases latency.', + defaultMessage: 'Correlation', } )} - > - <> - {i18n.translate( - 'xpack.apm.correlations.latencyCorrelations.correlationsTable.correlationLabel', +   + - - + size="s" + color="subdued" + type="questionInCircle" + className="eui-alignTop" + /> + ), render: (_, { correlation }) => { return
      {asPreciseDecimal(correlation, 2)}
      ; @@ -364,6 +366,7 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { {showCorrelationsTable && ( columns={mlCorrelationColumns} + rowHeader="correlation" significantTerms={histogramTerms} status={progress.isRunning ? FETCH_STATUS.LOADING : FETCH_STATUS.SUCCESS} setPinnedSignificantTerm={setPinnedSignificantTerm} From 3699c3a44986d648a49a2beb7a0a19383928f898 Mon Sep 17 00:00:00 2001 From: jennypavlova Date: Tue, 2 Jul 2024 11:45:24 +0200 Subject: [PATCH 060/128] [APM] Environment N/A badge for the classic view should not appear (#187329) Closed #187245 ## Summary This PR removed the Environment N/A badge from the classic view. The Environment N/A badge should still be visible for services coming from logs and if there is an environment value it should still be visible. image ## Testing 1. Enable `observability:apmEnableMultiSignal` in advanced settings
      2. Run the entities definition in the dev tools ``` POST kbn:/internal/api/entities/definition { "id": "apm-services-with-metadata", "name": "Services from logs and metrics", "displayNameTemplate": "test", "history": { "timestampField": "@timestamp", "interval": "5m" }, "type": "service", "indexPatterns": [ "logs-*", "metrics-*" ], "timestampField": "@timestamp", "lookback": "5m", "identityFields": [ { "field": "service.name", "optional": false }, { "field": "service.environment", "optional": true } ], "identityTemplate": "{{service.name}}:{{service.environment}}", "metadata": [ "tags", "host.name", "data_stream.type", "service.name", "service.instance.id", "service.namespace", "service.environment", "service.version", "service.runtime.name", "service.runtime.version", "service.node.name", "service.language.name", "agent.name", "cloud.provider", "cloud.instance.id", "cloud.availability_zone", "cloud.instance.name", "cloud.machine.type", "container.id" ], "metrics": [ { "name": "latency", "equation": "A", "metrics": [ { "name": "A", "aggregation": "avg", "field": "transaction.duration.histogram" } ] }, { "name": "throughput", "equation": "A / 5", "metrics": [ { "name": "A", "aggregation": "doc_count", "filter": "transaction.duration.histogram:*" } ] }, { "name": "failedTransactionRate", "equation": "A / B", "metrics": [ { "name": "A", "aggregation": "doc_count", "filter": "event.outcome: \"failure\"" }, { "name": "B", "aggregation": "doc_count", "filter": "event.outcome: *" } ] }, { "name": "logErrorRate", "equation": "A / B", "metrics": [ { "name": "A", "aggregation": "doc_count", "filter": "log.level: \"error\"" }, { "name": "B", "aggregation": "doc_count", "filter": "log.level: *" } ] }, { "name": "logRatePerMinute", "equation": "A / 5", "metrics": [ { "name": "A", "aggregation": "doc_count", "filter": "log.level: \"error\"" } ] } ] } ```
      3. Generate data with synthrace 1. logs only: `node scripts/synthtrace simple_logs.ts` 2. APM only: `node scripts/synthtrace simple_trace.ts` 3. Modify simple_trace: Change line 29 to ``` .service({ name: `synth-no-env-${index}`, agentName: 'nodejs' }) ``` and run it again 4. Oblt Cluster ( No N/A label ) image --- .../multi_signal_inventory/table/get_service_columns.tsx | 7 ++++++- .../public/components/shared/environment_badge/index.tsx | 7 ++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx index 349fc043c6f16..48667b7834656 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx @@ -68,7 +68,12 @@ export function getServiceColumns({ sortable: true, width: `${unit * 9}px`, dataType: 'number', - render: (_, { environments }) => , + render: (_, { environments, signalTypes }) => ( + + ), align: RIGHT_ALIGNMENT, }, { diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/environment_badge/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/environment_badge/index.tsx index 6863d76908a0f..135848bae13d4 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/environment_badge/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/environment_badge/index.tsx @@ -12,12 +12,13 @@ import { NotAvailableEnvironment } from '../not_available_environment'; interface Props { environments: string[]; + isMetricsSignalType?: boolean; } -export function EnvironmentBadge({ environments = [] }: Props) { - return environments && environments.length > 0 ? ( +export function EnvironmentBadge({ environments = [], isMetricsSignalType = true }: Props) { + return isMetricsSignalType || (environments && environments.length > 0) ? ( Date: Tue, 2 Jul 2024 11:45:46 +0200 Subject: [PATCH 061/128] [CI] Display command on failure page (#186999) ## Summary This PR adds the executed command line to the failures page. We tweak the reporters to export the executed command to the junit xmls, then we read those attributes after parsing the results. The tests needed some adjustment, because they're very brittle, and don't seem to be very accurate anymore. Closes: https://github.com/elastic/kibana-operations/issues/127 Check out the `[logs]` for the failed tests here (ftr/jest/jest_integration): https://buildkite.com/elastic/kibana-pull-request/builds/218457 --- .../__fixtures__/ftr_report.xml | 4 ++-- .../__fixtures__/jest_report.xml | 4 ++-- .../__fixtures__/mocha_report.xml | 4 ++-- .../add_messages_to_report.test.ts | 10 ++++----- .../get_failures.test.ts | 6 +++++ .../failed_tests_reporter/get_failures.ts | 21 ++++++++++++++++-- .../report_failures_to_file.ts | 21 +++++++++++++----- .../failed_tests_reporter/test_report.ts | 4 ++++ .../src/jest/junit_reporter/junit_reporter.ts | 3 +++ .../src/mocha/junit_report_generation.js | 19 +++++++++++++--- .../src/mocha/junit_report_generation.test.js | 6 +++-- .../kbn-test/src/prettify_command_line.ts | 22 +++++++++++++++++++ 12 files changed, 100 insertions(+), 24 deletions(-) create mode 100644 packages/kbn-test/src/prettify_command_line.ts diff --git a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/__fixtures__/ftr_report.xml b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/__fixtures__/ftr_report.xml index 3bfc686f9e845..07f1e79b0f5df 100644 --- a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/__fixtures__/ftr_report.xml +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/__fixtures__/ftr_report.xml @@ -1,6 +1,6 @@ - - + + - - + + diff --git a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/__fixtures__/mocha_report.xml b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/__fixtures__/mocha_report.xml index 64cb7ce551ee5..699ad47c58240 100644 --- a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/__fixtures__/mocha_report.xml +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/__fixtures__/mocha_report.xml @@ -1,6 +1,6 @@ - - + + diff --git a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/add_messages_to_report.test.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/add_messages_to_report.test.ts index 02197cf54fab0..42f3dcd120e9c 100644 --- a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/add_messages_to_report.test.ts +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/add_messages_to_report.test.ts @@ -60,8 +60,8 @@ it('rewrites ftr reports with minimal changes', async () => { +++ ftr.xml @@ -1,53 +1,56 @@ ‹?xml version="1.0" encoding="utf-8"?› - ‹testsuites› - ‹testsuite timestamp="2019-06-05T23:37:10" time="903.670" tests="129" failures="5" skipped="71"› + ‹testsuites name="ftr" timestamp="2019-06-05T23:37:10" time="903.670" tests="129" failures="5" skipped="71" command-line="node scripts/functional_tests --config=x-pack/test/api_integration/apis/status/config.ts"› + ‹testsuite timestamp="2019-06-05T23:37:10" time="903.670" tests="129" failures="5" skipped="71" command-line="node scripts/functional_tests --config=x-pack/test/api_integration/apis/status/config.ts"› ‹testcase name="maps app maps loaded from sample data ecommerce "before all" hook" classname="Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/maps/sample_data·js" time="154.378"› - ‹system-out› - ‹![CDATA[[00:00:00] │ @@ -155,7 +155,7 @@ it('rewrites jest reports with minimal changes', async () => { --- jest.xml +++ jest.xml @@ -3,13 +3,17 @@ - ‹testsuite name="x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts" timestamp="2019-06-07T03:42:21" time="14.504" tests="5" failures="1" skipped="0" file="/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts"› + ‹testsuite name="x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts" timestamp="2019-06-07T03:42:21" time="14.504" tests="5" failures="1" skipped="0" file="/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts" command-line="node scripts/jest --config some/jest/config.ts"› ‹testcase classname="X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp" name="launcher can start and end a process" time="1.316"/› ‹testcase classname="X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp" name="launcher can force kill the process if langServer can not exit" time="3.182"/› ‹testcase classname="X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp" name="launcher can reconnect if process died" time="7.060"› @@ -203,8 +203,8 @@ it('rewrites mocha reports with minimal changes', async () => { +++ mocha.xml @@ -1,13 +1,16 @@ ‹?xml version="1.0" encoding="utf-8"?› - ‹testsuites› - ‹testsuite timestamp="2019-06-13T23:29:36" time="30.739" tests="1444" failures="2" skipped="3"› + ‹testsuites command-line="node scripts/functional_tests --config super-mocha-test.config.js"› + ‹testsuite timestamp="2019-06-13T23:29:36" time="30.739" tests="1444" failures="2" skipped="3" command-line="node scripts/functional_tests --config super-mocha-test.config.js"› ‹testcase name="code in multiple nodes "before all" hook" classname="X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/multi_node·ts" time="0.121"› - ‹system-out› - ‹![CDATA[]]› diff --git a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/get_failures.test.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/get_failures.test.ts index 77d7cd93ce4b0..d76662c600724 100644 --- a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/get_failures.test.ts +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/get_failures.test.ts @@ -16,6 +16,7 @@ it('discovers failures in ftr report', async () => { Array [ Object { "classname": "Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/maps/sample_data·js", + "commandLine": "node scripts/functional_tests --config=x-pack/test/api_integration/apis/status/config.ts", "failure": " Error: retry.try timeout: TimeoutError: Waiting for element to be located By(css selector, [data-test-subj~=\\"layerTocActionsPanelToggleButtonRoad_Map_-_Bright\\"]) Wait timed out after 10055ms @@ -37,6 +38,7 @@ it('discovers failures in ftr report', async () => { }, Object { "classname": "Chrome X-Pack UI Functional Tests.x-pack/test/functional/apps/maps", + "commandLine": "node scripts/functional_tests --config=x-pack/test/api_integration/apis/status/config.ts", "failure": " { NoSuchSessionError: This driver instance does not have a valid session ID (did you call WebDriver.quit()?) and may no longer be used. at promise.finally (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-ciGroup7/node/immutable/kibana/node_modules/selenium-webdriver/lib/webdriver.js:726:38) @@ -56,6 +58,7 @@ it('discovers failures in ftr report', async () => { }, Object { "classname": "Firefox XPack UI Functional Tests.x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job·ts", + "commandLine": "node scripts/functional_tests --config=x-pack/test/api_integration/apis/status/config.ts", "failure": "{ NoSuchSessionError: Tried to run command without establishing a connection at Object.throwDecodedError (/dev/shm/workspace/kibana/node_modules/selenium-webdriver/lib/error.js:550:15) at parseHttpResponse (/dev/shm/workspace/kibana/node_modules/selenium-webdriver/lib/http.js:563:13) @@ -76,6 +79,7 @@ it('discovers failures in jest report', async () => { Array [ Object { "classname": "X-Pack Jest Tests.x-pack/legacy/plugins/code/server/lsp", + "commandLine": "node scripts/jest --config some/jest/config.ts", "failure": " TypeError: Cannot read property '0' of undefined at Object..test (/var/lib/jenkins/workspace/elastic+kibana+master/JOB/x-pack-intake/node/immutable/kibana/x-pack/legacy/plugins/code/server/lsp/abstract_launcher.test.ts:166:10) @@ -95,6 +99,7 @@ it('discovers failures in mocha report', async () => { Array [ Object { "classname": "X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/multi_node·ts", + "commandLine": "node scripts/functional_tests --config super-mocha-test.config.js", "failure": " Error: Unable to read artifact info from https://artifacts-api.elastic.co/v1/versions/8.0.0-SNAPSHOT/builds/latest/projects/elasticsearch: Service Temporarily Unavailable @@ -117,6 +122,7 @@ it('discovers failures in mocha report', async () => { }, Object { "classname": "X-Pack Mocha Tests.x-pack/legacy/plugins/code/server/__tests__/multi_node·ts", + "commandLine": "node scripts/functional_tests --config super-mocha-test.config.js", "failure": " TypeError: Cannot read property 'shutdown' of undefined at Context.shutdown (plugins/code/server/__tests__/multi_node.ts:125:23) diff --git a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/get_failures.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/get_failures.ts index e3230e3cdddce..f0955b17f9953 100644 --- a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/get_failures.ts +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/get_failures.ts @@ -16,6 +16,7 @@ export type TestFailure = FailedTestCase['$'] & { 'system-out'?: string; githubIssue?: string; failureCount?: number; + commandLine?: string; }; const getText = (node?: Array) => { @@ -71,19 +72,35 @@ const isLikelyIrrelevant = (name: string, failure: string) => { export function getFailures(report: TestReport) { const failures: TestFailure[] = []; + const commandLine = getCommandLineFromReport(report); + for (const testCase of makeFailedTestCaseIter(report)) { const failure = getText(testCase.failure); const likelyIrrelevant = isLikelyIrrelevant(testCase.$.name, failure); - failures.push({ + const failureObj = { // unwrap xml weirdness ...testCase.$, // Strip ANSI color characters failure, likelyIrrelevant, 'system-out': getText(testCase['system-out']), - }); + commandLine, + }; + + // cleaning up duplicates + delete failureObj['command-line']; + + failures.push(failureObj); } return failures; } + +function getCommandLineFromReport(report: TestReport) { + if ('testsuites' in report) { + return report.testsuites?.testsuite?.[0]?.$['command-line'] || ''; + } else { + return report.testsuite?.$['command-line'] || ''; + } +} diff --git a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_file.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_file.ts index ab54d7f60dfe5..d2c0fb705d1aa 100644 --- a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_file.ts +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/report_failures_to_file.ts @@ -170,14 +170,23 @@ export async function reportFailuresToFile(

      ${escape(failure.name)}

      - Failures in tracked branches: ${ - failure.failureCount || 0 - } + ${ + failure.commandLine + ? `

      + Command Line: +
      ${escape(failure.commandLine)}
      +
      ` + : '' + } +
      + Failures in tracked branches: + ${failure.failureCount || 0} +
      ${ failure.githubIssue - ? `
      ${escape( - failure.githubIssue - )}` + ? `` : '' }
      diff --git a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/test_report.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/test_report.ts index e70aa44a2a088..ce817f90079e0 100644 --- a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/test_report.ts +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/test_report.ts @@ -37,6 +37,8 @@ export interface TestSuite { skipped: string; /* optional JSON encoded metadata */ 'metadata-json'?: string; + /* the command that ran this suite */ + 'command-line'?: string; }; testcase?: TestCase[]; } @@ -51,6 +53,8 @@ export interface TestCase { time: string; /* optional JSON encoded metadata */ 'metadata-json'?: string; + /* the command that ran this suite */ + 'command-line'?: string; }; /* contents of system-out elements */ 'system-out'?: Array; diff --git a/packages/kbn-test/src/jest/junit_reporter/junit_reporter.ts b/packages/kbn-test/src/jest/junit_reporter/junit_reporter.ts index edb109eaa7000..ef6986183dd6c 100644 --- a/packages/kbn-test/src/jest/junit_reporter/junit_reporter.ts +++ b/packages/kbn-test/src/jest/junit_reporter/junit_reporter.ts @@ -17,6 +17,7 @@ import { AggregatedResult, Test, BaseReporter } from '@jest/reporters'; import { escapeCdata } from '../../mocha/xml'; import { getUniqueJunitReportPath } from '../../report_path'; +import { prettifyCommandLine } from '../../prettify_command_line'; interface ReporterOptions { reportName?: string; @@ -71,6 +72,7 @@ export default class JestJUnitReporter extends BaseReporter { tests: results.numTotalTests, failures: results.numFailedTests, skipped: results.numPendingTests, + 'command-line': prettifyCommandLine(process.argv), }); // top level test results are the files/suites @@ -83,6 +85,7 @@ export default class JestJUnitReporter extends BaseReporter { failures: suite.numFailingTests, skipped: suite.numPendingTests, file: suite.testFilePath, + 'command-line': prettifyCommandLine(process.argv), }); // nested in there are the tests in that file diff --git a/packages/kbn-test/src/mocha/junit_report_generation.js b/packages/kbn-test/src/mocha/junit_report_generation.js index 4b35fba4fb1e6..fcf31672d7996 100644 --- a/packages/kbn-test/src/mocha/junit_report_generation.js +++ b/packages/kbn-test/src/mocha/junit_report_generation.js @@ -17,6 +17,7 @@ import { getUniqueJunitReportPath } from '../report_path'; import { getSnapshotOfRunnableLogs } from './log_cache'; import { escapeCdata } from '../..'; +import { prettifyCommandLine } from '../prettify_command_line'; const dateNow = Date.now.bind(Date); @@ -95,14 +96,25 @@ export function setupJUnitReportGeneration(runner, options = {}) { // cache codeowners for quicker lookup const reversedCodeowners = getPathsWithOwnersReversed(); - const builder = xmlBuilder.create( + const commandLine = prettifyCommandLine(process.argv); + + const root = xmlBuilder.create( 'testsuites', { encoding: 'utf-8' }, {}, { skipNullAttributes: true } ); - const testsuitesEl = builder.ele('testsuite', { + root.att({ + name: 'ftr', + time: getDuration(stats), + tests: allTests.length + failedHooks.length, + failures: failures.length, + skipped: skippedResults.length, + 'command-line': commandLine, + }); + + const testsuitesEl = root.ele('testsuite', { name: reportName, timestamp: new Date(stats.startTime).toISOString().slice(0, -5), time: getDuration(stats), @@ -110,6 +122,7 @@ export function setupJUnitReportGeneration(runner, options = {}) { failures: failures.length, skipped: skippedResults.length, 'metadata-json': JSON.stringify(metadata ?? {}), + 'command-line': commandLine, }); function addTestcaseEl(node, failed) { @@ -147,7 +160,7 @@ export function setupJUnitReportGeneration(runner, options = {}) { }); const reportPath = getUniqueJunitReportPath(rootDirectory, reportName); - const reportXML = builder.end(); + const reportXML = root.end(); mkdirSync(dirname(reportPath), { recursive: true }); writeFileSync(reportPath, reportXML, 'utf8'); }); diff --git a/packages/kbn-test/src/mocha/junit_report_generation.test.js b/packages/kbn-test/src/mocha/junit_report_generation.test.js index b6bc2e951d1df..aad96a93fd860 100644 --- a/packages/kbn-test/src/mocha/junit_report_generation.test.js +++ b/packages/kbn-test/src/mocha/junit_report_generation.test.js @@ -45,9 +45,9 @@ describe('dev/mocha/junit report generation', () => { // test case results are wrapped in expect(report).toEqual({ - testsuites: { + testsuites: expect.objectContaining({ testsuite: [report.testsuites.testsuite[0]], - }, + }), }); // the single element at the root contains summary data for all tests results @@ -55,6 +55,8 @@ describe('dev/mocha/junit report generation', () => { expect(testsuite.$.time).toMatch(DURATION_REGEX); expect(testsuite.$.timestamp).toMatch(ISO_DATE_SEC_REGEX); expect(testsuite.$).toEqual({ + 'command-line': + 'node scripts/jest --config=packages/kbn-test/jest.config.js --runInBand --coverage=false --passWithNoTests', failures: '2', name: 'test', skipped: '1', diff --git a/packages/kbn-test/src/prettify_command_line.ts b/packages/kbn-test/src/prettify_command_line.ts new file mode 100644 index 0000000000000..0f8f1eb75570b --- /dev/null +++ b/packages/kbn-test/src/prettify_command_line.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { execSync } from 'child_process'; +import * as path from 'path'; + +const kibanaRoot = execSync('git rev-parse --show-toplevel').toString().trim() || process.cwd(); + +export function prettifyCommandLine(args: string[]) { + let [executable, ...rest] = args; + if (executable.endsWith('node')) { + executable = 'node'; + } + rest = rest.map((arg) => path.relative(kibanaRoot, arg)); + + return [executable, ...rest].join(' '); +} From d183092603cf4a40632733689d068d84f422b88f Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski Date: Tue, 2 Jul 2024 12:00:38 +0200 Subject: [PATCH 062/128] [EDR Workflows] Unskip add_integration.cy.ts (#187304) --- x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts index 0c423dc9cfe8c..6b847fb396967 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts @@ -103,8 +103,7 @@ describe('ALL - Add Integration', { tags: ['@ess', '@serverless'] }, () => { cy.contains(`version: ${oldVersion}`).should('not.exist'); }); }); - // FLAKY: https://github.com/elastic/kibana/issues/170593 - describe.skip('Add integration to policy', () => { + describe('Add integration to policy', () => { const [integrationName, policyName] = generateRandomStringName(2); let policyId: string; beforeEach(() => { From e73eb1d33e5b8e473e870b410604762d3da1e889 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 2 Jul 2024 12:32:53 +0200 Subject: [PATCH 063/128] Improves the performance of the table ES|QL visualization (#187142) ## Summary Closes https://github.com/elastic/kibana/issues/187089 Fixes the performance of the ES|QL charts: - the panel - the table visualization The ES|QL charts become imperformant for a big amount of fields. I am changing the implementation a bit in order to render (and hold to cache) only the fields that take part in the visualization and not all colums ### Checklist - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../map_to_columns/map_to_columns.test.ts | 52 +++++++++++++ .../map_to_columns/map_to_columns.ts | 12 ++- .../map_to_columns_fn_textbased.ts | 38 ++++++++++ .../expressions/map_to_columns/types.ts | 1 + .../shared/edit_on_the_fly/helpers.ts | 2 +- .../lens_configuration_flyout.tsx | 3 +- .../components/dimension_editor.tsx | 73 +++++++++++++------ .../components/dimension_trigger.tsx | 35 +-------- .../text_based/text_based_languages.test.ts | 3 + .../text_based/text_based_languages.tsx | 17 ++++- .../datasources/text_based/to_expression.ts | 1 + .../open_lens_config/create_action_helpers.ts | 2 +- 12 files changed, 174 insertions(+), 65 deletions(-) create mode 100644 x-pack/plugins/lens/common/expressions/map_to_columns/map_to_columns_fn_textbased.ts diff --git a/x-pack/plugins/lens/common/expressions/map_to_columns/map_to_columns.test.ts b/x-pack/plugins/lens/common/expressions/map_to_columns/map_to_columns.test.ts index e5d678b88e5a5..5a70c4385784c 100644 --- a/x-pack/plugins/lens/common/expressions/map_to_columns/map_to_columns.test.ts +++ b/x-pack/plugins/lens/common/expressions/map_to_columns/map_to_columns.test.ts @@ -279,4 +279,56 @@ describe('map_to_columns', () => { } `); }); + + describe('map_to_columns_text_based', () => { + it('should keep columns that exist in idMap only', async () => { + const input: Datatable = { + type: 'datatable', + columns: [ + { id: 'a', name: 'A', meta: { type: 'number' } }, + { id: 'b', name: 'B', meta: { type: 'number' } }, + { id: 'c', name: 'C', meta: { type: 'string' } }, + ], + rows: [ + { a: 1, b: 2, c: '3' }, + { a: 3, b: 4, c: '5' }, + { a: 5, b: 6, c: '7' }, + { a: 7, b: 8, c: '9' }, + ], + }; + + const idMap = { + a: [ + { + id: 'a', + label: 'A', + }, + ], + b: [ + { + id: 'b', + label: 'B', + }, + ], + }; + + const result = await mapToColumns.fn( + input, + { idMap: JSON.stringify(idMap), isTextBased: true }, + createMockExecutionContext() + ); + + expect(result.columns).toStrictEqual([ + { id: 'a', name: 'A', meta: { type: 'number' } }, + { id: 'b', name: 'B', meta: { type: 'number' } }, + ]); + + expect(result.rows).toStrictEqual([ + { a: 1, b: 2 }, + { a: 3, b: 4 }, + { a: 5, b: 6 }, + { a: 7, b: 8 }, + ]); + }); + }); }); diff --git a/x-pack/plugins/lens/common/expressions/map_to_columns/map_to_columns.ts b/x-pack/plugins/lens/common/expressions/map_to_columns/map_to_columns.ts index 3315cd4170dd9..0faa4de4fac41 100644 --- a/x-pack/plugins/lens/common/expressions/map_to_columns/map_to_columns.ts +++ b/x-pack/plugins/lens/common/expressions/map_to_columns/map_to_columns.ts @@ -22,11 +22,21 @@ export const mapToColumns: MapToColumnsExpressionFunction = { 'A JSON encoded object in which keys are the datatable column ids and values are the Lens column definitions. Any datatable columns not mentioned within the ID map will be kept unmapped.', }), }, + isTextBased: { + types: ['boolean'], + help: i18n.translate('xpack.lens.functions.mapToColumns.isESQL.help', { + defaultMessage: 'An optional flag to indicate if this is about the text based datasource.', + }), + }, }, inputTypes: ['datatable'], async fn(...args) { /** Build optimization: prevent adding extra code into initial bundle **/ const { mapToOriginalColumns } = await import('./map_to_columns_fn'); - return mapToOriginalColumns(...args); + const { mapToOriginalColumnsTextBased } = await import('./map_to_columns_fn_textbased'); + + return args?.[1]?.isTextBased + ? mapToOriginalColumnsTextBased(...args) + : mapToOriginalColumns(...args); }, }; diff --git a/x-pack/plugins/lens/common/expressions/map_to_columns/map_to_columns_fn_textbased.ts b/x-pack/plugins/lens/common/expressions/map_to_columns/map_to_columns_fn_textbased.ts new file mode 100644 index 0000000000000..cbaa8b8888dfe --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/map_to_columns/map_to_columns_fn_textbased.ts @@ -0,0 +1,38 @@ +/* + * 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 { OriginalColumn, MapToColumnsExpressionFunction } from './types'; + +export const mapToOriginalColumnsTextBased: MapToColumnsExpressionFunction['fn'] = ( + data, + { idMap: encodedIdMap } +) => { + const idMap = JSON.parse(encodedIdMap) as Record; + + return { + ...data, + rows: data.rows.map((row) => { + const mappedRow: Record = {}; + + for (const id in row) { + if (id in idMap) { + for (const cachedEntry of idMap[id]) { + mappedRow[cachedEntry.id] = row[id]; // <= I wrote idMap rather than mappedRow + } + } + } + + return mappedRow; + }), + columns: data.columns.flatMap((column) => { + if (!(column.id in idMap)) { + return []; + } + return idMap[column.id].map((originalColumn) => ({ ...column, id: originalColumn.id })); + }), + }; +}; diff --git a/x-pack/plugins/lens/common/expressions/map_to_columns/types.ts b/x-pack/plugins/lens/common/expressions/map_to_columns/types.ts index 4623f435e5a67..e6617c38863bf 100644 --- a/x-pack/plugins/lens/common/expressions/map_to_columns/types.ts +++ b/x-pack/plugins/lens/common/expressions/map_to_columns/types.ts @@ -17,6 +17,7 @@ export type MapToColumnsExpressionFunction = ExpressionFunctionDefinition< Datatable, { idMap: string; + isTextBased?: boolean; }, Datatable | Promise >; diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/helpers.ts b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/helpers.ts index 8da4c87607987..e0f6654287a3f 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/helpers.ts +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/helpers.ts @@ -42,7 +42,7 @@ export const getSuggestions = async ( signal: abortController?.signal, }); const context = { - dataViewSpec: dataView?.toSpec(), + dataViewSpec: dataView?.toSpec(false), fieldName: '', textBasedColumns: columns, query, diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx index d55cfddf5a488..5c163df2c0715 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx @@ -117,8 +117,7 @@ export function LensEditConfigurationFlyout({ // there are cases where a query can return a big amount of columns // at this case we don't suggest all columns in a table but the first // MAX_NUM_OF_COLUMNS - const columns = Object.keys(table.rows?.[0]) ?? []; - setSuggestsLimitedColumns(columns.length >= MAX_NUM_OF_COLUMNS); + setSuggestsLimitedColumns(table.columns.length >= MAX_NUM_OF_COLUMNS); layers.forEach((layer) => { activeData[layer] = table; }); diff --git a/x-pack/plugins/lens/public/datasources/text_based/components/dimension_editor.tsx b/x-pack/plugins/lens/public/datasources/text_based/components/dimension_editor.tsx index 0f82a65fc1ff7..d81ad37a22030 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/components/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/datasources/text_based/components/dimension_editor.tsx @@ -5,15 +5,15 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFormRow } from '@elastic/eui'; import { euiThemeVars } from '@kbn/ui-theme'; -import type { ExpressionsStart } from '@kbn/expressions-plugin/public'; +import type { ExpressionsStart, DatatableColumn } from '@kbn/expressions-plugin/public'; +import { fetchFieldsFromESQL } from '@kbn/text-based-editor'; import type { DatasourceDimensionEditorProps, DataType } from '../../../types'; import { FieldSelect } from './field_select'; import type { TextBasedPrivateState } from '../types'; -import { retrieveLayerColumnsFromCache, getColumnsFromCache } from '../fieldlist_cache'; import { isNotNumeric, isNumeric } from '../utils'; export type TextBasedDimensionEditorProps = @@ -22,30 +22,55 @@ export type TextBasedDimensionEditorProps = }; export function TextBasedDimensionEditor(props: TextBasedDimensionEditorProps) { + const [allColumns, setAllColumns] = useState([]); const query = props.state.layers[props.layerId]?.query; - const allColumns = retrieveLayerColumnsFromCache( - props.state.layers[props.layerId]?.columns ?? [], - query - ); - const allFields = query ? getColumnsFromCache(query) : []; + useEffect(() => { + // in case the columns are not in the cache, I refetch them + async function fetchColumns() { + if (query) { + const table = await fetchFieldsFromESQL( + { esql: `${query.esql} | limit 0` }, + props.expressions + ); + if (table) { + setAllColumns(table.columns); + } + } + } + fetchColumns(); + }, [props.expressions, query]); + const hasNumberTypeColumns = allColumns?.some(isNumeric); - const fields = allFields.map((col) => { - return { - id: col.id, - name: col.name, - meta: col?.meta ?? { type: 'number' }, - compatible: - props.isMetricDimension && hasNumberTypeColumns - ? props.filterOperations({ - dataType: col?.meta?.type as DataType, - isBucketed: Boolean(isNotNumeric(col)), - scale: 'ordinal', - }) - : true, - }; - }); - const selectedField = allColumns?.find((column) => column.columnId === props.columnId); + const fields = useMemo(() => { + return allColumns.map((col) => { + return { + id: col.id, + name: col.name, + meta: col?.meta ?? { type: 'number' }, + compatible: + props.isMetricDimension && hasNumberTypeColumns + ? props.filterOperations({ + dataType: col?.meta?.type as DataType, + isBucketed: Boolean(isNotNumeric(col)), + scale: 'ordinal', + }) + : true, + }; + }); + }, [allColumns, hasNumberTypeColumns, props]); + + const selectedField = useMemo(() => { + const field = fields?.find((column) => column.id === props.columnId); + if (field) { + return { + fieldName: field.name, + meta: field.meta, + columnId: field.id, + }; + } + return undefined; + }, [fields, props.columnId]); return ( <> diff --git a/x-pack/plugins/lens/public/datasources/text_based/components/dimension_trigger.tsx b/x-pack/plugins/lens/public/datasources/text_based/components/dimension_trigger.tsx index f6062068cee77..922c0b2ba9fab 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/components/dimension_trigger.tsx +++ b/x-pack/plugins/lens/public/datasources/text_based/components/dimension_trigger.tsx @@ -5,18 +5,12 @@ * 2.0. */ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; -import { fetchFieldsFromESQL } from '@kbn/text-based-editor'; import { DimensionTrigger } from '@kbn/visualization-ui-components'; import type { ExpressionsStart } from '@kbn/expressions-plugin/public'; import type { DatasourceDimensionTriggerProps } from '../../../types'; import type { TextBasedPrivateState } from '../types'; -import { - getColumnsFromCache, - addColumnsToCache, - retrieveLayerColumnsFromCache, -} from '../fieldlist_cache'; export type TextBasedDimensionTrigger = DatasourceDimensionTriggerProps & { columnLabelMap: Record; @@ -24,35 +18,12 @@ export type TextBasedDimensionTrigger = DatasourceDimensionTriggerProps { - // in case the columns are not in the cache, I refetch them - async function fetchColumns() { - const fieldList = query ? getColumnsFromCache(query) : []; + const customLabel: string | undefined = props.columnLabelMap[props.columnId]; - if (fieldList.length === 0 && query) { - const table = await fetchFieldsFromESQL(query, props.expressions); - if (table) { - addColumnsToCache(query, table.columns); - } - } - setDataHasLoaded(true); - } - fetchColumns(); - }, [props.expressions, query]); - const allColumns = dataHasLoaded - ? retrieveLayerColumnsFromCache(props.state.layers[props.layerId]?.columns ?? [], query) - : []; - const selectedField = allColumns?.find((column) => column.columnId === props.columnId); - let customLabel: string | undefined = props.columnLabelMap[props.columnId]; - if (!customLabel) { - customLabel = selectedField?.fieldName; - } return ( { "idMap": Array [ "{\\"Test 1\\":[{\\"id\\":\\"a\\",\\"label\\":\\"Test 1\\"}],\\"Test 2\\":[{\\"id\\":\\"b\\",\\"label\\":\\"Test 2\\"}]}", ], + "isTextBased": Array [ + true, + ], }, "function": "lens_map_to_columns", "type": "function", diff --git a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx index 8cf8ce7ebd360..411583d88ef13 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx +++ b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx @@ -11,7 +11,7 @@ import { CoreStart } from '@kbn/core/public'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { AggregateQuery, isOfAggregateQueryType, getAggregateQueryMode } from '@kbn/es-query'; import type { SavedObjectReference } from '@kbn/core/public'; -import type { ExpressionsStart } from '@kbn/expressions-plugin/public'; +import type { ExpressionsStart, DatatableColumn } from '@kbn/expressions-plugin/public'; import type { DataViewsPublicPluginStart, DataView } from '@kbn/data-views-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import memoizeOne from 'memoize-one'; @@ -180,6 +180,15 @@ export function getTextBasedDatasource({ return Object.entries(state.layers)?.flatMap(([id, layer]) => { const allColumns = retrieveLayerColumnsFromCache(layer.columns, layer.query); + if (!allColumns.length && layer.query) { + const layerColumns = layer.columns.map((c) => ({ + id: c.columnId, + name: c.fieldName, + meta: c.meta, + })) as DatatableColumn[]; + addColumnsToCache(layer.query, layerColumns); + } + const unchangedSuggestionTable = getUnchangedSuggestionTable(state, allColumns, id); // we are trying here to cover the most common cases for the charts we offer @@ -214,7 +223,7 @@ export function getTextBasedDatasource({ if (fieldName) return []; if (context && 'dataViewSpec' in context && context.dataViewSpec.title && context.query) { const newLayerId = generateId(); - const textBasedQueryColumns = context.textBasedColumns ?? []; + const textBasedQueryColumns = context.textBasedColumns?.slice(0, MAX_NUM_OF_COLUMNS) ?? []; // Number fields are assigned automatically as metrics (!isBucketed). There are cases where the query // will not return number fields. In these cases we want to suggest a datatable // Datatable works differently in this case. On the metrics dimension can be all type of fields @@ -258,7 +267,7 @@ export function getTextBasedDatasource({ [newLayerId]: { index, query, - columns: newColumns.slice(0, MAX_NUM_OF_COLUMNS) ?? [], + columns: newColumns ?? [], timeField: context.dataViewSpec.timeFieldName, }, }, @@ -275,7 +284,7 @@ export function getTextBasedDatasource({ notAssignedMetrics: !hasNumberTypeColumns, layerId: newLayerId, columns: - newColumns?.slice(0, MAX_NUM_OF_COLUMNS)?.map((f) => { + newColumns?.map((f) => { return { columnId: f.columnId, operation: { diff --git a/x-pack/plugins/lens/public/datasources/text_based/to_expression.ts b/x-pack/plugins/lens/public/datasources/text_based/to_expression.ts index 148a16232c980..a175f191d5916 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/to_expression.ts +++ b/x-pack/plugins/lens/public/datasources/text_based/to_expression.ts @@ -59,6 +59,7 @@ function getExpressionForLayer( function: 'lens_map_to_columns', arguments: { idMap: [JSON.stringify(idMapper)], + isTextBased: [true], }, }); return textBasedQueryToAst; diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts index 3a7f35a254b26..387349039fed0 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts @@ -78,7 +78,7 @@ export async function executeCreateAction({ }); const context = { - dataViewSpec: dataView.toSpec(), + dataViewSpec: dataView.toSpec(false), fieldName: '', textBasedColumns: columns, query: defaultEsqlQuery, From 8bf9aa56b47e404120f1d176fea3c3989f051ef7 Mon Sep 17 00:00:00 2001 From: Janki Salvi <117571355+js-jankisalvi@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:45:49 +0100 Subject: [PATCH 064/128] [ResponseOps][Cases] Allow users to create case using templates (#187138) ## Summary Fixes https://github.com/elastic/kibana/issues/181309 This PR - allows users to create, edit or delete templates via cases > settings page - allows users to create case using templates https://github.com/elastic/kibana/assets/117571355/39226aa4-9d9a-41a8-a900-ca765ed98e1b ## Testing 1. Go to all solutions and create cases with all fields (including all fields of all supported connectors) without using templates. Verify that everything is working as expected. 2. Go to all solutions and create and edit templates with various fields. Verify that everything is working as expected. 3. Go to all solutions, create different templates on each solution, and verify that when creating a case you can use templates and everything is working as expected. 4. Go to the alerts table of o11y and security and attach alerts to a new case. Verify that in the flyout the templates are working as expected. 5. Go to ML and try to attach an ML visualization to a new case. Verify that the solution picker is working as expected and it resets the form when changing solutions. 6. Create a template with custom fields. Delete one of the custom fields from the settings page. Verify that it is also deleted from the template. ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) **Flaky test runner**: https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6425 ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ## Release notes Allow users to create case using templates. --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Christos Nasikas Co-authored-by: adcoelho --- .../plugins/cases/common/constants/index.ts | 6 + .../plugins/cases/common/constants/owners.ts | 4 +- .../plugins/cases/common/types/api/case/v1.ts | 141 +-- .../common/types/api/configure/v1.test.ts | 425 ++++++++ .../cases/common/types/api/configure/v1.ts | 62 ++ .../cases/common/types/domain/case/v1.ts | 37 +- .../common/types/domain/configure/v1.test.ts | 118 +++ .../cases/common/types/domain/configure/v1.ts | 38 + x-pack/plugins/cases/common/ui/types.ts | 10 +- .../assignees.test.tsx | 120 ++- .../assignees.tsx | 159 +-- .../category.test.tsx | 3 +- .../{create => case_form_fields}/category.tsx | 2 +- .../case_form_fields/connector.test.tsx | 164 +++ .../connector.tsx | 57 +- .../custom_fields.test.tsx | 95 +- .../case_form_fields/custom_fields.tsx | 78 ++ .../description.test.tsx | 2 +- .../description.tsx | 2 +- .../case_form_fields/index.test.tsx | 330 ++++++ .../components/case_form_fields/index.tsx | 57 ++ .../components/case_form_fields/schema.tsx | 109 ++ .../severity.test.tsx | 0 .../{create => case_form_fields}/severity.tsx | 0 .../sync_alerts_toggle.test.tsx | 82 ++ .../sync_alerts_toggle.tsx | 13 +- .../tags.test.tsx | 6 +- .../{create => case_form_fields}/tags.tsx | 12 +- .../title.test.tsx | 6 +- .../{create => case_form_fields}/title.tsx | 5 +- .../case_form_fields/translations.ts | 14 + .../components/case_form_fields/utils.test.ts | 90 ++ .../components/case_form_fields/utils.ts | 64 ++ .../case_view/components/edit_tags.tsx | 8 +- .../category/category_component.test.tsx | 10 +- .../category/category_component.tsx | 11 +- .../category/category_form_field.tsx | 8 +- .../configure_cases/__mock__/index.tsx | 3 +- .../configure_cases/connectors_dropdown.tsx | 20 +- .../delete_confirmation_modal.test.tsx | 53 + .../delete_confirmation_modal.tsx | 42 + .../configure_cases/flyout.test.tsx | 798 +++++++++++++++ .../components/configure_cases/flyout.tsx | 126 +++ .../components/configure_cases/index.test.tsx | 258 ++++- .../components/configure_cases/index.tsx | 268 ++++- .../configure_cases/translations.ts | 11 + .../components/configure_cases/utils.ts | 2 +- .../public/components/connectors/constants.ts | 2 + .../connectors/jira/case_fields.test.tsx | 42 + .../connectors/jira/search_issues.tsx | 147 ++- .../connectors/jira/use_get_issue.test.tsx | 131 +++ .../connectors/jira/use_get_issue.tsx | 56 ++ .../connectors/jira/use_get_issues.tsx | 9 +- .../connectors/resilient/case_fields.tsx | 15 +- .../components/create/connector.test.tsx | 210 ---- .../components/create/custom_fields.tsx | 85 -- .../public/components/create/form.test.tsx | 379 +++---- .../cases/public/components/create/form.tsx | 247 ++--- .../components/create/form_context.test.tsx | 378 +++---- .../public/components/create/form_context.tsx | 183 +--- .../public/components/create/form_fields.tsx | 204 ++++ .../components/create/owner_selector.test.tsx | 105 +- .../components/create/owner_selector.tsx | 131 +-- .../cases/public/components/create/schema.tsx | 128 +-- .../create/sync_alerts_toggle.test.tsx | 82 -- .../components/create/template.test.tsx | 80 ++ .../public/components/create/templates.tsx | 69 ++ .../public/components/create/translations.ts | 20 +- .../public/components/create/utils.test.ts | 383 +++++++ .../cases/public/components/create/utils.ts | 118 +++ .../custom_fields_list/index.test.tsx | 10 +- .../custom_fields_list/index.tsx | 5 +- .../components/custom_fields/flyout.test.tsx | 270 ----- .../components/custom_fields/flyout.tsx | 105 -- .../components/custom_fields/form.test.tsx | 30 +- .../public/components/custom_fields/form.tsx | 9 +- .../custom_fields/text/create.test.tsx | 16 + .../components/custom_fields/text/create.tsx | 8 +- .../custom_fields/toggle/create.test.tsx | 14 + .../custom_fields/toggle/create.tsx | 3 +- .../public/components/custom_fields/types.ts | 2 + .../components/custom_fields/utils.test.ts | 193 +--- .../public/components/custom_fields/utils.ts | 21 - .../components/markdown_editor/eui_form.tsx | 4 +- .../use_markdown_session_storage.test.tsx | 11 + .../use_markdown_session_storage.tsx | 6 +- .../optional_field_label/index.test.tsx | 0 .../optional_field_label/index.tsx | 2 +- .../public/components/templates/form.test.tsx | 790 +++++++++++++++ .../public/components/templates/form.tsx | 75 ++ .../components/templates/form_fields.test.tsx | 398 ++++++++ .../components/templates/form_fields.tsx | 105 ++ .../components/templates/index.test.tsx | 138 +++ .../public/components/templates/index.tsx | 135 +++ .../components/templates/schema.test.tsx | 115 +++ .../public/components/templates/schema.tsx | 108 ++ .../templates/template_fields.test.tsx | 141 +++ .../components/templates/template_fields.tsx | 50 + .../templates/template_tags.test.tsx | 128 +++ .../components/templates/template_tags.tsx | 46 + .../templates/templates_list.test.tsx | 145 +++ .../components/templates/templates_list.tsx | 133 +++ .../components/templates/translations.ts | 84 ++ .../public/components/templates/types.ts | 15 + .../public/components/templates/utils.test.ts | 389 ++++++++ .../public/components/templates/utils.ts | 120 +++ .../cases/public/components/utils.test.ts | 282 +++++- .../plugins/cases/public/components/utils.ts | 77 +- .../cases/public/containers/configure/api.ts | 5 +- .../cases/public/containers/configure/mock.ts | 4 +- .../use_get_all_case_configurations.test.ts | 2 + .../use_get_supported_action_connectors.tsx | 1 + .../use_persist_configuration.test.tsx | 134 ++- .../configure/use_persist_configuration.tsx | 4 +- .../public/containers/configure/utils.ts | 1 + .../plugins/cases/public/containers/mock.ts | 82 ++ .../use_bulk_get_user_profiles.test.ts | 10 + .../use_bulk_get_user_profiles.ts | 2 + .../server/client/cases/bulk_create.test.ts | 2 +- .../server/client/cases/bulk_update.test.ts | 2 +- .../cases/server/client/cases/create.test.ts | 2 +- .../cases/server/client/cases/validators.ts | 7 +- .../server/client/configure/client.test.ts | 944 +++++++++++++++++- .../cases/server/client/configure/client.ts | 89 +- .../client/configure/validators.test.ts | 307 +++++- .../server/client/configure/validators.ts | 49 +- .../plugins/cases/server/client/utils.test.ts | 286 ++++++ x-pack/plugins/cases/server/client/utils.ts | 35 + .../cases/server/client/validators.test.ts | 21 +- .../plugins/cases/server/client/validators.ts | 12 +- .../cases/server/common/types/configure.ts | 30 +- .../server/services/configure/index.test.ts | 122 ++- .../cases/server/services/configure/index.ts | 5 + .../common/lib/api/configuration.ts | 1 + .../tests/common/configure/get_configure.ts | 61 +- .../tests/common/configure/patch_configure.ts | 221 +++- .../tests/common/configure/post_configure.ts | 139 ++- x-pack/test/functional/services/cases/api.ts | 18 + .../test/functional/services/cases/create.ts | 11 +- .../apps/cases/group1/create_case_form.ts | 6 +- .../apps/cases/group2/attachment_framework.ts | 4 +- .../apps/cases/group2/configure.ts | 109 +- .../e2e/explore/cases/connectors.cy.ts | 1 + .../cypress/tasks/create_new_case.ts | 1 + .../observability/cases/configure.ts | 86 +- .../observability/cases/create_case_form.ts | 2 +- .../security/ftr/cases/configure.ts | 86 +- .../security/ftr/cases/create_case_form.ts | 2 +- 148 files changed, 11701 insertions(+), 2521 deletions(-) rename x-pack/plugins/cases/public/components/{create => case_form_fields}/assignees.test.tsx (56%) rename x-pack/plugins/cases/public/components/{create => case_form_fields}/assignees.tsx (61%) rename x-pack/plugins/cases/public/components/{create => case_form_fields}/category.test.tsx (96%) rename x-pack/plugins/cases/public/components/{create => case_form_fields}/category.tsx (93%) create mode 100644 x-pack/plugins/cases/public/components/case_form_fields/connector.test.tsx rename x-pack/plugins/cases/public/components/{create => case_form_fields}/connector.tsx (62%) rename x-pack/plugins/cases/public/components/{create => case_form_fields}/custom_fields.test.tsx (67%) create mode 100644 x-pack/plugins/cases/public/components/case_form_fields/custom_fields.tsx rename x-pack/plugins/cases/public/components/{create => case_form_fields}/description.test.tsx (98%) rename x-pack/plugins/cases/public/components/{create => case_form_fields}/description.tsx (98%) create mode 100644 x-pack/plugins/cases/public/components/case_form_fields/index.test.tsx create mode 100644 x-pack/plugins/cases/public/components/case_form_fields/index.tsx create mode 100644 x-pack/plugins/cases/public/components/case_form_fields/schema.tsx rename x-pack/plugins/cases/public/components/{create => case_form_fields}/severity.test.tsx (100%) rename x-pack/plugins/cases/public/components/{create => case_form_fields}/severity.tsx (100%) create mode 100644 x-pack/plugins/cases/public/components/case_form_fields/sync_alerts_toggle.test.tsx rename x-pack/plugins/cases/public/components/{create => case_form_fields}/sync_alerts_toggle.tsx (76%) rename x-pack/plugins/cases/public/components/{create => case_form_fields}/tags.test.tsx (95%) rename x-pack/plugins/cases/public/components/{create => case_form_fields}/tags.tsx (80%) rename x-pack/plugins/cases/public/components/{create => case_form_fields}/title.test.tsx (92%) rename x-pack/plugins/cases/public/components/{create => case_form_fields}/title.tsx (87%) create mode 100644 x-pack/plugins/cases/public/components/case_form_fields/translations.ts create mode 100644 x-pack/plugins/cases/public/components/case_form_fields/utils.test.ts create mode 100644 x-pack/plugins/cases/public/components/case_form_fields/utils.ts create mode 100644 x-pack/plugins/cases/public/components/configure_cases/delete_confirmation_modal.test.tsx create mode 100644 x-pack/plugins/cases/public/components/configure_cases/delete_confirmation_modal.tsx create mode 100644 x-pack/plugins/cases/public/components/configure_cases/flyout.test.tsx create mode 100644 x-pack/plugins/cases/public/components/configure_cases/flyout.tsx create mode 100644 x-pack/plugins/cases/public/components/connectors/jira/use_get_issue.test.tsx create mode 100644 x-pack/plugins/cases/public/components/connectors/jira/use_get_issue.tsx delete mode 100644 x-pack/plugins/cases/public/components/create/connector.test.tsx delete mode 100644 x-pack/plugins/cases/public/components/create/custom_fields.tsx create mode 100644 x-pack/plugins/cases/public/components/create/form_fields.tsx delete mode 100644 x-pack/plugins/cases/public/components/create/sync_alerts_toggle.test.tsx create mode 100644 x-pack/plugins/cases/public/components/create/template.test.tsx create mode 100644 x-pack/plugins/cases/public/components/create/templates.tsx create mode 100644 x-pack/plugins/cases/public/components/create/utils.test.ts create mode 100644 x-pack/plugins/cases/public/components/create/utils.ts delete mode 100644 x-pack/plugins/cases/public/components/custom_fields/flyout.test.tsx delete mode 100644 x-pack/plugins/cases/public/components/custom_fields/flyout.tsx rename x-pack/plugins/cases/public/components/{create => }/optional_field_label/index.test.tsx (100%) rename x-pack/plugins/cases/public/components/{create => }/optional_field_label/index.tsx (89%) create mode 100644 x-pack/plugins/cases/public/components/templates/form.test.tsx create mode 100644 x-pack/plugins/cases/public/components/templates/form.tsx create mode 100644 x-pack/plugins/cases/public/components/templates/form_fields.test.tsx create mode 100644 x-pack/plugins/cases/public/components/templates/form_fields.tsx create mode 100644 x-pack/plugins/cases/public/components/templates/index.test.tsx create mode 100644 x-pack/plugins/cases/public/components/templates/index.tsx create mode 100644 x-pack/plugins/cases/public/components/templates/schema.test.tsx create mode 100644 x-pack/plugins/cases/public/components/templates/schema.tsx create mode 100644 x-pack/plugins/cases/public/components/templates/template_fields.test.tsx create mode 100644 x-pack/plugins/cases/public/components/templates/template_fields.tsx create mode 100644 x-pack/plugins/cases/public/components/templates/template_tags.test.tsx create mode 100644 x-pack/plugins/cases/public/components/templates/template_tags.tsx create mode 100644 x-pack/plugins/cases/public/components/templates/templates_list.test.tsx create mode 100644 x-pack/plugins/cases/public/components/templates/templates_list.tsx create mode 100644 x-pack/plugins/cases/public/components/templates/translations.ts create mode 100644 x-pack/plugins/cases/public/components/templates/types.ts create mode 100644 x-pack/plugins/cases/public/components/templates/utils.test.ts create mode 100644 x-pack/plugins/cases/public/components/templates/utils.ts diff --git a/x-pack/plugins/cases/common/constants/index.ts b/x-pack/plugins/cases/common/constants/index.ts index a8868010d2312..557899e322ae7 100644 --- a/x-pack/plugins/cases/common/constants/index.ts +++ b/x-pack/plugins/cases/common/constants/index.ts @@ -133,6 +133,12 @@ export const MAX_CUSTOM_FIELDS_PER_CASE = 10 as const; export const MAX_CUSTOM_FIELD_KEY_LENGTH = 36 as const; // uuidv4 length export const MAX_CUSTOM_FIELD_LABEL_LENGTH = 50 as const; export const MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH = 160 as const; +export const MAX_TEMPLATE_KEY_LENGTH = 36 as const; // uuidv4 length +export const MAX_TEMPLATE_NAME_LENGTH = 50 as const; +export const MAX_TEMPLATE_DESCRIPTION_LENGTH = 1000 as const; +export const MAX_TEMPLATES_LENGTH = 10 as const; +export const MAX_TEMPLATE_TAG_LENGTH = 50 as const; +export const MAX_TAGS_PER_TEMPLATE = 10 as const; /** * Cases features diff --git a/x-pack/plugins/cases/common/constants/owners.ts b/x-pack/plugins/cases/common/constants/owners.ts index 8ac7164ef75cc..a7628628a7dcc 100644 --- a/x-pack/plugins/cases/common/constants/owners.ts +++ b/x-pack/plugins/cases/common/constants/owners.ts @@ -56,8 +56,8 @@ export const OWNER_INFO: Record = { [GENERAL_CASES_OWNER]: { id: GENERAL_CASES_OWNER, appId: 'management', - label: 'Stack', - iconType: 'casesApp', + label: 'Management', + iconType: 'managementApp', appRoute: '/app/management/insightsAndAlerting', validRuleConsumers: [AlertConsumers.ML, AlertConsumers.STACK_ALERTS, AlertConsumers.EXAMPLE], }, diff --git a/x-pack/plugins/cases/common/types/api/case/v1.ts b/x-pack/plugins/cases/common/types/api/case/v1.ts index 0dff1cac0d95d..7a45f92fa4668 100644 --- a/x-pack/plugins/cases/common/types/api/case/v1.ts +++ b/x-pack/plugins/cases/common/types/api/case/v1.ts @@ -58,6 +58,81 @@ export const CaseRequestCustomFieldsRt = limitedArraySchema({ max: MAX_CUSTOM_FIELDS_PER_CASE, }); +export const CaseBaseOptionalFieldsRequestRt = rt.exact( + rt.partial({ + /** + * The description of the case + */ + description: limitedStringSchema({ + fieldName: 'description', + min: 1, + max: MAX_DESCRIPTION_LENGTH, + }), + /** + * The identifying strings for filter a case + */ + tags: limitedArraySchema({ + codec: limitedStringSchema({ fieldName: 'tag', min: 1, max: MAX_LENGTH_PER_TAG }), + min: 0, + max: MAX_TAGS_PER_CASE, + fieldName: 'tags', + }), + /** + * The title of a case + */ + title: limitedStringSchema({ fieldName: 'title', min: 1, max: MAX_TITLE_LENGTH }), + /** + * The external system that the case can be synced with + */ + connector: CaseConnectorRt, + /** + * The severity of the case + */ + severity: CaseSeverityRt, + /** + * The users assigned to this case + */ + assignees: limitedArraySchema({ + codec: CaseUserProfileRt, + fieldName: 'assignees', + min: 0, + max: MAX_ASSIGNEES_PER_CASE, + }), + /** + * The category of the case. + */ + category: rt.union([ + limitedStringSchema({ fieldName: 'category', min: 1, max: MAX_CATEGORY_LENGTH }), + rt.null, + ]), + /** + * Custom fields of the case + */ + customFields: CaseRequestCustomFieldsRt, + /** + * The alert sync settings + */ + settings: CaseSettingsRt, + }) +); + +export const CaseRequestFieldsRt = rt.intersection([ + CaseBaseOptionalFieldsRequestRt, + rt.exact( + rt.partial({ + /** + * The current status of the case (open, closed, in-progress) + */ + status: CaseStatusRt, + + /** + * The plugin owner of the case + */ + owner: rt.string, + }) + ), +]); + /** * Create case */ @@ -356,71 +431,7 @@ export const CasesBulkGetResponseRt = rt.strict({ * Update cases */ export const CasePatchRequestRt = rt.intersection([ - rt.exact( - rt.partial({ - /** - * The description of the case - */ - description: limitedStringSchema({ - fieldName: 'description', - min: 1, - max: MAX_DESCRIPTION_LENGTH, - }), - /** - * The current status of the case (open, closed, in-progress) - */ - status: CaseStatusRt, - /** - * The identifying strings for filter a case - */ - tags: limitedArraySchema({ - codec: limitedStringSchema({ fieldName: 'tag', min: 1, max: MAX_LENGTH_PER_TAG }), - min: 0, - max: MAX_TAGS_PER_CASE, - fieldName: 'tags', - }), - /** - * The title of a case - */ - title: limitedStringSchema({ fieldName: 'title', min: 1, max: MAX_TITLE_LENGTH }), - /** - * The external system that the case can be synced with - */ - connector: CaseConnectorRt, - /** - * The alert sync settings - */ - settings: CaseSettingsRt, - /** - * The plugin owner of the case - */ - owner: rt.string, - /** - * The severity of the case - */ - severity: CaseSeverityRt, - /** - * The users assigned to this case - */ - assignees: limitedArraySchema({ - codec: CaseUserProfileRt, - fieldName: 'assignees', - min: 0, - max: MAX_ASSIGNEES_PER_CASE, - }), - /** - * The category of the case. - */ - category: rt.union([ - limitedStringSchema({ fieldName: 'category', min: 1, max: MAX_CATEGORY_LENGTH }), - rt.null, - ]), - /** - * Custom fields of the case - */ - customFields: CaseRequestCustomFieldsRt, - }) - ), + CaseRequestFieldsRt, /** * The saved object ID and version */ diff --git a/x-pack/plugins/cases/common/types/api/configure/v1.test.ts b/x-pack/plugins/cases/common/types/api/configure/v1.test.ts index 3369cb8473c0c..c16dfbc60eaf7 100644 --- a/x-pack/plugins/cases/common/types/api/configure/v1.test.ts +++ b/x-pack/plugins/cases/common/types/api/configure/v1.test.ts @@ -8,11 +8,24 @@ import { PathReporter } from 'io-ts/lib/PathReporter'; import { v4 as uuidv4 } from 'uuid'; import { + MAX_ASSIGNEES_PER_CASE, + MAX_CATEGORY_LENGTH, MAX_CUSTOM_FIELDS_PER_CASE, MAX_CUSTOM_FIELD_KEY_LENGTH, MAX_CUSTOM_FIELD_LABEL_LENGTH, MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH, + MAX_DESCRIPTION_LENGTH, + MAX_LENGTH_PER_TAG, + MAX_TAGS_PER_CASE, + MAX_TAGS_PER_TEMPLATE, + MAX_TEMPLATES_LENGTH, + MAX_TEMPLATE_DESCRIPTION_LENGTH, + MAX_TEMPLATE_KEY_LENGTH, + MAX_TEMPLATE_NAME_LENGTH, + MAX_TEMPLATE_TAG_LENGTH, + MAX_TITLE_LENGTH, } from '../../../constants'; +import { CaseSeverity } from '../../domain'; import { ConnectorTypes } from '../../domain/connector/v1'; import { CustomFieldTypes } from '../../domain/custom_field/v1'; import { @@ -23,6 +36,7 @@ import { CustomFieldConfigurationWithoutTypeRt, TextCustomFieldConfigurationRt, ToggleCustomFieldConfigurationRt, + TemplateConfigurationRt, } from './v1'; describe('configure', () => { @@ -90,6 +104,51 @@ describe('configure', () => { ); }); + it('has expected attributes in request with templates', () => { + const request = { + ...defaultRequest, + templates: [ + { + key: 'template_key_1', + name: 'Template 1', + description: 'this is first template', + tags: ['foo', 'bar'], + caseFields: { + title: 'case using sample template', + }, + }, + { + key: 'template_key_2', + name: 'Template 2', + description: 'this is second template', + tags: [], + caseFields: null, + }, + ], + }; + const query = ConfigurationRequestRt.decode(request); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: request, + }); + }); + + it(`limits templates to ${MAX_TEMPLATES_LENGTH}`, () => { + const templates = new Array(MAX_TEMPLATES_LENGTH + 1).fill({ + key: 'template_key_1', + name: 'Template 1', + description: 'this is first template', + caseFields: { + title: 'case using sample template', + }, + }); + + expect( + PathReporter.report(ConfigurationRequestRt.decode({ ...defaultRequest, templates }))[0] + ).toContain(`The length of the field templates is too long. Array must be of length <= 10.`); + }); + it('removes foo:bar attributes from request', () => { const query = ConfigurationRequestRt.decode({ ...defaultRequest, foo: 'bar' }); @@ -159,6 +218,51 @@ describe('configure', () => { ); }); + it('has expected attributes in request with templates', () => { + const request = { + ...defaultRequest, + templates: [ + { + key: 'template_key_1', + name: 'Template 1', + description: 'this is first template', + tags: ['foo', 'bar'], + caseFields: { + title: 'case using sample template', + }, + }, + { + key: 'template_key_2', + name: 'Template 2', + description: 'this is second template', + caseFields: null, + }, + ], + }; + const query = ConfigurationPatchRequestRt.decode(request); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: request, + }); + }); + + it(`limits templates to ${MAX_TEMPLATES_LENGTH}`, () => { + const templates = new Array(MAX_TEMPLATES_LENGTH + 1).fill({ + key: 'template_key_1', + name: 'Template 1', + description: 'this is first template', + tags: [], + caseFields: { + title: 'case using sample template', + }, + }); + + expect( + PathReporter.report(ConfigurationPatchRequestRt.decode({ ...defaultRequest, templates }))[0] + ).toContain(`The length of the field templates is too long. Array must be of length <= 10.`); + }); + it('removes foo:bar attributes from request', () => { const query = ConfigurationPatchRequestRt.decode({ ...defaultRequest, foo: 'bar' }); @@ -407,4 +511,325 @@ describe('configure', () => { ).toContain('Invalid value "foobar" supplied'); }); }); + + describe('TemplateConfigurationRt', () => { + const defaultRequest = { + key: 'template_key_1', + name: 'Template 1', + description: 'this is first template', + tags: ['foo', 'bar'], + caseFields: { + title: 'case using sample template', + }, + }; + + it('has expected attributes in request', () => { + const query = TemplateConfigurationRt.decode(defaultRequest); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: { ...defaultRequest }, + }); + }); + + it('removes foo:bar attributes from request', () => { + const query = TemplateConfigurationRt.decode({ ...defaultRequest, foo: 'bar' }); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: { ...defaultRequest }, + }); + }); + + it('limits key to 36 characters', () => { + const longKey = 'x'.repeat(MAX_TEMPLATE_KEY_LENGTH + 1); + + expect( + PathReporter.report(TemplateConfigurationRt.decode({ ...defaultRequest, key: longKey })) + ).toContain('The length of the key is too long. The maximum length is 36.'); + }); + + it('return error if key is empty', () => { + expect( + PathReporter.report(TemplateConfigurationRt.decode({ ...defaultRequest, key: '' })) + ).toContain('The key field cannot be an empty string.'); + }); + + it('returns an error if they key is not in the expected format', () => { + const key = 'Not a proper key'; + + expect( + PathReporter.report(TemplateConfigurationRt.decode({ ...defaultRequest, key })) + ).toContain(`Key must be lower case, a-z, 0-9, '_', and '-' are allowed`); + }); + + it('accepts a uuid as an key', () => { + const key = uuidv4(); + + const query = TemplateConfigurationRt.decode({ ...defaultRequest, key }); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: { ...defaultRequest, key }, + }); + }); + + it('accepts a slug as an key', () => { + const key = 'abc_key-1'; + + const query = TemplateConfigurationRt.decode({ ...defaultRequest, key }); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: { ...defaultRequest, key }, + }); + }); + + it('does not throw when there is no description or tags', () => { + const newRequest = { + key: 'template_key_1', + name: 'Template 1', + caseFields: null, + }; + + expect(PathReporter.report(TemplateConfigurationRt.decode({ ...newRequest }))).toContain( + 'No errors!' + ); + }); + + it('limits name to 50 characters', () => { + const longName = 'x'.repeat(MAX_TEMPLATE_NAME_LENGTH + 1); + + expect( + PathReporter.report(TemplateConfigurationRt.decode({ ...defaultRequest, name: longName })) + ).toContain('The length of the name is too long. The maximum length is 50.'); + }); + + it('limits description to 1000 characters', () => { + const longDesc = 'x'.repeat(MAX_TEMPLATE_DESCRIPTION_LENGTH + 1); + + expect( + PathReporter.report( + TemplateConfigurationRt.decode({ ...defaultRequest, description: longDesc }) + ) + ).toContain('The length of the description is too long. The maximum length is 1000.'); + }); + + it(`throws an error when there are more than ${MAX_TAGS_PER_TEMPLATE} tags`, async () => { + const tags = Array(MAX_TAGS_PER_TEMPLATE + 1).fill('foobar'); + + expect( + PathReporter.report(TemplateConfigurationRt.decode({ ...defaultRequest, tags })) + ).toContain( + `The length of the field template's tags is too long. Array must be of length <= 10.` + ); + }); + + it(`throws an error when the a tag is more than ${MAX_TEMPLATE_TAG_LENGTH} characters`, async () => { + const tag = 'a'.repeat(MAX_TEMPLATE_TAG_LENGTH + 1); + + expect( + PathReporter.report(TemplateConfigurationRt.decode({ ...defaultRequest, tags: [tag] })) + ).toContain(`The length of the template's tag is too long. The maximum length is 50.`); + }); + + it(`throws an error when the a tag is empty string`, async () => { + expect( + PathReporter.report(TemplateConfigurationRt.decode({ ...defaultRequest, tags: [''] })) + ).toContain(`The template's tag field cannot be an empty string.`); + }); + + describe('caseFields', () => { + it('removes foo:bar attributes from caseFields', () => { + const query = TemplateConfigurationRt.decode({ + ...defaultRequest, + caseFields: { ...defaultRequest.caseFields, foo: 'bar' }, + }); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: { ...defaultRequest }, + }); + }); + + it('accepts caseFields as null', () => { + const query = TemplateConfigurationRt.decode({ + ...defaultRequest, + caseFields: null, + }); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: { ...defaultRequest, caseFields: null }, + }); + }); + + it('accepts caseFields as {}', () => { + const query = TemplateConfigurationRt.decode({ + ...defaultRequest, + caseFields: {}, + }); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: { ...defaultRequest, caseFields: {} }, + }); + }); + + it('accepts caseFields with all fields', () => { + const caseFieldsAll = { + title: 'Case with sample template 1', + description: 'case desc', + severity: CaseSeverity.LOW, + category: null, + tags: ['sample-1'], + assignees: [{ uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0' }], + customFields: [ + { + key: 'first_custom_field_key', + type: 'text', + value: 'this is a text field value', + }, + ], + connector: { + id: 'none', + name: 'My Connector', + type: ConnectorTypes.none, + fields: null, + }, + }; + + const query = TemplateConfigurationRt.decode({ + ...defaultRequest, + caseFields: caseFieldsAll, + }); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: { ...defaultRequest, caseFields: caseFieldsAll }, + }); + }); + + it(`throws an error when the assignees are more than ${MAX_ASSIGNEES_PER_CASE}`, async () => { + const assignees = Array(MAX_ASSIGNEES_PER_CASE + 1).fill({ uid: 'foobar' }); + + expect( + PathReporter.report( + TemplateConfigurationRt.decode({ + ...defaultRequest, + caseFields: { ...defaultRequest.caseFields, assignees }, + }) + ) + ).toContain( + 'The length of the field assignees is too long. Array must be of length <= 10.' + ); + }); + + it(`throws an error when the description contains more than ${MAX_DESCRIPTION_LENGTH} characters`, async () => { + const description = 'a'.repeat(MAX_DESCRIPTION_LENGTH + 1); + + expect( + PathReporter.report( + TemplateConfigurationRt.decode({ + ...defaultRequest, + caseFields: { ...defaultRequest.caseFields, description }, + }) + ) + ).toContain('The length of the description is too long. The maximum length is 30000.'); + }); + + it(`throws an error when there are more than ${MAX_TAGS_PER_CASE} tags`, async () => { + const tags = Array(MAX_TAGS_PER_CASE + 1).fill('foobar'); + + expect( + PathReporter.report( + TemplateConfigurationRt.decode({ + ...defaultRequest, + caseFields: { ...defaultRequest.caseFields, tags }, + }) + ) + ).toContain('The length of the field tags is too long. Array must be of length <= 200.'); + }); + + it(`throws an error when the tag is more than ${MAX_LENGTH_PER_TAG} characters`, async () => { + const tag = 'a'.repeat(MAX_LENGTH_PER_TAG + 1); + + expect( + PathReporter.report( + TemplateConfigurationRt.decode({ + ...defaultRequest, + caseFields: { ...defaultRequest.caseFields, tags: [tag] }, + }) + ) + ).toContain('The length of the tag is too long. The maximum length is 256.'); + }); + + it(`throws an error when the title contains more than ${MAX_TITLE_LENGTH} characters`, async () => { + const title = 'a'.repeat(MAX_TITLE_LENGTH + 1); + + expect( + PathReporter.report( + TemplateConfigurationRt.decode({ + ...defaultRequest, + caseFields: { ...defaultRequest.caseFields, title }, + }) + ) + ).toContain('The length of the title is too long. The maximum length is 160.'); + }); + + it(`throws an error when the category contains more than ${MAX_CATEGORY_LENGTH} characters`, async () => { + const category = 'a'.repeat(MAX_CATEGORY_LENGTH + 1); + + expect( + PathReporter.report( + TemplateConfigurationRt.decode({ + ...defaultRequest, + caseFields: { ...defaultRequest.caseFields, category }, + }) + ) + ).toContain('The length of the category is too long. The maximum length is 50.'); + }); + + it(`limits customFields to ${MAX_CUSTOM_FIELDS_PER_CASE}`, () => { + const customFields = Array(MAX_CUSTOM_FIELDS_PER_CASE + 1).fill({ + key: 'first_custom_field_key', + type: CustomFieldTypes.TEXT, + value: 'this is a text field value', + }); + + expect( + PathReporter.report( + TemplateConfigurationRt.decode({ + ...defaultRequest, + caseFields: { ...defaultRequest.caseFields, customFields }, + }) + ) + ).toContain( + `The length of the field customFields is too long. Array must be of length <= ${MAX_CUSTOM_FIELDS_PER_CASE}.` + ); + }); + + it(`throws an error when a text customFields is longer than ${MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH}`, () => { + expect( + PathReporter.report( + TemplateConfigurationRt.decode({ + ...defaultRequest, + caseFields: { + ...defaultRequest.caseFields, + customFields: [ + { + key: 'first_custom_field_key', + type: CustomFieldTypes.TEXT, + value: '#'.repeat(MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH + 1), + }, + ], + }, + }) + ) + ).toContain( + `The length of the value is too long. The maximum length is ${MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH}.` + ); + }); + }); + }); }); diff --git a/x-pack/plugins/cases/common/types/api/configure/v1.ts b/x-pack/plugins/cases/common/types/api/configure/v1.ts index 8e986677ae8a9..bd2e1f5c11af0 100644 --- a/x-pack/plugins/cases/common/types/api/configure/v1.ts +++ b/x-pack/plugins/cases/common/types/api/configure/v1.ts @@ -10,12 +10,19 @@ import { MAX_CUSTOM_FIELDS_PER_CASE, MAX_CUSTOM_FIELD_KEY_LENGTH, MAX_CUSTOM_FIELD_LABEL_LENGTH, + MAX_TAGS_PER_TEMPLATE, + MAX_TEMPLATES_LENGTH, + MAX_TEMPLATE_DESCRIPTION_LENGTH, + MAX_TEMPLATE_KEY_LENGTH, + MAX_TEMPLATE_NAME_LENGTH, + MAX_TEMPLATE_TAG_LENGTH, } from '../../../constants'; import { limitedArraySchema, limitedStringSchema, regexStringRt } from '../../../schema'; import { CustomFieldTextTypeRt, CustomFieldToggleTypeRt } from '../../domain'; import type { Configurations, Configuration } from '../../domain/configure/v1'; import { ConfigurationBasicWithoutOwnerRt, ClosureTypeRt } from '../../domain/configure/v1'; import { CaseConnectorRt } from '../../domain/connector/v1'; +import { CaseBaseOptionalFieldsRequestRt } from '../case/v1'; import { CaseCustomFieldTextWithValidationValueRt } from '../custom_field/v1'; export const CustomFieldConfigurationWithoutTypeRt = rt.strict({ @@ -64,6 +71,59 @@ export const CustomFieldsConfigurationRt = limitedArraySchema({ fieldName: 'customFields', }); +export const TemplateConfigurationRt = rt.intersection([ + rt.strict({ + /** + * key of template + */ + key: regexStringRt({ + codec: limitedStringSchema({ fieldName: 'key', min: 1, max: MAX_TEMPLATE_KEY_LENGTH }), + pattern: '^[a-z0-9_-]+$', + message: `Key must be lower case, a-z, 0-9, '_', and '-' are allowed`, + }), + /** + * name of template + */ + name: limitedStringSchema({ fieldName: 'name', min: 1, max: MAX_TEMPLATE_NAME_LENGTH }), + /** + * case fields + */ + caseFields: rt.union([rt.null, CaseBaseOptionalFieldsRequestRt]), + }), + rt.exact( + rt.partial({ + /** + * description of templates + */ + description: limitedStringSchema({ + fieldName: 'description', + min: 0, + max: MAX_TEMPLATE_DESCRIPTION_LENGTH, + }), + /** + * tags of templates + */ + tags: limitedArraySchema({ + codec: limitedStringSchema({ + fieldName: `template's tag`, + min: 1, + max: MAX_TEMPLATE_TAG_LENGTH, + }), + min: 0, + max: MAX_TAGS_PER_TEMPLATE, + fieldName: `template's tags`, + }), + }) + ), +]); + +export const TemplatesConfigurationRt = limitedArraySchema({ + codec: TemplateConfigurationRt, + min: 0, + max: MAX_TEMPLATES_LENGTH, + fieldName: 'templates', +}); + export const ConfigurationRequestRt = rt.intersection([ rt.strict({ /** @@ -82,6 +142,7 @@ export const ConfigurationRequestRt = rt.intersection([ rt.exact( rt.partial({ customFields: CustomFieldsConfigurationRt, + templates: TemplatesConfigurationRt, }) ), ]); @@ -106,6 +167,7 @@ export const ConfigurationPatchRequestRt = rt.intersection([ closure_type: ConfigurationBasicWithoutOwnerRt.type.props.closure_type, connector: ConfigurationBasicWithoutOwnerRt.type.props.connector, customFields: CustomFieldsConfigurationRt, + templates: TemplatesConfigurationRt, }) ), rt.strict({ version: rt.string }), diff --git a/x-pack/plugins/cases/common/types/domain/case/v1.ts b/x-pack/plugins/cases/common/types/domain/case/v1.ts index d8da843e46a0c..83d48df363bd2 100644 --- a/x-pack/plugins/cases/common/types/domain/case/v1.ts +++ b/x-pack/plugins/cases/common/types/domain/case/v1.ts @@ -52,15 +52,11 @@ export const CaseSettingsRt = rt.strict({ syncAlerts: rt.boolean, }); -const CaseBasicRt = rt.strict({ +const CaseBaseFields = { /** * The description of the case */ description: rt.string, - /** - * The current status of the case (open, closed, in-progress) - */ - status: CaseStatusRt, /** * The identifying strings for filter a case */ @@ -73,14 +69,6 @@ const CaseBasicRt = rt.strict({ * The external system that the case can be synced with */ connector: CaseConnectorRt, - /** - * The alert sync settings - */ - settings: CaseSettingsRt, - /** - * The plugin owner of the case - */ - owner: rt.string, /** * The severity of the case */ @@ -98,6 +86,28 @@ const CaseBasicRt = rt.strict({ * user-configured custom fields. */ customFields: CaseCustomFieldsRt, + /** + * The alert sync settings + */ + settings: CaseSettingsRt, +}; + +export const CaseBaseOptionalFieldsRt = rt.exact( + rt.partial({ + ...CaseBaseFields, + }) +); + +const CaseBasicRt = rt.strict({ + /** + * The current status of the case (open, closed, in-progress) + */ + status: CaseStatusRt, + /** + * The plugin owner of the case + */ + owner: rt.string, + ...CaseBaseFields, }); export const CaseAttributesRt = rt.intersection([ @@ -151,3 +161,4 @@ export type CaseAttributes = rt.TypeOf; export type CaseSettings = rt.TypeOf; export type RelatedCase = rt.TypeOf; export type AttachmentTotals = rt.TypeOf; +export type CaseBaseOptionalFields = rt.TypeOf; diff --git a/x-pack/plugins/cases/common/types/domain/configure/v1.test.ts b/x-pack/plugins/cases/common/types/domain/configure/v1.test.ts index 400d69700fe12..13637fb4d8c68 100644 --- a/x-pack/plugins/cases/common/types/domain/configure/v1.test.ts +++ b/x-pack/plugins/cases/common/types/domain/configure/v1.test.ts @@ -6,12 +6,14 @@ */ import { PathReporter } from 'io-ts/lib/PathReporter'; +import { CaseSeverity } from '../case/v1'; import { ConnectorTypes } from '../connector/v1'; import { CustomFieldTypes } from '../custom_field/v1'; import { ConfigurationAttributesRt, ConfigurationRt, CustomFieldConfigurationWithoutTypeRt, + TemplateConfigurationRt, TextCustomFieldConfigurationRt, ToggleCustomFieldConfigurationRt, } from './v1'; @@ -45,11 +47,59 @@ describe('configure', () => { required: false, }; + const templateWithAllCaseFields = { + key: 'template_sample_1', + name: 'Sample template 1', + description: 'this is first sample template', + tags: ['foo', 'bar', 'foobar'], + caseFields: { + title: 'Case with sample template 1', + description: 'case desc', + severity: CaseSeverity.LOW, + category: null, + tags: ['sample-1'], + assignees: [{ uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0' }], + customFields: [ + { + key: 'first_custom_field_key', + type: 'text', + value: 'this is a text field value', + }, + ], + connector: { + id: 'none', + name: 'My Connector', + type: ConnectorTypes.none, + fields: null, + }, + settings: { + syncAlerts: true, + }, + }, + }; + + const templateWithFewCaseFields = { + key: 'template_sample_2', + name: 'Sample template 2', + tags: [], + caseFields: { + title: 'Case with sample template 2', + tags: ['sample-2'], + }, + }; + + const templateWithNoCaseFields = { + key: 'template_sample_3', + name: 'Sample template 3', + caseFields: null, + }; + describe('ConfigurationAttributesRt', () => { const defaultRequest = { connector: resilient, closure_type: 'close-by-user', customFields: [textCustomField, toggleCustomField], + templates: [], owner: 'cases', created_at: '2020-02-19T23:06:33.798Z', created_by: { @@ -110,6 +160,7 @@ describe('configure', () => { connector: serviceNow, closure_type: 'close-by-user', customFields: [], + templates: [templateWithAllCaseFields, templateWithFewCaseFields, templateWithNoCaseFields], created_at: '2020-02-19T23:06:33.798Z', created_by: { full_name: 'Leslie Knope', @@ -299,4 +350,71 @@ describe('configure', () => { }); }); }); + + describe('TemplateConfigurationRt', () => { + const defaultRequest = templateWithAllCaseFields; + + it('has expected attributes in request ', () => { + const query = TemplateConfigurationRt.decode(defaultRequest); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: { ...defaultRequest }, + }); + }); + + it('removes foo:bar attributes from request', () => { + const query = TemplateConfigurationRt.decode({ ...defaultRequest, foo: 'bar' }); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: { ...defaultRequest }, + }); + }); + + it('removes foo:bar attributes from caseFields', () => { + const query = TemplateConfigurationRt.decode({ + ...defaultRequest, + caseFields: { ...templateWithAllCaseFields.caseFields, foo: 'bar' }, + }); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: { ...defaultRequest }, + }); + }); + + it('accepts few caseFields', () => { + const query = TemplateConfigurationRt.decode(templateWithFewCaseFields); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: { ...templateWithFewCaseFields }, + }); + }); + + it('accepts null for caseFields', () => { + const query = TemplateConfigurationRt.decode({ + ...defaultRequest, + caseFields: null, + }); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: { ...defaultRequest, caseFields: null }, + }); + }); + + it('accepts {} for caseFields', () => { + const query = TemplateConfigurationRt.decode({ + ...defaultRequest, + caseFields: {}, + }); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: { ...defaultRequest, caseFields: {} }, + }); + }); + }); }); diff --git a/x-pack/plugins/cases/common/types/domain/configure/v1.ts b/x-pack/plugins/cases/common/types/domain/configure/v1.ts index 65882ad40753e..1e4e30c95e381 100644 --- a/x-pack/plugins/cases/common/types/domain/configure/v1.ts +++ b/x-pack/plugins/cases/common/types/domain/configure/v1.ts @@ -9,6 +9,7 @@ import * as rt from 'io-ts'; import { CaseConnectorRt, ConnectorMappingsRt } from '../connector/v1'; import { UserRt } from '../user/v1'; import { CustomFieldTextTypeRt, CustomFieldToggleTypeRt } from '../custom_field/v1'; +import { CaseBaseOptionalFieldsRt } from '../case/v1'; export const ClosureTypeRt = rt.union([ rt.literal('close-by-user'), @@ -57,6 +58,37 @@ export const CustomFieldConfigurationRt = rt.union([ export const CustomFieldsConfigurationRt = rt.array(CustomFieldConfigurationRt); +export const TemplateConfigurationRt = rt.intersection([ + rt.strict({ + /** + * key of template + */ + key: rt.string, + /** + * name of template + */ + name: rt.string, + /** + * case fields of template + */ + caseFields: rt.union([rt.null, CaseBaseOptionalFieldsRt]), + }), + rt.exact( + rt.partial({ + /** + * description of template + */ + description: rt.string, + /** + * tags of template + */ + tags: rt.array(rt.string), + }) + ), +]); + +export const TemplatesConfigurationRt = rt.array(TemplateConfigurationRt); + export const ConfigurationBasicWithoutOwnerRt = rt.strict({ /** * The external connector @@ -70,6 +102,10 @@ export const ConfigurationBasicWithoutOwnerRt = rt.strict({ * The custom fields configured for the case */ customFields: CustomFieldsConfigurationRt, + /** + * Templates configured for the case + */ + templates: TemplatesConfigurationRt, }); export const CasesConfigureBasicRt = rt.intersection([ @@ -109,6 +145,8 @@ export const ConfigurationsRt = rt.array(ConfigurationRt); export type CustomFieldsConfiguration = rt.TypeOf; export type CustomFieldConfiguration = rt.TypeOf; +export type TemplatesConfiguration = rt.TypeOf; +export type TemplateConfiguration = rt.TypeOf; export type ClosureType = rt.TypeOf; export type ConfigurationAttributes = rt.TypeOf; export type Configuration = rt.TypeOf; diff --git a/x-pack/plugins/cases/common/ui/types.ts b/x-pack/plugins/cases/common/ui/types.ts index 3854c14c79de8..6d75b30dd119d 100644 --- a/x-pack/plugins/cases/common/ui/types.ts +++ b/x-pack/plugins/cases/common/ui/types.ts @@ -119,10 +119,18 @@ export interface ResolvedCase { export type CasesConfigurationUI = Pick< SnakeToCamelCase, - 'closureType' | 'connector' | 'mappings' | 'customFields' | 'id' | 'version' | 'owner' + | 'closureType' + | 'connector' + | 'mappings' + | 'customFields' + | 'templates' + | 'id' + | 'version' + | 'owner' >; export type CasesConfigurationUICustomField = CasesConfigurationUI['customFields'][number]; +export type CasesConfigurationUITemplate = CasesConfigurationUI['templates'][number]; export type SortOrder = 'asc' | 'desc'; diff --git a/x-pack/plugins/cases/public/components/create/assignees.test.tsx b/x-pack/plugins/cases/public/components/case_form_fields/assignees.test.tsx similarity index 56% rename from x-pack/plugins/cases/public/components/create/assignees.test.tsx rename to x-pack/plugins/cases/public/components/case_form_fields/assignees.test.tsx index 83b7802ce4a12..f0b73cb8bf990 100644 --- a/x-pack/plugins/cases/public/components/create/assignees.test.tsx +++ b/x-pack/plugins/cases/public/components/case_form_fields/assignees.test.tsx @@ -15,7 +15,6 @@ import type { FormHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_l import { useForm, Form } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { userProfiles } from '../../containers/user_profiles/api.mock'; import { Assignees } from './assignees'; -import type { FormProps } from './schema'; import { act, waitFor, screen } from '@testing-library/react'; import * as api from '../../containers/user_profiles/api'; import type { UserProfile } from '@kbn/user-profile-components'; @@ -29,7 +28,7 @@ describe('Assignees', () => { let appMockRender: AppMockRenderer; const MockHookWrapperComponent: FC> = ({ children }) => { - const { form } = useForm(); + const { form } = useForm(); globalForm = form; return
      {children}
      ; @@ -41,113 +40,99 @@ describe('Assignees', () => { }); it('renders', async () => { - const result = appMockRender.render( + appMockRender.render( ); await waitFor(() => { - expect(result.getByTestId('comboBoxSearchInput')).not.toBeDisabled(); + expect(screen.queryByTestId('comboBoxSearchInput')).not.toBeDisabled(); }); - expect(result.getByTestId('createCaseAssigneesComboBox')).toBeInTheDocument(); + expect(await screen.findByTestId('createCaseAssigneesComboBox')).toBeInTheDocument(); }); it('does not render the assign yourself link when the current user profile is undefined', async () => { const spyOnGetCurrentUserProfile = jest.spyOn(api, 'getCurrentUserProfile'); spyOnGetCurrentUserProfile.mockResolvedValue(undefined as unknown as UserProfile); - const result = appMockRender.render( + appMockRender.render( ); await waitFor(() => { - expect(result.getByTestId('comboBoxSearchInput')).not.toBeDisabled(); + expect(screen.queryByTestId('comboBoxSearchInput')).not.toBeDisabled(); }); - expect(result.queryByTestId('create-case-assign-yourself-link')).not.toBeInTheDocument(); - expect(result.getByTestId('createCaseAssigneesComboBox')).toBeInTheDocument(); + expect(screen.queryByTestId('create-case-assign-yourself-link')).not.toBeInTheDocument(); + expect(await screen.findByTestId('createCaseAssigneesComboBox')).toBeInTheDocument(); }); it('selects the current user correctly', async () => { const spyOnGetCurrentUserProfile = jest.spyOn(api, 'getCurrentUserProfile'); spyOnGetCurrentUserProfile.mockResolvedValue(currentUserProfile); - const result = appMockRender.render( + appMockRender.render( ); await waitFor(() => { - expect(result.getByTestId('comboBoxSearchInput')).not.toBeDisabled(); + expect(screen.queryByTestId('comboBoxSearchInput')).not.toBeDisabled(); }); - act(() => { - userEvent.click(result.getByTestId('create-case-assign-yourself-link')); - }); + userEvent.click(await screen.findByTestId('create-case-assign-yourself-link')); - await waitFor(() => { - expect(globalForm.getFormData()).toEqual({ assignees: [{ uid: currentUserProfile.uid }] }); - }); + expect(globalForm.getFormData()).toEqual({ assignees: [{ uid: currentUserProfile.uid }] }); }); it('disables the assign yourself button if the current user is already selected', async () => { const spyOnGetCurrentUserProfile = jest.spyOn(api, 'getCurrentUserProfile'); spyOnGetCurrentUserProfile.mockResolvedValue(currentUserProfile); - const result = appMockRender.render( + appMockRender.render( ); await waitFor(() => { - expect(result.getByTestId('comboBoxSearchInput')).not.toBeDisabled(); + expect(screen.queryByTestId('comboBoxSearchInput')).not.toBeDisabled(); }); - act(() => { - userEvent.click(result.getByTestId('create-case-assign-yourself-link')); - }); + userEvent.click(await screen.findByTestId('create-case-assign-yourself-link')); await waitFor(() => { expect(globalForm.getFormData()).toEqual({ assignees: [{ uid: currentUserProfile.uid }] }); }); - expect(result.getByTestId('create-case-assign-yourself-link')).toBeDisabled(); + expect(await screen.findByTestId('create-case-assign-yourself-link')).toBeDisabled(); }); it('assignees users correctly', async () => { - const result = appMockRender.render( + appMockRender.render( ); await waitFor(() => { - expect(result.getByTestId('comboBoxSearchInput')).not.toBeDisabled(); + expect(screen.queryByTestId('comboBoxSearchInput')).not.toBeDisabled(); }); - await act(async () => { - await userEvent.type(result.getByTestId('comboBoxSearchInput'), 'dr', { delay: 1 }); - }); + await userEvent.type(await screen.findByTestId('comboBoxSearchInput'), 'dr', { delay: 1 }); - await waitFor(() => { - expect( - result.getByTestId('comboBoxOptionsList createCaseAssigneesComboBox-optionsList') - ).toBeInTheDocument(); - }); + expect( + await screen.findByTestId('comboBoxOptionsList createCaseAssigneesComboBox-optionsList') + ).toBeInTheDocument(); - await waitFor(async () => { - expect(result.getByText(`${currentUserProfile.user.full_name}`)).toBeInTheDocument(); - }); + expect(await screen.findByText(`${currentUserProfile.user.full_name}`)).toBeInTheDocument(); - act(() => { - userEvent.click(result.getByText(`${currentUserProfile.user.full_name}`)); - }); + userEvent.click(await screen.findByText(`${currentUserProfile.user.full_name}`)); await waitFor(() => { expect(globalForm.getFormData()).toEqual({ assignees: [{ uid: currentUserProfile.uid }] }); @@ -186,25 +171,62 @@ describe('Assignees', () => { ); await waitFor(() => { - expect(screen.getByTestId('comboBoxSearchInput')).not.toBeDisabled(); + expect(screen.queryByTestId('comboBoxSearchInput')).not.toBeDisabled(); }); + userEvent.click(await screen.findByTestId('comboBoxSearchInput')); + + expect(await screen.findByText('Turtle')).toBeInTheDocument(); + expect(await screen.findByText('turtle')).toBeInTheDocument(); + + userEvent.click(screen.getByText('Turtle'), undefined, { skipPointerEventsCheck: true }); + + // ensure that the similar user is still available for selection + expect(await screen.findByText('turtle')).toBeInTheDocument(); + }); + + it('fetches the unknown user profiles using bulk_get', async () => { + // the profile is not returned by the suggest API + const userProfile = { + uid: 'u_qau3P4T1H-_f1dNHyEOWJzVkGQhLH1gnNMVvYxqmZcs_0', + enabled: true, + data: {}, + user: { + username: 'uncertain_crawdad', + email: 'uncertain_crawdad@profiles.elastic.co', + full_name: 'Uncertain Crawdad', + }, + }; + + const spyOnBulkGetUserProfiles = jest.spyOn(api, 'bulkGetUserProfiles'); + spyOnBulkGetUserProfiles.mockResolvedValue([userProfile]); + + appMockRender.render( + + + + ); + + expect(screen.queryByText(userProfile.user.full_name)).not.toBeInTheDocument(); + act(() => { - userEvent.click(screen.getByTestId('comboBoxSearchInput')); + globalForm.setFieldValue('assignees', [{ uid: userProfile.uid }]); }); await waitFor(() => { - expect(screen.getByText('Turtle')).toBeInTheDocument(); - expect(screen.getByText('turtle')).toBeInTheDocument(); + expect(globalForm.getFormData()).toEqual({ + assignees: [{ uid: userProfile.uid }], + }); }); - act(() => { - userEvent.click(screen.getByText('Turtle'), undefined, { skipPointerEventsCheck: true }); - }); - - // ensure that the similar user is still available for selection await waitFor(() => { - expect(screen.getByText('turtle')).toBeInTheDocument(); + expect(spyOnBulkGetUserProfiles).toBeCalledTimes(1); + expect(spyOnBulkGetUserProfiles).toHaveBeenCalledWith({ + security: expect.anything(), + uids: [userProfile.uid], + }); }); + + expect(await screen.findByText(userProfile.user.full_name)).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/cases/public/components/create/assignees.tsx b/x-pack/plugins/cases/public/components/case_form_fields/assignees.tsx similarity index 61% rename from x-pack/plugins/cases/public/components/create/assignees.tsx rename to x-pack/plugins/cases/public/components/case_form_fields/assignees.tsx index 1e8464dc1a2ed..6e56e7d154a2a 100644 --- a/x-pack/plugins/cases/public/components/create/assignees.tsx +++ b/x-pack/plugins/cases/public/components/case_form_fields/assignees.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { isEmpty } from 'lodash'; +import { isEmpty, differenceWith } from 'lodash'; import React, { memo, useCallback, useState } from 'react'; import type { EuiComboBoxOptionOption } from '@elastic/eui'; import { @@ -23,31 +23,35 @@ import type { FieldConfig, FieldHook } from '@kbn/es-ui-shared-plugin/static/for import { UseField, getFieldValidityAndErrorMessage, + useFormData, } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import type { CaseAssignees } from '../../../common/types/domain'; import { MAX_ASSIGNEES_PER_CASE } from '../../../common/constants'; import { useSuggestUserProfiles } from '../../containers/user_profiles/use_suggest_user_profiles'; import { useCasesContext } from '../cases_context/use_cases_context'; import { useGetCurrentUserProfile } from '../../containers/user_profiles/use_get_current_user_profile'; -import { OptionalFieldLabel } from './optional_field_label'; -import * as i18n from './translations'; +import { OptionalFieldLabel } from '../optional_field_label'; +import * as i18n from '../create/translations'; import { bringCurrentUserToFrontAndSort } from '../user_profiles/sort'; import { useAvailableCasesOwners } from '../app/use_available_owners'; import { getAllPermissionsExceptFrom } from '../../utils/permissions'; import { useIsUserTyping } from '../../common/use_is_user_typing'; +import { useBulkGetUserProfiles } from '../../containers/user_profiles/use_bulk_get_user_profiles'; + +const FIELD_ID = 'assignees'; interface Props { isLoading: boolean; } +type UserProfileComboBoxOption = EuiComboBoxOptionOption & UserProfileWithAvatar; + interface FieldProps { - field: FieldHook; - options: EuiComboBoxOptionOption[]; + field: FieldHook; + options: UserProfileComboBoxOption[]; isLoading: boolean; isDisabled: boolean; currentUserProfile?: UserProfile; - selectedOptions: EuiComboBoxOptionOption[]; - setSelectedOptions: React.Dispatch>; onSearchComboChange: (value: string) => void; } @@ -73,28 +77,32 @@ const userProfileToComboBoxOption = (userProfile: UserProfileWithAvatar) => ({ data: userProfile.data, }); -const comboBoxOptionToAssignee = (option: EuiComboBoxOptionOption) => ({ uid: option.value }); +const comboBoxOptionToAssignee = (option: EuiComboBoxOptionOption) => ({ + uid: option.value ?? '', +}); const AssigneesFieldComponent: React.FC = React.memo( - ({ - field, - isLoading, - isDisabled, - options, - currentUserProfile, - selectedOptions, - setSelectedOptions, - onSearchComboChange, - }) => { - const { setValue } = field; + ({ field, isLoading, isDisabled, options, currentUserProfile, onSearchComboChange }) => { + const { setValue, value: selectedAssignees } = field; const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + const selectedOptions: UserProfileComboBoxOption[] = selectedAssignees + .map(({ uid }) => { + const selectedUserProfile = options.find((userProfile) => userProfile.key === uid); + + if (selectedUserProfile) { + return selectedUserProfile; + } + + return null; + }) + .filter((value): value is UserProfileComboBoxOption => value != null); + const onComboChange = useCallback( - (currentOptions: EuiComboBoxOptionOption[]) => { - setSelectedOptions(currentOptions); + (currentOptions: Array>) => { setValue(currentOptions.map((option) => comboBoxOptionToAssignee(option))); }, - [setSelectedOptions, setValue] + [setValue] ); const onSelfAssign = useCallback(() => { @@ -102,62 +110,51 @@ const AssigneesFieldComponent: React.FC = React.memo( return; } - setSelectedOptions((prev) => [ - ...(prev ?? []), - userProfileToComboBoxOption(currentUserProfile), - ]); - - setValue([ - ...(selectedOptions?.map((option) => comboBoxOptionToAssignee(option)) ?? []), - { uid: currentUserProfile.uid }, - ]); - }, [currentUserProfile, selectedOptions, setSelectedOptions, setValue]); + setValue([...selectedAssignees, { uid: currentUserProfile.uid }]); + }, [currentUserProfile, selectedAssignees, setValue]); - const renderOption = useCallback( - (option: EuiComboBoxOptionOption, searchValue: string, contentClassName: string) => { - const { user, data } = option as EuiComboBoxOptionOption & UserProfileWithAvatar; + const renderOption = useCallback((option, searchValue: string, contentClassName: string) => { + const { user, data } = option as UserProfileComboBoxOption; - const displayName = getUserDisplayName(user); + const displayName = getUserDisplayName(user); - return ( + return ( + + + + - - + + + {displayName} + - - - - {displayName} - + {user.email && user.email !== displayName ? ( + + + + {user.email} + + - {user.email && user.email !== displayName ? ( - - - - {user.email} - - - - ) : null} - + ) : null} - ); - }, - [] - ); + + ); + }, []); const isCurrentUserSelected = Boolean( - selectedOptions?.find((option) => option.value === currentUserProfile?.uid) + selectedAssignees?.find((assignee) => assignee.uid === currentUserProfile?.uid) ); return ( @@ -179,6 +176,7 @@ const AssigneesFieldComponent: React.FC = React.memo( } isInvalid={isInvalid} error={errorMessage} + data-test-subj="caseAssignees" > = ({ isLoading: isLoadingForm }) => { const { owner: owners } = useCasesContext(); + const [{ assignees }] = useFormData<{ assignees?: CaseAssignees }>({ watch: [FIELD_ID] }); const availableOwners = useAvailableCasesOwners(getAllPermissionsExceptFrom('delete')); const [searchTerm, setSearchTerm] = useState(''); - const [selectedOptions, setSelectedOptions] = useState(); const { isUserTyping, onContentChange, onDebounce } = useIsUserTyping(); const hasOwners = owners.length > 0; @@ -212,7 +210,7 @@ const AssigneesComponent: React.FC = ({ isLoading: isLoadingForm }) => { useGetCurrentUserProfile(); const { - data: userProfiles, + data: userProfiles = [], isLoading: isLoadingSuggest, isFetching: isFetchingSuggest, } = useSuggestUserProfiles({ @@ -221,10 +219,22 @@ const AssigneesComponent: React.FC = ({ isLoading: isLoadingForm }) => { onDebounce, }); + const assigneesWithoutProfiles = differenceWith( + assignees ?? [], + userProfiles ?? [], + (assignee, userProfile) => assignee.uid === userProfile.uid + ); + + const { data: bulkUserProfiles = new Map(), isFetching: isLoadingBulkGetUserProfiles } = + useBulkGetUserProfiles({ uids: assigneesWithoutProfiles.map((assignee) => assignee.uid) }); + + const bulkUserProfilesAsArray = Array.from(bulkUserProfiles).map(([_, profile]) => profile); + const options = - bringCurrentUserToFrontAndSort(currentUserProfile, userProfiles)?.map((userProfile) => - userProfileToComboBoxOption(userProfile) - ) ?? []; + bringCurrentUserToFrontAndSort(currentUserProfile, [ + ...userProfiles, + ...bulkUserProfilesAsArray, + ])?.map((userProfile) => userProfileToComboBoxOption(userProfile)) ?? []; const onSearchComboChange = (value: string) => { if (!isEmpty(value)) { @@ -237,22 +247,21 @@ const AssigneesComponent: React.FC = ({ isLoading: isLoadingForm }) => { const isLoading = isLoadingForm || isLoadingCurrentUserProfile || + isLoadingBulkGetUserProfiles || isLoadingSuggest || isFetchingSuggest || isUserTyping; - const isDisabled = isLoadingForm || isLoadingCurrentUserProfile; + const isDisabled = isLoadingForm || isLoadingCurrentUserProfile || isLoadingBulkGetUserProfiles; return ( { const onSubmit = jest.fn(); const FormComponent: FC> = ({ children }) => { - const { form } = useForm({ onSubmit }); + const { form } = useForm({ onSubmit }); return (
      diff --git a/x-pack/plugins/cases/public/components/create/category.tsx b/x-pack/plugins/cases/public/components/case_form_fields/category.tsx similarity index 93% rename from x-pack/plugins/cases/public/components/create/category.tsx rename to x-pack/plugins/cases/public/components/case_form_fields/category.tsx index 879a8dfb9bbea..d5df6118094e6 100644 --- a/x-pack/plugins/cases/public/components/create/category.tsx +++ b/x-pack/plugins/cases/public/components/case_form_fields/category.tsx @@ -8,7 +8,7 @@ import React, { memo } from 'react'; import { useGetCategories } from '../../containers/use_get_categories'; import { CategoryFormField } from '../category/category_form_field'; -import { OptionalFieldLabel } from './optional_field_label'; +import { OptionalFieldLabel } from '../optional_field_label'; interface Props { isLoading: boolean; diff --git a/x-pack/plugins/cases/public/components/case_form_fields/connector.test.tsx b/x-pack/plugins/cases/public/components/case_form_fields/connector.test.tsx new file mode 100644 index 0000000000000..0f80652c9ac03 --- /dev/null +++ b/x-pack/plugins/cases/public/components/case_form_fields/connector.test.tsx @@ -0,0 +1,164 @@ +/* + * 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 { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import type { AppMockRenderer } from '../../common/mock'; +import { connectorsMock } from '../../containers/mock'; +import { Connector } from './connector'; +import { useGetIncidentTypes } from '../connectors/resilient/use_get_incident_types'; +import { useGetSeverity } from '../connectors/resilient/use_get_severity'; +import { useGetChoices } from '../connectors/servicenow/use_get_choices'; +import { incidentTypes, severity, choices } from '../connectors/mock'; +import { noConnectorsCasePermission, createAppMockRenderer } from '../../common/mock'; + +import { FormTestComponent } from '../../common/test_utils'; +import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; + +jest.mock('../connectors/resilient/use_get_incident_types'); +jest.mock('../connectors/resilient/use_get_severity'); +jest.mock('../connectors/servicenow/use_get_choices'); + +const useGetIncidentTypesMock = useGetIncidentTypes as jest.Mock; +const useGetSeverityMock = useGetSeverity as jest.Mock; +const useGetChoicesMock = useGetChoices as jest.Mock; + +const useGetIncidentTypesResponse = { + isLoading: false, + incidentTypes, +}; + +const useGetSeverityResponse = { + isLoading: false, + severity, +}; + +const useGetChoicesResponse = { + isLoading: false, + choices, +}; + +const defaultProps = { + connectors: connectorsMock, + isLoading: false, + isLoadingConnectors: false, +}; + +describe('Connector', () => { + let appMockRender: AppMockRenderer; + + beforeEach(() => { + jest.clearAllMocks(); + appMockRender = createAppMockRenderer(); + useGetIncidentTypesMock.mockReturnValue(useGetIncidentTypesResponse); + useGetSeverityMock.mockReturnValue(useGetSeverityResponse); + useGetChoicesMock.mockReturnValue(useGetChoicesResponse); + }); + + it('renders correctly', async () => { + appMockRender.render( + + + + ); + + expect(await screen.findByTestId('caseConnectors')).toBeInTheDocument(); + expect(screen.queryByTestId('connector-fields')).not.toBeInTheDocument(); + }); + + it('renders loading state correctly', async () => { + appMockRender.render( + + + + ); + + expect(await screen.findByRole('progressbar')).toBeInTheDocument(); + expect(await screen.findByLabelText('Loading')).toBeInTheDocument(); + expect(await screen.findByTestId('dropdown-connectors')).toBeDisabled(); + }); + + it('renders default connector correctly', async () => { + appMockRender.render( + + + + ); + + expect(await screen.findByTestId('caseConnectors')).toBeInTheDocument(); + expect(await screen.findByText('Jira')).toBeInTheDocument(); + + expect(await screen.findByTestId('connector-fields-jira')).toBeInTheDocument(); + }); + + it('shows all connectors in dropdown', async () => { + appMockRender.render( + + + + ); + + expect(await screen.findByTestId('caseConnectors')).toBeInTheDocument(); + userEvent.click(await screen.findByTestId('dropdown-connectors')); + + await waitForEuiPopoverOpen(); + + expect( + await screen.findByTestId(`dropdown-connector-${connectorsMock[0].id}`) + ).toBeInTheDocument(); + expect( + await screen.findByTestId(`dropdown-connector-${connectorsMock[1].id}`) + ).toBeInTheDocument(); + }); + + it('changes connector correctly', async () => { + appMockRender.render( + + + + ); + + expect(await screen.findByTestId('caseConnectors')).toBeInTheDocument(); + userEvent.click(await screen.findByTestId('dropdown-connectors')); + + await waitForEuiPopoverOpen(); + + userEvent.click(await screen.findByTestId('dropdown-connector-resilient-2')); + + expect(await screen.findByTestId('connector-fields-resilient')).toBeInTheDocument(); + }); + + it('shows the actions permission message if the user does not have read access to actions', async () => { + appMockRender.coreStart.application.capabilities = { + ...appMockRender.coreStart.application.capabilities, + actions: { save: false, show: false }, + }; + + appMockRender.render( + + + + ); + expect( + await screen.findByTestId('create-case-connector-permissions-error-msg') + ).toBeInTheDocument(); + expect(screen.queryByTestId('caseConnectors')).not.toBeInTheDocument(); + }); + + it('shows the actions permission message if the user does not have access to case connector', async () => { + appMockRender = createAppMockRenderer({ permissions: noConnectorsCasePermission() }); + + appMockRender.render( + + + + ); + expect(screen.getByTestId('create-case-connector-permissions-error-msg')).toBeInTheDocument(); + expect(screen.queryByTestId('caseConnectors')).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/create/connector.tsx b/x-pack/plugins/cases/public/components/case_form_fields/connector.tsx similarity index 62% rename from x-pack/plugins/cases/public/components/create/connector.tsx rename to x-pack/plugins/cases/public/components/case_form_fields/connector.tsx index 39e04f7bc0be3..5ed37c262ec17 100644 --- a/x-pack/plugins/cases/public/components/create/connector.tsx +++ b/x-pack/plugins/cases/public/components/case_form_fields/connector.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import React, { memo, useMemo } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import React, { memo } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiFormRow } from '@elastic/eui'; import type { FieldConfig } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { UseField, useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; @@ -14,7 +14,6 @@ import type { ActionConnector } from '../../../common/types/domain'; import { ConnectorSelector } from '../connector_selector/form'; import { ConnectorFieldsForm } from '../connectors/fields_form'; import { schema } from './schema'; -import { useGetCaseConfiguration } from '../../containers/configure/use_get_case_configuration'; import { getConnectorById, getConnectorsFormValidators } from '../utils'; import { useApplicationCapabilities } from '../../common/lib/kibana'; import * as i18n from '../../common/translations'; @@ -29,21 +28,10 @@ interface Props { const ConnectorComponent: React.FC = ({ connectors, isLoading, isLoadingConnectors }) => { const [{ connectorId }] = useFormData({ watch: ['connectorId'] }); const connector = getConnectorById(connectorId, connectors) ?? null; - - const { - data: { connector: configurationConnector }, - } = useGetCaseConfiguration(); - const { actions } = useApplicationCapabilities(); const { permissions } = useCasesContext(); const hasReadPermissions = permissions.connectors && actions.read; - const defaultConnectorId = useMemo(() => { - return connectors.some((c) => c.id === configurationConnector.id) - ? configurationConnector.id - : 'none'; - }, [configurationConnector.id, connectors]); - const connectorIdConfig = getConnectorsFormValidators({ config: schema.connectorId as FieldConfig, connectors, @@ -58,26 +46,27 @@ const ConnectorComponent: React.FC = ({ connectors, isLoading, isLoadingC } return ( - - - - - - - - + + + + + + + + + + ); }; diff --git a/x-pack/plugins/cases/public/components/create/custom_fields.test.tsx b/x-pack/plugins/cases/public/components/case_form_fields/custom_fields.test.tsx similarity index 67% rename from x-pack/plugins/cases/public/components/create/custom_fields.test.tsx rename to x-pack/plugins/cases/public/components/case_form_fields/custom_fields.test.tsx index 8ab517c497cde..95f7ef1aaa09b 100644 --- a/x-pack/plugins/cases/public/components/create/custom_fields.test.tsx +++ b/x-pack/plugins/cases/public/components/case_form_fields/custom_fields.test.tsx @@ -15,40 +15,32 @@ import { FormTestComponent } from '../../common/test_utils'; import { customFieldsConfigurationMock } from '../../containers/mock'; import { CustomFields } from './custom_fields'; import * as i18n from './translations'; -import { useGetAllCaseConfigurations } from '../../containers/configure/use_get_all_case_configurations'; -import { useGetAllCaseConfigurationsResponse } from '../configure_cases/__mock__'; - -jest.mock('../../containers/configure/use_get_all_case_configurations'); - -const useGetAllCaseConfigurationsMock = useGetAllCaseConfigurations as jest.Mock; describe('CustomFields', () => { let appMockRender: AppMockRenderer; const onSubmit = jest.fn(); + const defaultProps = { + configurationCustomFields: customFieldsConfigurationMock, + isLoading: false, + setCustomFieldsOptional: false, + isEditMode: false, + }; + beforeEach(() => { jest.clearAllMocks(); appMockRender = createAppMockRenderer(); - useGetAllCaseConfigurationsMock.mockImplementation(() => ({ - ...useGetAllCaseConfigurationsResponse, - data: [ - { - ...useGetAllCaseConfigurationsResponse.data[0], - customFields: customFieldsConfigurationMock, - }, - ], - })); }); it('renders correctly', async () => { appMockRender.render( - + ); expect(await screen.findByText(i18n.ADDITIONAL_FIELDS)).toBeInTheDocument(); - expect(await screen.findByTestId('create-case-custom-fields')).toBeInTheDocument(); + expect(await screen.findByTestId('caseCustomFields')).toBeInTheDocument(); for (const item of customFieldsConfigurationMock) { expect( @@ -58,19 +50,13 @@ describe('CustomFields', () => { }); it('should not show the custom fields if the configuration is empty', async () => { - useGetAllCaseConfigurationsMock.mockImplementation(() => ({ - ...useGetAllCaseConfigurationsResponse, - data: [ - { - ...useGetAllCaseConfigurationsResponse.data[0], - customFields: [], - }, - ], - })); - appMockRender.render( - + ); @@ -78,26 +64,51 @@ describe('CustomFields', () => { expect(screen.queryAllByTestId('create-custom-field', { exact: false }).length).toEqual(0); }); + it('should render as optional fields for text custom fields', async () => { + appMockRender.render( + + + + ); + + expect(screen.getAllByTestId('form-optional-field-label')).toHaveLength(2); + }); + + it('should not set default value when in edit mode', async () => { + appMockRender.render( + + + + ); + + expect( + screen.queryByText(`${customFieldsConfigurationMock[0].defaultValue}`) + ).not.toBeInTheDocument(); + }); + it('should sort the custom fields correctly', async () => { const reversedCustomFieldsConfiguration = [...customFieldsConfigurationMock].reverse(); - useGetAllCaseConfigurationsMock.mockImplementation(() => ({ - ...useGetAllCaseConfigurationsResponse, - data: [ - { - ...useGetAllCaseConfigurationsResponse.data[0], - customFields: reversedCustomFieldsConfiguration, - }, - ], - })); - appMockRender.render( - + ); - const customFieldsWrapper = await screen.findByTestId('create-case-custom-fields'); + const customFieldsWrapper = await screen.findByTestId('caseCustomFields'); const customFields = customFieldsWrapper.querySelectorAll('.euiFormRow'); @@ -110,11 +121,9 @@ describe('CustomFields', () => { }); it('should update the custom fields', async () => { - appMockRender = createAppMockRenderer(); - appMockRender.render( - + ); diff --git a/x-pack/plugins/cases/public/components/case_form_fields/custom_fields.tsx b/x-pack/plugins/cases/public/components/case_form_fields/custom_fields.tsx new file mode 100644 index 0000000000000..f2b39b352a964 --- /dev/null +++ b/x-pack/plugins/cases/public/components/case_form_fields/custom_fields.tsx @@ -0,0 +1,78 @@ +/* + * 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, { useMemo } from 'react'; +import { sortBy } from 'lodash'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiFormRow } from '@elastic/eui'; + +import type { CasesConfigurationUI } from '../../../common/ui'; +import { builderMap as customFieldsBuilderMap } from '../custom_fields/builder'; +import * as i18n from './translations'; + +interface Props { + isLoading: boolean; + configurationCustomFields: CasesConfigurationUI['customFields']; + setCustomFieldsOptional?: boolean; + isEditMode?: boolean; +} + +const CustomFieldsComponent: React.FC = ({ + isLoading, + setCustomFieldsOptional = false, + configurationCustomFields, + isEditMode, +}) => { + const sortedCustomFields = useMemo( + () => sortCustomFieldsByLabel(configurationCustomFields), + [configurationCustomFields] + ); + + const customFieldsComponents = sortedCustomFields.map( + (customField: CasesConfigurationUI['customFields'][number]) => { + const customFieldFactory = customFieldsBuilderMap[customField.type]; + const customFieldType = customFieldFactory().build(); + + const CreateComponent = customFieldType.Create; + + return ( + + ); + } + ); + + if (!configurationCustomFields.length) { + return null; + } + + return ( + + + +

      {i18n.ADDITIONAL_FIELDS}

      +
      + + {customFieldsComponents} +
      +
      + ); +}; + +CustomFieldsComponent.displayName = 'CustomFields'; + +export const CustomFields = React.memo(CustomFieldsComponent); + +const sortCustomFieldsByLabel = (configCustomFields: CasesConfigurationUI['customFields']) => { + return sortBy(configCustomFields, (configCustomField) => { + return configCustomField.label; + }); +}; diff --git a/x-pack/plugins/cases/public/components/create/description.test.tsx b/x-pack/plugins/cases/public/components/case_form_fields/description.test.tsx similarity index 98% rename from x-pack/plugins/cases/public/components/create/description.test.tsx rename to x-pack/plugins/cases/public/components/case_form_fields/description.test.tsx index 5acd5a3b4f5c8..8d841da78b362 100644 --- a/x-pack/plugins/cases/public/components/create/description.test.tsx +++ b/x-pack/plugins/cases/public/components/case_form_fields/description.test.tsx @@ -10,7 +10,7 @@ import { waitFor, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Description } from './description'; -import { schema } from './schema'; +import { schema } from '../create/schema'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; import { MAX_DESCRIPTION_LENGTH } from '../../../common/constants'; diff --git a/x-pack/plugins/cases/public/components/create/description.tsx b/x-pack/plugins/cases/public/components/case_form_fields/description.tsx similarity index 98% rename from x-pack/plugins/cases/public/components/create/description.tsx rename to x-pack/plugins/cases/public/components/case_form_fields/description.tsx index 5c512e701c123..881ea13c19c3d 100644 --- a/x-pack/plugins/cases/public/components/create/description.tsx +++ b/x-pack/plugins/cases/public/components/case_form_fields/description.tsx @@ -12,7 +12,7 @@ import { ID as LensPluginId } from '../markdown_editor/plugins/lens/constants'; interface Props { isLoading: boolean; - draftStorageKey: string; + draftStorageKey?: string; } export const fieldName = 'description'; diff --git a/x-pack/plugins/cases/public/components/case_form_fields/index.test.tsx b/x-pack/plugins/cases/public/components/case_form_fields/index.test.tsx new file mode 100644 index 0000000000000..e095a8a915b76 --- /dev/null +++ b/x-pack/plugins/cases/public/components/case_form_fields/index.test.tsx @@ -0,0 +1,330 @@ +/* + * 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 { screen, waitFor, within } from '@testing-library/react'; +import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; + +import type { AppMockRenderer } from '../../common/mock'; +import { createAppMockRenderer } from '../../common/mock'; +import { FormTestComponent } from '../../common/test_utils'; +import { customFieldsConfigurationMock } from '../../containers/mock'; +import { userProfiles } from '../../containers/user_profiles/api.mock'; + +import { CaseFormFields } from '.'; +import userEvent from '@testing-library/user-event'; +import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; + +jest.mock('../../containers/user_profiles/api'); + +describe('CaseFormFields', () => { + let appMock: AppMockRenderer; + const onSubmit = jest.fn(); + const formDefaultValue = { tags: [] }; + const defaultProps = { + isLoading: false, + configurationCustomFields: [], + }; + + beforeEach(() => { + appMock = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + it('renders correctly', async () => { + appMock.render( + + + + ); + + expect(await screen.findByTestId('case-form-fields')).toBeInTheDocument(); + }); + + it('renders case fields correctly', async () => { + appMock.render( + + + + ); + + expect(await screen.findByTestId('caseTitle')).toBeInTheDocument(); + expect(await screen.findByTestId('caseTags')).toBeInTheDocument(); + expect(await screen.findByTestId('caseCategory')).toBeInTheDocument(); + expect(await screen.findByTestId('caseSeverity')).toBeInTheDocument(); + expect(await screen.findByTestId('caseDescription')).toBeInTheDocument(); + }); + + it('does not render customFields when empty', () => { + appMock.render( + + + + ); + + expect(screen.queryByTestId('caseCustomFields')).not.toBeInTheDocument(); + }); + + it('renders customFields when not empty', async () => { + appMock.render( + + + + ); + + expect(await screen.findByTestId('caseCustomFields')).toBeInTheDocument(); + }); + + it('does not render assignees when no platinum license', () => { + appMock.render( + + + + ); + + expect(screen.queryByTestId('createCaseAssigneesComboBox')).not.toBeInTheDocument(); + }); + + it('renders assignees when platinum license', async () => { + const license = licensingMock.createLicense({ + license: { type: 'platinum' }, + }); + + appMock = createAppMockRenderer({ license }); + + appMock.render( + + + + ); + + expect(await screen.findByTestId('createCaseAssigneesComboBox')).toBeInTheDocument(); + }); + + it('calls onSubmit with case fields', async () => { + appMock.render( + + + + ); + + const caseTitle = await screen.findByTestId('caseTitle'); + userEvent.paste(within(caseTitle).getByTestId('input'), 'Case with Template 1'); + + const caseDescription = await screen.findByTestId('caseDescription'); + userEvent.paste( + within(caseDescription).getByTestId('euiMarkdownEditorTextArea'), + 'This is a case description' + ); + + const caseTags = await screen.findByTestId('caseTags'); + userEvent.paste(within(caseTags).getByRole('combobox'), 'template-1'); + userEvent.keyboard('{enter}'); + + const caseCategory = await screen.findByTestId('caseCategory'); + userEvent.type(within(caseCategory).getByRole('combobox'), 'new {enter}'); + + userEvent.click(await screen.findByText('Submit')); + + await waitFor(() => { + expect(onSubmit).toBeCalledWith( + { + category: 'new', + tags: ['template-1'], + description: 'This is a case description', + title: 'Case with Template 1', + }, + true + ); + }); + }); + + it('calls onSubmit with existing case fields', async () => { + appMock.render( + + + + ); + + userEvent.click(await screen.findByText('Submit')); + + await waitFor(() => { + expect(onSubmit).toBeCalledWith( + { + category: null, + tags: ['case-tag-1', 'case-tag-2'], + description: 'This is a case description', + title: 'Case with Template 1', + }, + true + ); + }); + }); + + it('calls onSubmit with custom fields', async () => { + const newProps = { + ...defaultProps, + configurationCustomFields: customFieldsConfigurationMock, + }; + + appMock.render( + + + + ); + + expect(await screen.findByTestId('caseCustomFields')).toBeInTheDocument(); + + const textField = customFieldsConfigurationMock[0]; + const toggleField = customFieldsConfigurationMock[1]; + + const textCustomField = await screen.findByTestId( + `${textField.key}-${textField.type}-create-custom-field` + ); + + userEvent.clear(textCustomField); + userEvent.paste(textCustomField, 'My text test value 1'); + + userEvent.click( + await screen.findByTestId(`${toggleField.key}-${toggleField.type}-create-custom-field`) + ); + + userEvent.click(await screen.findByText('Submit')); + + await waitFor(() => { + expect(onSubmit).toBeCalledWith( + { + category: null, + tags: [], + customFields: { + test_key_1: 'My text test value 1', + test_key_2: false, + test_key_4: false, + }, + }, + true + ); + }); + }); + + it('calls onSubmit with existing custom fields', async () => { + const newProps = { + ...defaultProps, + configurationCustomFields: customFieldsConfigurationMock, + }; + + appMock.render( + + + + ); + + expect(await screen.findByTestId('caseCustomFields')).toBeInTheDocument(); + + userEvent.click(await screen.findByText('Submit')); + + await waitFor(() => { + expect(onSubmit).toBeCalledWith( + { + category: null, + tags: [], + customFields: { + test_key_1: 'Test custom filed value', + test_key_2: true, + test_key_4: false, + }, + }, + true + ); + }); + }); + + it('calls onSubmit with assignees', async () => { + const license = licensingMock.createLicense({ + license: { type: 'platinum' }, + }); + + appMock = createAppMockRenderer({ license }); + + appMock.render( + + + + ); + + const assigneesComboBox = await screen.findByTestId('createCaseAssigneesComboBox'); + + userEvent.click(await within(assigneesComboBox).findByTestId('comboBoxToggleListButton')); + + await waitForEuiPopoverOpen(); + + userEvent.click(screen.getByText(`${userProfiles[0].user.full_name}`)); + + userEvent.click(await screen.findByText('Submit')); + + await waitFor(() => { + expect(onSubmit).toBeCalledWith( + { + category: null, + tags: [], + assignees: [{ uid: userProfiles[0].uid }], + }, + true + ); + }); + }); + + it('calls onSubmit with existing assignees', async () => { + const license = licensingMock.createLicense({ + license: { type: 'platinum' }, + }); + + appMock = createAppMockRenderer({ license }); + + appMock.render( + + + + ); + + userEvent.click(await screen.findByText('Submit')); + + await waitFor(() => { + expect(onSubmit).toBeCalledWith( + { + category: null, + tags: [], + assignees: [{ uid: userProfiles[1].uid }], + }, + true + ); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/case_form_fields/index.tsx b/x-pack/plugins/cases/public/components/case_form_fields/index.tsx new file mode 100644 index 0000000000000..5232529e59cef --- /dev/null +++ b/x-pack/plugins/cases/public/components/case_form_fields/index.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo } from 'react'; +import { EuiFlexGroup } from '@elastic/eui'; +import { Title } from './title'; +import { Tags } from './tags'; +import { Category } from './category'; +import { Severity } from './severity'; +import { Description } from './description'; +import { useCasesFeatures } from '../../common/use_cases_features'; +import { Assignees } from './assignees'; +import { CustomFields } from './custom_fields'; +import type { CasesConfigurationUI } from '../../containers/types'; + +interface Props { + isLoading: boolean; + configurationCustomFields: CasesConfigurationUI['customFields']; + setCustomFieldsOptional?: boolean; + isEditMode?: boolean; + draftStorageKey?: string; +} + +const CaseFormFieldsComponent: React.FC = ({ + isLoading, + configurationCustomFields, + setCustomFieldsOptional = false, + isEditMode, + draftStorageKey, +}) => { + const { caseAssignmentAuthorized } = useCasesFeatures(); + + return ( + + + {caseAssignmentAuthorized ? <Assignees isLoading={isLoading} /> : null} + <Tags isLoading={isLoading} /> + <Category isLoading={isLoading} /> + <Severity isLoading={isLoading} /> + <Description isLoading={isLoading} draftStorageKey={draftStorageKey} /> + <CustomFields + isLoading={isLoading} + setCustomFieldsOptional={setCustomFieldsOptional} + configurationCustomFields={configurationCustomFields} + isEditMode={isEditMode} + /> + </EuiFlexGroup> + ); +}; + +CaseFormFieldsComponent.displayName = 'CaseFormFields'; + +export const CaseFormFields = memo(CaseFormFieldsComponent); diff --git a/x-pack/plugins/cases/public/components/case_form_fields/schema.tsx b/x-pack/plugins/cases/public/components/case_form_fields/schema.tsx new file mode 100644 index 0000000000000..9c501dafff883 --- /dev/null +++ b/x-pack/plugins/cases/public/components/case_form_fields/schema.tsx @@ -0,0 +1,109 @@ +/* + * 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 { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; +import type { FormSchema } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { VALIDATION_TYPES } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import type { CasePostRequest } from '../../../common'; +import { + MAX_DESCRIPTION_LENGTH, + MAX_LENGTH_PER_TAG, + MAX_TAGS_PER_CASE, + MAX_TITLE_LENGTH, +} from '../../../common/constants'; +import { SEVERITY_TITLE } from '../severity/translations'; +import type { ConnectorTypeFields } from '../../../common/types/domain'; +import * as i18n from './translations'; +import { validateEmptyTags, validateMaxLength, validateMaxTagsLength } from './utils'; +import { OptionalFieldLabel } from '../optional_field_label'; + +const { maxLengthField } = fieldValidators; + +export type CaseFormFieldsSchemaProps = Omit< + CasePostRequest, + 'connector' | 'settings' | 'owner' | 'customFields' +> & { + connectorId: string; + fields: ConnectorTypeFields['fields']; + syncAlerts: boolean; + customFields: Record<string, string | boolean>; +}; + +export const schema: FormSchema<CaseFormFieldsSchemaProps> = { + title: { + label: i18n.NAME, + validations: [ + { + validator: maxLengthField({ + length: MAX_TITLE_LENGTH, + message: i18n.MAX_LENGTH_ERROR('name', MAX_TITLE_LENGTH), + }), + }, + ], + }, + description: { + label: i18n.DESCRIPTION, + validations: [ + { + validator: maxLengthField({ + length: MAX_DESCRIPTION_LENGTH, + message: i18n.MAX_LENGTH_ERROR('description', MAX_DESCRIPTION_LENGTH), + }), + }, + ], + }, + tags: { + label: i18n.TAGS, + helpText: i18n.TAGS_HELP, + labelAppend: OptionalFieldLabel, + validations: [ + { + validator: ({ value }: { value: string | string[] }) => + validateEmptyTags({ value, message: i18n.TAGS_EMPTY_ERROR }), + type: VALIDATION_TYPES.ARRAY_ITEM, + isBlocking: false, + }, + { + validator: ({ value }: { value: string | string[] }) => + validateMaxLength({ + value, + message: i18n.MAX_LENGTH_ERROR('tag', MAX_LENGTH_PER_TAG), + limit: MAX_LENGTH_PER_TAG, + }), + type: VALIDATION_TYPES.ARRAY_ITEM, + isBlocking: false, + }, + { + validator: ({ value }: { value: string[] }) => + validateMaxTagsLength({ + value, + message: i18n.MAX_TAGS_ERROR(MAX_TAGS_PER_CASE), + limit: MAX_TAGS_PER_CASE, + }), + }, + ], + }, + severity: { + label: SEVERITY_TITLE, + }, + assignees: { labelAppend: OptionalFieldLabel }, + category: { + labelAppend: OptionalFieldLabel, + }, + syncAlerts: { + helpText: i18n.SYNC_ALERTS_HELP, + defaultValue: true, + }, + customFields: {}, + connectorId: { + label: i18n.CONNECTORS, + defaultValue: 'none', + }, + fields: { + defaultValue: null, + }, +}; diff --git a/x-pack/plugins/cases/public/components/create/severity.test.tsx b/x-pack/plugins/cases/public/components/case_form_fields/severity.test.tsx similarity index 100% rename from x-pack/plugins/cases/public/components/create/severity.test.tsx rename to x-pack/plugins/cases/public/components/case_form_fields/severity.test.tsx diff --git a/x-pack/plugins/cases/public/components/create/severity.tsx b/x-pack/plugins/cases/public/components/case_form_fields/severity.tsx similarity index 100% rename from x-pack/plugins/cases/public/components/create/severity.tsx rename to x-pack/plugins/cases/public/components/case_form_fields/severity.tsx diff --git a/x-pack/plugins/cases/public/components/case_form_fields/sync_alerts_toggle.test.tsx b/x-pack/plugins/cases/public/components/case_form_fields/sync_alerts_toggle.test.tsx new file mode 100644 index 0000000000000..959dcba6d4e7e --- /dev/null +++ b/x-pack/plugins/cases/public/components/case_form_fields/sync_alerts_toggle.test.tsx @@ -0,0 +1,82 @@ +/* + * 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 { screen, within, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { SyncAlertsToggle } from './sync_alerts_toggle'; +import { schema } from '../create/schema'; +import { FormTestComponent } from '../../common/test_utils'; +import type { AppMockRenderer } from '../../common/mock'; +import { createAppMockRenderer } from '../../common/mock'; + +describe('SyncAlertsToggle', () => { + let appMockRender: AppMockRenderer; + const onSubmit = jest.fn(); + const defaultFormProps = { + onSubmit, + formDefaultValue: { syncAlerts: true }, + schema: { + syncAlerts: schema.syncAlerts, + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + appMockRender = createAppMockRenderer(); + }); + + it('it renders', async () => { + appMockRender.render( + <FormTestComponent> + <SyncAlertsToggle isLoading={false} /> + </FormTestComponent> + ); + + expect(await screen.findByTestId('caseSyncAlerts')).toBeInTheDocument(); + expect(await screen.findByRole('switch')).toHaveAttribute('aria-checked', 'true'); + expect(await screen.findByText('On')).toBeInTheDocument(); + }); + + it('it toggles the switch', async () => { + appMockRender.render( + <FormTestComponent> + <SyncAlertsToggle isLoading={false} /> + </FormTestComponent> + ); + + const synAlerts = await screen.findByTestId('caseSyncAlerts'); + + userEvent.click(within(synAlerts).getByRole('switch')); + + expect(await screen.findByRole('switch')).toHaveAttribute('aria-checked', 'false'); + expect(await screen.findByText('Off')).toBeInTheDocument(); + }); + + it('calls onSubmit with correct data', async () => { + appMockRender.render( + <FormTestComponent {...defaultFormProps}> + <SyncAlertsToggle isLoading={false} /> + </FormTestComponent> + ); + + const synAlerts = await screen.findByTestId('caseSyncAlerts'); + + userEvent.click(within(synAlerts).getByRole('switch')); + + userEvent.click(screen.getByText('Submit')); + + await waitFor(() => { + expect(onSubmit).toBeCalledWith( + { + syncAlerts: false, + }, + true + ); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/create/sync_alerts_toggle.tsx b/x-pack/plugins/cases/public/components/case_form_fields/sync_alerts_toggle.tsx similarity index 76% rename from x-pack/plugins/cases/public/components/create/sync_alerts_toggle.tsx rename to x-pack/plugins/cases/public/components/case_form_fields/sync_alerts_toggle.tsx index 1a189de3e17ec..de9395946ffa7 100644 --- a/x-pack/plugins/cases/public/components/create/sync_alerts_toggle.tsx +++ b/x-pack/plugins/cases/public/components/case_form_fields/sync_alerts_toggle.tsx @@ -6,11 +6,9 @@ */ import React, { memo } from 'react'; -import { getUseField, useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { Field } from '@kbn/es-ui-shared-plugin/static/forms/components'; -import * as i18n from './translations'; - -const CommonUseField = getUseField({ component: Field }); +import { UseField, useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { ToggleField } from '@kbn/es-ui-shared-plugin/static/forms/components'; +import * as i18n from '../create/translations'; interface Props { isLoading: boolean; @@ -18,9 +16,12 @@ interface Props { const SyncAlertsToggleComponent: React.FC<Props> = ({ isLoading }) => { const [{ syncAlerts }] = useFormData({ watch: ['syncAlerts'] }); + return ( - <CommonUseField + <UseField path="syncAlerts" + component={ToggleField} + config={{ defaultValue: true }} componentProps={{ idAria: 'caseSyncAlerts', 'data-test-subj': 'caseSyncAlerts', diff --git a/x-pack/plugins/cases/public/components/create/tags.test.tsx b/x-pack/plugins/cases/public/components/case_form_fields/tags.test.tsx similarity index 95% rename from x-pack/plugins/cases/public/components/create/tags.test.tsx rename to x-pack/plugins/cases/public/components/case_form_fields/tags.test.tsx index ed78d78928f0e..78f0cfce49f5f 100644 --- a/x-pack/plugins/cases/public/components/create/tags.test.tsx +++ b/x-pack/plugins/cases/public/components/case_form_fields/tags.test.tsx @@ -13,12 +13,12 @@ import userEvent from '@testing-library/user-event'; import type { FormHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { useForm, Form } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { Tags } from './tags'; -import type { FormProps } from './schema'; -import { schema } from './schema'; +import { schema } from '../create/schema'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer, TestProviders } from '../../common/mock'; import { useGetTags } from '../../containers/use_get_tags'; import { MAX_LENGTH_PER_TAG } from '../../../common/constants'; +import type { CaseFormFieldsSchemaProps } from './schema'; jest.mock('../../common/lib/kibana'); jest.mock('../../containers/use_get_tags'); @@ -30,7 +30,7 @@ describe('Tags', () => { let appMockRender: AppMockRenderer; const MockHookWrapperComponent: FC<PropsWithChildren<unknown>> = ({ children }) => { - const { form } = useForm<FormProps>({ + const { form } = useForm<CaseFormFieldsSchemaProps>({ defaultValue: { tags: [] }, schema: { tags: schema.tags, diff --git a/x-pack/plugins/cases/public/components/create/tags.tsx b/x-pack/plugins/cases/public/components/case_form_fields/tags.tsx similarity index 80% rename from x-pack/plugins/cases/public/components/create/tags.tsx rename to x-pack/plugins/cases/public/components/case_form_fields/tags.tsx index f3d4319dfea37..422e89a91afd8 100644 --- a/x-pack/plugins/cases/public/components/create/tags.tsx +++ b/x-pack/plugins/cases/public/components/case_form_fields/tags.tsx @@ -7,13 +7,10 @@ import React, { memo, useMemo } from 'react'; -import { getUseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { Field } from '@kbn/es-ui-shared-plugin/static/forms/components'; +import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { ComboBoxField } from '@kbn/es-ui-shared-plugin/static/forms/components'; import { useGetTags } from '../../containers/use_get_tags'; -import * as i18n from './translations'; - -const CommonUseField = getUseField({ component: Field }); - +import * as i18n from '../create/translations'; interface Props { isLoading: boolean; } @@ -29,8 +26,9 @@ const TagsComponent: React.FC<Props> = ({ isLoading }) => { ); return ( - <CommonUseField + <UseField path="tags" + component={ComboBoxField} componentProps={{ idAria: 'caseTags', 'data-test-subj': 'caseTags', diff --git a/x-pack/plugins/cases/public/components/create/title.test.tsx b/x-pack/plugins/cases/public/components/case_form_fields/title.test.tsx similarity index 92% rename from x-pack/plugins/cases/public/components/create/title.test.tsx rename to x-pack/plugins/cases/public/components/case_form_fields/title.test.tsx index 382ee67cc494c..73e6c19f90118 100644 --- a/x-pack/plugins/cases/public/components/create/title.test.tsx +++ b/x-pack/plugins/cases/public/components/case_form_fields/title.test.tsx @@ -13,14 +13,14 @@ import { act } from '@testing-library/react'; import type { FormHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { useForm, Form } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { Title } from './title'; -import type { FormProps } from './schema'; -import { schema } from './schema'; +import { schema } from '../create/schema'; +import type { CaseFormFieldsSchemaProps } from './schema'; describe('Title', () => { let globalForm: FormHook; const MockHookWrapperComponent: FC<PropsWithChildren<unknown>> = ({ children }) => { - const { form } = useForm<FormProps>({ + const { form } = useForm<CaseFormFieldsSchemaProps>({ defaultValue: { title: 'My title' }, schema: { title: schema.title, diff --git a/x-pack/plugins/cases/public/components/create/title.tsx b/x-pack/plugins/cases/public/components/case_form_fields/title.tsx similarity index 87% rename from x-pack/plugins/cases/public/components/create/title.tsx rename to x-pack/plugins/cases/public/components/case_form_fields/title.tsx index 35de4c7a41ccb..8727a3cc01964 100644 --- a/x-pack/plugins/cases/public/components/create/title.tsx +++ b/x-pack/plugins/cases/public/components/case_form_fields/title.tsx @@ -12,16 +12,17 @@ const CommonUseField = getUseField({ component: Field }); interface Props { isLoading: boolean; + autoFocus?: boolean; } -const TitleComponent: React.FC<Props> = ({ isLoading }) => ( +const TitleComponent: React.FC<Props> = ({ isLoading, autoFocus = false }) => ( <CommonUseField path="title" componentProps={{ idAria: 'caseTitle', 'data-test-subj': 'caseTitle', euiFieldProps: { - autoFocus: true, + autoFocus, fullWidth: true, disabled: isLoading, }, diff --git a/x-pack/plugins/cases/public/components/case_form_fields/translations.ts b/x-pack/plugins/cases/public/components/case_form_fields/translations.ts new file mode 100644 index 0000000000000..b8359958025b3 --- /dev/null +++ b/x-pack/plugins/cases/public/components/case_form_fields/translations.ts @@ -0,0 +1,14 @@ +/* + * 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 * from '../../common/translations'; + +export const ADDITIONAL_FIELDS = i18n.translate('xpack.cases.additionalFields', { + defaultMessage: 'Additional fields', +}); diff --git a/x-pack/plugins/cases/public/components/case_form_fields/utils.test.ts b/x-pack/plugins/cases/public/components/case_form_fields/utils.test.ts new file mode 100644 index 0000000000000..a8a948d88a158 --- /dev/null +++ b/x-pack/plugins/cases/public/components/case_form_fields/utils.test.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { validateEmptyTags, validateMaxLength, validateMaxTagsLength } from './utils'; +import * as i18n from './translations'; + +describe('utils', () => { + describe('validateEmptyTags', () => { + const message = i18n.TAGS_EMPTY_ERROR; + it('returns no error for non empty tags', () => { + expect(validateEmptyTags({ value: ['coke', 'pepsi'], message })).toBeUndefined(); + }); + + it('returns no error for non empty tag', () => { + expect(validateEmptyTags({ value: 'coke', message })).toBeUndefined(); + }); + + it('returns error for empty tags', () => { + expect(validateEmptyTags({ value: [' ', 'pepsi'], message })).toEqual({ message }); + }); + + it('returns error for empty tag', () => { + expect(validateEmptyTags({ value: ' ', message })).toEqual({ message }); + }); + }); + + describe('validateMaxLength', () => { + const limit = 5; + const message = i18n.MAX_LENGTH_ERROR('tag', limit); + + it('returns error for tags exceeding length', () => { + expect( + validateMaxLength({ + value: ['coke', 'pepsi!'], + message, + limit, + }) + ).toEqual({ message }); + }); + + it('returns error for tag exceeding length', () => { + expect( + validateMaxLength({ + value: 'Hello!', + message, + limit, + }) + ).toEqual({ message }); + }); + + it('returns no error for tags not exceeding length', () => { + expect( + validateMaxLength({ + value: ['coke', 'pepsi'], + message, + limit, + }) + ).toBeUndefined(); + }); + + it('returns no error for tag not exceeding length', () => { + expect( + validateMaxLength({ + value: 'Hello', + message, + limit, + }) + ).toBeUndefined(); + }); + }); + + describe('validateMaxTagsLength', () => { + const limit = 2; + const message = i18n.MAX_TAGS_ERROR(limit); + + it('returns error when tags exceed length', () => { + expect(validateMaxTagsLength({ value: ['coke', 'pepsi', 'fanta'], message, limit })).toEqual({ + message, + }); + }); + + it('returns no error when tags do not exceed length', () => { + expect(validateMaxTagsLength({ value: ['coke', 'pepsi'], message, limit })).toBeUndefined(); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/case_form_fields/utils.ts b/x-pack/plugins/cases/public/components/case_form_fields/utils.ts new file mode 100644 index 0000000000000..1fde95ff54089 --- /dev/null +++ b/x-pack/plugins/cases/public/components/case_form_fields/utils.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +const isInvalidTag = (value: string) => value.trim() === ''; + +const isTagCharactersInLimit = (value: string, limit: number) => value.trim().length > limit; + +export const validateEmptyTags = ({ + value, + message, +}: { + value: string | string[]; + message: string; +}) => { + if ( + (!Array.isArray(value) && isInvalidTag(value)) || + (Array.isArray(value) && value.length > 0 && value.find((item) => isInvalidTag(item))) + ) { + return { + message, + }; + } +}; + +export const validateMaxLength = ({ + value, + message, + limit, +}: { + value: string | string[]; + message: string; + limit: number; +}) => { + if ( + (!Array.isArray(value) && isTagCharactersInLimit(value, limit)) || + (Array.isArray(value) && + value.length > 0 && + value.some((item) => isTagCharactersInLimit(item, limit))) + ) { + return { + message, + }; + } +}; + +export const validateMaxTagsLength = ({ + value, + message, + limit, +}: { + value: string | string[]; + message: string; + limit: number; +}) => { + if (Array.isArray(value) && value.length > limit) { + return { + message, + }; + } +}; diff --git a/x-pack/plugins/cases/public/components/case_view/components/edit_tags.tsx b/x-pack/plugins/cases/public/components/case_view/components/edit_tags.tsx index 52a40ca065214..d8c98e42f2e38 100644 --- a/x-pack/plugins/cases/public/components/case_view/components/edit_tags.tsx +++ b/x-pack/plugins/cases/public/components/case_view/components/edit_tags.tsx @@ -18,17 +18,17 @@ import { useEuiTheme, } from '@elastic/eui'; import { css } from '@emotion/react'; -import type { FormSchema } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import type { FieldConfig } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { Form, useForm, UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { ComboBoxField } from '@kbn/es-ui-shared-plugin/static/forms/components'; import * as i18n from '../../tags/translations'; import { useGetTags } from '../../../containers/use_get_tags'; import { Tags } from '../../tags/tags'; import { useCasesContext } from '../../cases_context/use_cases_context'; -import { schemaTags } from '../../create/schema'; +import { schema as createCaseSchema } from '../../create/schema'; -export const schema: FormSchema = { - tags: schemaTags, +export const schema = { + tags: createCaseSchema.tags as FieldConfig<string[]>, }; export interface EditTagsProps { diff --git a/x-pack/plugins/cases/public/components/category/category_component.test.tsx b/x-pack/plugins/cases/public/components/category/category_component.test.tsx index 6eb95600cd58c..e5be97ec20585 100644 --- a/x-pack/plugins/cases/public/components/category/category_component.test.tsx +++ b/x-pack/plugins/cases/public/components/category/category_component.test.tsx @@ -54,9 +54,9 @@ describe('Category ', () => { render(<CategoryComponent {...defaultProps} />); userEvent.type(screen.getByRole('combobox'), 'new{enter}'); - - expect(onChange).toBeCalledWith('new'); - expect(screen.getByRole('combobox')).toHaveValue('new'); + await waitFor(() => { + expect(onChange).toBeCalledWith('new'); + }); }); it('renders current option list', async () => { @@ -74,7 +74,6 @@ describe('Category ', () => { userEvent.click(screen.getByText('foo')); expect(onChange).toHaveBeenCalledWith('foo'); - expect(screen.getByTestId('comboBoxInput')).toHaveTextContent('foo'); }); it('should call onChange when adding new category', async () => { @@ -84,7 +83,6 @@ describe('Category ', () => { await waitFor(() => { expect(onChange).toHaveBeenCalledWith('hi'); - expect(screen.getByTestId('comboBoxInput')).toHaveTextContent('hi'); }); }); @@ -100,7 +98,7 @@ describe('Category ', () => { userEvent.type(screen.getByRole('combobox'), ' there{enter}'); await waitFor(() => { - expect(onChange).toHaveBeenCalledWith('hi there'); + expect(onChange).toHaveBeenCalledWith('there'); }); }); }); diff --git a/x-pack/plugins/cases/public/components/category/category_component.tsx b/x-pack/plugins/cases/public/components/category/category_component.tsx index ee6f84a244062..f57ba7b36a5ad 100644 --- a/x-pack/plugins/cases/public/components/category/category_component.tsx +++ b/x-pack/plugins/cases/public/components/category/category_component.tsx @@ -5,10 +5,13 @@ * 2.0. */ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo } from 'react'; import type { EuiComboBoxOptionOption } from '@elastic/eui'; import { EuiComboBox } from '@elastic/eui'; import { ADD_CATEGORY_CUSTOM_OPTION_LABEL_COMBO_BOX } from './translations'; +import type { CaseUI } from '../../../common/ui'; + +export type CategoryField = CaseUI['category'] | undefined; export interface CategoryComponentProps { isLoading: boolean; @@ -26,15 +29,11 @@ export const CategoryComponent: React.FC<CategoryComponentProps> = React.memo( })); }, [availableCategories]); - const [selectedOptions, setSelectedOptions] = useState<Array<EuiComboBoxOptionOption<string>>>( - category != null ? [{ label: category }] : [] - ); + const selectedOptions = category != null ? [{ label: category }] : []; const onComboChange = useCallback( (currentOptions: Array<EuiComboBoxOptionOption<string>>) => { const value = currentOptions[0]?.label; - - setSelectedOptions(currentOptions); onChange(value); }, [onChange] diff --git a/x-pack/plugins/cases/public/components/category/category_form_field.tsx b/x-pack/plugins/cases/public/components/category/category_form_field.tsx index 060e0928b8986..f8bb2221ce7d8 100644 --- a/x-pack/plugins/cases/public/components/category/category_form_field.tsx +++ b/x-pack/plugins/cases/public/components/category/category_form_field.tsx @@ -15,7 +15,7 @@ import { import { isEmpty } from 'lodash'; import React, { memo } from 'react'; import { MAX_CATEGORY_LENGTH } from '../../../common/constants'; -import type { CaseUI } from '../../../common/ui'; +import type { CategoryField } from './category_component'; import { CategoryComponent } from './category_component'; import { CATEGORY, EMPTY_CATEGORY_VALIDATION_MSG, MAX_LENGTH_ERROR } from './translations'; @@ -25,8 +25,6 @@ interface Props { formRowProps?: Partial<EuiFormRowProps>; } -type CategoryField = CaseUI['category'] | undefined; - const getCategoryConfig = (): FieldConfig<CategoryField> => ({ defaultValue: null, validations: [ @@ -65,7 +63,7 @@ const CategoryFormFieldComponent: React.FC<Props> = ({ formRowProps, }) => { return ( - <UseField<CategoryField> path={'category'} config={getCategoryConfig()}> + <UseField<CategoryField> path="category" config={getCategoryConfig()}> {(field) => { const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); @@ -79,7 +77,7 @@ const CategoryFormFieldComponent: React.FC<Props> = ({ label={CATEGORY} error={errorMessage} isInvalid={isInvalid} - data-test-subj="case-create-form-category" + data-test-subj="caseCategory" fullWidth > <CategoryComponent diff --git a/x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx b/x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx index e0161e437e70d..bf1ace60ced91 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx @@ -19,7 +19,7 @@ export const searchURL = '?timerange=(global:(linkTo:!(),timerange:(from:1585487656371,fromStr:now-24h,kind:relative,to:1585574056371,toStr:now)),timeline:(linkTo:!(),timerange:(from:1585227005527,kind:absolute,to:1585313405527)))'; const mockConfigurationData = { - closureType: 'close-by-user', + closureType: 'close-by-user' as const, connector: { fields: null, id: 'none', @@ -27,6 +27,7 @@ const mockConfigurationData = { type: ConnectorTypes.none, }, customFields: [], + templates: [], mappings: [], version: '', id: '', diff --git a/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.tsx b/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.tsx index 73e6c60a90054..71df212399bc2 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React, { Suspense, useMemo } from 'react'; import type { EuiThemeComputed } from '@elastic/eui'; import { EuiFlexGroup, @@ -14,6 +14,7 @@ import { EuiIconTip, EuiSuperSelect, useEuiTheme, + EuiLoadingSpinner, } from '@elastic/eui'; import { css } from '@emotion/react'; @@ -31,6 +32,15 @@ export interface Props { appendAddConnectorButton?: boolean; } +const suspendedComponentWithProps = (ComponentToSuspend: React.ComponentType) => { + // eslint-disable-next-line react/display-name + return (props: Record<string, unknown>) => ( + <Suspense fallback={<EuiLoadingSpinner size={'m'} />}> + <ComponentToSuspend {...props} /> + </Suspense> + ); +}; + const ICON_SIZE = 'm'; const noConnectorOption = { @@ -90,6 +100,8 @@ const ConnectorsDropdownComponent: React.FC<Props> = ({ const connectorsAsOptions = useMemo(() => { const connectorsFormatted = connectors.reduce( (acc, connector) => { + const iconClass = getConnectorIcon(triggersActionsUi, connector.actionTypeId); + return [ ...acc, { @@ -102,7 +114,11 @@ const ConnectorsDropdownComponent: React.FC<Props> = ({ margin-right: ${euiTheme.size.m}; margin-bottom: 0 !important; `} - type={getConnectorIcon(triggersActionsUi, connector.actionTypeId)} + type={ + typeof iconClass === 'string' + ? iconClass + : suspendedComponentWithProps(iconClass) + } size={ICON_SIZE} /> </EuiFlexItem> diff --git a/x-pack/plugins/cases/public/components/configure_cases/delete_confirmation_modal.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/delete_confirmation_modal.test.tsx new file mode 100644 index 0000000000000..ce46d368a5d2e --- /dev/null +++ b/x-pack/plugins/cases/public/components/configure_cases/delete_confirmation_modal.test.tsx @@ -0,0 +1,53 @@ +/* + * 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 userEvent from '@testing-library/user-event'; +import React from 'react'; +import type { AppMockRenderer } from '../../common/mock'; +import { createAppMockRenderer } from '../../common/mock'; +import { DeleteConfirmationModal } from './delete_confirmation_modal'; + +describe('DeleteConfirmationModal', () => { + let appMock: AppMockRenderer; + const props = { + title: 'My custom field', + message: 'This is a sample message', + onCancel: jest.fn(), + onConfirm: jest.fn(), + }; + + beforeEach(() => { + appMock = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + it('renders correctly', async () => { + const result = appMock.render(<DeleteConfirmationModal {...props} />); + + expect(result.getByTestId('confirm-delete-modal')).toBeInTheDocument(); + expect(result.getByText('Delete')).toBeInTheDocument(); + expect(result.getByText('Cancel')).toBeInTheDocument(); + }); + + it('calls onConfirm', async () => { + const result = appMock.render(<DeleteConfirmationModal {...props} />); + + expect(result.getByText('Delete')).toBeInTheDocument(); + userEvent.click(result.getByText('Delete')); + + expect(props.onConfirm).toHaveBeenCalled(); + }); + + it('calls onCancel', async () => { + const result = appMock.render(<DeleteConfirmationModal {...props} />); + + expect(result.getByText('Cancel')).toBeInTheDocument(); + userEvent.click(result.getByText('Cancel')); + + expect(props.onCancel).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/configure_cases/delete_confirmation_modal.tsx b/x-pack/plugins/cases/public/components/configure_cases/delete_confirmation_modal.tsx new file mode 100644 index 0000000000000..a994c8720cc17 --- /dev/null +++ b/x-pack/plugins/cases/public/components/configure_cases/delete_confirmation_modal.tsx @@ -0,0 +1,42 @@ +/* + * 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 { EuiConfirmModal } from '@elastic/eui'; +import * as i18n from '../custom_fields/translations'; + +interface ConfirmDeleteCaseModalProps { + title: string; + message: string; + onCancel: () => void; + onConfirm: () => void; +} + +const DeleteConfirmationModalComponent: React.FC<ConfirmDeleteCaseModalProps> = ({ + title, + message, + onCancel, + onConfirm, +}) => { + return ( + <EuiConfirmModal + buttonColor="danger" + cancelButtonText={i18n.CANCEL} + data-test-subj="confirm-delete-modal" + defaultFocusedButton="confirm" + onCancel={onCancel} + onConfirm={onConfirm} + title={title} + confirmButtonText={i18n.DELETE} + > + {message} + </EuiConfirmModal> + ); +}; +DeleteConfirmationModalComponent.displayName = 'DeleteConfirmationModal'; + +export const DeleteConfirmationModal = React.memo(DeleteConfirmationModalComponent); diff --git a/x-pack/plugins/cases/public/components/configure_cases/flyout.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/flyout.test.tsx new file mode 100644 index 0000000000000..555f5e6f553b8 --- /dev/null +++ b/x-pack/plugins/cases/public/components/configure_cases/flyout.test.tsx @@ -0,0 +1,798 @@ +/* + * 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 { fireEvent, screen, waitFor, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import type { AppMockRenderer } from '../../common/mock'; +import { createAppMockRenderer, mockedTestProvidersOwner } from '../../common/mock'; +import { + connectorsMock, + customFieldsConfigurationMock, + templatesConfigurationMock, +} from '../../containers/mock'; +import { + MAX_CUSTOM_FIELD_LABEL_LENGTH, + MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH, + MAX_TEMPLATE_DESCRIPTION_LENGTH, + MAX_TEMPLATE_NAME_LENGTH, +} from '../../../common/constants'; +import { ConnectorTypes, CustomFieldTypes } from '../../../common/types/domain'; +import type { CustomFieldConfiguration } from '../../../common/types/domain'; +import { useGetChoices } from '../connectors/servicenow/use_get_choices'; +import { useGetChoicesResponse } from '../create/mock'; +import { FIELD_LABEL, DEFAULT_VALUE } from '../custom_fields/translations'; +import { CustomFieldsForm } from '../custom_fields/form'; +import { TemplateForm } from '../templates/form'; +import * as i18n from './translations'; +import type { FlyOutBodyProps } from './flyout'; +import { CommonFlyout } from './flyout'; +import type { TemplateFormProps } from '../templates/types'; +import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; + +jest.mock('../connectors/servicenow/use_get_choices'); +jest.mock('../../containers/user_profiles/api'); + +const useGetChoicesMock = useGetChoices as jest.Mock; + +describe('CommonFlyout ', () => { + let appMockRender: AppMockRenderer; + + const props = { + onCloseFlyout: jest.fn(), + onSaveField: jest.fn(), + isLoading: false, + disabled: false, + renderHeader: () => <div>{`Flyout header`}</div>, + }; + + const children = ({ onChange }: FlyOutBodyProps<CustomFieldConfiguration>) => ( + <CustomFieldsForm onChange={onChange} initialValue={null} /> + ); + + beforeEach(() => { + jest.clearAllMocks(); + appMockRender = createAppMockRenderer(); + }); + + it('renders flyout correctly', async () => { + appMockRender.render(<CommonFlyout {...props}>{children}</CommonFlyout>); + + expect(await screen.findByTestId('common-flyout')).toBeInTheDocument(); + expect(await screen.findByTestId('common-flyout-header')).toBeInTheDocument(); + expect(await screen.findByTestId('common-flyout-cancel')).toBeInTheDocument(); + expect(await screen.findByTestId('common-flyout-save')).toBeInTheDocument(); + }); + + it('renders flyout header correctly', async () => { + appMockRender.render(<CommonFlyout {...props}>{children}</CommonFlyout>); + + expect(await screen.findByText('Flyout header')); + }); + + it('renders loading state correctly', async () => { + appMockRender.render( + <CommonFlyout {...{ ...props, isLoading: true }}>{children}</CommonFlyout> + ); + + expect(await screen.findAllByRole('progressbar')).toHaveLength(2); + }); + + it('renders disable state correctly', async () => { + appMockRender.render(<CommonFlyout {...{ ...props, disabled: true }}>{children}</CommonFlyout>); + + expect(await screen.findByTestId('common-flyout-cancel')).toBeDisabled(); + expect(await screen.findByTestId('common-flyout-save')).toBeDisabled(); + }); + + it('calls onCloseFlyout on cancel', async () => { + appMockRender.render(<CommonFlyout {...props}>{children}</CommonFlyout>); + + userEvent.click(await screen.findByTestId('common-flyout-cancel')); + + await waitFor(() => { + expect(props.onCloseFlyout).toBeCalled(); + }); + }); + + it('calls onCloseFlyout on close', async () => { + appMockRender.render(<CommonFlyout {...props}>{children}</CommonFlyout>); + + userEvent.click(await screen.findByTestId('euiFlyoutCloseButton')); + + await waitFor(() => { + expect(props.onCloseFlyout).toBeCalled(); + }); + }); + + it('does not call onSaveField when not valid data', async () => { + appMockRender.render(<CommonFlyout {...props}>{children}</CommonFlyout>); + + userEvent.click(await screen.findByTestId('common-flyout-save')); + + expect(props.onSaveField).not.toBeCalled(); + }); + + describe('CustomFieldsFlyout', () => { + const renderBody = ({ onChange }: FlyOutBodyProps<CustomFieldConfiguration>) => ( + <CustomFieldsForm onChange={onChange} initialValue={null} /> + ); + + it('should render custom field form in flyout', async () => { + appMockRender.render(<CommonFlyout {...props}>{renderBody}</CommonFlyout>); + + expect(await screen.findByTestId('custom-field-label-input')).toBeInTheDocument(); + expect(await screen.findByTestId('custom-field-type-selector')).toBeInTheDocument(); + expect(await screen.findByTestId('text-custom-field-required-wrapper')).toBeInTheDocument(); + expect(await screen.findByTestId('text-custom-field-default-value')).toBeInTheDocument(); + }); + + it('calls onSaveField form correctly', async () => { + appMockRender.render(<CommonFlyout {...props}>{renderBody}</CommonFlyout>); + + userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); + userEvent.click(await screen.findByTestId('common-flyout-save')); + + await waitFor(() => { + expect(props.onSaveField).toBeCalledWith({ + key: expect.anything(), + label: 'Summary', + required: false, + type: CustomFieldTypes.TEXT, + }); + }); + }); + + it('shows error if field label is too long', async () => { + appMockRender.render(<CommonFlyout {...props}>{renderBody}</CommonFlyout>); + + const message = 'z'.repeat(MAX_CUSTOM_FIELD_LABEL_LENGTH + 1); + + userEvent.type(await screen.findByTestId('custom-field-label-input'), message); + + expect( + await screen.findByText( + i18n.MAX_LENGTH_ERROR(FIELD_LABEL.toLocaleLowerCase(), MAX_CUSTOM_FIELD_LABEL_LENGTH) + ) + ).toBeInTheDocument(); + }); + + describe('Text custom field', () => { + it('calls onSaveField with correct params when a custom field is NOT required', async () => { + appMockRender.render(<CommonFlyout {...props}>{renderBody}</CommonFlyout>); + + userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); + userEvent.click(await screen.findByTestId('common-flyout-save')); + + await waitFor(() => { + expect(props.onSaveField).toBeCalledWith({ + key: expect.anything(), + label: 'Summary', + required: false, + type: CustomFieldTypes.TEXT, + }); + }); + }); + + it('calls onSaveField with correct params when a custom field is NOT required and has a default value', async () => { + appMockRender.render(<CommonFlyout {...props}>{renderBody}</CommonFlyout>); + + userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); + userEvent.paste( + await screen.findByTestId('text-custom-field-default-value'), + 'Default value' + ); + userEvent.click(await screen.findByTestId('common-flyout-save')); + + await waitFor(() => { + expect(props.onSaveField).toBeCalledWith({ + key: expect.anything(), + label: 'Summary', + required: false, + type: CustomFieldTypes.TEXT, + defaultValue: 'Default value', + }); + }); + }); + + it('calls onSaveField with the correct params when a custom field is required', async () => { + appMockRender.render(<CommonFlyout {...props}>{renderBody}</CommonFlyout>); + + userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); + userEvent.click(await screen.findByTestId('text-custom-field-required')); + userEvent.paste( + await screen.findByTestId('text-custom-field-default-value'), + 'Default value' + ); + userEvent.click(await screen.findByTestId('common-flyout-save')); + + await waitFor(() => { + expect(props.onSaveField).toBeCalledWith({ + key: expect.anything(), + label: 'Summary', + required: true, + type: CustomFieldTypes.TEXT, + defaultValue: 'Default value', + }); + }); + }); + + it('calls onSaveField with the correct params when a custom field is required and the defaultValue is missing', async () => { + appMockRender.render(<CommonFlyout {...props}>{renderBody}</CommonFlyout>); + + userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); + userEvent.click(await screen.findByTestId('text-custom-field-required')); + userEvent.click(await screen.findByTestId('common-flyout-save')); + + await waitFor(() => { + expect(props.onSaveField).toBeCalledWith({ + key: expect.anything(), + label: 'Summary', + required: true, + type: CustomFieldTypes.TEXT, + }); + }); + }); + + it('renders flyout with the correct data when an initial customField value exists', async () => { + const newRenderBody = ({ onChange }: FlyOutBodyProps<CustomFieldConfiguration>) => ( + <CustomFieldsForm onChange={onChange} initialValue={customFieldsConfigurationMock[0]} /> + ); + + const modifiedProps = { + ...props, + data: customFieldsConfigurationMock[0], + }; + + appMockRender.render(<CommonFlyout {...modifiedProps}>{newRenderBody}</CommonFlyout>); + + expect(await screen.findByTestId('custom-field-label-input')).toHaveAttribute( + 'value', + customFieldsConfigurationMock[0].label + ); + expect(await screen.findByTestId('custom-field-type-selector')).toHaveAttribute('disabled'); + expect(await screen.findByTestId('text-custom-field-required')).toHaveAttribute('checked'); + expect(await screen.findByTestId('text-custom-field-default-value')).toHaveAttribute( + 'value', + customFieldsConfigurationMock[0].defaultValue + ); + }); + + it('shows an error if default value is too long', async () => { + appMockRender.render(<CommonFlyout {...props}>{renderBody}</CommonFlyout>); + + userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); + userEvent.click(await screen.findByTestId('text-custom-field-required')); + userEvent.paste( + await screen.findByTestId('text-custom-field-default-value'), + 'z'.repeat(MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH + 1) + ); + + expect( + await screen.findByText( + i18n.MAX_LENGTH_ERROR(DEFAULT_VALUE.toLowerCase(), MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH) + ) + ).toBeInTheDocument(); + }); + }); + + describe('Toggle custom field', () => { + it('calls onSaveField with correct params when a custom field is NOT required', async () => { + appMockRender.render(<CommonFlyout {...props}>{renderBody}</CommonFlyout>); + + fireEvent.change(await screen.findByTestId('custom-field-type-selector'), { + target: { value: CustomFieldTypes.TOGGLE }, + }); + + userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); + userEvent.click(await screen.findByTestId('common-flyout-save')); + + await waitFor(() => { + expect(props.onSaveField).toBeCalledWith({ + key: expect.anything(), + label: 'Summary', + required: false, + type: CustomFieldTypes.TOGGLE, + defaultValue: false, + }); + }); + }); + + it('calls onSaveField with the correct default value when a custom field is required', async () => { + appMockRender.render(<CommonFlyout {...props}>{renderBody}</CommonFlyout>); + + fireEvent.change(await screen.findByTestId('custom-field-type-selector'), { + target: { value: CustomFieldTypes.TOGGLE }, + }); + + userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); + userEvent.click(await screen.findByTestId('toggle-custom-field-required')); + userEvent.click(await screen.findByTestId('common-flyout-save')); + + await waitFor(() => { + expect(props.onSaveField).toBeCalledWith({ + key: expect.anything(), + label: 'Summary', + required: true, + type: CustomFieldTypes.TOGGLE, + defaultValue: false, + }); + }); + }); + + it('renders flyout with the correct data when an initial customField value exists', async () => { + const newRenderBody = ({ onChange }: FlyOutBodyProps<CustomFieldConfiguration>) => ( + <CustomFieldsForm onChange={onChange} initialValue={customFieldsConfigurationMock[1]} /> + ); + + appMockRender.render(<CommonFlyout {...props}>{newRenderBody}</CommonFlyout>); + + expect(await screen.findByTestId('custom-field-label-input')).toHaveAttribute( + 'value', + customFieldsConfigurationMock[1].label + ); + expect(await screen.findByTestId('custom-field-type-selector')).toHaveAttribute('disabled'); + expect(await screen.findByTestId('toggle-custom-field-required')).toHaveAttribute( + 'checked' + ); + expect(await screen.findByTestId('toggle-custom-field-default-value')).toHaveAttribute( + 'aria-checked', + 'true' + ); + }); + }); + }); + + describe('TemplateFlyout', () => { + const currentConfiguration = { + closureType: 'close-by-user' as const, + connector: { + fields: null, + id: 'none', + name: 'none', + type: ConnectorTypes.none, + }, + customFields: [], + templates: [], + mappings: [], + version: '', + id: '', + owner: mockedTestProvidersOwner[0], + }; + + const renderBody = ({ onChange }: FlyOutBodyProps<TemplateFormProps>) => ( + <TemplateForm + initialValue={null} + connectors={connectorsMock} + currentConfiguration={currentConfiguration} + onChange={onChange} + /> + ); + + it('should render template form in flyout', async () => { + appMockRender.render(<CommonFlyout {...props}>{renderBody}</CommonFlyout>); + + expect(await screen.findByTestId('common-flyout')).toBeInTheDocument(); + expect(await screen.findByTestId('template-creation-form-steps')).toBeInTheDocument(); + }); + + it('should render all fields with details', async () => { + const license = licensingMock.createLicense({ + license: { type: 'platinum' }, + }); + + const newConfiguration = { + ...currentConfiguration, + customFields: [ + { + key: 'first_custom_field_key', + type: CustomFieldTypes.TEXT, + label: 'First custom field', + required: true, + }, + ], + }; + + appMockRender = createAppMockRenderer({ license }); + + appMockRender.render( + <CommonFlyout {...props}> + {({ onChange }: FlyOutBodyProps<TemplateFormProps>) => ( + <TemplateForm + initialValue={templatesConfigurationMock[3]} + connectors={[]} + currentConfiguration={newConfiguration} + onChange={onChange} + /> + )} + </CommonFlyout> + ); + + // template fields + expect(await screen.findByTestId('template-name-input')).toHaveValue('Fourth test template'); + expect(await screen.findByTestId('template-description-input')).toHaveTextContent( + 'This is a fourth test template' + ); + + const templateTags = await screen.findByTestId('template-tags'); + expect(await within(templateTags).findByTestId('comboBoxInput')).toHaveTextContent('foo'); + expect(await within(templateTags).findByTestId('comboBoxInput')).toHaveTextContent('bar'); + + const caseTitle = await screen.findByTestId('caseTitle'); + expect(within(caseTitle).getByTestId('input')).toHaveValue('Case with sample template 4'); + + const caseDescription = await screen.findByTestId('caseDescription'); + expect(within(caseDescription).getByTestId('euiMarkdownEditorTextArea')).toHaveTextContent( + 'case desc' + ); + + const caseCategory = await screen.findByTestId('caseCategory'); + expect(within(caseCategory).getByRole('combobox')).toHaveTextContent(''); + + const caseTags = await screen.findByTestId('caseTags'); + expect(await within(caseTags).findByTestId('comboBoxInput')).toHaveTextContent('sample-4'); + + expect(await screen.findByTestId('case-severity-selection-low')).toBeInTheDocument(); + + const assigneesComboBox = await screen.findByTestId('createCaseAssigneesComboBox'); + + expect(await within(assigneesComboBox).findByTestId('comboBoxInput')).toHaveTextContent( + 'Damaged Raccoon' + ); + + // custom fields + expect( + await screen.findByTestId('first_custom_field_key-text-create-custom-field') + ).toHaveValue('this is a text field value'); + + // connector + expect(await screen.findByTestId('dropdown-connector-no-connector')).toBeInTheDocument(); + }); + + it('calls onSaveField form correctly', async () => { + appMockRender.render(<CommonFlyout {...props}>{renderBody}</CommonFlyout>); + + userEvent.paste(await screen.findByTestId('template-name-input'), 'Template name'); + userEvent.paste( + await screen.findByTestId('template-description-input'), + 'Template description' + ); + const templateTags = await screen.findByTestId('template-tags'); + userEvent.paste(within(templateTags).getByRole('combobox'), 'foo'); + userEvent.keyboard('{enter}'); + + userEvent.click(await screen.findByTestId('common-flyout-save')); + + await waitFor(() => { + expect(props.onSaveField).toBeCalledWith({ + key: expect.anything(), + caseFields: { + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + customFields: [], + settings: { + syncAlerts: true, + }, + }, + description: 'Template description', + name: 'Template name', + tags: ['foo'], + }); + }); + }); + + it('calls onSaveField with case fields correctly', async () => { + const newRenderBody = ({ onChange }: FlyOutBodyProps<TemplateFormProps>) => ( + <TemplateForm + initialValue={{ + key: 'random_key', + name: 'Template 1', + description: 'test description', + caseFields: null, + }} + connectors={[]} + currentConfiguration={currentConfiguration} + onChange={onChange} + /> + ); + + appMockRender.render(<CommonFlyout {...props}>{newRenderBody}</CommonFlyout>); + + const caseTitle = await screen.findByTestId('caseTitle'); + userEvent.paste(within(caseTitle).getByTestId('input'), 'Case using template'); + + const caseDescription = await screen.findByTestId('caseDescription'); + userEvent.paste( + within(caseDescription).getByTestId('euiMarkdownEditorTextArea'), + 'This is a case description' + ); + + const caseCategory = await screen.findByTestId('caseCategory'); + userEvent.type(within(caseCategory).getByRole('combobox'), 'new {enter}'); + + userEvent.click(await screen.findByTestId('common-flyout-save')); + + await waitFor(() => { + expect(props.onSaveField).toBeCalledWith({ + key: 'random_key', + name: 'Template 1', + description: 'test description', + tags: [], + caseFields: { + title: 'Case using template', + description: 'This is a case description', + category: 'new', + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + customFields: [], + settings: { + syncAlerts: true, + }, + }, + }); + }); + }); + + it('calls onSaveField form with custom fields correctly', async () => { + const newConfig = { ...currentConfiguration, customFields: customFieldsConfigurationMock }; + const newRenderBody = ({ onChange }: FlyOutBodyProps<TemplateFormProps>) => ( + <TemplateForm + initialValue={{ + key: 'random_key', + name: 'Template 1', + description: 'test description', + caseFields: null, + }} + connectors={[]} + currentConfiguration={newConfig} + onChange={onChange} + /> + ); + + appMockRender.render(<CommonFlyout {...props}>{newRenderBody}</CommonFlyout>); + + const textCustomField = await screen.findByTestId( + `${customFieldsConfigurationMock[0].key}-text-create-custom-field` + ); + + userEvent.clear(textCustomField); + userEvent.paste(textCustomField, 'this is a sample text!'); + + userEvent.click(await screen.findByTestId('common-flyout-save')); + + await waitFor(() => { + expect(props.onSaveField).toBeCalledWith({ + key: 'random_key', + name: 'Template 1', + description: 'test description', + tags: [], + caseFields: { + connector: { + id: 'none', + name: 'none', + type: '.none', + fields: null, + }, + settings: { + syncAlerts: true, + }, + customFields: [ + { + key: 'test_key_1', + type: 'text', + value: 'this is a sample text!', + }, + { + key: 'test_key_2', + type: 'toggle', + value: true, + }, + { + key: 'test_key_4', + type: 'toggle', + value: false, + }, + ], + }, + }); + }); + }); + + it('calls onSaveField form with connector fields correctly', async () => { + useGetChoicesMock.mockReturnValue(useGetChoicesResponse); + + const connector = { + id: 'servicenow-1', + name: 'My SN connector', + type: ConnectorTypes.serviceNowITSM, + fields: null, + }; + + const newConfig = { + ...currentConfiguration, + connector, + }; + + const newRenderBody = ({ onChange }: FlyOutBodyProps<TemplateFormProps>) => ( + <TemplateForm + initialValue={{ + key: 'random_key', + name: 'Template 1', + description: 'test description', + caseFields: { connector }, + }} + connectors={connectorsMock} + currentConfiguration={newConfig} + onChange={onChange} + /> + ); + + appMockRender.render(<CommonFlyout {...props}>{newRenderBody}</CommonFlyout>); + + expect(await screen.findByTestId('connector-fields-sn-itsm')).toBeInTheDocument(); + + userEvent.selectOptions(await screen.findByTestId('urgencySelect'), '1'); + userEvent.selectOptions(await screen.findByTestId('categorySelect'), ['software']); + userEvent.click(await screen.findByTestId('common-flyout-save')); + + await waitFor(() => { + expect(props.onSaveField).toBeCalledWith({ + key: 'random_key', + name: 'Template 1', + description: 'test description', + tags: [], + caseFields: { + customFields: [], + connector: { + ...connector, + fields: { + urgency: '1', + severity: null, + impact: null, + category: 'software', + subcategory: null, + }, + }, + settings: { + syncAlerts: true, + }, + }, + }); + }); + }); + + it('calls onSaveField with edited fields correctly', async () => { + const newConfig = { + ...currentConfiguration, + customFields: [ + { + key: 'first_custom_field_key', + type: CustomFieldTypes.TEXT, + label: 'First custom field', + required: true, + }, + ], + connector: { + id: 'servicenow-1', + name: 'My SN connector', + type: ConnectorTypes.serviceNowITSM, + fields: null, + }, + }; + + const newRenderBody = ({ onChange }: FlyOutBodyProps<TemplateFormProps>) => ( + <TemplateForm + initialValue={templatesConfigurationMock[3]} + connectors={connectorsMock} + currentConfiguration={newConfig} + onChange={onChange} + isEditMode={true} + /> + ); + + appMockRender.render(<CommonFlyout {...props}>{newRenderBody}</CommonFlyout>); + + userEvent.clear(await screen.findByTestId('template-name-input')); + userEvent.paste(await screen.findByTestId('template-name-input'), 'Template name'); + + const caseTitle = await screen.findByTestId('caseTitle'); + userEvent.clear(within(caseTitle).getByTestId('input')); + userEvent.paste(within(caseTitle).getByTestId('input'), 'Updated case using template'); + + const customField = await screen.findByTestId( + 'first_custom_field_key-text-create-custom-field' + ); + userEvent.clear(customField); + userEvent.paste(customField, 'Updated custom field value'); + + userEvent.click(await screen.findByTestId('common-flyout-save')); + + await waitFor(() => { + expect(props.onSaveField).toBeCalledWith({ + caseFields: { + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + customFields: [ + { + key: 'first_custom_field_key', + type: 'text', + value: 'Updated custom field value', + }, + ], + description: 'case desc', + settings: { + syncAlerts: true, + }, + severity: 'low', + tags: ['sample-4'], + title: 'Updated case using template', + }, + description: 'This is a fourth test template', + key: 'test_template_4', + name: 'Template name', + tags: ['foo', 'bar'], + }); + }); + }); + + it('shows error when template name is empty', async () => { + appMockRender.render(<CommonFlyout {...props}>{renderBody}</CommonFlyout>); + + userEvent.paste( + await screen.findByTestId('template-description-input'), + 'Template description' + ); + + userEvent.click(await screen.findByTestId('common-flyout-save')); + + await waitFor(() => { + expect(props.onSaveField).not.toHaveBeenCalled(); + }); + + expect(await screen.findByText('A Template name is required.')).toBeInTheDocument(); + }); + + it('shows error if template name is too long', async () => { + appMockRender.render(<CommonFlyout {...props}>{renderBody}</CommonFlyout>); + + const message = 'z'.repeat(MAX_TEMPLATE_NAME_LENGTH + 1); + + userEvent.paste(await screen.findByTestId('template-name-input'), message); + + expect( + await screen.findByText(i18n.MAX_LENGTH_ERROR('template name', MAX_TEMPLATE_NAME_LENGTH)) + ).toBeInTheDocument(); + }); + + it('shows error if template description is too long', async () => { + appMockRender.render(<CommonFlyout {...props}>{renderBody}</CommonFlyout>); + + const message = 'z'.repeat(MAX_TEMPLATE_DESCRIPTION_LENGTH + 1); + + userEvent.paste(await screen.findByTestId('template-description-input'), message); + + expect( + await screen.findByText( + i18n.MAX_LENGTH_ERROR('template description', MAX_TEMPLATE_DESCRIPTION_LENGTH) + ) + ).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/configure_cases/flyout.tsx b/x-pack/plugins/cases/public/components/configure_cases/flyout.tsx new file mode 100644 index 0000000000000..37d16d01e5681 --- /dev/null +++ b/x-pack/plugins/cases/public/components/configure_cases/flyout.tsx @@ -0,0 +1,126 @@ +/* + * 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, useMemo, useState } from 'react'; +import { + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiButton, +} from '@elastic/eui'; +import type { FormHook, FormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib/types'; + +import * as i18n from './translations'; + +export interface FormState<T extends FormData = FormData, I extends FormData = T> { + isValid: boolean | undefined; + submit: FormHook<T, I>['submit']; +} + +export interface FlyOutBodyProps<T extends FormData = FormData, I extends FormData = T> { + onChange: (state: FormState<T, I>) => void; +} + +export interface FlyoutProps<T extends FormData = FormData, I extends FormData = T> { + disabled: boolean; + isLoading: boolean; + onCloseFlyout: () => void; + onSaveField: (data: I) => void; + renderHeader: () => React.ReactNode; + children: ({ onChange }: FlyOutBodyProps<T, I>) => React.ReactNode; +} + +export const CommonFlyout = <T extends FormData = FormData, I extends FormData = T>({ + onCloseFlyout, + onSaveField, + isLoading, + disabled, + renderHeader, + children, +}: FlyoutProps<T, I>) => { + const [formState, setFormState] = useState<FormState<T, I>>({ + isValid: undefined, + submit: async () => ({ + isValid: false, + data: {} as T, + }), + }); + + const { submit } = formState; + + const handleSaveField = useCallback(async () => { + const { isValid, data } = await submit(); + + if (isValid) { + /** + * The serializer transforms the data + * from the form format to the backend + * format. The I generic is the correct + * format of the data. + */ + onSaveField(data as unknown as I); + } + }, [onSaveField, submit]); + + /** + * The children will call setFormState which in turn will make the parent + * to rerender which in turn will rerender the children etc. + * To avoid an infinitive loop we need to memoize the children. + */ + const memoizedChildren = useMemo( + () => + children({ + onChange: setFormState, + }), + [children] + ); + + return ( + <EuiFlyout onClose={onCloseFlyout} data-test-subj="common-flyout"> + <EuiFlyoutHeader hasBorder data-test-subj="common-flyout-header"> + <EuiTitle size="s"> + <h3 id="flyoutTitle">{renderHeader()}</h3> + </EuiTitle> + </EuiFlyoutHeader> + <EuiFlyoutBody>{memoizedChildren}</EuiFlyoutBody> + <EuiFlyoutFooter data-test-subj={'common-flyout-footer'}> + <EuiFlexGroup justifyContent="flexStart"> + <EuiFlexItem grow={false}> + <EuiButtonEmpty + onClick={onCloseFlyout} + data-test-subj={'common-flyout-cancel'} + disabled={disabled} + isLoading={isLoading} + > + {i18n.CANCEL} + </EuiButtonEmpty> + </EuiFlexItem> + <EuiFlexGroup justifyContent="flexEnd"> + <EuiFlexItem grow={false}> + <EuiButton + fill + onClick={handleSaveField} + data-test-subj={'common-flyout-save'} + disabled={disabled} + isLoading={isLoading} + > + {i18n.SAVE} + </EuiButton> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexGroup> + </EuiFlyoutFooter> + </EuiFlyout> + ); +}; + +CommonFlyout.displayName = 'CommonFlyout'; diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx index ba3e7850533c9..b424b2ca62fc0 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx @@ -13,7 +13,7 @@ import userEvent from '@testing-library/user-event'; import { ConfigureCases } from '.'; import { noUpdateCasesPermissions, TestProviders, createAppMockRenderer } from '../../common/mock'; -import { customFieldsConfigurationMock } from '../../containers/mock'; +import { customFieldsConfigurationMock, templatesConfigurationMock } from '../../containers/mock'; import type { AppMockRenderer } from '../../common/mock'; import { Connectors } from './connectors'; import { ClosureOptions } from './closure_options'; @@ -36,6 +36,7 @@ import { actionTypeRegistryMock } from '@kbn/triggers-actions-ui-plugin/public/a import { useGetActionTypes } from '../../containers/configure/use_action_types'; import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors'; import { useLicense } from '../../common/use_license'; +import * as i18n from './translations'; jest.mock('../../common/lib/kibana'); jest.mock('../../containers/configure/use_get_supported_action_connectors'); @@ -78,7 +79,11 @@ describe('ConfigureCases', () => { beforeEach(() => { useGetCaseConfigurationMock.mockImplementation(() => useCaseConfigureResponse); usePersistConfigurationMock.mockImplementation(() => usePersistConfigurationMockResponse); - useGetConnectorsMock.mockImplementation(() => ({ ...useConnectorsResponse, data: [] })); + useGetConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + data: [], + isLoading: false, + })); useGetUrlSearchMock.mockImplementation(() => searchURL); wrapper = mount(<ConfigureCases />, { @@ -126,7 +131,11 @@ describe('ConfigureCases', () => { }, })); - useGetConnectorsMock.mockImplementation(() => ({ ...useConnectorsResponse, data: [] })); + useGetConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + data: [], + isLoading: false, + })); useGetUrlSearchMock.mockImplementation(() => searchURL); wrapper = mount(<ConfigureCases />, { wrappingComponent: TestProviders, @@ -425,6 +434,7 @@ describe('ConfigureCases', () => { }, closureType: 'close-by-user', customFields: [], + templates: [], id: '', version: '', }); @@ -521,6 +531,7 @@ describe('ConfigureCases', () => { }, closureType: 'close-by-pushing', customFields: [], + templates: [], id: '', version: '', }); @@ -688,7 +699,7 @@ describe('ConfigureCases', () => { within(list).getByTestId(`${customFieldsConfigurationMock[0].key}-custom-field-delete`) ); - expect(await screen.findByTestId('confirm-delete-custom-field-modal')).toBeInTheDocument(); + expect(await screen.findByTestId('confirm-delete-modal')).toBeInTheDocument(); userEvent.click(screen.getByText('Delete')); @@ -706,6 +717,7 @@ describe('ConfigureCases', () => { { ...customFieldsConfigurationMock[2] }, { ...customFieldsConfigurationMock[3] }, ], + templates: [], id: '', version: '', }); @@ -729,11 +741,11 @@ describe('ConfigureCases', () => { within(list).getByTestId(`${customFieldsConfigurationMock[0].key}-custom-field-edit`) ); - expect(await screen.findByTestId('custom-field-flyout')).toBeInTheDocument(); + expect(await screen.findByTestId('common-flyout')).toBeInTheDocument(); userEvent.paste(screen.getByTestId('custom-field-label-input'), '!!'); userEvent.click(screen.getByTestId('text-custom-field-required')); - userEvent.click(screen.getByTestId('custom-field-flyout-save')); + userEvent.click(screen.getByTestId('common-flyout-save')); await waitFor(() => { expect(persistCaseConfigure).toHaveBeenCalledWith({ @@ -756,6 +768,7 @@ describe('ConfigureCases', () => { { ...customFieldsConfigurationMock[2] }, { ...customFieldsConfigurationMock[3] }, ], + templates: [], id: '', version: '', }); @@ -767,7 +780,7 @@ describe('ConfigureCases', () => { userEvent.click(screen.getByTestId('add-custom-field')); - expect(await screen.findByTestId('custom-field-flyout')).toBeInTheDocument(); + expect(await screen.findByTestId('common-flyout')).toBeInTheDocument(); }); it('closes fly out for when click on cancel', async () => { @@ -775,12 +788,12 @@ describe('ConfigureCases', () => { userEvent.click(screen.getByTestId('add-custom-field')); - expect(await screen.findByTestId('custom-field-flyout')).toBeInTheDocument(); + expect(await screen.findByTestId('common-flyout')).toBeInTheDocument(); - userEvent.click(screen.getByTestId('custom-field-flyout-cancel')); + userEvent.click(screen.getByTestId('common-flyout-cancel')); expect(await screen.findByTestId('custom-fields-form-group')).toBeInTheDocument(); - expect(screen.queryByTestId('custom-field-flyout')).not.toBeInTheDocument(); + expect(screen.queryByTestId('common-flyout')).not.toBeInTheDocument(); }); it('closes fly out for when click on save field', async () => { @@ -788,11 +801,11 @@ describe('ConfigureCases', () => { userEvent.click(screen.getByTestId('add-custom-field')); - expect(await screen.findByTestId('custom-field-flyout')).toBeInTheDocument(); + expect(await screen.findByTestId('common-flyout')).toBeInTheDocument(); userEvent.paste(screen.getByTestId('custom-field-label-input'), 'Summary'); - userEvent.click(screen.getByTestId('custom-field-flyout-save')); + userEvent.click(screen.getByTestId('common-flyout-save')); await waitFor(() => { expect(persistCaseConfigure).toHaveBeenCalledWith({ @@ -812,20 +825,237 @@ describe('ConfigureCases', () => { required: false, }, ], + templates: [], id: '', version: '', }); }); expect(screen.getByTestId('custom-fields-form-group')).toBeInTheDocument(); - expect(screen.queryByTestId('custom-field-flyout')).not.toBeInTheDocument(); + expect(screen.queryByTestId('common-flyout')).not.toBeInTheDocument(); + }); + }); + + describe('templates', () => { + let appMockRender: AppMockRenderer; + const persistCaseConfigure = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + appMockRender = createAppMockRenderer(); + usePersistConfigurationMock.mockImplementation(() => ({ + ...usePersistConfigurationMockResponse, + mutate: persistCaseConfigure, + })); + useLicenseMock.mockReturnValue({ isAtLeastPlatinum: () => false, isAtLeastGold: () => true }); + }); + + it('should render template section', async () => { + appMockRender.render(<ConfigureCases />); + + expect(await screen.findByTestId('templates-form-group')).toBeInTheDocument(); + expect(await screen.findByTestId('add-template')).toBeInTheDocument(); + }); + + it('should render template form in flyout', async () => { + appMockRender.render(<ConfigureCases />); + + expect(await screen.findByTestId('templates-form-group')).toBeInTheDocument(); + + userEvent.click(await screen.findByTestId('add-template')); + + expect(await screen.findByTestId('common-flyout')).toBeInTheDocument(); + expect(await screen.findByTestId('common-flyout-header')).toHaveTextContent( + i18n.CREATE_TEMPLATE + ); + expect(await screen.findByTestId('template-creation-form-steps')).toBeInTheDocument(); + }); + + it('should add template', async () => { + appMockRender.render(<ConfigureCases />); + + expect(await screen.findByTestId('templates-form-group')).toBeInTheDocument(); + + userEvent.click(await screen.findByTestId('add-template')); + + expect(await screen.findByTestId('common-flyout')).toBeInTheDocument(); + + userEvent.paste(await screen.findByTestId('template-name-input'), 'Template name'); + userEvent.paste( + await screen.findByTestId('template-description-input'), + 'Template description' + ); + + const caseTitle = await screen.findByTestId('caseTitle'); + userEvent.paste(within(caseTitle).getByTestId('input'), 'Case using template'); + + userEvent.click(screen.getByTestId('common-flyout-save')); + + await waitFor(() => { + expect(persistCaseConfigure).toHaveBeenCalledWith({ + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + closureType: 'close-by-user', + customFields: customFieldsConfigurationMock, + templates: [ + { + key: expect.anything(), + name: 'Template name', + description: 'Template description', + tags: [], + caseFields: { + title: 'Case using template', + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + settings: { + syncAlerts: true, + }, + customFields: [ + { + key: customFieldsConfigurationMock[0].key, + type: customFieldsConfigurationMock[0].type, + value: customFieldsConfigurationMock[0].defaultValue, + }, + { + key: customFieldsConfigurationMock[1].key, + type: customFieldsConfigurationMock[1].type, + value: customFieldsConfigurationMock[1].defaultValue, + }, + { + key: customFieldsConfigurationMock[3].key, + type: customFieldsConfigurationMock[3].type, + value: false, // when no default value for toggle, we set it to false + }, + ], + }, + }, + ], + id: '', + version: '', + }); + }); + + expect(screen.getByTestId('templates-form-group')).toBeInTheDocument(); + expect(screen.queryByTestId('common-flyout')).not.toBeInTheDocument(); + }); + + it('should delete a template', async () => { + useGetConnectorsMock.mockImplementation(() => useConnectorsResponse); + + useGetCaseConfigurationMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + data: { + ...useCaseConfigureResponse.data, + templates: templatesConfigurationMock, + }, + })); + + appMockRender.render(<ConfigureCases />); + + const list = screen.getByTestId('templates-list'); + + userEvent.click( + within(list).getByTestId(`${templatesConfigurationMock[0].key}-template-delete`) + ); + + expect(await screen.findByTestId('confirm-delete-modal')).toBeInTheDocument(); + + userEvent.click(screen.getByText('Delete')); + + await waitFor(() => { + expect(persistCaseConfigure).toHaveBeenCalledWith({ + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + closureType: 'close-by-user', + customFields: [], + templates: [ + { ...templatesConfigurationMock[1] }, + { ...templatesConfigurationMock[2] }, + { ...templatesConfigurationMock[3] }, + { ...templatesConfigurationMock[4] }, + ], + id: '', + version: '', + }); + }); + }); + + it('should update a template', async () => { + useGetCaseConfigurationMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + data: { + ...useCaseConfigureResponse.data, + templates: [templatesConfigurationMock[0], templatesConfigurationMock[3]], + }, + })); + + appMockRender.render(<ConfigureCases />); + + const list = screen.getByTestId('templates-list'); + + userEvent.click( + within(list).getByTestId(`${templatesConfigurationMock[0].key}-template-edit`) + ); + + expect(await screen.findByTestId('common-flyout')).toBeInTheDocument(); + + userEvent.clear(await screen.findByTestId('template-name-input')); + userEvent.paste(await screen.findByTestId('template-name-input'), 'Updated template name'); + + userEvent.click(screen.getByTestId('common-flyout-save')); + + await waitFor(() => { + expect(persistCaseConfigure).toHaveBeenCalledWith({ + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + closureType: 'close-by-user', + customFields: [], + templates: [ + { + ...templatesConfigurationMock[0], + name: 'Updated template name', + tags: [], + caseFields: { + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + customFields: [], + settings: { + syncAlerts: true, + }, + }, + }, + { ...templatesConfigurationMock[3] }, + ], + id: '', + version: '', + }); + }); }); }); describe('rendering with license limitations', () => { let appMockRender: AppMockRenderer; let persistCaseConfigure: jest.Mock; - beforeEach(() => { // Default setup jest.clearAllMocks(); diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.tsx index d33726d7ccdfe..1003a10646e8c 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/index.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/index.tsx @@ -5,6 +5,8 @@ * 2.0. */ +/* eslint-disable complexity */ + import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { css } from '@emotion/react'; @@ -22,7 +24,7 @@ import { import type { ActionConnectorTableItem } from '@kbn/triggers-actions-ui-plugin/public/types'; import { CasesConnectorFeatureId } from '@kbn/actions-plugin/common'; -import type { CustomFieldConfiguration } from '../../../common/types/domain'; +import type { CustomFieldConfiguration, TemplateConfiguration } from '../../../common/types/domain'; import { useKibana } from '../../common/lib/kibana'; import { useGetActionTypes } from '../../containers/configure/use_action_types'; import { useGetCaseConfiguration } from '../../containers/configure/use_get_case_configuration'; @@ -32,17 +34,20 @@ import { Connectors } from './connectors'; import { ClosureOptions } from './closure_options'; import { getNoneConnector, normalizeActionConnector, normalizeCaseConnector } from './utils'; import * as i18n from './translations'; -import { getConnectorById } from '../utils'; +import { getConnectorById, addOrReplaceField } from '../utils'; import { HeaderPage } from '../header_page'; import { useCasesContext } from '../cases_context/use_cases_context'; import { useCasesBreadcrumbs } from '../use_breadcrumbs'; import { CasesDeepLinkId } from '../../common/navigation'; import { CustomFields } from '../custom_fields'; -import { CustomFieldFlyout } from '../custom_fields/flyout'; +import { CommonFlyout } from './flyout'; import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors'; import { usePersistConfiguration } from '../../containers/configure/use_persist_configuration'; -import { addOrReplaceCustomField } from '../custom_fields/utils'; import { useLicense } from '../../common/use_license'; +import { Templates } from '../templates'; +import type { TemplateFormProps } from '../templates/types'; +import { CustomFieldsForm } from '../custom_fields/form'; +import { TemplateForm } from '../templates/form'; const sectionWrapperCss = css` box-sizing: content-box; @@ -58,6 +63,11 @@ const getFormWrapperCss = (euiTheme: EuiThemeComputed<{}>) => css` } `; +interface Flyout { + type: 'addConnector' | 'editConnector' | 'customField' | 'template'; + visible: boolean; +} + export const ConfigureCases: React.FC = React.memo(() => { const { permissions } = useCasesContext(); const { triggersActionsUi } = useKibana().services; @@ -66,28 +76,30 @@ export const ConfigureCases: React.FC = React.memo(() => { const hasMinimumLicensePermissions = license.isAtLeastGold(); const [connectorIsValid, setConnectorIsValid] = useState(true); - const [addFlyoutVisible, setAddFlyoutVisibility] = useState<boolean>(false); - const [editFlyoutVisible, setEditFlyoutVisibility] = useState<boolean>(false); + const [flyOutVisibility, setFlyOutVisibility] = useState<Flyout | null>(null); const [editedConnectorItem, setEditedConnectorItem] = useState<ActionConnectorTableItem | null>( null ); - const [customFieldFlyoutVisible, setCustomFieldFlyoutVisibility] = useState<boolean>(false); const [customFieldToEdit, setCustomFieldToEdit] = useState<CustomFieldConfiguration | null>(null); + const [templateToEdit, setTemplateToEdit] = useState<TemplateConfiguration | null>(null); const { euiTheme } = useEuiTheme(); const { - data: { - id: configurationId, - version: configurationVersion, - closureType, - connector, - mappings, - customFields, - }, + data: currentConfiguration, isLoading: loadingCaseConfigure, refetch: refetchCaseConfigure, } = useGetCaseConfiguration(); + const { + id: configurationId, + version: configurationVersion, + closureType, + connector, + mappings, + customFields, + templates, + } = currentConfiguration; + const { mutate: persistCaseConfigure, mutateAsync: persistCaseConfigureAsync, @@ -95,7 +107,6 @@ export const ConfigureCases: React.FC = React.memo(() => { } = usePersistConfiguration(); const isLoadingCaseConfiguration = loadingCaseConfigure || isPersistingConfiguration; - const { isLoading: isLoadingConnectors, data: connectors = [], @@ -125,6 +136,7 @@ export const ConfigureCases: React.FC = React.memo(() => { connector: caseConnector, closureType, customFields, + templates, id: configurationId, version: configurationVersion, }); @@ -135,6 +147,7 @@ export const ConfigureCases: React.FC = React.memo(() => { persistCaseConfigureAsync, closureType, customFields, + templates, configurationId, configurationVersion, onConnectorUpdated, @@ -148,20 +161,23 @@ export const ConfigureCases: React.FC = React.memo(() => { isLoadingActionTypes; const updateConnectorDisabled = isLoadingAny || !connectorIsValid || connector.id === 'none'; const onClickUpdateConnector = useCallback(() => { - setEditFlyoutVisibility(true); + setFlyOutVisibility({ type: 'editConnector', visible: true }); }, []); const onCloseAddFlyout = useCallback( - () => setAddFlyoutVisibility(false), - [setAddFlyoutVisibility] + () => setFlyOutVisibility({ type: 'addConnector', visible: false }), + [setFlyOutVisibility] ); - const onCloseEditFlyout = useCallback(() => setEditFlyoutVisibility(false), []); + const onCloseEditFlyout = useCallback( + () => setFlyOutVisibility({ type: 'editConnector', visible: false }), + [] + ); const onChangeConnector = useCallback( (id: string) => { if (id === 'add-connector') { - setAddFlyoutVisibility(true); + setFlyOutVisibility({ type: 'addConnector', visible: true }); return; } @@ -173,6 +189,7 @@ export const ConfigureCases: React.FC = React.memo(() => { connector: caseConnector, closureType, customFields, + templates, id: configurationId, version: configurationVersion, }); @@ -182,6 +199,7 @@ export const ConfigureCases: React.FC = React.memo(() => { persistCaseConfigure, closureType, customFields, + templates, configurationId, configurationVersion, ] @@ -192,12 +210,20 @@ export const ConfigureCases: React.FC = React.memo(() => { persistCaseConfigure({ connector, customFields, + templates, id: configurationId, version: configurationVersion, closureType: type, }); }, - [configurationId, configurationVersion, connector, customFields, persistCaseConfigure] + [ + configurationId, + configurationVersion, + connector, + customFields, + templates, + persistCaseConfigure, + ] ); useEffect(() => { @@ -225,7 +251,7 @@ export const ConfigureCases: React.FC = React.memo(() => { const ConnectorAddFlyout = useMemo( () => - addFlyoutVisible + flyOutVisibility?.type === 'addConnector' && flyOutVisibility?.visible ? triggersActionsUi.getAddConnectorFlyout({ onClose: onCloseAddFlyout, featureId: CasesConnectorFeatureId, @@ -233,12 +259,12 @@ export const ConfigureCases: React.FC = React.memo(() => { }) : null, // eslint-disable-next-line react-hooks/exhaustive-deps - [addFlyoutVisible] + [flyOutVisibility] ); const ConnectorEditFlyout = useMemo( () => - editedConnectorItem && editFlyoutVisible + editedConnectorItem && flyOutVisibility?.type === 'editConnector' && flyOutVisibility?.visible ? triggersActionsUi.getEditConnectorFlyout({ connector: editedConnectorItem, onClose: onCloseEditFlyout, @@ -246,20 +272,31 @@ export const ConfigureCases: React.FC = React.memo(() => { }) : null, // eslint-disable-next-line react-hooks/exhaustive-deps - [connector.id, editedConnectorItem, editFlyoutVisible] + [connector.id, editedConnectorItem, flyOutVisibility] ); - const onAddCustomFields = useCallback(() => { - setCustomFieldFlyoutVisibility(true); - }, [setCustomFieldFlyoutVisibility]); - const onDeleteCustomField = useCallback( (key: string) => { const remainingCustomFields = customFields.filter((field) => field.key !== key); + // delete the same custom field from each template as well + const templatesWithRemainingCustomFields = templates.map((template) => { + const templateCustomFields = + template.caseFields?.customFields?.filter((field) => field.key !== key) ?? []; + + return { + ...template, + caseFields: { + ...template.caseFields, + customFields: [...templateCustomFields], + }, + }; + }); + persistCaseConfigure({ connector, customFields: [...remainingCustomFields], + templates: [...templatesWithRemainingCustomFields], id: configurationId, version: configurationVersion, closureType, @@ -271,6 +308,7 @@ export const ConfigureCases: React.FC = React.memo(() => { configurationVersion, connector, customFields, + templates, persistCaseConfigure, ] ); @@ -282,28 +320,30 @@ export const ConfigureCases: React.FC = React.memo(() => { if (selectedCustomField) { setCustomFieldToEdit(selectedCustomField); } - setCustomFieldFlyoutVisibility(true); + setFlyOutVisibility({ type: 'customField', visible: true }); }, - [setCustomFieldFlyoutVisibility, setCustomFieldToEdit, customFields] + [setFlyOutVisibility, setCustomFieldToEdit, customFields] ); - const onCloseAddFieldFlyout = useCallback(() => { - setCustomFieldFlyoutVisibility(false); + const onCloseCustomFieldFlyout = useCallback(() => { + setFlyOutVisibility({ type: 'customField', visible: false }); setCustomFieldToEdit(null); - }, [setCustomFieldFlyoutVisibility, setCustomFieldToEdit]); + }, [setFlyOutVisibility, setCustomFieldToEdit]); + + const onCustomFieldSave = useCallback( + (data: CustomFieldConfiguration) => { + const updatedCustomFields = addOrReplaceField(customFields, data); - const onSaveCustomField = useCallback( - (customFieldData: CustomFieldConfiguration) => { - const updatedFields = addOrReplaceCustomField(customFields, customFieldData); persistCaseConfigure({ connector, - customFields: updatedFields, + customFields: updatedCustomFields, + templates, id: configurationId, version: configurationVersion, closureType, }); - setCustomFieldFlyoutVisibility(false); + setFlyOutVisibility({ type: 'customField', visible: false }); setCustomFieldToEdit(null); }, [ @@ -312,24 +352,124 @@ export const ConfigureCases: React.FC = React.memo(() => { configurationVersion, connector, customFields, + templates, persistCaseConfigure, ] ); - const CustomFieldAddFlyout = customFieldFlyoutVisible ? ( - <CustomFieldFlyout - isLoading={loadingCaseConfigure || isPersistingConfiguration} - disabled={ - !permissions.create || - !permissions.update || - loadingCaseConfigure || - isPersistingConfiguration + const onDeleteTemplate = useCallback( + (key: string) => { + const remainingTemplates = templates.filter((field) => field.key !== key); + + persistCaseConfigure({ + connector, + customFields, + templates: [...remainingTemplates], + id: configurationId, + version: configurationVersion, + closureType, + }); + }, + [ + closureType, + configurationId, + configurationVersion, + connector, + customFields, + templates, + persistCaseConfigure, + ] + ); + + const onEditTemplate = useCallback( + (key: string) => { + const selectedTemplate = templates.find((item) => item.key === key); + + if (selectedTemplate) { + setTemplateToEdit(selectedTemplate); } - customField={customFieldToEdit} - onCloseFlyout={onCloseAddFieldFlyout} - onSaveField={onSaveCustomField} - /> - ) : null; + setFlyOutVisibility({ type: 'template', visible: true }); + }, + [setFlyOutVisibility, setTemplateToEdit, templates] + ); + + const onCloseTemplateFlyout = useCallback(() => { + setFlyOutVisibility({ type: 'template', visible: false }); + setTemplateToEdit(null); + }, [setFlyOutVisibility, setTemplateToEdit]); + + const onTemplateSave = useCallback( + (data: TemplateConfiguration) => { + const updatedTemplates = addOrReplaceField(templates, data); + + persistCaseConfigure({ + connector, + customFields, + templates: updatedTemplates, + id: configurationId, + version: configurationVersion, + closureType, + }); + + setFlyOutVisibility({ type: 'template', visible: false }); + setTemplateToEdit(null); + }, + [ + closureType, + configurationId, + configurationVersion, + connector, + customFields, + templates, + persistCaseConfigure, + ] + ); + + const AddOrEditCustomFieldFlyout = + flyOutVisibility?.type === 'customField' && flyOutVisibility?.visible ? ( + <CommonFlyout<CustomFieldConfiguration> + isLoading={loadingCaseConfigure || isPersistingConfiguration} + disabled={ + !permissions.create || + !permissions.update || + loadingCaseConfigure || + isPersistingConfiguration + } + onCloseFlyout={onCloseCustomFieldFlyout} + onSaveField={onCustomFieldSave} + renderHeader={() => <span>{i18n.ADD_CUSTOM_FIELD}</span>} + > + {({ onChange }) => ( + <CustomFieldsForm onChange={onChange} initialValue={customFieldToEdit} /> + )} + </CommonFlyout> + ) : null; + + const AddOrEditTemplateFlyout = + flyOutVisibility?.type === 'template' && flyOutVisibility?.visible ? ( + <CommonFlyout<TemplateFormProps, TemplateConfiguration> + isLoading={loadingCaseConfigure || isPersistingConfiguration} + disabled={ + !permissions.create || + !permissions.update || + loadingCaseConfigure || + isPersistingConfiguration + } + onCloseFlyout={onCloseTemplateFlyout} + onSaveField={onTemplateSave} + renderHeader={() => <span>{i18n.CREATE_TEMPLATE}</span>} + > + {({ onChange }) => ( + <TemplateForm + initialValue={templateToEdit} + connectors={connectors ?? []} + currentConfiguration={currentConfiguration} + isEditMode={Boolean(templateToEdit)} + onChange={onChange} + /> + )} + </CommonFlyout> + ) : null; return ( <EuiPageSection restrictWidth={true}> @@ -397,16 +537,34 @@ export const ConfigureCases: React.FC = React.memo(() => { customFields={customFields} isLoading={isLoadingCaseConfiguration} disabled={isLoadingCaseConfiguration} - handleAddCustomField={onAddCustomFields} + handleAddCustomField={() => + setFlyOutVisibility({ type: 'customField', visible: true }) + } handleDeleteCustomField={onDeleteCustomField} handleEditCustomField={onEditCustomField} /> </EuiFlexItem> </div> + + <EuiSpacer size="xl" /> + + <div css={sectionWrapperCss}> + <EuiFlexItem grow={false}> + <Templates + templates={templates} + isLoading={isLoadingCaseConfiguration} + disabled={isLoadingCaseConfiguration} + onAddTemplate={() => setFlyOutVisibility({ type: 'template', visible: true })} + onEditTemplate={onEditTemplate} + onDeleteTemplate={onDeleteTemplate} + /> + </EuiFlexItem> + </div> <EuiSpacer size="xl" /> {ConnectorAddFlyout} {ConnectorEditFlyout} - {CustomFieldAddFlyout} + {AddOrEditCustomFieldFlyout} + {AddOrEditTemplateFlyout} </div> </EuiPageBody> </EuiPageSection> diff --git a/x-pack/plugins/cases/public/components/configure_cases/translations.ts b/x-pack/plugins/cases/public/components/configure_cases/translations.ts index e10f6fcad2fb9..08c83c9564f1e 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/translations.ts +++ b/x-pack/plugins/cases/public/components/configure_cases/translations.ts @@ -160,3 +160,14 @@ export const CASES_WEBHOOK_MAPPINGS = i18n.translate( 'Webhook - Case Management field mappings are configured in the connector settings in the third-party REST API JSON.', } ); + +export const ADD_CUSTOM_FIELD = i18n.translate( + 'xpack.cases.configureCases.customFields.addCustomField', + { + defaultMessage: 'Add field', + } +); + +export const CREATE_TEMPLATE = i18n.translate('xpack.cases.configureCases.templates.flyoutTitle', { + defaultMessage: 'Create template', +}); diff --git a/x-pack/plugins/cases/public/components/configure_cases/utils.ts b/x-pack/plugins/cases/public/components/configure_cases/utils.ts index 2177ea7af81d9..a46b85f756941 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/utils.ts +++ b/x-pack/plugins/cases/public/components/configure_cases/utils.ts @@ -51,7 +51,7 @@ export const setThirdPartyToMapping = ( export const getNoneConnector = (): CaseConnector => ({ id: 'none', name: 'none', - type: ConnectorTypes.none, + type: ConnectorTypes.none as const, fields: null, }); diff --git a/x-pack/plugins/cases/public/components/connectors/constants.ts b/x-pack/plugins/cases/public/components/connectors/constants.ts index 486698330d860..1443b6ae49b05 100644 --- a/x-pack/plugins/cases/public/components/connectors/constants.ts +++ b/x-pack/plugins/cases/public/components/connectors/constants.ts @@ -15,6 +15,8 @@ export const connectorsQueriesKeys = { [...connectorsQueriesKeys.jira, connectorId, 'getIssueType'] as const, jiraGetIssues: (connectorId: string, query: string) => [...connectorsQueriesKeys.jira, connectorId, 'getIssues', query] as const, + jiraGetIssue: (connectorId: string, id: string) => + [...connectorsQueriesKeys.jira, connectorId, 'getIssue', id] as const, resilientGetIncidentTypes: (connectorId: string) => [...connectorsQueriesKeys.resilient, connectorId, 'getIncidentTypes'] as const, resilientGetSeverity: (connectorId: string) => diff --git a/x-pack/plugins/cases/public/components/connectors/jira/case_fields.test.tsx b/x-pack/plugins/cases/public/components/connectors/jira/case_fields.test.tsx index 743ecac4cdc91..5d172539ea29a 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/case_fields.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/case_fields.test.tsx @@ -13,6 +13,7 @@ import userEvent from '@testing-library/user-event'; import { connector, issues } from '../mock'; import { useGetIssueTypes } from './use_get_issue_types'; import { useGetFieldsByIssueType } from './use_get_fields_by_issue_type'; +import { useGetIssue } from './use_get_issue'; import Fields from './case_fields'; import { useGetIssues } from './use_get_issues'; import type { AppMockRenderer } from '../../../common/mock'; @@ -22,11 +23,13 @@ import { MockFormWrapperComponent } from '../test_utils'; jest.mock('./use_get_issue_types'); jest.mock('./use_get_fields_by_issue_type'); jest.mock('./use_get_issues'); +jest.mock('./use_get_issue'); jest.mock('../../../common/lib/kibana'); const useGetIssueTypesMock = useGetIssueTypes as jest.Mock; const useGetFieldsByIssueTypeMock = useGetFieldsByIssueType as jest.Mock; const useGetIssuesMock = useGetIssues as jest.Mock; +const useGetIssueMock = useGetIssue as jest.Mock; describe('Jira Fields', () => { const useGetIssueTypesResponse = { @@ -84,6 +87,12 @@ describe('Jira Fields', () => { data: { data: issues }, }; + const useGetIssueResponse = { + isLoading: false, + isFetching: false, + data: { data: issues[0] }, + }; + let appMockRenderer: AppMockRenderer; beforeEach(() => { @@ -91,6 +100,7 @@ describe('Jira Fields', () => { useGetIssueTypesMock.mockReturnValue(useGetIssueTypesResponse); useGetFieldsByIssueTypeMock.mockReturnValue(useGetFieldsByIssueTypeResponse); useGetIssuesMock.mockReturnValue(useGetIssuesResponse); + useGetIssueMock.mockReturnValue(useGetIssueResponse); jest.clearAllMocks(); }); @@ -237,6 +247,38 @@ describe('Jira Fields', () => { expect(await screen.findByTestId('prioritySelect')).toHaveValue('Low'); }); + it('sets existing parent correctly', async () => { + const newFields = { ...fields, parent: 'personKey' }; + + appMockRenderer.render( + <MockFormWrapperComponent fields={newFields}> + <Fields connector={connector} /> + </MockFormWrapperComponent> + ); + + expect(await screen.findByText('Person Task')).toBeInTheDocument(); + }); + + it('resets existing parent correctly', async () => { + const newFields = { ...fields, parent: 'personKey' }; + + appMockRenderer.render( + <MockFormWrapperComponent fields={newFields}> + <Fields connector={connector} /> + </MockFormWrapperComponent> + ); + + const checkbox = within(await screen.findByTestId('search-parent-issues')).getByTestId( + 'comboBoxSearchInput' + ); + + expect(await screen.findByText('Person Task')).toBeInTheDocument(); + + userEvent.click(await screen.findByTestId('comboBoxClearButton')); + + expect(checkbox).toHaveValue(''); + }); + it('should submit Jira connector', async () => { appMockRenderer.render( <MockFormWrapperComponent fields={fields}> diff --git a/x-pack/plugins/cases/public/components/connectors/jira/search_issues.tsx b/x-pack/plugins/cases/public/components/connectors/jira/search_issues.tsx index 27df975ac5864..c4089c7f14c60 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/search_issues.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/search_issues.tsx @@ -6,83 +6,140 @@ */ import React, { useState, memo } from 'react'; +import { isEmpty } from 'lodash'; import type { EuiComboBoxOptionOption } from '@elastic/eui'; import { EuiComboBox, EuiFormRow } from '@elastic/eui'; +import type { FieldHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { getFieldValidityAndErrorMessage, UseField, + useFormData, } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { useIsUserTyping } from '../../../common/use_is_user_typing'; import { useKibana } from '../../../common/lib/kibana'; import type { ActionConnector } from '../../../../common/types/domain'; import { useGetIssues } from './use_get_issues'; import * as i18n from './translations'; +import { useGetIssue } from './use_get_issue'; + +interface FieldProps { + field: FieldHook<string>; + options: Array<EuiComboBoxOptionOption<string>>; + isLoading: boolean; + onSearchComboChange: (value: string) => void; +} interface Props { actionConnector?: ActionConnector; } -const SearchIssuesComponent: React.FC<Props> = ({ actionConnector }) => { - const [query, setQuery] = useState<string | null>(null); - const [selectedOptions, setSelectedOptions] = useState<Array<EuiComboBoxOptionOption<string>>>( - [] +const SearchIssuesFieldComponent: React.FC<FieldProps> = ({ + field, + options, + isLoading, + onSearchComboChange, +}) => { + const { value: parent } = field; + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + + const selectedOptions = [parent] + .map((currentParent: string) => { + const selectedParent = options.find((issue) => issue.value === currentParent); + + if (selectedParent) { + return selectedParent; + } + + return null; + }) + .filter((value): value is EuiComboBoxOptionOption<string> => value != null); + + const onChangeComboBox = (changedOptions: Array<EuiComboBoxOptionOption<string>>) => { + field.setValue(changedOptions.length ? changedOptions[0].value ?? '' : ''); + }; + + return ( + <EuiFormRow + id="indexConnectorSelectSearchBox" + fullWidth + label={i18n.PARENT_ISSUE} + isInvalid={isInvalid} + error={errorMessage} + > + <EuiComboBox + fullWidth + singleSelection + async + placeholder={i18n.SEARCH_ISSUES_PLACEHOLDER} + aria-label={i18n.SEARCH_ISSUES_COMBO_BOX_ARIA_LABEL} + isLoading={isLoading} + isInvalid={isInvalid} + noSuggestions={!options.length} + options={options} + data-test-subj="search-parent-issues" + data-testid="search-parent-issues" + selectedOptions={selectedOptions} + onChange={onChangeComboBox} + onSearchChange={onSearchComboChange} + /> + </EuiFormRow> ); +}; +SearchIssuesFieldComponent.displayName = 'SearchIssuesField'; + +const SearchIssuesComponent: React.FC<Props> = ({ actionConnector }) => { const { http } = useKibana().services; + const [{ fields }] = useFormData<{ fields?: { parent: string } }>({ + watch: ['fields.parent'], + }); + + const [query, setQuery] = useState<string | null>(null); + const { isUserTyping, onContentChange, onDebounce } = useIsUserTyping(); const { isFetching: isLoadingIssues, data: issuesData } = useGetIssues({ http, actionConnector, query, + onDebounce, + }); + + const { isFetching: isLoadingIssue, data: issueData } = useGetIssue({ + http, + actionConnector, + id: fields?.parent ?? '', }); const issues = issuesData?.data ?? []; + const issue = issueData?.data ? [issueData.data] : []; + + const onSearchComboChange = (value: string) => { + if (!isEmpty(value)) { + setQuery(value); + } - const options = issues.map((issue) => ({ label: issue.title, value: issue.key })); + onContentChange(value); + }; + + const isLoading = isUserTyping || isLoadingIssues || isLoadingIssue; + const options = [...issues, ...issue].map((_issue) => ({ + label: _issue.title, + value: _issue.key, + })); return ( - <UseField path="fields.parent"> - {(field) => { - const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); - - const onSearchChange = (searchVal: string) => { - setQuery(searchVal); - }; - - const onChangeComboBox = (changedOptions: Array<EuiComboBoxOptionOption<string>>) => { - setSelectedOptions(changedOptions); - field.setValue(changedOptions[0].value ?? ''); - }; - - return ( - <EuiFormRow - id="indexConnectorSelectSearchBox" - fullWidth - label={i18n.PARENT_ISSUE} - isInvalid={isInvalid} - error={errorMessage} - > - <EuiComboBox - fullWidth - singleSelection - async - placeholder={i18n.SEARCH_ISSUES_PLACEHOLDER} - aria-label={i18n.SEARCH_ISSUES_COMBO_BOX_ARIA_LABEL} - isLoading={isLoadingIssues} - isInvalid={isInvalid} - noSuggestions={!options.length} - options={options} - data-test-subj="search-parent-issues" - data-testid="search-parent-issues" - selectedOptions={selectedOptions} - onChange={onChangeComboBox} - onSearchChange={onSearchChange} - /> - </EuiFormRow> - ); + <UseField<string> + path="fields.parent" + component={SearchIssuesFieldComponent} + componentProps={{ + isLoading, + onSearchComboChange, + options, }} - </UseField> + /> ); }; + SearchIssuesComponent.displayName = 'SearchIssues'; export const SearchIssues = memo(SearchIssuesComponent); diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue.test.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue.test.tsx new file mode 100644 index 0000000000000..876738025e6a8 --- /dev/null +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue.test.tsx @@ -0,0 +1,131 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; + +import { useKibana, useToasts } from '../../../common/lib/kibana'; +import { connector as actionConnector } from '../mock'; +import { useGetIssue } from './use_get_issue'; +import * as api from './api'; +import type { AppMockRenderer } from '../../../common/mock'; +import { createAppMockRenderer } from '../../../common/mock'; + +jest.mock('../../../common/lib/kibana'); +jest.mock('./api'); + +const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>; + +describe('useGetIssue', () => { + const { http } = useKibanaMock().services; + let appMockRender: AppMockRenderer; + + beforeEach(() => { + appMockRender = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + it('calls the api when invoked with the correct parameters', async () => { + const spy = jest.spyOn(api, 'getIssue'); + const { result, waitFor } = renderHook( + () => + useGetIssue({ + http, + actionConnector, + id: 'RJ-107', + }), + { wrapper: appMockRender.AppWrapper } + ); + + await waitFor(() => result.current.isSuccess); + + expect(spy).toHaveBeenCalledWith({ + http, + signal: expect.anything(), + connectorId: actionConnector.id, + id: 'RJ-107', + }); + }); + + it('does not call the api when the connector is missing', async () => { + const spy = jest.spyOn(api, 'getIssue'); + renderHook( + () => + useGetIssue({ + http, + id: 'RJ-107', + }), + { wrapper: appMockRender.AppWrapper } + ); + + expect(spy).not.toHaveBeenCalledWith(); + }); + + it('does not call the api when the id is missing', async () => { + const spy = jest.spyOn(api, 'getIssue'); + renderHook( + () => + useGetIssue({ + http, + actionConnector, + id: '', + }), + { wrapper: appMockRender.AppWrapper } + ); + + expect(spy).not.toHaveBeenCalledWith(); + }); + + it('calls addError when the getIssue api throws an error', async () => { + const spyOnGetCases = jest.spyOn(api, 'getIssue'); + spyOnGetCases.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + const addError = jest.fn(); + (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); + + const { result, waitFor } = renderHook( + () => + useGetIssue({ + http, + actionConnector, + id: 'RJ-107', + }), + { wrapper: appMockRender.AppWrapper } + ); + + await waitFor(() => result.current.isError); + + expect(addError).toHaveBeenCalled(); + }); + + it('calls addError when the getIssue api returns successfully but contains an error', async () => { + const spyOnGetCases = jest.spyOn(api, 'getIssue'); + spyOnGetCases.mockResolvedValue({ + status: 'error', + message: 'Error message', + actionId: 'test', + }); + + const addError = jest.fn(); + (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); + + const { result, waitFor } = renderHook( + () => + useGetIssue({ + http, + actionConnector, + id: 'RJ-107', + }), + { wrapper: appMockRender.AppWrapper } + ); + + await waitFor(() => result.current.isSuccess); + + expect(addError).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue.tsx new file mode 100644 index 0000000000000..ed3bfcf61f2f8 --- /dev/null +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue.tsx @@ -0,0 +1,56 @@ +/* + * 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 { HttpSetup } from '@kbn/core/public'; +import type { ActionTypeExecutorResult } from '@kbn/actions-plugin/common'; +import { useQuery } from '@tanstack/react-query'; +import { isEmpty } from 'lodash'; +import type { ActionConnector } from '../../../../common/types/domain'; +import { getIssue } from './api'; +import type { Issue } from './types'; +import * as i18n from './translations'; +import { useCasesToast } from '../../../common/use_cases_toast'; +import type { ServerError } from '../../../types'; +import { connectorsQueriesKeys } from '../constants'; + +interface Props { + http: HttpSetup; + id: string; + actionConnector?: ActionConnector; +} + +export const useGetIssue = ({ http, actionConnector, id }: Props) => { + const { showErrorToast } = useCasesToast(); + return useQuery<ActionTypeExecutorResult<Issue>, ServerError>( + connectorsQueriesKeys.jiraGetIssue(actionConnector?.id ?? '', id), + ({ signal }) => { + return getIssue({ + http, + signal, + connectorId: actionConnector?.id ?? '', + id, + }); + }, + { + enabled: Boolean(actionConnector && !isEmpty(id)), + staleTime: 60 * 1000, // one minute + onSuccess: (res) => { + if (res.status && res.status === 'error') { + showErrorToast(new Error(i18n.GET_ISSUE_API_ERROR(id)), { + title: i18n.GET_ISSUE_API_ERROR(id), + toastMessage: `${res.serviceMessage ?? res.message}`, + }); + } + }, + onError: (error: ServerError) => { + showErrorToast(error, { title: i18n.GET_ISSUE_API_ERROR(id) }); + }, + } + ); +}; + +export type UseGetIssueTypes = ReturnType<typeof useGetIssue>; diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.tsx index 037fcc6bb8d8e..01f4ad0a3edb3 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.tsx @@ -10,7 +10,8 @@ import useDebounce from 'react-use/lib/useDebounce'; import type { HttpSetup } from '@kbn/core/public'; import type { ActionTypeExecutorResult } from '@kbn/actions-plugin/common'; import { useQuery } from '@tanstack/react-query'; -import { isEmpty } from 'lodash'; +import { isEmpty, noop } from 'lodash'; +import { SEARCH_DEBOUNCE_MS } from '../../../../common/constants'; import type { ActionConnector } from '../../../../common/types/domain'; import { getIssues } from './api'; import type { Issues } from './types'; @@ -23,16 +24,16 @@ interface Props { http: HttpSetup; query: string | null; actionConnector?: ActionConnector; + onDebounce?: () => void; } -const SEARCH_DEBOUNCE_MS = 500; - -export const useGetIssues = ({ http, actionConnector, query }: Props) => { +export const useGetIssues = ({ http, actionConnector, query, onDebounce = noop }: Props) => { const [debouncedQuery, setDebouncedQuery] = useState(query); useDebounce( () => { setDebouncedQuery(query); + onDebounce(); }, SEARCH_DEBOUNCE_MS, [query] diff --git a/x-pack/plugins/cases/public/components/connectors/resilient/case_fields.tsx b/x-pack/plugins/cases/public/components/connectors/resilient/case_fields.tsx index e8260a69a3301..ee7538543ec41 100644 --- a/x-pack/plugins/cases/public/components/connectors/resilient/case_fields.tsx +++ b/x-pack/plugins/cases/public/components/connectors/resilient/case_fields.tsx @@ -77,12 +77,15 @@ const ResilientFieldsComponent: React.FunctionComponent<ConnectorFieldsProps> = field.setValue(changedOptions.map((option) => option.value as string)); }; - const selectedOptions = (field.value ?? []).map((incidentType) => ({ - value: incidentType, - label: - (allIncidentTypes ?? []).find((type) => incidentType === type.id.toString())?.name ?? - '', - })); + const selectedOptions = + field.value && allIncidentTypes?.length + ? field.value.map((incidentType) => ({ + value: incidentType, + label: + allIncidentTypes.find((type) => incidentType === type.id.toString())?.name ?? + '', + })) + : []; return ( <EuiFormRow diff --git a/x-pack/plugins/cases/public/components/create/connector.test.tsx b/x-pack/plugins/cases/public/components/create/connector.test.tsx deleted file mode 100644 index 1cf7c82075136..0000000000000 --- a/x-pack/plugins/cases/public/components/create/connector.test.tsx +++ /dev/null @@ -1,210 +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 type { FC, PropsWithChildren } from 'react'; -import React from 'react'; -import { mount } from 'enzyme'; -import { act, waitFor } from '@testing-library/react'; -import type { EuiComboBoxOptionOption } from '@elastic/eui'; -import { EuiComboBox } from '@elastic/eui'; - -import type { FormHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { useForm, Form } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { connectorsMock } from '../../containers/mock'; -import { Connector } from './connector'; -import { useGetIncidentTypes } from '../connectors/resilient/use_get_incident_types'; -import { useGetSeverity } from '../connectors/resilient/use_get_severity'; -import { useGetChoices } from '../connectors/servicenow/use_get_choices'; -import { incidentTypes, severity, choices } from '../connectors/mock'; -import type { FormProps } from './schema'; -import { schema } from './schema'; -import type { AppMockRenderer } from '../../common/mock'; -import { - noConnectorsCasePermission, - createAppMockRenderer, - TestProviders, -} from '../../common/mock'; -import { useGetCaseConfiguration } from '../../containers/configure/use_get_case_configuration'; -import { useCaseConfigureResponse } from '../configure_cases/__mock__'; - -jest.mock('../connectors/resilient/use_get_incident_types'); -jest.mock('../connectors/resilient/use_get_severity'); -jest.mock('../connectors/servicenow/use_get_choices'); -jest.mock('../../containers/configure/use_get_case_configuration'); - -const useGetIncidentTypesMock = useGetIncidentTypes as jest.Mock; -const useGetSeverityMock = useGetSeverity as jest.Mock; -const useGetChoicesMock = useGetChoices as jest.Mock; -const useGetCaseConfigurationMock = useGetCaseConfiguration as jest.Mock; - -const useGetIncidentTypesResponse = { - isLoading: false, - incidentTypes, -}; - -const useGetSeverityResponse = { - isLoading: false, - severity, -}; - -const useGetChoicesResponse = { - isLoading: false, - choices, -}; - -const defaultProps = { - connectors: connectorsMock, - isLoading: false, - isLoadingConnectors: false, -}; - -describe('Connector', () => { - let appMockRender: AppMockRenderer; - let globalForm: FormHook; - - const MockHookWrapperComponent: FC<PropsWithChildren<unknown>> = ({ children }) => { - const { form } = useForm<FormProps>({ - defaultValue: { connectorId: connectorsMock[0].id, fields: null }, - schema: { - connectorId: schema.connectorId, - fields: schema.fields, - }, - }); - - globalForm = form; - - return <Form form={form}>{children}</Form>; - }; - - beforeEach(() => { - jest.clearAllMocks(); - appMockRender = createAppMockRenderer(); - useGetIncidentTypesMock.mockReturnValue(useGetIncidentTypesResponse); - useGetSeverityMock.mockReturnValue(useGetSeverityResponse); - useGetChoicesMock.mockReturnValue(useGetChoicesResponse); - useGetCaseConfigurationMock.mockImplementation(() => useCaseConfigureResponse); - }); - - it('it renders', async () => { - const wrapper = mount( - <TestProviders> - <MockHookWrapperComponent> - <Connector {...defaultProps} /> - </MockHookWrapperComponent> - </TestProviders> - ); - - expect(wrapper.find(`[data-test-subj="caseConnectors"]`).exists()).toBeTruthy(); - // Selected connector is set to none so no fields should be displayed - expect(wrapper.find(`[data-test-subj="connector-fields"]`).exists()).toBeFalsy(); - }); - - it('it is disabled and loading when isLoadingConnectors=true', async () => { - const wrapper = mount( - <TestProviders> - <MockHookWrapperComponent> - <Connector {...{ ...defaultProps, isLoadingConnectors: true }} /> - </MockHookWrapperComponent> - </TestProviders> - ); - - expect( - wrapper.find('[data-test-subj="dropdown-connectors"]').first().prop('isLoading') - ).toEqual(true); - - expect(wrapper.find('[data-test-subj="dropdown-connectors"]').first().prop('disabled')).toEqual( - true - ); - }); - - it('it is disabled and loading when isLoading=true', async () => { - const wrapper = mount( - <TestProviders> - <MockHookWrapperComponent> - <Connector {...{ ...defaultProps, isLoading: true }} /> - </MockHookWrapperComponent> - </TestProviders> - ); - - expect( - wrapper.find('[data-test-subj="dropdown-connectors"]').first().prop('isLoading') - ).toEqual(true); - expect(wrapper.find('[data-test-subj="dropdown-connectors"]').first().prop('disabled')).toEqual( - true - ); - }); - - it(`it should change connector`, async () => { - const wrapper = mount( - <TestProviders> - <MockHookWrapperComponent> - <Connector {...defaultProps} /> - </MockHookWrapperComponent> - </TestProviders> - ); - - expect(wrapper.find(`[data-test-subj="connector-fields-resilient"]`).exists()).toBeFalsy(); - wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); - wrapper.find(`button[data-test-subj="dropdown-connector-resilient-2"]`).simulate('click'); - - await waitFor(() => { - wrapper.update(); - expect(wrapper.find(`[data-test-subj="connector-fields-resilient"]`).exists()).toBeTruthy(); - }); - - act(() => { - ( - wrapper.find(EuiComboBox).props() as unknown as { - onChange: (a: EuiComboBoxOptionOption[]) => void; - } - ).onChange([{ value: '19', label: 'Denial of Service' }]); - }); - - act(() => { - wrapper - .find('select[data-test-subj="severitySelect"]') - .first() - .simulate('change', { - target: { value: '4' }, - }); - }); - - await waitFor(() => { - expect(globalForm.getFormData()).toEqual({ - connectorId: 'resilient-2', - fields: { incidentTypes: ['19'], severityCode: '4' }, - }); - }); - }); - - it('shows the actions permission message if the user does not have read access to actions', async () => { - appMockRender.coreStart.application.capabilities = { - ...appMockRender.coreStart.application.capabilities, - actions: { save: false, show: false }, - }; - - const result = appMockRender.render( - <MockHookWrapperComponent> - <Connector {...defaultProps} /> - </MockHookWrapperComponent> - ); - expect(result.getByTestId('create-case-connector-permissions-error-msg')).toBeInTheDocument(); - expect(result.queryByTestId('caseConnectors')).toBe(null); - }); - - it('shows the actions permission message if the user does not have access to case connector', async () => { - appMockRender = createAppMockRenderer({ permissions: noConnectorsCasePermission() }); - - const result = appMockRender.render( - <MockHookWrapperComponent> - <Connector {...defaultProps} /> - </MockHookWrapperComponent> - ); - expect(result.getByTestId('create-case-connector-permissions-error-msg')).toBeInTheDocument(); - expect(result.queryByTestId('caseConnectors')).toBe(null); - }); -}); diff --git a/x-pack/plugins/cases/public/components/create/custom_fields.tsx b/x-pack/plugins/cases/public/components/create/custom_fields.tsx deleted file mode 100644 index 28cebde65db27..0000000000000 --- a/x-pack/plugins/cases/public/components/create/custom_fields.tsx +++ /dev/null @@ -1,85 +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, { useMemo } from 'react'; -import { sortBy } from 'lodash'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; - -import { useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import type { CasesConfigurationUI } from '../../../common/ui'; -import { builderMap as customFieldsBuilderMap } from '../custom_fields/builder'; -import * as i18n from './translations'; -import { useCasesContext } from '../cases_context/use_cases_context'; -import { useGetAllCaseConfigurations } from '../../containers/configure/use_get_all_case_configurations'; -import { getConfigurationByOwner } from '../../containers/configure/utils'; - -interface Props { - isLoading: boolean; -} - -const CustomFieldsComponent: React.FC<Props> = ({ isLoading }) => { - const { owner } = useCasesContext(); - const [{ selectedOwner }] = useFormData<{ selectedOwner: string }>({ watch: ['selectedOwner'] }); - const { data: configurations, isLoading: isLoadingCaseConfiguration } = - useGetAllCaseConfigurations(); - - const configurationOwner: string | undefined = selectedOwner ? selectedOwner : owner[0]; - const customFieldsConfiguration = useMemo( - () => - getConfigurationByOwner({ - configurations, - owner: configurationOwner, - }).customFields ?? [], - [configurations, configurationOwner] - ); - - const sortedCustomFields = useMemo( - () => sortCustomFieldsByLabel(customFieldsConfiguration), - [customFieldsConfiguration] - ); - - const customFieldsComponents = sortedCustomFields.map( - (customField: CasesConfigurationUI['customFields'][number]) => { - const customFieldFactory = customFieldsBuilderMap[customField.type]; - const customFieldType = customFieldFactory().build(); - - const CreateComponent = customFieldType.Create; - - return ( - <CreateComponent - isLoading={isLoading || isLoadingCaseConfiguration} - customFieldConfiguration={customField} - key={customField.key} - /> - ); - } - ); - - if (!customFieldsConfiguration.length) { - return null; - } - - return ( - <EuiFlexGroup direction="column" gutterSize="s"> - <EuiText size="m"> - <h3>{i18n.ADDITIONAL_FIELDS}</h3> - </EuiText> - <EuiSpacer size="xs" /> - <EuiFlexItem data-test-subj="create-case-custom-fields">{customFieldsComponents}</EuiFlexItem> - </EuiFlexGroup> - ); -}; - -CustomFieldsComponent.displayName = 'CustomFields'; - -export const CustomFields = React.memo(CustomFieldsComponent); - -const sortCustomFieldsByLabel = (configCustomFields: CasesConfigurationUI['customFields']) => { - return sortBy(configCustomFields, (configCustomField) => { - return configCustomField.label; - }); -}; diff --git a/x-pack/plugins/cases/public/components/create/form.test.tsx b/x-pack/plugins/cases/public/components/create/form.test.tsx index b5b3f7bf7b677..885e25e959ac9 100644 --- a/x-pack/plugins/cases/public/components/create/form.test.tsx +++ b/x-pack/plugins/cases/public/components/create/form.test.tsx @@ -5,48 +5,44 @@ * 2.0. */ -import type { FC, PropsWithChildren } from 'react'; import React from 'react'; -import { mount } from 'enzyme'; -import { act, render, within, fireEvent, waitFor } from '@testing-library/react'; +import { within, fireEvent, waitFor, screen } from '@testing-library/react'; import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; -import { NONE_CONNECTOR_ID } from '../../../common/constants'; -import type { FormHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { useForm, Form } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { connectorsMock, customFieldsConfigurationMock } from '../../containers/mock'; -import type { FormProps } from './schema'; -import { schema } from './schema'; +import { + connectorsMock, + customFieldsConfigurationMock, + templatesConfigurationMock, +} from '../../containers/mock'; import type { CreateCaseFormProps } from './form'; import { CreateCaseForm } from './form'; import { useGetAllCaseConfigurations } from '../../containers/configure/use_get_all_case_configurations'; import { useGetAllCaseConfigurationsResponse } from '../configure_cases/__mock__'; -import { TestProviders } from '../../common/mock'; import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors'; import { useGetTags } from '../../containers/use_get_tags'; import { useAvailableCasesOwners } from '../app/use_available_owners'; +import type { AppMockRenderer } from '../../common/mock'; +import { createAppMockRenderer } from '../../common/mock'; +import userEvent from '@testing-library/user-event'; +import { CustomFieldTypes } from '../../../common/types/domain'; +import { useSuggestUserProfiles } from '../../containers/user_profiles/use_suggest_user_profiles'; +import { useGetCurrentUserProfile } from '../../containers/user_profiles/use_get_current_user_profile'; +import { userProfiles } from '../../containers/user_profiles/api.mock'; jest.mock('../../containers/use_get_tags'); jest.mock('../../containers/configure/use_get_supported_action_connectors'); jest.mock('../../containers/configure/use_get_all_case_configurations'); +jest.mock('../../containers/user_profiles/use_suggest_user_profiles'); +jest.mock('../../containers/user_profiles/use_get_current_user_profile'); jest.mock('../markdown_editor/plugins/lens/use_lens_draft_comment'); jest.mock('../app/use_available_owners'); const useGetTagsMock = useGetTags as jest.Mock; -const useGetConnectorsMock = useGetSupportedActionConnectors as jest.Mock; +const useGetSupportedActionConnectorsMock = useGetSupportedActionConnectors as jest.Mock; const useGetAllCaseConfigurationsMock = useGetAllCaseConfigurations as jest.Mock; const useAvailableOwnersMock = useAvailableCasesOwners as jest.Mock; - -const initialCaseValue: FormProps = { - description: '', - tags: [], - title: '', - connectorId: NONE_CONNECTOR_ID, - fields: null, - syncAlerts: true, - assignees: [], - customFields: {}, -}; +const useSuggestUserProfilesMock = useSuggestUserProfiles as jest.Mock; +const useGetCurrentUserProfileMock = useGetCurrentUserProfile as jest.Mock; const casesFormProps: CreateCaseFormProps = { onCancel: jest.fn(), @@ -54,36 +50,18 @@ const casesFormProps: CreateCaseFormProps = { }; describe('CreateCaseForm', () => { - let globalForm: FormHook; - const draftStorageKey = `cases.caseView.createCase.description.markdownEditor`; - - const MockHookWrapperComponent: FC< - PropsWithChildren<{ - testProviderProps?: unknown; - }> - > = ({ children, testProviderProps = {} }) => { - const { form } = useForm<FormProps>({ - defaultValue: initialCaseValue, - options: { stripEmptyFields: false }, - schema, - }); - - globalForm = form; - - return ( - // @ts-expect-error ts upgrade v4.7.4 - <TestProviders {...testProviderProps}> - <Form form={form}>{children}</Form> - </TestProviders> - ); - }; + const draftStorageKey = 'cases.caseView.createCase.description.markdownEditor'; + let appMockRenderer: AppMockRenderer; beforeEach(() => { jest.clearAllMocks(); + appMockRenderer = createAppMockRenderer(); useAvailableOwnersMock.mockReturnValue(['securitySolution', 'observability']); useGetTagsMock.mockReturnValue({ data: ['test'] }); - useGetConnectorsMock.mockReturnValue({ isLoading: false, data: connectorsMock }); + useGetSupportedActionConnectorsMock.mockReturnValue({ isLoading: false, data: connectorsMock }); useGetAllCaseConfigurationsMock.mockImplementation(() => useGetAllCaseConfigurationsResponse); + useSuggestUserProfilesMock.mockReturnValue({ data: userProfiles, isLoading: false }); + useGetCurrentUserProfileMock.mockReturnValue({ data: userProfiles[0], isLoading: false }); }); afterEach(() => { @@ -91,136 +69,86 @@ describe('CreateCaseForm', () => { }); it('renders with steps', async () => { - const wrapper = mount( - <MockHookWrapperComponent> - <CreateCaseForm {...casesFormProps} /> - </MockHookWrapperComponent> - ); + appMockRenderer.render(<CreateCaseForm {...casesFormProps} />); - expect(wrapper.find(`[data-test-subj="case-creation-form-steps"]`).exists()).toBeTruthy(); + expect(await screen.findByTestId('case-creation-form-steps')).toBeInTheDocument(); }); it('renders without steps', async () => { - const wrapper = mount( - <MockHookWrapperComponent> - <CreateCaseForm {...casesFormProps} withSteps={false} /> - </MockHookWrapperComponent> - ); + appMockRenderer.render(<CreateCaseForm {...casesFormProps} withSteps={false} />); - expect(wrapper.find(`[data-test-subj="case-creation-form-steps"]`).exists()).toBeFalsy(); + expect(screen.queryByText('case-creation-form-steps')).not.toBeInTheDocument(); }); it('renders all form fields except case selection', async () => { - const wrapper = mount( - <MockHookWrapperComponent> - <CreateCaseForm {...casesFormProps} /> - </MockHookWrapperComponent> - ); - - expect(wrapper.find(`[data-test-subj="caseTitle"]`).exists()).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="caseTags"]`).exists()).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="caseDescription"]`).exists()).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="caseSyncAlerts"]`).exists()).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="caseConnectors"]`).exists()).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="categories-list"]`).exists()).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="caseOwnerSelector"]`).exists()).toBeFalsy(); + appMockRenderer.render(<CreateCaseForm {...casesFormProps} />); + + expect(await screen.findByTestId('caseTitle')).toBeInTheDocument(); + expect(await screen.findByTestId('caseTags')).toBeInTheDocument(); + expect(await screen.findByTestId('caseDescription')).toBeInTheDocument(); + expect(await screen.findByTestId('caseSyncAlerts')).toBeInTheDocument(); + expect(await screen.findByTestId('caseConnectors')).toBeInTheDocument(); + expect(await screen.findByTestId('categories-list')).toBeInTheDocument(); + expect(screen.queryByText('caseOwnerSelector')).not.toBeInTheDocument(); }); it('renders all form fields including case selection if has permissions and no owner', async () => { - const wrapper = mount( - <MockHookWrapperComponent testProviderProps={{ owner: [] }}> - <CreateCaseForm {...casesFormProps} /> - </MockHookWrapperComponent> - ); - - expect(wrapper.find(`[data-test-subj="caseTitle"]`).exists()).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="caseTags"]`).exists()).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="caseDescription"]`).exists()).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="caseSyncAlerts"]`).exists()).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="caseConnectors"]`).exists()).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="categories-list"]`).exists()).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="caseOwnerSelector"]`).exists()).toBeTruthy(); + appMockRenderer = createAppMockRenderer({ owner: [] }); + appMockRenderer.render(<CreateCaseForm {...casesFormProps} />); + + expect(await screen.findByTestId('caseTitle')).toBeInTheDocument(); + expect(await screen.findByTestId('caseTags')).toBeInTheDocument(); + expect(await screen.findByTestId('caseDescription')).toBeInTheDocument(); + expect(await screen.findByTestId('caseSyncAlerts')).toBeInTheDocument(); + expect(await screen.findByTestId('caseConnectors')).toBeInTheDocument(); + expect(await screen.findByTestId('categories-list')).toBeInTheDocument(); + expect(await screen.findByTestId('caseOwnerSelector')).toBeInTheDocument(); }); it('does not render solution picker when only one owner is available', async () => { useAvailableOwnersMock.mockReturnValue(['securitySolution']); - const wrapper = mount( - <MockHookWrapperComponent> - <CreateCaseForm {...casesFormProps} /> - </MockHookWrapperComponent> - ); + appMockRenderer.render(<CreateCaseForm {...casesFormProps} />); - expect(wrapper.find(`[data-test-subj="caseOwnerSelector"]`).exists()).toBeFalsy(); + expect(screen.queryByTestId('caseOwnerSelector')).not.toBeInTheDocument(); }); - it('hides the sync alerts toggle', () => { - const { queryByText } = render( - <MockHookWrapperComponent testProviderProps={{ features: { alerts: { sync: false } } }}> - <CreateCaseForm {...casesFormProps} /> - </MockHookWrapperComponent> - ); + it('hides the sync alerts toggle', async () => { + appMockRenderer = createAppMockRenderer({ features: { alerts: { sync: false } } }); + appMockRenderer.render(<CreateCaseForm {...casesFormProps} />); - expect(queryByText('Sync alert')).not.toBeInTheDocument(); - }); - - it('should render spinner when loading', async () => { - const wrapper = mount( - <MockHookWrapperComponent> - <CreateCaseForm {...casesFormProps} /> - </MockHookWrapperComponent> - ); - - expect(wrapper.find(`[data-test-subj="create-case-submit"]`).exists()).toBeTruthy(); - - await act(async () => { - globalForm.setFieldValue('title', 'title'); - globalForm.setFieldValue('description', 'description'); - await wrapper.find(`button[data-test-subj="create-case-submit"]`).simulate('click'); - wrapper.update(); - }); - - expect(wrapper.find(`[data-test-subj="create-case-loading-spinner"]`).exists()).toBeTruthy(); + expect(screen.queryByText('Sync alert')).not.toBeInTheDocument(); }); it('should not render the assignees on basic license', () => { - const result = render( - <MockHookWrapperComponent> - <CreateCaseForm {...casesFormProps} /> - </MockHookWrapperComponent> - ); - - expect(result.queryByTestId('createCaseAssigneesComboBox')).toBeNull(); + appMockRenderer.render(<CreateCaseForm {...casesFormProps} />); + expect(screen.queryByTestId('createCaseAssigneesComboBox')).not.toBeInTheDocument(); }); - it('should render the assignees on platinum license', () => { + it('should render the assignees on platinum license', async () => { const license = licensingMock.createLicense({ license: { type: 'platinum' }, }); - const result = render( - <MockHookWrapperComponent testProviderProps={{ license }}> - <CreateCaseForm {...casesFormProps} /> - </MockHookWrapperComponent> - ); + appMockRenderer = createAppMockRenderer({ license }); + appMockRenderer.render(<CreateCaseForm {...casesFormProps} />); - expect(result.getByTestId('createCaseAssigneesComboBox')).toBeInTheDocument(); + expect(await screen.findByTestId('createCaseAssigneesComboBox')).toBeInTheDocument(); }); - it('should not prefill the form when no initialValue provided', () => { - const { getByTestId } = render( - <MockHookWrapperComponent> - <CreateCaseForm {...casesFormProps} /> - </MockHookWrapperComponent> + it('should not prefill the form when no initialValue provided', async () => { + appMockRenderer.render(<CreateCaseForm {...casesFormProps} />); + + const titleInput = within(await screen.findByTestId('caseTitle')).getByTestId('input'); + const descriptionInput = within(await screen.findByTestId('caseDescription')).getByRole( + 'textbox' ); - const titleInput = within(getByTestId('caseTitle')).getByTestId('input'); - const descriptionInput = within(getByTestId('caseDescription')).getByRole('textbox'); expect(titleInput).toHaveValue(''); expect(descriptionInput).toHaveValue(''); }); - it('should render custom fields when available', () => { + it('should render custom fields when available', async () => { useGetAllCaseConfigurationsMock.mockImplementation(() => ({ ...useGetAllCaseConfigurationsResponse, data: [ @@ -231,70 +159,62 @@ describe('CreateCaseForm', () => { ], })); - const result = render( - <MockHookWrapperComponent> - <CreateCaseForm {...casesFormProps} /> - </MockHookWrapperComponent> - ); + appMockRenderer.render(<CreateCaseForm {...casesFormProps} />); - expect(result.getByTestId('create-case-custom-fields')).toBeInTheDocument(); + expect(await screen.findByTestId('caseCustomFields')).toBeInTheDocument(); for (const item of customFieldsConfigurationMock) { expect( - result.getByTestId(`${item.key}-${item.type}-create-custom-field`) + await screen.findByTestId(`${item.key}-${item.type}-create-custom-field`) ).toBeInTheDocument(); } }); - it('should prefill the form when provided with initialValue', () => { - const { getByTestId } = render( - <MockHookWrapperComponent> - <CreateCaseForm - {...casesFormProps} - initialValue={{ title: 'title', description: 'description' }} - /> - </MockHookWrapperComponent> + it('should prefill the form when provided with initialValue', async () => { + appMockRenderer.render( + <CreateCaseForm + {...casesFormProps} + initialValue={{ title: 'title', description: 'description' }} + /> ); - const titleInput = within(getByTestId('caseTitle')).getByTestId('input'); - const descriptionInput = within(getByTestId('caseDescription')).getByRole('textbox'); + const titleInput = within(await screen.findByTestId('caseTitle')).getByTestId('input'); + const descriptionInput = within(await screen.findByTestId('caseDescription')).getByRole( + 'textbox' + ); expect(titleInput).toHaveValue('title'); expect(descriptionInput).toHaveValue('description'); }); describe('draft comment ', () => { - it('should clear session storage key on cancel', () => { - const result = render( - <MockHookWrapperComponent> - <CreateCaseForm - {...casesFormProps} - initialValue={{ title: 'title', description: 'description' }} - /> - </MockHookWrapperComponent> + it('should clear session storage key on cancel', async () => { + appMockRenderer.render( + <CreateCaseForm + {...casesFormProps} + initialValue={{ title: 'title', description: 'description' }} + /> ); - const cancelBtn = result.getByTestId('create-case-cancel'); + const cancelBtn = await screen.findByTestId('create-case-cancel'); fireEvent.click(cancelBtn); - fireEvent.click(result.getByTestId('confirmModalConfirmButton')); + fireEvent.click(await screen.findByTestId('confirmModalConfirmButton')); expect(casesFormProps.onCancel).toHaveBeenCalled(); expect(sessionStorage.getItem(draftStorageKey)).toBe(null); }); - it('should clear session storage key on submit', () => { - const result = render( - <MockHookWrapperComponent> - <CreateCaseForm - {...casesFormProps} - initialValue={{ title: 'title', description: 'description' }} - /> - </MockHookWrapperComponent> + it('should clear session storage key on submit', async () => { + appMockRenderer.render( + <CreateCaseForm + {...casesFormProps} + initialValue={{ title: 'title', description: 'description' }} + /> ); - const submitBtn = result.getByTestId('create-case-submit'); + const submitBtn = await screen.findByTestId('create-case-submit'); fireEvent.click(submitBtn); @@ -304,4 +224,115 @@ describe('CreateCaseForm', () => { }); }); }); + + describe('templates', () => { + beforeEach(() => { + useGetAllCaseConfigurationsMock.mockReturnValue({ + ...useGetAllCaseConfigurationsResponse, + data: [ + { + ...useGetAllCaseConfigurationsResponse.data[0], + customFields: [ + { + key: 'first_custom_field_key', + type: CustomFieldTypes.TEXT, + required: false, + label: 'My test label 1', + }, + ], + templates: templatesConfigurationMock, + }, + ], + }); + }); + + it('should populate the cases fields correctly when selecting a case template', async () => { + const license = licensingMock.createLicense({ + license: { type: 'platinum' }, + }); + const selectedTemplate = templatesConfigurationMock[4]; + + appMockRenderer = createAppMockRenderer({ license }); + appMockRenderer.render(<CreateCaseForm {...casesFormProps} />); + + userEvent.selectOptions( + await screen.findByTestId('create-case-template-select'), + selectedTemplate.name + ); + + const title = within(await screen.findByTestId('caseTitle')).getByTestId('input'); + const description = within(await screen.findByTestId('caseDescription')).getByRole('textbox'); + const tags = within(await screen.findByTestId('caseTags')).getByTestId('comboBoxInput'); + const category = within(await screen.findByTestId('caseCategory')).getByTestId( + 'comboBoxSearchInput' + ); + const severity = await screen.findByTestId('case-severity-selection'); + const customField = await screen.findByTestId( + 'first_custom_field_key-text-create-custom-field' + ); + + expect(title).toHaveValue(selectedTemplate.caseFields?.title); + expect(description).toHaveValue(selectedTemplate.caseFields?.description); + expect(tags).toHaveTextContent(selectedTemplate.caseFields?.tags?.[0]!); + expect(category).toHaveValue(selectedTemplate.caseFields?.category); + expect(severity).toHaveTextContent('High'); + expect(customField).toHaveValue('this is a text field value'); + expect(await screen.findByText('Damaged Raccoon')).toBeInTheDocument(); + + expect(await screen.findByText('Jira')).toBeInTheDocument(); + expect(await screen.findByTestId('connector-fields-jira')).toBeInTheDocument(); + }); + + it('changes templates correctly', async () => { + const license = licensingMock.createLicense({ + license: { type: 'platinum' }, + }); + const firstTemplate = templatesConfigurationMock[4]; + const secondTemplate = templatesConfigurationMock[2]; + + appMockRenderer = createAppMockRenderer({ license }); + appMockRenderer.render(<CreateCaseForm {...casesFormProps} />); + + userEvent.selectOptions( + await screen.findByTestId('create-case-template-select'), + firstTemplate.name + ); + + const title = within(await screen.findByTestId('caseTitle')).getByTestId('input'); + const description = within(await screen.findByTestId('caseDescription')).getByRole('textbox'); + const tags = within(await screen.findByTestId('caseTags')).getByTestId('comboBoxInput'); + const category = within(await screen.findByTestId('caseCategory')).getByTestId( + 'comboBoxSearchInput' + ); + const assignees = within(await screen.findByTestId('caseAssignees')).getByTestId( + 'comboBoxSearchInput' + ); + const severity = await screen.findByTestId('case-severity-selection'); + const customField = await screen.findByTestId( + 'first_custom_field_key-text-create-custom-field' + ); + + expect(title).toHaveValue(firstTemplate.caseFields?.title); + + userEvent.selectOptions( + await screen.findByTestId('create-case-template-select'), + secondTemplate.name + ); + + expect(title).toHaveValue(secondTemplate.caseFields?.title); + expect(description).not.toHaveValue(); + expect(tags).toHaveTextContent(secondTemplate.caseFields?.tags?.[0]!); + expect(tags).toHaveTextContent(secondTemplate.caseFields?.tags?.[1]!); + expect(category).not.toHaveValue(); + expect(severity).toHaveTextContent('Medium'); + expect(customField).not.toHaveValue(); + expect(assignees).not.toHaveValue(); + + expect(screen.queryByText('Damaged Raccoon')).not.toBeInTheDocument(); + expect(screen.queryByText('Jira')).not.toBeInTheDocument(); + expect(screen.queryByTestId('connector-fields-jira')).not.toBeInTheDocument(); + + expect(await screen.findByText('No connector selected')).toBeInTheDocument(); + }); + }); }); diff --git a/x-pack/plugins/cases/public/components/create/form.tsx b/x-pack/plugins/cases/public/components/create/form.tsx index 4c95b6e11a11a..db6df19308e51 100644 --- a/x-pack/plugins/cases/public/components/create/form.tsx +++ b/x-pack/plugins/cases/public/components/create/form.tsx @@ -5,30 +5,13 @@ * 2.0. */ -import React, { useMemo } from 'react'; -import type { EuiThemeComputed } from '@elastic/eui'; -import { - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiLoadingSpinner, - EuiSteps, - useEuiTheme, - logicalCSS, -} from '@elastic/eui'; -import { css } from '@emotion/react'; - +import React, { useCallback, useState, useMemo } from 'react'; +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiFormRow } from '@elastic/eui'; import { useFormContext } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; - -import type { ActionConnector } from '../../../common/types/domain'; import type { CasePostRequest } from '../../../common/types/api'; -import { Title } from './title'; -import { Description, fieldName as descriptionFieldName } from './description'; -import { Tags } from './tags'; -import { Connector } from './connector'; +import { fieldName as descriptionFieldName } from '../case_form_fields/description'; import * as i18n from './translations'; -import { SyncAlertsToggle } from './sync_alerts_toggle'; -import type { CaseUI } from '../../containers/types'; +import type { CasesConfigurationUI, CaseUI } from '../../containers/types'; import type { CasesTimelineIntegration } from '../timeline_context'; import { CasesTimelineIntegrationProvider } from '../timeline_context'; import { InsertTimeline } from '../insert_timeline'; @@ -37,33 +20,19 @@ import type { UseCreateAttachments } from '../../containers/use_create_attachmen import { getMarkdownEditorStorageKey } from '../markdown_editor/utils'; import { SubmitCaseButton } from './submit_button'; import { FormContext } from './form_context'; -import { useCasesFeatures } from '../../common/use_cases_features'; -import { CreateCaseOwnerSelector } from './owner_selector'; import { useCasesContext } from '../cases_context/use_cases_context'; -import { useAvailableCasesOwners } from '../app/use_available_owners'; import type { CaseAttachmentsWithoutOwner } from '../../types'; -import { Severity } from './severity'; -import { Assignees } from './assignees'; import { useCancelCreationAction } from './use_cancel_creation_action'; import { CancelCreationConfirmationModal } from './cancel_creation_confirmation_modal'; -import { Category } from './category'; -import { CustomFields } from './custom_fields'; - -const containerCss = (euiTheme: EuiThemeComputed<{}>, big?: boolean) => - big - ? css` - ${logicalCSS('margin-top', euiTheme.size.xl)}; - ` - : css` - ${logicalCSS('margin-top', euiTheme.size.base)}; - `; +import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors'; +import { useGetAllCaseConfigurations } from '../../containers/configure/use_get_all_case_configurations'; +import type { CreateCaseFormFieldsProps } from './form_fields'; +import { CreateCaseFormFields } from './form_fields'; +import { getConfigurationByOwner } from '../../containers/configure/utils'; +import { CreateCaseOwnerSelector } from './owner_selector'; +import { useAvailableCasesOwners } from '../app/use_available_owners'; +import { getInitialCaseValue, getOwnerDefaultValue } from './utils'; -export interface CreateCaseFormFieldsProps { - connectors: ActionConnector[]; - isLoadingConnectors: boolean; - withSteps: boolean; - draftStorageKey: string; -} export interface CreateCaseFormProps extends Pick<Partial<CreateCaseFormFieldsProps>, 'withSteps'> { onCancel: () => void; onSuccess: (theCase: CaseUI) => void; @@ -76,130 +45,70 @@ export interface CreateCaseFormProps extends Pick<Partial<CreateCaseFormFieldsPr initialValue?: Pick<CasePostRequest, 'title' | 'description'>; } -const empty: ActionConnector[] = []; -export const CreateCaseFormFields: React.FC<CreateCaseFormFieldsProps> = React.memo( - ({ connectors, isLoadingConnectors, withSteps, draftStorageKey }) => { +type FormFieldsWithFormContextProps = Pick< + CreateCaseFormFieldsProps, + 'withSteps' | 'draftStorageKey' +> & { + isLoadingCaseConfiguration: boolean; + currentConfiguration: CasesConfigurationUI; + selectedOwner: string; + onSelectedOwner: (owner: string) => void; +}; + +export const FormFieldsWithFormContext: React.FC<FormFieldsWithFormContextProps> = React.memo( + ({ + currentConfiguration, + isLoadingCaseConfiguration, + withSteps, + draftStorageKey, + selectedOwner, + onSelectedOwner, + }) => { const { owner } = useCasesContext(); - const { isSubmitting } = useFormContext(); - const { isSyncAlertsEnabled, caseAssignmentAuthorized } = useCasesFeatures(); - const { euiTheme } = useEuiTheme(); const availableOwners = useAvailableCasesOwners(); - const canShowCaseSolutionSelection = !owner.length && availableOwners.length > 1; - - const firstStep = useMemo( - () => ({ - title: i18n.STEP_ONE_TITLE, - children: ( - <> - <Title isLoading={isSubmitting} /> - {caseAssignmentAuthorized ? ( - <div css={containerCss(euiTheme)}> - <Assignees isLoading={isSubmitting} /> - </div> - ) : null} - <div css={containerCss(euiTheme)}> - <Tags isLoading={isSubmitting} /> - </div> - <div css={containerCss(euiTheme)}> - <Category isLoading={isSubmitting} /> - </div> - <div css={containerCss(euiTheme)}> - <Severity isLoading={isSubmitting} /> - </div> - {canShowCaseSolutionSelection && ( - <div css={containerCss(euiTheme, true)}> - <CreateCaseOwnerSelector - availableOwners={availableOwners} - isLoading={isSubmitting} - /> - </div> - )} - <div css={containerCss(euiTheme, true)}> - <Description isLoading={isSubmitting} draftStorageKey={draftStorageKey} /> - </div> - <div css={containerCss(euiTheme)}> - <CustomFields isLoading={isSubmitting} /> - </div> - <div css={containerCss(euiTheme)} /> - </> - ), - }), - [ - isSubmitting, - euiTheme, - caseAssignmentAuthorized, - canShowCaseSolutionSelection, - availableOwners, - draftStorageKey, - ] - ); - - const secondStep = useMemo( - () => ({ - title: i18n.STEP_TWO_TITLE, - children: ( - <div> - <SyncAlertsToggle isLoading={isSubmitting} /> - </div> - ), - }), - [isSubmitting] - ); - - const thirdStep = useMemo( - () => ({ - title: i18n.STEP_THREE_TITLE, - children: ( - <div> - <Connector - connectors={connectors} - isLoadingConnectors={isLoadingConnectors} - isLoading={isSubmitting} - /> - </div> - ), - }), - [connectors, isLoadingConnectors, isSubmitting] - ); - - const allSteps = useMemo( - () => [firstStep, ...(isSyncAlertsEnabled ? [secondStep] : []), thirdStep], - [isSyncAlertsEnabled, firstStep, secondStep, thirdStep] + const shouldShowOwnerSelector = Boolean(!owner.length && availableOwners.length > 1); + const { reset } = useFormContext(); + + const { data: connectors = [], isLoading: isLoadingConnectors } = + useGetSupportedActionConnectors(); + + const onOwnerChange = useCallback( + (newOwner: string) => { + onSelectedOwner(newOwner); + reset({ + resetValues: true, + defaultValue: getInitialCaseValue({ + owner: newOwner, + connector: currentConfiguration.connector, + }), + }); + }, + [currentConfiguration.connector, onSelectedOwner, reset] ); return ( <> - {isSubmitting && ( - <EuiLoadingSpinner - css={css` - position: absolute; - top: 50%; - left: 50%; - z-index: 99; - `} - data-test-subj="create-case-loading-spinner" - size="xl" - /> - )} - {withSteps ? ( - <EuiSteps - headingElement="h2" - steps={allSteps} - data-test-subj={'case-creation-form-steps'} + {shouldShowOwnerSelector && ( + <CreateCaseOwnerSelector + selectedOwner={selectedOwner} + availableOwners={availableOwners} + isLoading={isLoadingCaseConfiguration} + onOwnerChange={onOwnerChange} /> - ) : ( - <> - {firstStep.children} - {isSyncAlertsEnabled && secondStep.children} - {thirdStep.children} - </> )} + <CreateCaseFormFields + connectors={connectors} + isLoading={isLoadingConnectors || isLoadingCaseConfiguration} + withSteps={withSteps} + draftStorageKey={draftStorageKey} + configuration={currentConfiguration} + /> </> ); } ); -CreateCaseFormFields.displayName = 'CreateCaseFormFields'; +FormFieldsWithFormContext.displayName = 'FormFieldsWithFormContext'; export const CreateCaseForm: React.FC<CreateCaseFormProps> = React.memo( ({ @@ -212,6 +121,13 @@ export const CreateCaseForm: React.FC<CreateCaseFormProps> = React.memo( initialValue, }) => { const { owner } = useCasesContext(); + const availableOwners = useAvailableCasesOwners(); + const defaultOwnerValue = owner[0] ?? getOwnerDefaultValue(availableOwners); + const [selectedOwner, onSelectedOwner] = useState<string>(defaultOwnerValue); + + const { data: configurations, isLoading: isLoadingCaseConfiguration } = + useGetAllCaseConfigurations(); + const draftStorageKey = getMarkdownEditorStorageKey({ appId: owner[0], caseId: 'createCase', @@ -233,6 +149,15 @@ export const CreateCaseForm: React.FC<CreateCaseFormProps> = React.memo( return onSuccess(theCase); }; + const currentConfiguration = useMemo( + () => + getConfigurationByOwner({ + configurations, + owner: selectedOwner, + }), + [configurations, selectedOwner] + ); + return ( <CasesTimelineIntegrationProvider timelineIntegration={timelineIntegration}> <FormContext @@ -240,14 +165,18 @@ export const CreateCaseForm: React.FC<CreateCaseFormProps> = React.memo( onSuccess={handleOnSuccess} attachments={attachments} initialValue={initialValue} + currentConfiguration={currentConfiguration} + selectedOwner={selectedOwner} > - <CreateCaseFormFields - connectors={empty} - isLoadingConnectors={false} + <FormFieldsWithFormContext withSteps={withSteps} draftStorageKey={draftStorageKey} + selectedOwner={selectedOwner} + onSelectedOwner={onSelectedOwner} + isLoadingCaseConfiguration={isLoadingCaseConfiguration} + currentConfiguration={currentConfiguration} /> - <div> + <EuiFormRow fullWidth> <EuiFlexGroup alignItems="center" justifyContent="flexEnd" @@ -275,7 +204,7 @@ export const CreateCaseForm: React.FC<CreateCaseFormProps> = React.memo( <SubmitCaseButton /> </EuiFlexItem> </EuiFlexGroup> - </div> + </EuiFormRow> <InsertTimeline fieldName={descriptionFieldName} /> </FormContext> </CasesTimelineIntegrationProvider> diff --git a/x-pack/plugins/cases/public/components/create/form_context.test.tsx b/x-pack/plugins/cases/public/components/create/form_context.test.tsx index 4c8991f0cb590..5417807edf168 100644 --- a/x-pack/plugins/cases/public/components/create/form_context.test.tsx +++ b/x-pack/plugins/cases/public/components/create/form_context.test.tsx @@ -16,7 +16,6 @@ import { createAppMockRenderer } from '../../common/mock'; import { usePostCase } from '../../containers/use_post_case'; import { useCreateAttachments } from '../../containers/use_create_attachments'; -import { useGetCaseConfiguration } from '../../containers/configure/use_get_case_configuration'; import { useGetAllCaseConfigurations } from '../../containers/configure/use_get_all_case_configurations'; import { useGetIncidentTypes } from '../connectors/resilient/use_get_incident_types'; @@ -39,8 +38,6 @@ import { useGetChoicesResponse, } from './mock'; import { FormContext } from './form_context'; -import type { CreateCaseFormFieldsProps } from './form'; -import { CreateCaseFormFields } from './form'; import { SubmitCaseButton } from './submit_button'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; import userEvent from '@testing-library/user-event'; @@ -60,13 +57,15 @@ import { CustomFieldTypes, } from '../../../common/types/domain'; import { useAvailableCasesOwners } from '../app/use_available_owners'; +import type { CreateCaseFormFieldsProps } from './form_fields'; +import { CreateCaseFormFields } from './form_fields'; +import { SECURITY_SOLUTION_OWNER } from '../../../common'; jest.mock('../../containers/use_post_case'); jest.mock('../../containers/use_create_attachments'); jest.mock('../../containers/use_post_push_to_service'); jest.mock('../../containers/use_get_tags'); jest.mock('../../containers/configure/use_get_supported_action_connectors'); -jest.mock('../../containers/configure/use_get_case_configuration'); jest.mock('../../containers/configure/use_get_all_case_configurations'); jest.mock('../connectors/resilient/use_get_incident_types'); jest.mock('../connectors/resilient/use_get_severity'); @@ -81,7 +80,6 @@ jest.mock('../../containers/use_get_categories'); jest.mock('../app/use_available_owners'); const useGetConnectorsMock = useGetSupportedActionConnectors as jest.Mock; -const useGetCaseConfigurationMock = useGetCaseConfiguration as jest.Mock; const useGetAllCaseConfigurationsMock = useGetAllCaseConfigurations as jest.Mock; const usePostCaseMock = usePostCase as jest.Mock; const useCreateAttachmentsMock = useCreateAttachments as jest.Mock; @@ -106,8 +104,11 @@ const defaultPostCase = { mutateAsync: postCase, }; +const currentConfiguration = useGetAllCaseConfigurationsResponse.data[0]; + const defaultCreateCaseForm: CreateCaseFormFieldsProps = { - isLoadingConnectors: false, + configuration: currentConfiguration, + isLoading: false, connectors: [], withSteps: true, draftStorageKey: 'cases.kibana.createCase.description.markdownEditor', @@ -205,7 +206,6 @@ describe('Create case', () => { useCreateAttachmentsMock.mockImplementation(() => ({ mutateAsync: createAttachments })); usePostPushToServiceMock.mockImplementation(() => defaultPostPushToService); useGetConnectorsMock.mockReturnValue(sampleConnectorData); - useGetCaseConfigurationMock.mockImplementation(() => useCaseConfigureResponse); useGetAllCaseConfigurationsMock.mockImplementation(() => useGetAllCaseConfigurationsResponse); useGetIncidentTypesMock.mockReturnValue(useGetIncidentTypesResponse); useGetSeverityMock.mockReturnValue(useGetSeverityResponse); @@ -244,7 +244,11 @@ describe('Create case', () => { describe('Step 1 - Case Fields', () => { it('renders correctly', async () => { appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + currentConfiguration={currentConfiguration} + > <CreateCaseFormFields {...defaultCreateCaseForm} /> <SubmitCaseButton /> </FormContext> @@ -269,7 +273,11 @@ describe('Create case', () => { }); appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + currentConfiguration={currentConfiguration} + > <CreateCaseFormFields {...defaultCreateCaseForm} /> <SubmitCaseButton /> </FormContext> @@ -294,7 +302,11 @@ describe('Create case', () => { }); appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + currentConfiguration={currentConfiguration} + > <CreateCaseFormFields {...defaultCreateCaseForm} /> <SubmitCaseButton /> </FormContext> @@ -328,7 +340,11 @@ describe('Create case', () => { const newCategory = 'First '; appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + currentConfiguration={currentConfiguration} + > <CreateCaseFormFields {...defaultCreateCaseForm} /> <SubmitCaseButton /> </FormContext> @@ -373,7 +389,11 @@ describe('Create case', () => { }); appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + currentConfiguration={currentConfiguration} + > <CreateCaseFormFields {...defaultCreateCaseForm} /> <SubmitCaseButton /> </FormContext> @@ -408,7 +428,11 @@ describe('Create case', () => { }); appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + currentConfiguration={currentConfiguration} + > <CreateCaseFormFields {...defaultCreateCaseForm} /> <SubmitCaseButton /> </FormContext> @@ -431,7 +455,11 @@ describe('Create case', () => { it('should select LOW as the default severity', async () => { appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + currentConfiguration={currentConfiguration} + > <CreateCaseFormFields {...defaultCreateCaseForm} /> <SubmitCaseButton /> </FormContext> @@ -446,27 +474,28 @@ describe('Create case', () => { }); it('should submit form with custom fields', async () => { - useGetAllCaseConfigurationsMock.mockImplementation(() => ({ - ...useGetAllCaseConfigurationsResponse, - data: [ - { - ...useGetAllCaseConfigurationsResponse.data[0], - customFields: [ - ...customFieldsConfigurationMock, - { - key: 'my_custom_field_key', - type: CustomFieldTypes.TEXT, - label: 'my custom field label', - required: false, - }, - ], - }, - ], - })); + const configurations = [ + { + ...useGetAllCaseConfigurationsResponse.data[0], + customFields: [ + ...customFieldsConfigurationMock, + { + key: 'my_custom_field_key', + type: CustomFieldTypes.TEXT, + label: 'my custom field label', + required: false, + }, + ], + }, + ]; appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> - <CreateCaseFormFields {...defaultCreateCaseForm} /> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + currentConfiguration={configurations[0]} + > + <CreateCaseFormFields {...defaultCreateCaseForm} configuration={configurations[0]} /> <SubmitCaseButton /> </FormContext> ); @@ -477,7 +506,7 @@ describe('Create case', () => { const textField = customFieldsConfigurationMock[0]; const toggleField = customFieldsConfigurationMock[1]; - expect(await screen.findByTestId('create-case-custom-fields')).toBeInTheDocument(); + expect(await screen.findByTestId('caseCustomFields')).toBeInTheDocument(); const textCustomField = await screen.findByTestId( `${textField.key}-${textField.type}-create-custom-field` @@ -512,147 +541,20 @@ describe('Create case', () => { }); }); - it('should change custom fields based on the selected owner', async () => { - appMockRender = createAppMockRenderer({ owner: [] }); - - const securityCustomField = { - key: 'security_custom_field', - type: CustomFieldTypes.TEXT, - label: 'security custom field', - required: false, - }; - const o11yCustomField = { - key: 'o11y_field_key', - type: CustomFieldTypes.TEXT, - label: 'observability custom field', - required: false, - }; - const stackCustomField = { - key: 'stack_field_key', - type: CustomFieldTypes.TEXT, - label: 'stack custom field', - required: false, - }; - - useGetAllCaseConfigurationsMock.mockImplementation(() => ({ - ...useGetAllCaseConfigurationsResponse, - data: [ - { - ...useGetAllCaseConfigurationsResponse.data[0], - owner: 'securitySolution', - customFields: [securityCustomField], - }, - { - ...useGetAllCaseConfigurationsResponse.data[0], - owner: 'observability', - customFields: [o11yCustomField], - }, - { - ...useGetAllCaseConfigurationsResponse.data[0], - owner: 'cases', - customFields: [stackCustomField], - }, - ], - })); - - appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> - <CreateCaseFormFields {...defaultCreateCaseForm} /> - <SubmitCaseButton /> - </FormContext> - ); - - await waitForFormToRender(screen); - await fillFormReactTestingLib({ renderer: screen }); - - const createCaseCustomFields = await screen.findByTestId('create-case-custom-fields'); - - // the default selectedOwner is securitySolution - // only the security custom field should be displayed - expect( - await within(createCaseCustomFields).findByTestId( - `${securityCustomField.key}-${securityCustomField.type}-create-custom-field` - ) - ).toBeInTheDocument(); - expect( - await within(createCaseCustomFields).queryByTestId( - `${o11yCustomField.key}-${o11yCustomField.type}-create-custom-field` - ) - ).not.toBeInTheDocument(); - expect( - await within(createCaseCustomFields).queryByTestId( - `${stackCustomField.key}-${stackCustomField.type}-create-custom-field` - ) - ).not.toBeInTheDocument(); - - const caseOwnerSelector = await screen.findByTestId('caseOwnerSelector'); - - userEvent.click(await within(caseOwnerSelector).findByLabelText('Observability')); - - // only the o11y custom field should be displayed - expect( - await within(createCaseCustomFields).findByTestId( - `${o11yCustomField.key}-${o11yCustomField.type}-create-custom-field` - ) - ).toBeInTheDocument(); - expect( - await within(createCaseCustomFields).queryByTestId( - `${securityCustomField.key}-${securityCustomField.type}-create-custom-field` - ) - ).not.toBeInTheDocument(); - expect( - await within(createCaseCustomFields).queryByTestId( - `${stackCustomField.key}-${stackCustomField.type}-create-custom-field` - ) - ).not.toBeInTheDocument(); - - userEvent.click(await within(caseOwnerSelector).findByLabelText('Stack')); - - // only the stack custom field should be displayed - expect( - await within(createCaseCustomFields).findByTestId( - `${stackCustomField.key}-${stackCustomField.type}-create-custom-field` - ) - ).toBeInTheDocument(); - expect( - await within(createCaseCustomFields).queryByTestId( - `${securityCustomField.key}-${securityCustomField.type}-create-custom-field` - ) - ).not.toBeInTheDocument(); - expect( - await within(createCaseCustomFields).queryByTestId( - `${o11yCustomField.key}-${o11yCustomField.type}-create-custom-field` - ) - ).not.toBeInTheDocument(); - }); - it('should select the default connector set in the configuration', async () => { - useGetCaseConfigurationMock.mockImplementation(() => ({ - ...useCaseConfigureResponse, - data: { - ...useCaseConfigureResponse.data, - connector: { - id: 'servicenow-1', - name: 'SN', - type: ConnectorTypes.serviceNowITSM, - fields: null, - }, + const configuration = { + ...useCaseConfigureResponse.data, + connector: { + id: 'servicenow-1', + name: 'SN', + type: ConnectorTypes.serviceNowITSM, + fields: null, }, - })); + }; useGetAllCaseConfigurationsMock.mockImplementation(() => ({ ...useGetAllCaseConfigurationsResponse, - data: [ - { - ...useGetAllCaseConfigurationsResponse.data, - connector: { - id: 'servicenow-1', - name: 'SN', - type: ConnectorTypes.serviceNowITSM, - fields: null, - }, - }, - ], + data: [configuration], })); useGetConnectorsMock.mockReturnValue({ @@ -661,8 +563,16 @@ describe('Create case', () => { }); appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> - <CreateCaseFormFields {...defaultCreateCaseForm} /> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + currentConfiguration={currentConfiguration} + > + <CreateCaseFormFields + {...defaultCreateCaseForm} + configuration={configuration} + connectors={connectorsMock} + /> <SubmitCaseButton /> </FormContext> ); @@ -694,32 +604,19 @@ describe('Create case', () => { }); it('should default to none if the default connector does not exist in connectors', async () => { - useGetCaseConfigurationMock.mockImplementation(() => ({ - ...useCaseConfigureResponse, - data: { - ...useCaseConfigureResponse.data, - connector: { - id: 'not-exist', - name: 'SN', - type: ConnectorTypes.serviceNowITSM, - fields: null, - }, + const configuration = { + ...useCaseConfigureResponse.data, + connector: { + id: 'not-exist', + name: 'SN', + type: ConnectorTypes.serviceNowITSM, + fields: null, }, - })); + }; useGetAllCaseConfigurationsMock.mockImplementation(() => ({ ...useGetAllCaseConfigurationsResponse, - data: [ - { - ...useGetAllCaseConfigurationsResponse.data, - connector: { - id: 'not-exist', - name: 'SN', - type: ConnectorTypes.serviceNowITSM, - fields: null, - }, - }, - ], + data: [configuration], })); useGetConnectorsMock.mockReturnValue({ @@ -728,8 +625,16 @@ describe('Create case', () => { }); appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> - <CreateCaseFormFields {...defaultCreateCaseForm} /> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + currentConfiguration={currentConfiguration} + > + <CreateCaseFormFields + {...defaultCreateCaseForm} + configuration={configuration} + connectors={connectorsMock} + /> <SubmitCaseButton /> </FormContext> ); @@ -757,7 +662,11 @@ describe('Create case', () => { }); appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + currentConfiguration={currentConfiguration} + > <CreateCaseFormFields {...defaultCreateCaseForm} /> <SubmitCaseButton /> </FormContext> @@ -788,8 +697,12 @@ describe('Create case', () => { }); appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> - <CreateCaseFormFields {...defaultCreateCaseForm} /> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + currentConfiguration={currentConfiguration} + > + <CreateCaseFormFields {...defaultCreateCaseForm} connectors={connectorsMock} /> <SubmitCaseButton /> </FormContext> ); @@ -861,8 +774,12 @@ describe('Create case', () => { }); appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> - <CreateCaseFormFields {...defaultCreateCaseForm} /> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + currentConfiguration={currentConfiguration} + > + <CreateCaseFormFields {...defaultCreateCaseForm} connectors={connectors} /> <SubmitCaseButton /> </FormContext> ); @@ -914,8 +831,13 @@ describe('Create case', () => { }); appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess} afterCaseCreated={afterCaseCreated}> - <CreateCaseFormFields {...defaultCreateCaseForm} /> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + afterCaseCreated={afterCaseCreated} + currentConfiguration={currentConfiguration} + > + <CreateCaseFormFields {...defaultCreateCaseForm} connectors={connectorsMock} /> <SubmitCaseButton /> </FormContext> ); @@ -977,7 +899,12 @@ describe('Create case', () => { ]; appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess} attachments={attachments}> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + attachments={attachments} + currentConfiguration={currentConfiguration} + > <CreateCaseFormFields {...defaultCreateCaseForm} /> <SubmitCaseButton /> </FormContext> @@ -1008,7 +935,12 @@ describe('Create case', () => { const attachments: CaseAttachments = []; appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess} attachments={attachments}> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + attachments={attachments} + currentConfiguration={currentConfiguration} + > <CreateCaseFormFields {...defaultCreateCaseForm} /> <SubmitCaseButton /> </FormContext> @@ -1044,11 +976,13 @@ describe('Create case', () => { appMockRender.render( <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + currentConfiguration={currentConfiguration} onSuccess={onFormSubmitSuccess} afterCaseCreated={afterCaseCreated} attachments={attachments} > - <CreateCaseFormFields {...defaultCreateCaseForm} /> + <CreateCaseFormFields {...defaultCreateCaseForm} connectors={connectorsMock} /> <SubmitCaseButton /> </FormContext> ); @@ -1098,7 +1032,11 @@ describe('Create case', () => { }; appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + currentConfiguration={currentConfiguration} + > <CreateCaseFormFields {...defaultCreateCaseForm} /> <SubmitCaseButton /> </FormContext> @@ -1129,7 +1067,11 @@ describe('Create case', () => { it('should submit assignees', async () => { appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + currentConfiguration={currentConfiguration} + > <CreateCaseFormFields {...defaultCreateCaseForm} /> <SubmitCaseButton /> </FormContext> @@ -1168,7 +1110,11 @@ describe('Create case', () => { useLicenseMock.mockReturnValue({ isAtLeastPlatinum: () => false }); appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + currentConfiguration={currentConfiguration} + > <CreateCaseFormFields {...defaultCreateCaseForm} /> <SubmitCaseButton /> </FormContext> @@ -1193,7 +1139,11 @@ describe('Create case', () => { it('should have session storage value same as draft comment', async () => { appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + currentConfiguration={currentConfiguration} + > <CreateCaseFormFields {...defaultCreateCaseForm} /> <SubmitCaseButton /> </FormContext> @@ -1221,14 +1171,18 @@ describe('Create case', () => { it('should have session storage value same as draft comment', async () => { appMockRender.render( - <FormContext onSuccess={onFormSubmitSuccess}> + <FormContext + selectedOwner={SECURITY_SOLUTION_OWNER} + onSuccess={onFormSubmitSuccess} + currentConfiguration={currentConfiguration} + > <CreateCaseFormFields {...defaultCreateCaseForm} /> <SubmitCaseButton /> </FormContext> ); await waitForFormToRender(screen); - const descriptionInput = within(screen.getByTestId('caseDescription')).getByTestId( + const descriptionInput = within(await screen.findByTestId('caseDescription')).getByTestId( 'euiMarkdownEditorTextArea' ); diff --git a/x-pack/plugins/cases/public/components/create/form_context.tsx b/x-pack/plugins/cases/public/components/create/form_context.tsx index 04a327868418f..54198f8510e5e 100644 --- a/x-pack/plugins/cases/public/components/create/form_context.tsx +++ b/x-pack/plugins/cases/public/components/create/form_context.tsx @@ -5,46 +5,22 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback } from 'react'; import { Form, useForm } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { NONE_CONNECTOR_ID } from '../../../common/constants'; -import { CaseSeverity } from '../../../common/types/domain'; -import type { FormProps } from './schema'; import { schema } from './schema'; -import { getNoneConnector, normalizeActionConnector } from '../configure_cases/utils'; import { usePostCase } from '../../containers/use_post_case'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; -import type { CasesConfigurationUI, CaseUI, CaseUICustomField } from '../../containers/types'; +import type { CasesConfigurationUI, CaseUI } from '../../containers/types'; import type { CasePostRequest } from '../../../common/types/api'; import type { UseCreateAttachments } from '../../containers/use_create_attachments'; import { useCreateAttachments } from '../../containers/use_create_attachments'; -import { useCasesContext } from '../cases_context/use_cases_context'; -import { useCasesFeatures } from '../../common/use_cases_features'; -import { - getConnectorById, - getConnectorsFormDeserializer, - getConnectorsFormSerializer, - convertCustomFieldValue, -} from '../utils'; -import { useAvailableCasesOwners } from '../app/use_available_owners'; import type { CaseAttachmentsWithoutOwner } from '../../types'; import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors'; import { useCreateCaseWithAttachmentsTransaction } from '../../common/apm/use_cases_transactions'; -import { useGetAllCaseConfigurations } from '../../containers/configure/use_get_all_case_configurations'; import { useApplication } from '../../common/lib/kibana/use_application'; - -const initialCaseValue: FormProps = { - description: '', - tags: [], - title: '', - severity: CaseSeverity.LOW, - connectorId: NONE_CONNECTOR_ID, - fields: null, - syncAlerts: true, - assignees: [], - customFields: {}, -}; +import { createFormSerializer, createFormDeserializer, getInitialCaseValue } from './utils'; +import type { CaseFormFieldsSchemaProps } from '../case_form_fields/schema'; interface Props { afterCaseCreated?: ( @@ -55,6 +31,8 @@ interface Props { onSuccess?: (theCase: CaseUI) => void; attachments?: CaseAttachmentsWithoutOwner; initialValue?: Pick<CasePostRequest, 'title' | 'description'>; + currentConfiguration: CasesConfigurationUI; + selectedOwner: string; } export const FormContext: React.FC<Props> = ({ @@ -63,111 +41,23 @@ export const FormContext: React.FC<Props> = ({ onSuccess, attachments, initialValue, + currentConfiguration, + selectedOwner, }) => { - const { data: connectors = [], isLoading: isLoadingConnectors } = - useGetSupportedActionConnectors(); - const { data: allConfigurations } = useGetAllCaseConfigurations(); - const { owner } = useCasesContext(); const { appId } = useApplication(); - const { isSyncAlertsEnabled } = useCasesFeatures(); + const { data: connectors = [] } = useGetSupportedActionConnectors(); const { mutateAsync: postCase } = usePostCase(); const { mutateAsync: createAttachments } = useCreateAttachments(); const { mutateAsync: pushCaseToExternalService } = usePostPushToService(); const { startTransaction } = useCreateCaseWithAttachmentsTransaction(); - const availableOwners = useAvailableCasesOwners(); - - const trimUserFormData = (userFormData: CaseUI) => { - let formData = { - ...userFormData, - title: userFormData.title.trim(), - description: userFormData.description.trim(), - }; - - if (userFormData.category) { - formData = { ...formData, category: userFormData.category.trim() }; - } - - if (userFormData.tags) { - formData = { ...formData, tags: userFormData.tags.map((tag: string) => tag.trim()) }; - } - - return formData; - }; - - const transformCustomFieldsData = useCallback( - ( - customFields: Record<string, string | boolean>, - selectedCustomFieldsConfiguration: CasesConfigurationUI['customFields'] - ) => { - const transformedCustomFields: CaseUI['customFields'] = []; - - if (!customFields || !selectedCustomFieldsConfiguration.length) { - return []; - } - - for (const [key, value] of Object.entries(customFields)) { - const configCustomField = selectedCustomFieldsConfiguration.find( - (item) => item.key === key - ); - if (configCustomField) { - transformedCustomFields.push({ - key: configCustomField.key, - type: configCustomField.type, - value: convertCustomFieldValue(value), - } as CaseUICustomField); - } - } - - return transformedCustomFields; - }, - [] - ); const submitCase = useCallback( - async ( - { - connectorId: dataConnectorId, - fields, - syncAlerts = isSyncAlertsEnabled, - ...dataWithoutConnectorId - }, - isValid - ) => { + async (data: CasePostRequest, isValid) => { if (isValid) { - const { selectedOwner, customFields, ...userFormData } = dataWithoutConnectorId; - const caseConnector = getConnectorById(dataConnectorId, connectors); - const defaultOwner = owner[0] ?? availableOwners[0]; - startTransaction({ appId, attachments }); - const connectorToUpdate = caseConnector - ? normalizeActionConnector(caseConnector, fields) - : getNoneConnector(); - - const configurationOwner: string | undefined = selectedOwner ? selectedOwner : owner[0]; - const selectedConfiguration = allConfigurations.find( - (element: CasesConfigurationUI) => element.owner === configurationOwner - ); - - const customFieldsConfiguration = selectedConfiguration - ? selectedConfiguration.customFields - : []; - - const transformedCustomFields = transformCustomFieldsData( - customFields, - customFieldsConfiguration ?? [] - ); - - const trimmedData = trimUserFormData(userFormData); - const theCase = await postCase({ - request: { - ...trimmedData, - connector: connectorToUpdate, - settings: { syncAlerts }, - owner: selectedOwner ?? defaultOwner, - customFields: transformedCustomFields, - }, + request: data, }); // add attachments to the case @@ -183,10 +73,10 @@ export const FormContext: React.FC<Props> = ({ await afterCaseCreated(theCase, createAttachments); } - if (theCase?.id && connectorToUpdate.id !== 'none') { + if (theCase?.id && data.connector.id !== 'none') { await pushCaseToExternalService({ caseId: theCase.id, - connector: connectorToUpdate, + connector: data.connector, }); } @@ -196,15 +86,9 @@ export const FormContext: React.FC<Props> = ({ } }, [ - isSyncAlertsEnabled, - connectors, - owner, - availableOwners, startTransaction, appId, attachments, - transformCustomFieldsData, - allConfigurations, postCase, afterCaseCreated, onSuccess, @@ -213,27 +97,34 @@ export const FormContext: React.FC<Props> = ({ ] ); - const { form } = useForm<FormProps>({ - defaultValue: { ...initialCaseValue, ...initialValue }, + const { form } = useForm({ + defaultValue: { + /** + * This is needed to initiate the connector + * with the one set in the configuration + * when creating a case. + */ + ...getInitialCaseValue({ + owner: selectedOwner, + connector: currentConfiguration.connector, + }), + ...initialValue, + }, options: { stripEmptyFields: false }, schema, onSubmit: submitCase, - serializer: getConnectorsFormSerializer, - deserializer: getConnectorsFormDeserializer, + serializer: (data: CaseFormFieldsSchemaProps) => + createFormSerializer( + connectors, + { + ...currentConfiguration, + owner: selectedOwner, + }, + data + ), + deserializer: createFormDeserializer, }); - const childrenWithExtraProp = useMemo( - () => - children != null - ? React.Children.map(children, (child: React.ReactElement) => - React.cloneElement(child, { - connectors, - isLoadingConnectors, - }) - ) - : null, - [children, connectors, isLoadingConnectors] - ); return ( <Form onKeyDown={(e: KeyboardEvent) => { @@ -245,7 +136,7 @@ export const FormContext: React.FC<Props> = ({ }} form={form} > - {childrenWithExtraProp} + {children} </Form> ); }; diff --git a/x-pack/plugins/cases/public/components/create/form_fields.tsx b/x-pack/plugins/cases/public/components/create/form_fields.tsx new file mode 100644 index 0000000000000..26189e33b7f12 --- /dev/null +++ b/x-pack/plugins/cases/public/components/create/form_fields.tsx @@ -0,0 +1,204 @@ +/* + * 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, useMemo, useEffect } from 'react'; +import { + EuiLoadingSpinner, + EuiSteps, + EuiTitle, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { useFormContext } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; + +import type { CasePostRequest } from '../../../common'; +import type { ActionConnector } from '../../../common/types/domain'; +import { Connector } from '../case_form_fields/connector'; +import * as i18n from './translations'; +import { SyncAlertsToggle } from '../case_form_fields/sync_alerts_toggle'; +import type { CasesConfigurationUI, CasesConfigurationUITemplate } from '../../containers/types'; +import { removeEmptyFields } from '../utils'; +import { useCasesFeatures } from '../../common/use_cases_features'; +import { TemplateSelector } from './templates'; +import { getInitialCaseValue } from './utils'; +import { CaseFormFields } from '../case_form_fields'; + +export interface CreateCaseFormFieldsProps { + configuration: CasesConfigurationUI; + connectors: ActionConnector[]; + isLoading: boolean; + withSteps: boolean; + draftStorageKey: string; +} + +const transformTemplateCaseFieldsToCaseFormFields = ( + owner: string, + caseTemplateFields: CasesConfigurationUITemplate['caseFields'] +): CasePostRequest => { + const caseFields = removeEmptyFields(caseTemplateFields ?? {}); + return getInitialCaseValue({ owner, ...caseFields }); +}; + +export const CreateCaseFormFields: React.FC<CreateCaseFormFieldsProps> = React.memo( + ({ configuration, connectors, isLoading, withSteps, draftStorageKey }) => { + const { reset, updateFieldValues, isSubmitting, setFieldValue } = useFormContext(); + const { isSyncAlertsEnabled } = useCasesFeatures(); + const configurationOwner = configuration.owner; + + /** + * Changes the selected connector + * when the user selects a solution. + * Each solution has its own configuration + * so the connector has to change. + */ + useEffect(() => { + setFieldValue('connectorId', configuration.connector.id); + }, [configuration.connector.id, setFieldValue]); + + const onTemplateChange = useCallback( + (caseFields: CasesConfigurationUITemplate['caseFields']) => { + const caseFormFields = transformTemplateCaseFieldsToCaseFormFields( + configurationOwner, + caseFields + ); + + reset({ + resetValues: true, + defaultValue: getInitialCaseValue({ owner: configurationOwner }), + }); + updateFieldValues(caseFormFields); + }, + [configurationOwner, reset, updateFieldValues] + ); + + const firstStep = useMemo( + () => ({ + title: i18n.STEP_ONE_TITLE, + children: ( + <TemplateSelector + isLoading={isSubmitting || isLoading} + templates={configuration.templates} + onTemplateChange={onTemplateChange} + /> + ), + }), + [configuration.templates, isLoading, isSubmitting, onTemplateChange] + ); + + const secondStep = useMemo( + () => ({ + title: i18n.STEP_TWO_TITLE, + children: ( + <CaseFormFields + configurationCustomFields={configuration.customFields} + isLoading={isSubmitting} + setCustomFieldsOptional={false} + isEditMode={false} + draftStorageKey={draftStorageKey} + /> + ), + }), + [configuration.customFields, draftStorageKey, isSubmitting] + ); + + const thirdStep = useMemo( + () => ({ + title: i18n.STEP_THREE_TITLE, + children: <SyncAlertsToggle isLoading={isSubmitting} />, + }), + [isSubmitting] + ); + + const fourthStep = useMemo( + () => ({ + title: i18n.STEP_FOUR_TITLE, + children: ( + <Connector + connectors={connectors} + isLoadingConnectors={isLoading} + isLoading={isSubmitting} + key={configuration.id} + /> + ), + }), + [configuration.id, connectors, isLoading, isSubmitting] + ); + + const allSteps = useMemo( + () => [firstStep, secondStep, ...(isSyncAlertsEnabled ? [thirdStep] : []), fourthStep], + [firstStep, secondStep, isSyncAlertsEnabled, thirdStep, fourthStep] + ); + + return ( + <> + {isSubmitting && ( + <EuiLoadingSpinner + css={css` + position: absolute; + top: 50%; + left: 50%; + z-index: 99; + `} + data-test-subj="create-case-loading-spinner" + size="xl" + /> + )} + {withSteps ? ( + <EuiSteps + headingElement="h2" + steps={allSteps} + data-test-subj={'case-creation-form-steps'} + /> + ) : ( + <> + <EuiSpacer size="l" /> + <EuiFlexGroup direction="column"> + <EuiFlexGroup direction="column"> + <EuiFlexItem> + <EuiTitle size="s"> + <h2>{i18n.STEP_ONE_TITLE}</h2> + </EuiTitle> + </EuiFlexItem> + <EuiFlexItem>{firstStep.children}</EuiFlexItem> + </EuiFlexGroup> + <EuiFlexGroup direction="column"> + <EuiFlexItem> + <EuiTitle size="s"> + <h2>{i18n.STEP_TWO_TITLE}</h2> + </EuiTitle> + </EuiFlexItem> + <EuiFlexItem>{secondStep.children}</EuiFlexItem> + </EuiFlexGroup> + {isSyncAlertsEnabled && ( + <EuiFlexGroup direction="column"> + <EuiFlexItem> + <EuiTitle size="s"> + <h2>{i18n.STEP_THREE_TITLE}</h2> + </EuiTitle> + </EuiFlexItem> + <EuiFlexItem>{thirdStep.children}</EuiFlexItem> + </EuiFlexGroup> + )} + <EuiFlexGroup direction="column"> + <EuiFlexItem> + <EuiTitle size="s"> + <h2>{i18n.STEP_FOUR_TITLE}</h2> + </EuiTitle> + </EuiFlexItem> + <EuiFlexItem>{fourthStep.children}</EuiFlexItem> + </EuiFlexGroup> + </EuiFlexGroup> + </> + )} + </> + ); + } +); + +CreateCaseFormFields.displayName = 'CreateCaseFormFields'; diff --git a/x-pack/plugins/cases/public/components/create/owner_selector.test.tsx b/x-pack/plugins/cases/public/components/create/owner_selector.test.tsx index 451207b080dfb..c61dd83dea42f 100644 --- a/x-pack/plugins/cases/public/components/create/owner_selector.test.tsx +++ b/x-pack/plugins/cases/public/components/create/owner_selector.test.tsx @@ -11,13 +11,14 @@ import { waitFor, screen } from '@testing-library/react'; import { SECURITY_SOLUTION_OWNER } from '../../../common'; import { OBSERVABILITY_OWNER, OWNER_INFO } from '../../../common/constants'; import { CreateCaseOwnerSelector } from './owner_selector'; -import { FormTestComponent } from '../../common/test_utils'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; import userEvent from '@testing-library/user-event'; describe('Case Owner Selection', () => { - const onSubmit = jest.fn(); + const onOwnerChange = jest.fn(); + const selectedOwner = SECURITY_SOLUTION_OWNER; + let appMockRender: AppMockRenderer; beforeEach(() => { @@ -25,92 +26,66 @@ describe('Case Owner Selection', () => { appMockRender = createAppMockRenderer(); }); - it('renders', async () => { + it('renders all options', async () => { appMockRender.render( - <FormTestComponent onSubmit={onSubmit}> - <CreateCaseOwnerSelector availableOwners={[SECURITY_SOLUTION_OWNER]} isLoading={false} /> - </FormTestComponent> + <CreateCaseOwnerSelector + availableOwners={[SECURITY_SOLUTION_OWNER, OBSERVABILITY_OWNER]} + isLoading={false} + onOwnerChange={onOwnerChange} + selectedOwner={selectedOwner} + /> ); expect(await screen.findByTestId('caseOwnerSelector')).toBeInTheDocument(); - }); - it.each([ - [OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER], - [SECURITY_SOLUTION_OWNER, OBSERVABILITY_OWNER], - ])('disables %s button if user only has %j', async (disabledButton, permission) => { - appMockRender.render( - <FormTestComponent onSubmit={onSubmit}> - <CreateCaseOwnerSelector availableOwners={[permission]} isLoading={false} /> - </FormTestComponent> - ); + userEvent.click(await screen.findByTestId('caseOwnerSuperSelect')); - expect(await screen.findByLabelText(OWNER_INFO[disabledButton].label)).toBeDisabled(); - expect(await screen.findByLabelText(OWNER_INFO[permission].label)).not.toBeDisabled(); + const options = await screen.findAllByRole('option'); + expect(options[0]).toHaveTextContent(OWNER_INFO[SECURITY_SOLUTION_OWNER].label); + expect(options[1]).toHaveTextContent(OWNER_INFO[OBSERVABILITY_OWNER].label); }); - it('defaults to security Solution', async () => { - appMockRender.render( - <FormTestComponent onSubmit={onSubmit}> + it.each([[SECURITY_SOLUTION_OWNER], [OBSERVABILITY_OWNER]])( + 'only displays %s option if available', + async (available) => { + appMockRender.render( <CreateCaseOwnerSelector - availableOwners={[OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER]} + availableOwners={[available]} isLoading={false} + onOwnerChange={onOwnerChange} + selectedOwner={available} /> - </FormTestComponent> - ); - - expect(await screen.findByLabelText('Observability')).not.toBeChecked(); - expect(await screen.findByLabelText('Security')).toBeChecked(); - - userEvent.click(await screen.findByTestId('form-test-component-submit-button')); - - await waitFor(() => { - // data, isValid - expect(onSubmit).toBeCalledWith({ selectedOwner: 'securitySolution' }, true); - }); - }); + ); - it('defaults to security Solution with empty owners', async () => { - appMockRender.render( - <FormTestComponent onSubmit={onSubmit}> - <CreateCaseOwnerSelector availableOwners={[]} isLoading={false} /> - </FormTestComponent> - ); + expect(await screen.findByText(OWNER_INFO[available].label)).toBeInTheDocument(); - expect(await screen.findByLabelText('Observability')).not.toBeChecked(); - expect(await screen.findByLabelText('Security')).toBeChecked(); + userEvent.click(await screen.findByTestId('caseOwnerSuperSelect')); - userEvent.click(await screen.findByTestId('form-test-component-submit-button')); - - await waitFor(() => { - // data, isValid - expect(onSubmit).toBeCalledWith({ selectedOwner: 'securitySolution' }, true); - }); - }); + expect((await screen.findAllByRole('option')).length).toBe(1); + } + ); it('changes the selection', async () => { appMockRender.render( - <FormTestComponent onSubmit={onSubmit}> - <CreateCaseOwnerSelector - availableOwners={[OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER]} - isLoading={false} - /> - </FormTestComponent> + <CreateCaseOwnerSelector + availableOwners={[OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER]} + isLoading={false} + onOwnerChange={onOwnerChange} + selectedOwner={selectedOwner} + /> ); - expect(await screen.findByLabelText('Security')).toBeChecked(); - expect(await screen.findByLabelText('Observability')).not.toBeChecked(); + expect(await screen.findByText('Security')).toBeInTheDocument(); + expect(screen.queryByText('Observability')).not.toBeInTheDocument(); - userEvent.click(await screen.findByLabelText('Observability')); - - expect(await screen.findByLabelText('Observability')).toBeChecked(); - expect(await screen.findByLabelText('Security')).not.toBeChecked(); - - userEvent.click(await screen.findByTestId('form-test-component-submit-button')); + userEvent.click(await screen.findByTestId('caseOwnerSuperSelect')); + userEvent.click(await screen.findByText('Observability'), undefined, { + skipPointerEventsCheck: true, + }); await waitFor(() => { // data, isValid - expect(onSubmit).toBeCalledWith({ selectedOwner: 'observability' }, true); + expect(onOwnerChange).toBeCalledWith('observability'); }); }); }); diff --git a/x-pack/plugins/cases/public/components/create/owner_selector.tsx b/x-pack/plugins/cases/public/components/create/owner_selector.tsx index 00dd4a03f2664..314bbaefc95c8 100644 --- a/x-pack/plugins/cases/public/components/create/owner_selector.tsx +++ b/x-pack/plugins/cases/public/components/create/owner_selector.tsx @@ -5,113 +5,72 @@ * 2.0. */ -import React, { memo, useCallback } from 'react'; +import React, { memo } from 'react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiFormRow, - EuiIcon, - EuiKeyPadMenu, - EuiKeyPadMenuItem, - useGeneratedHtmlId, -} from '@elastic/eui'; -import type { FieldHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { - getFieldValidityAndErrorMessage, - UseField, -} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { euiStyled } from '@kbn/kibana-react-plugin/common'; -import { SECURITY_SOLUTION_OWNER } from '../../../common'; +import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIcon, EuiSuperSelect } from '@elastic/eui'; import { OWNER_INFO } from '../../../common/constants'; import * as i18n from './translations'; -interface OwnerSelectorProps { - field: FieldHook<string>; - isLoading: boolean; - availableOwners: string[]; -} - interface Props { + selectedOwner: string; availableOwners: string[]; isLoading: boolean; + onOwnerChange: (owner: string) => void; } -const DEFAULT_SELECTABLE_OWNERS = Object.keys(OWNER_INFO) as Array<keyof typeof OWNER_INFO>; - -const FIELD_NAME = 'selectedOwner'; - -const FullWidthKeyPadMenu = euiStyled(EuiKeyPadMenu)` - width: 100%; -`; - -const FullWidthKeyPadItem = euiStyled(EuiKeyPadMenuItem)` - - width: 100%; -`; - -const OwnerSelector = ({ +const CaseOwnerSelector: React.FC<Props> = ({ availableOwners, - field, - isLoading = false, -}: OwnerSelectorProps): JSX.Element => { - const { errorMessage, isInvalid } = getFieldValidityAndErrorMessage(field); - const radioGroupName = useGeneratedHtmlId({ prefix: 'caseOwnerRadioGroup' }); - - const onChange = useCallback((val: string) => field.setValue(val), [field]); + isLoading, + onOwnerChange, + selectedOwner, +}) => { + const onChange = (owner: string) => { + onOwnerChange(owner); + }; + + const options = Object.entries(OWNER_INFO) + .filter(([owner]) => availableOwners.includes(owner)) + .map(([owner, definition]) => ({ + value: owner, + inputDisplay: ( + <EuiFlexGroup gutterSize="xs" alignItems="center" responsive={false}> + <EuiFlexItem grow={false}> + <EuiIcon + type={definition.iconType} + size="m" + title={definition.label} + className="eui-alignMiddle" + /> + </EuiFlexItem> + <EuiFlexItem> + <small>{definition.label}</small> + </EuiFlexItem> + </EuiFlexGroup> + ), + 'data-test-subj': `${definition.id}OwnerOption`, + })); return ( <EuiFormRow + display="columnCompressed" + label={i18n.SOLUTION_SELECTOR_LABEL} data-test-subj="caseOwnerSelector" fullWidth - isInvalid={isInvalid} - error={errorMessage} - helpText={field.helpText} - label={field.label} - labelAppend={field.labelAppend} > - <FullWidthKeyPadMenu checkable={{ ariaLegend: i18n.ARIA_KEYPAD_LEGEND }}> - <EuiFlexGroup> - {DEFAULT_SELECTABLE_OWNERS.map((owner) => ( - <EuiFlexItem key={owner}> - <FullWidthKeyPadItem - data-test-subj={`${owner}RadioButton`} - onChange={onChange} - checkable="single" - name={radioGroupName} - id={owner} - label={OWNER_INFO[owner].label} - isSelected={field.value === owner} - isDisabled={isLoading || !availableOwners.includes(owner)} - > - <EuiIcon type={OWNER_INFO[owner].iconType} size="xl" /> - </FullWidthKeyPadItem> - </EuiFlexItem> - ))} - </EuiFlexGroup> - </FullWidthKeyPadMenu> + <EuiSuperSelect + data-test-subj="caseOwnerSuperSelect" + options={options} + isLoading={isLoading} + fullWidth + valueOfSelected={selectedOwner} + onChange={(owner) => onChange(owner)} + compressed + /> </EuiFormRow> ); }; -OwnerSelector.displayName = 'OwnerSelector'; - -const CaseOwnerSelector: React.FC<Props> = ({ availableOwners, isLoading }) => { - const defaultValue = availableOwners.includes(SECURITY_SOLUTION_OWNER) - ? SECURITY_SOLUTION_OWNER - : availableOwners[0] ?? SECURITY_SOLUTION_OWNER; - - return ( - <UseField - path={FIELD_NAME} - config={{ defaultValue }} - component={OwnerSelector} - componentProps={{ availableOwners, isLoading }} - /> - ); -}; - CaseOwnerSelector.displayName = 'CaseOwnerSelectionComponent'; export const CreateCaseOwnerSelector = memo(CaseOwnerSelector); diff --git a/x-pack/plugins/cases/public/components/create/schema.tsx b/x-pack/plugins/cases/public/components/create/schema.tsx index 9d07efbf36111..2f92857930d98 100644 --- a/x-pack/plugins/cases/public/components/create/schema.tsx +++ b/x-pack/plugins/cases/public/components/create/schema.tsx @@ -5,140 +5,34 @@ * 2.0. */ -import type { FormSchema } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { FIELD_TYPES, VALIDATION_TYPES } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import type { FieldConfig, FormSchema } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; -import type { ConnectorTypeFields } from '../../../common/types/domain'; -import type { CasePostRequest } from '../../../common/types/api'; -import { - MAX_TITLE_LENGTH, - MAX_DESCRIPTION_LENGTH, - MAX_LENGTH_PER_TAG, - MAX_TAGS_PER_CASE, -} from '../../../common/constants'; import * as i18n from './translations'; -import { OptionalFieldLabel } from './optional_field_label'; -import { SEVERITY_TITLE } from '../severity/translations'; -const { emptyField, maxLengthField } = fieldValidators; +const { emptyField } = fieldValidators; +import type { CaseFormFieldsSchemaProps } from '../case_form_fields/schema'; +import { schema as caseFormFieldsSchema } from '../case_form_fields/schema'; -const isInvalidTag = (value: string) => value.trim() === ''; +const caseFormFieldsSchemaTyped = caseFormFieldsSchema as Record<string, FieldConfig<string>>; -const isTagCharactersInLimit = (value: string) => value.trim().length > MAX_LENGTH_PER_TAG; - -export const schemaTags = { - type: FIELD_TYPES.COMBO_BOX, - label: i18n.TAGS, - helpText: i18n.TAGS_HELP, - labelAppend: OptionalFieldLabel, - validations: [ - { - validator: ({ value }: { value: string | string[] }) => { - if ( - (!Array.isArray(value) && isInvalidTag(value)) || - (Array.isArray(value) && value.length > 0 && value.find(isInvalidTag)) - ) { - return { - message: i18n.TAGS_EMPTY_ERROR, - }; - } - }, - type: VALIDATION_TYPES.ARRAY_ITEM, - isBlocking: false, - }, - { - validator: ({ value }: { value: string | string[] }) => { - if ( - (!Array.isArray(value) && isTagCharactersInLimit(value)) || - (Array.isArray(value) && value.length > 0 && value.some(isTagCharactersInLimit)) - ) { - return { - message: i18n.MAX_LENGTH_ERROR('tag', MAX_LENGTH_PER_TAG), - }; - } - }, - type: VALIDATION_TYPES.ARRAY_ITEM, - isBlocking: false, - }, - { - validator: ({ value }: { value: string[] }) => { - if (Array.isArray(value) && value.length > MAX_TAGS_PER_CASE) { - return { - message: i18n.MAX_TAGS_ERROR(MAX_TAGS_PER_CASE), - }; - } - }, - }, - ], -}; - -export type FormProps = Omit< - CasePostRequest, - 'connector' | 'settings' | 'owner' | 'customFields' -> & { - connectorId: string; - fields: ConnectorTypeFields['fields']; - syncAlerts: boolean; - selectedOwner?: string | null; - customFields: Record<string, string | boolean>; -}; - -export const schema: FormSchema<FormProps> = { +export const schema: FormSchema<CaseFormFieldsSchemaProps> = { + ...caseFormFieldsSchema, title: { - type: FIELD_TYPES.TEXT, - label: i18n.NAME, + ...caseFormFieldsSchemaTyped.title, validations: [ { validator: emptyField(i18n.TITLE_REQUIRED), }, - { - validator: maxLengthField({ - length: MAX_TITLE_LENGTH, - message: i18n.MAX_LENGTH_ERROR('name', MAX_TITLE_LENGTH), - }), - }, + ...(caseFormFieldsSchemaTyped.title.validations ?? []), ], }, description: { - label: i18n.DESCRIPTION, + ...caseFormFieldsSchemaTyped.description, validations: [ { validator: emptyField(i18n.DESCRIPTION_REQUIRED), }, - { - validator: maxLengthField({ - length: MAX_DESCRIPTION_LENGTH, - message: i18n.MAX_LENGTH_ERROR('description', MAX_DESCRIPTION_LENGTH), - }), - }, - ], - }, - selectedOwner: { - label: i18n.SOLUTION, - type: FIELD_TYPES.RADIO_GROUP, - validations: [ - { - validator: emptyField(i18n.SOLUTION_REQUIRED), - }, + ...(caseFormFieldsSchemaTyped.description.validations ?? []), ], }, - tags: schemaTags, - severity: { - label: SEVERITY_TITLE, - }, - connectorId: { - type: FIELD_TYPES.SUPER_SELECT, - label: i18n.CONNECTORS, - defaultValue: 'none', - }, - fields: { - defaultValue: null, - }, - syncAlerts: { - helpText: i18n.SYNC_ALERTS_HELP, - type: FIELD_TYPES.TOGGLE, - defaultValue: true, - }, - assignees: {}, - category: {}, }; diff --git a/x-pack/plugins/cases/public/components/create/sync_alerts_toggle.test.tsx b/x-pack/plugins/cases/public/components/create/sync_alerts_toggle.test.tsx deleted file mode 100644 index 9ac7658547725..0000000000000 --- a/x-pack/plugins/cases/public/components/create/sync_alerts_toggle.test.tsx +++ /dev/null @@ -1,82 +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 type { FC, PropsWithChildren } from 'react'; -import React from 'react'; -import { mount } from 'enzyme'; -import { waitFor } from '@testing-library/react'; - -import type { FormHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { useForm, Form } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { SyncAlertsToggle } from './sync_alerts_toggle'; -import type { FormProps } from './schema'; -import { schema } from './schema'; - -describe('SyncAlertsToggle', () => { - let globalForm: FormHook; - - const MockHookWrapperComponent: FC<PropsWithChildren<unknown>> = ({ children }) => { - const { form } = useForm<FormProps>({ - defaultValue: { syncAlerts: true }, - schema: { - syncAlerts: schema.syncAlerts, - }, - }); - - globalForm = form; - - return <Form form={form}>{children}</Form>; - }; - - beforeEach(() => { - jest.resetAllMocks(); - }); - - it('it renders', async () => { - const wrapper = mount( - <MockHookWrapperComponent> - <SyncAlertsToggle isLoading={false} /> - </MockHookWrapperComponent> - ); - - expect(wrapper.find(`[data-test-subj="caseSyncAlerts"]`).exists()).toBeTruthy(); - }); - - it('it toggles the switch', async () => { - const wrapper = mount( - <MockHookWrapperComponent> - <SyncAlertsToggle isLoading={false} /> - </MockHookWrapperComponent> - ); - - wrapper.find('[data-test-subj="caseSyncAlerts"] button').first().simulate('click'); - - await waitFor(() => { - expect(globalForm.getFormData()).toEqual({ syncAlerts: false }); - }); - }); - - it('it shows the correct labels', async () => { - const wrapper = mount( - <MockHookWrapperComponent> - <SyncAlertsToggle isLoading={false} /> - </MockHookWrapperComponent> - ); - - expect(wrapper.find(`[data-test-subj="caseSyncAlerts"] .euiSwitch__label`).first().text()).toBe( - 'On' - ); - - wrapper.find('[data-test-subj="caseSyncAlerts"] button').first().simulate('click'); - - await waitFor(() => { - expect( - wrapper.find(`[data-test-subj="caseSyncAlerts"] .euiSwitch__label`).first().text() - ).toBe('Off'); - }); - }); -}); diff --git a/x-pack/plugins/cases/public/components/create/template.test.tsx b/x-pack/plugins/cases/public/components/create/template.test.tsx new file mode 100644 index 0000000000000..d3b1c59b71254 --- /dev/null +++ b/x-pack/plugins/cases/public/components/create/template.test.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import type { AppMockRenderer } from '../../common/mock'; +import { createAppMockRenderer } from '../../common/mock'; +import { templatesConfigurationMock } from '../../containers/mock'; +import { TemplateSelector } from './templates'; + +describe('CustomFields', () => { + let appMockRender: AppMockRenderer; + const onTemplateChange = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + appMockRender = createAppMockRenderer(); + }); + + it('renders correctly', async () => { + appMockRender.render( + <TemplateSelector + isLoading={false} + templates={templatesConfigurationMock} + onTemplateChange={onTemplateChange} + /> + ); + + expect(await screen.findByText('Template name')).toBeInTheDocument(); + expect(await screen.findByTestId('create-case-template-select')).toBeInTheDocument(); + }); + + it('selects a template correctly', async () => { + const selectedTemplate = templatesConfigurationMock[2]; + + appMockRender.render( + <TemplateSelector + isLoading={false} + templates={templatesConfigurationMock} + onTemplateChange={onTemplateChange} + /> + ); + + userEvent.selectOptions( + await screen.findByTestId('create-case-template-select'), + selectedTemplate.key + ); + + await waitFor(() => { + expect(onTemplateChange).toHaveBeenCalledWith(selectedTemplate.caseFields); + }); + }); + + it('shows the selected option correctly', async () => { + const selectedTemplate = templatesConfigurationMock[2]; + + appMockRender.render( + <TemplateSelector + isLoading={false} + templates={templatesConfigurationMock} + onTemplateChange={onTemplateChange} + /> + ); + + userEvent.selectOptions( + await screen.findByTestId('create-case-template-select'), + selectedTemplate.key + ); + + expect( + (await screen.findByRole<HTMLOptionElement>('option', { name: selectedTemplate.name })) + .selected + ).toBe(true); + }); +}); diff --git a/x-pack/plugins/cases/public/components/create/templates.tsx b/x-pack/plugins/cases/public/components/create/templates.tsx new file mode 100644 index 0000000000000..612a7d8a24a70 --- /dev/null +++ b/x-pack/plugins/cases/public/components/create/templates.tsx @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EuiSelectOption } from '@elastic/eui'; +import { EuiFormRow, EuiSelect } from '@elastic/eui'; +import React, { useCallback, useState } from 'react'; +import type { CasesConfigurationUI, CasesConfigurationUITemplate } from '../../containers/types'; +import { OptionalFieldLabel } from '../optional_field_label'; +import { TEMPLATE_HELP_TEXT, TEMPLATE_LABEL } from './translations'; + +interface Props { + isLoading: boolean; + templates: CasesConfigurationUI['templates']; + onTemplateChange: (caseFields: CasesConfigurationUITemplate['caseFields']) => void; +} + +export const TemplateSelectorComponent: React.FC<Props> = ({ + isLoading, + templates, + onTemplateChange, +}) => { + const [selectedTemplate, onSelectTemplate] = useState<string>(); + + const options: EuiSelectOption[] = templates.map((template) => ({ + text: template.name, + value: template.key, + })); + + const onChange: React.ChangeEventHandler<HTMLSelectElement> = useCallback( + (e) => { + const selectedTemplated = templates.find((template) => template.key === e.target.value); + + if (selectedTemplated) { + onSelectTemplate(selectedTemplated.key); + onTemplateChange(selectedTemplated.caseFields); + } + }, + [onTemplateChange, templates] + ); + + return ( + <EuiFormRow + id="createCaseTemplate" + fullWidth + label={TEMPLATE_LABEL} + labelAppend={OptionalFieldLabel} + helpText={TEMPLATE_HELP_TEXT} + > + <EuiSelect + onChange={onChange} + options={options} + disabled={isLoading} + isLoading={isLoading} + data-test-subj="create-case-template-select" + fullWidth + hasNoInitialSelection + value={selectedTemplate} + /> + </EuiFormRow> + ); +}; + +TemplateSelectorComponent.displayName = 'TemplateSelector'; + +export const TemplateSelector = React.memo(TemplateSelectorComponent); diff --git a/x-pack/plugins/cases/public/components/create/translations.ts b/x-pack/plugins/cases/public/components/create/translations.ts index 473cc40a6a3f8..aef9c7c525acd 100644 --- a/x-pack/plugins/cases/public/components/create/translations.ts +++ b/x-pack/plugins/cases/public/components/create/translations.ts @@ -11,14 +11,18 @@ export * from '../../common/translations'; export * from '../user_profiles/translations'; export const STEP_ONE_TITLE = i18n.translate('xpack.cases.create.stepOneTitle', { - defaultMessage: 'Case fields', + defaultMessage: 'Select template', }); export const STEP_TWO_TITLE = i18n.translate('xpack.cases.create.stepTwoTitle', { - defaultMessage: 'Case settings', + defaultMessage: 'Case fields', }); export const STEP_THREE_TITLE = i18n.translate('xpack.cases.create.stepThreeTitle', { + defaultMessage: 'Case settings', +}); + +export const STEP_FOUR_TITLE = i18n.translate('xpack.cases.create.stepFourTitle', { defaultMessage: 'External Connector Fields', }); @@ -45,3 +49,15 @@ export const CANCEL_MODAL_BUTTON = i18n.translate('xpack.cases.create.cancelModa export const CONFIRM_MODAL_BUTTON = i18n.translate('xpack.cases.create.confirmModalButton', { defaultMessage: 'Exit without saving', }); + +export const TEMPLATE_LABEL = i18n.translate('xpack.cases.create.templateLabel', { + defaultMessage: 'Template name', +}); + +export const TEMPLATE_HELP_TEXT = i18n.translate('xpack.cases.create.templateHelpText', { + defaultMessage: 'Selecting a template will pre-fill certain case fields below', +}); + +export const SOLUTION_SELECTOR_LABEL = i18n.translate('xpack.cases.create.solutionSelectorLabel', { + defaultMessage: 'Create case under:', +}); diff --git a/x-pack/plugins/cases/public/components/create/utils.test.ts b/x-pack/plugins/cases/public/components/create/utils.test.ts new file mode 100644 index 0000000000000..6b8c9c9017fc4 --- /dev/null +++ b/x-pack/plugins/cases/public/components/create/utils.test.ts @@ -0,0 +1,383 @@ +/* + * 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 { + getInitialCaseValue, + trimUserFormData, + getOwnerDefaultValue, + createFormDeserializer, + createFormSerializer, +} from './utils'; +import { ConnectorTypes, CaseSeverity, CustomFieldTypes } from '../../../common/types/domain'; +import { GENERAL_CASES_OWNER } from '../../../common'; +import { casesConfigurationsMock } from '../../containers/configure/mock'; + +describe('utils', () => { + describe('getInitialCaseValue', () => { + it('returns expected initial values', () => { + const params = { + owner: 'foobar', + connector: { + id: 'foo', + name: 'bar', + type: ConnectorTypes.jira as const, + fields: null, + }, + }; + expect(getInitialCaseValue(params)).toEqual({ + assignees: [], + category: undefined, + customFields: [], + description: '', + settings: { + syncAlerts: true, + }, + severity: 'low', + tags: [], + title: '', + ...params, + }); + }); + + it('returns none connector when none is specified', () => { + expect(getInitialCaseValue({ owner: 'foobar' })).toEqual({ + assignees: [], + category: undefined, + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + customFields: [], + description: '', + owner: 'foobar', + settings: { + syncAlerts: true, + }, + severity: 'low', + tags: [], + title: '', + }); + }); + + it('returns extra fields', () => { + const extraFields = { + owner: 'foobar', + title: 'my title', + assignees: [ + { + uid: 'uid', + }, + ], + tags: ['my tag'], + category: 'categorty', + severity: CaseSeverity.HIGH as const, + description: 'Cool description', + settings: { syncAlerts: false }, + customFields: [{ key: 'key', type: CustomFieldTypes.TEXT as const, value: 'text' }], + }; + + expect(getInitialCaseValue(extraFields)).toEqual({ + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + ...extraFields, + }); + }); + }); + + describe('trimUserFormData', () => { + it('trims applicable fields in the user form data', () => { + const userFormData = { + title: ' title ', + description: ' description ', + category: ' category ', + tags: [' tag 1 ', ' tag 2 '], + }; + + expect(trimUserFormData(userFormData)).toEqual({ + title: userFormData.title.trim(), + description: userFormData.description.trim(), + category: userFormData.category.trim(), + tags: ['tag 1', 'tag 2'], + }); + }); + + it('ignores category and tags if they are missing', () => { + const userFormData = { + title: ' title ', + description: ' description ', + tags: [], + }; + + expect(trimUserFormData(userFormData)).toEqual({ + title: userFormData.title.trim(), + description: userFormData.description.trim(), + tags: [], + }); + }); + }); + + describe('getOwnerDefaultValue', () => { + it('returns the general cases owner if it exists', () => { + expect(getOwnerDefaultValue(['foobar', GENERAL_CASES_OWNER])).toEqual(GENERAL_CASES_OWNER); + }); + + it('returns the first available owner if the general cases owner is not available', () => { + expect(getOwnerDefaultValue(['foo', 'bar'])).toEqual('foo'); + }); + + it('returns the general cases owner if no owner is available', () => { + expect(getOwnerDefaultValue([])).toEqual(GENERAL_CASES_OWNER); + }); + }); + + describe('createFormSerializer', () => { + const dataToSerialize = { + title: 'title', + description: 'description', + tags: [], + connectorId: '', + fields: { incidentTypes: null, severityCode: null }, + customFields: {}, + syncAlerts: false, + }; + const serializedFormData = { + title: 'title', + description: 'description', + customFields: [], + settings: { + syncAlerts: false, + }, + tags: [], + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + owner: casesConfigurationsMock.owner, + }; + + it('returns empty values with owner and connector from configuration when data is empty', () => { + // @ts-ignore: this is what we are trying to test + expect(createFormSerializer([], casesConfigurationsMock, {})).toEqual({ + assignees: [], + category: undefined, + customFields: [], + description: '', + settings: { + syncAlerts: true, + }, + severity: 'low', + tags: [], + title: '', + connector: casesConfigurationsMock.connector, + owner: casesConfigurationsMock.owner, + }); + }); + + it('normalizes action connectors', () => { + expect( + createFormSerializer( + [ + { + id: 'test', + actionTypeId: '.test', + name: 'My connector', + isDeprecated: false, + isPreconfigured: false, + config: { foo: 'bar' }, + isMissingSecrets: false, + isSystemAction: false, + }, + ], + casesConfigurationsMock, + { + ...dataToSerialize, + connectorId: 'test', + fields: { + issueType: '1', + priority: 'test', + parent: null, + }, + } + ) + ).toEqual({ + ...serializedFormData, + connector: { + id: 'test', + name: 'My connector', + type: '.test', + fields: { + issueType: '1', + priority: 'test', + parent: null, + }, + }, + }); + }); + + it('transforms custom fields', () => { + expect( + createFormSerializer([], casesConfigurationsMock, { + ...dataToSerialize, + customFields: { + test_key_1: 'first value', + test_key_2: true, + test_key_3: 'second value', + }, + }) + ).toEqual({ + ...serializedFormData, + customFields: [ + { + key: 'test_key_1', + type: 'text', + value: 'first value', + }, + { + key: 'test_key_2', + type: 'toggle', + value: true, + }, + { + key: 'test_key_3', + type: 'text', + value: 'second value', + }, + ], + }); + }); + + it('trims form data', () => { + const untrimmedData = { + title: ' title ', + description: ' description ', + category: ' category ', + tags: [' tag 1 ', ' tag 2 '], + }; + + expect( + // @ts-ignore: expected incomplete form data + createFormSerializer([], casesConfigurationsMock, { ...dataToSerialize, ...untrimmedData }) + ).toEqual({ + ...serializedFormData, + title: untrimmedData.title.trim(), + description: untrimmedData.description.trim(), + category: untrimmedData.category.trim(), + tags: ['tag 1', 'tag 2'], + }); + }); + }); + + describe('createFormDeserializer', () => { + it('deserializes data as expected', () => { + expect( + createFormDeserializer({ + title: 'title', + description: 'description', + settings: { + syncAlerts: false, + }, + tags: [], + connector: { + id: 'foobar', + name: 'none', + type: ConnectorTypes.swimlane as const, + fields: { + issueType: '1', + priority: 'test', + parent: null, + caseId: null, + }, + }, + owner: casesConfigurationsMock.owner, + customFields: [], + }) + ).toEqual({ + title: 'title', + description: 'description', + syncAlerts: false, + tags: [], + owner: casesConfigurationsMock.owner, + connectorId: 'foobar', + fields: { + issueType: '1', + priority: 'test', + parent: null, + caseId: null, + }, + customFields: {}, + }); + }); + + it('deserializes customFields as expected', () => { + expect( + createFormDeserializer({ + title: 'title', + description: 'description', + settings: { + syncAlerts: false, + }, + tags: [], + connector: { + id: 'foobar', + name: 'none', + type: ConnectorTypes.swimlane as const, + fields: { + issueType: '1', + priority: 'test', + parent: null, + caseId: null, + }, + }, + owner: casesConfigurationsMock.owner, + customFields: [ + { + key: 'test_key_1', + type: CustomFieldTypes.TEXT, + value: 'first value', + }, + { + key: 'test_key_2', + type: CustomFieldTypes.TOGGLE, + value: true, + }, + { + key: 'test_key_3', + type: CustomFieldTypes.TEXT, + value: 'second value', + }, + ], + }) + ).toEqual({ + title: 'title', + description: 'description', + syncAlerts: false, + tags: [], + owner: casesConfigurationsMock.owner, + connectorId: 'foobar', + fields: { + issueType: '1', + priority: 'test', + parent: null, + caseId: null, + }, + customFields: { + test_key_1: 'first value', + test_key_2: true, + test_key_3: 'second value', + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/create/utils.ts b/x-pack/plugins/cases/public/components/create/utils.ts new file mode 100644 index 0000000000000..daeac67066c9e --- /dev/null +++ b/x-pack/plugins/cases/public/components/create/utils.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 { isEmpty } from 'lodash'; +import type { CasePostRequest } from '../../../common'; +import { GENERAL_CASES_OWNER } from '../../../common'; +import type { ActionConnector } from '../../../common/types/domain'; +import { CaseSeverity } from '../../../common/types/domain'; +import type { CasesConfigurationUI } from '../../containers/types'; +import type { CaseFormFieldsSchemaProps } from '../case_form_fields/schema'; +import { normalizeActionConnector, getNoneConnector } from '../configure_cases/utils'; +import { + customFieldsFormDeserializer, + customFieldsFormSerializer, + getConnectorById, + getConnectorsFormSerializer, +} from '../utils'; + +type GetInitialCaseValueArgs = Partial<Omit<CasePostRequest, 'owner'>> & + Pick<CasePostRequest, 'owner'>; + +export const getInitialCaseValue = ({ + owner, + connector, + ...restFields +}: GetInitialCaseValueArgs): CasePostRequest => ({ + title: '', + assignees: [], + tags: [], + category: undefined, + severity: CaseSeverity.LOW as const, + description: '', + settings: { syncAlerts: true }, + customFields: [], + ...restFields, + connector: connector ?? getNoneConnector(), + owner, +}); + +export const trimUserFormData = ( + userFormData: Omit< + CaseFormFieldsSchemaProps, + 'connectorId' | 'fields' | 'syncAlerts' | 'customFields' + > +) => { + let formData = { + ...userFormData, + title: userFormData.title.trim(), + description: userFormData.description.trim(), + }; + + if (userFormData.category) { + formData = { ...formData, category: userFormData.category.trim() }; + } + + if (userFormData.tags) { + formData = { ...formData, tags: userFormData.tags.map((tag: string) => tag.trim()) }; + } + + return formData; +}; + +export const createFormDeserializer = (data: CasePostRequest): CaseFormFieldsSchemaProps => { + const { connector, settings, customFields, ...restData } = data; + + return { + ...restData, + connectorId: connector.id, + fields: connector.fields, + syncAlerts: settings.syncAlerts, + customFields: customFieldsFormDeserializer(customFields) ?? {}, + }; +}; + +export const createFormSerializer = ( + connectors: ActionConnector[], + currentConfiguration: CasesConfigurationUI, + data: CaseFormFieldsSchemaProps +): CasePostRequest => { + if (data == null || isEmpty(data)) { + return getInitialCaseValue({ + owner: currentConfiguration.owner, + connector: currentConfiguration.connector, + }); + } + + const { connectorId: dataConnectorId, fields, syncAlerts, customFields, ...restData } = data; + + const serializedConnectorFields = getConnectorsFormSerializer({ fields }); + const caseConnector = getConnectorById(dataConnectorId, connectors); + const connectorToUpdate = caseConnector + ? normalizeActionConnector(caseConnector, serializedConnectorFields.fields) + : getNoneConnector(); + + const transformedCustomFields = customFieldsFormSerializer( + customFields, + currentConfiguration.customFields + ); + + const trimmedData = trimUserFormData(restData); + + return { + ...trimmedData, + connector: connectorToUpdate, + settings: { syncAlerts: syncAlerts ?? false }, + owner: currentConfiguration.owner, + customFields: transformedCustomFields, + }; +}; + +export const getOwnerDefaultValue = (availableOwners: string[]) => + availableOwners.includes(GENERAL_CASES_OWNER) + ? GENERAL_CASES_OWNER + : availableOwners[0] ?? GENERAL_CASES_OWNER; diff --git a/x-pack/plugins/cases/public/components/custom_fields/custom_fields_list/index.test.tsx b/x-pack/plugins/cases/public/components/custom_fields/custom_fields_list/index.test.tsx index 002d3e65b4e61..fab80347300d0 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/custom_fields_list/index.test.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/custom_fields_list/index.test.tsx @@ -99,7 +99,7 @@ describe('CustomFieldsList', () => { ) ); - expect(await screen.findByTestId('confirm-delete-custom-field-modal')).toBeInTheDocument(); + expect(await screen.findByTestId('confirm-delete-modal')).toBeInTheDocument(); }); it('calls onDeleteCustomField when confirm', async () => { @@ -113,12 +113,12 @@ describe('CustomFieldsList', () => { ) ); - expect(await screen.findByTestId('confirm-delete-custom-field-modal')).toBeInTheDocument(); + expect(await screen.findByTestId('confirm-delete-modal')).toBeInTheDocument(); userEvent.click(await screen.findByText('Delete')); await waitFor(() => { - expect(screen.queryByTestId('confirm-delete-custom-field-modal')).not.toBeInTheDocument(); + expect(screen.queryByTestId('confirm-delete-modal')).not.toBeInTheDocument(); expect(props.onDeleteCustomField).toHaveBeenCalledWith( customFieldsConfigurationMock[0].key ); @@ -136,12 +136,12 @@ describe('CustomFieldsList', () => { ) ); - expect(await screen.findByTestId('confirm-delete-custom-field-modal')).toBeInTheDocument(); + expect(await screen.findByTestId('confirm-delete-modal')).toBeInTheDocument(); userEvent.click(await screen.findByText('Cancel')); await waitFor(() => { - expect(screen.queryByTestId('confirm-delete-custom-field-modal')).not.toBeInTheDocument(); + expect(screen.queryByTestId('confirm-delete-modal')).not.toBeInTheDocument(); expect(props.onDeleteCustomField).not.toHaveBeenCalledWith(); }); }); diff --git a/x-pack/plugins/cases/public/components/custom_fields/custom_fields_list/index.tsx b/x-pack/plugins/cases/public/components/custom_fields/custom_fields_list/index.tsx index cfccb53e48db3..f8475a90b94ad 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/custom_fields_list/index.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/custom_fields_list/index.tsx @@ -20,7 +20,7 @@ import * as i18n from '../translations'; import type { CustomFieldTypes, CustomFieldsConfiguration } from '../../../../common/types/domain'; import { builderMap } from '../builder'; -import { DeleteConfirmationModal } from '../delete_confirmation_modal'; +import { DeleteConfirmationModal } from '../../configure_cases/delete_confirmation_modal'; export interface Props { customFields: CustomFieldsConfiguration; @@ -111,7 +111,8 @@ const CustomFieldsListComponent: React.FC<Props> = (props) => { </EuiFlexItem> {showModal && selectedItem ? ( <DeleteConfirmationModal - label={selectedItem.label} + title={i18n.DELETE_FIELD_TITLE(selectedItem.label)} + message={i18n.DELETE_FIELD_DESCRIPTION} onCancel={onCancel} onConfirm={onConfirm} /> diff --git a/x-pack/plugins/cases/public/components/custom_fields/flyout.test.tsx b/x-pack/plugins/cases/public/components/custom_fields/flyout.test.tsx deleted file mode 100644 index 508f124a7746c..0000000000000 --- a/x-pack/plugins/cases/public/components/custom_fields/flyout.test.tsx +++ /dev/null @@ -1,270 +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 { fireEvent, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; - -import type { AppMockRenderer } from '../../common/mock'; -import { createAppMockRenderer } from '../../common/mock'; -import { CustomFieldFlyout } from './flyout'; -import { customFieldsConfigurationMock } from '../../containers/mock'; -import { - MAX_CUSTOM_FIELD_LABEL_LENGTH, - MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH, -} from '../../../common/constants'; -import { CustomFieldTypes } from '../../../common/types/domain'; - -import * as i18n from './translations'; - -describe('CustomFieldFlyout ', () => { - let appMockRender: AppMockRenderer; - - const props = { - onCloseFlyout: jest.fn(), - onSaveField: jest.fn(), - isLoading: false, - disabled: false, - customField: null, - }; - - beforeEach(() => { - jest.clearAllMocks(); - appMockRender = createAppMockRenderer(); - }); - - it('renders correctly', async () => { - appMockRender.render(<CustomFieldFlyout {...props} />); - - expect(await screen.findByTestId('custom-field-flyout-header')).toBeInTheDocument(); - expect(await screen.findByTestId('custom-field-flyout-cancel')).toBeInTheDocument(); - expect(await screen.findByTestId('custom-field-flyout-save')).toBeInTheDocument(); - }); - - it('shows error if field label is too long', async () => { - appMockRender.render(<CustomFieldFlyout {...props} />); - - const message = 'z'.repeat(MAX_CUSTOM_FIELD_LABEL_LENGTH + 1); - - userEvent.type(await screen.findByTestId('custom-field-label-input'), message); - - expect( - await screen.findByText( - i18n.MAX_LENGTH_ERROR(i18n.FIELD_LABEL.toLocaleLowerCase(), MAX_CUSTOM_FIELD_LABEL_LENGTH) - ) - ).toBeInTheDocument(); - }); - - it('does not call onSaveField when error', async () => { - appMockRender.render(<CustomFieldFlyout {...props} />); - - userEvent.click(await screen.findByTestId('custom-field-flyout-save')); - - expect( - await screen.findByText(i18n.REQUIRED_FIELD(i18n.FIELD_LABEL.toLocaleLowerCase())) - ).toBeInTheDocument(); - - expect(props.onSaveField).not.toBeCalled(); - }); - - it('calls onCloseFlyout on cancel', async () => { - appMockRender.render(<CustomFieldFlyout {...props} />); - - userEvent.click(await screen.findByTestId('custom-field-flyout-cancel')); - - await waitFor(() => { - expect(props.onCloseFlyout).toBeCalled(); - }); - }); - - it('calls onCloseFlyout on close', async () => { - appMockRender.render(<CustomFieldFlyout {...props} />); - - userEvent.click(await screen.findByTestId('euiFlyoutCloseButton')); - - await waitFor(() => { - expect(props.onCloseFlyout).toBeCalled(); - }); - }); - - describe('Text custom field', () => { - it('calls onSaveField with correct params when a custom field is NOT required', async () => { - appMockRender.render(<CustomFieldFlyout {...props} />); - - userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); - userEvent.click(await screen.findByTestId('custom-field-flyout-save')); - - await waitFor(() => { - expect(props.onSaveField).toBeCalledWith({ - key: expect.anything(), - label: 'Summary', - required: false, - type: CustomFieldTypes.TEXT, - }); - }); - }); - - it('calls onSaveField with correct params when a custom field is NOT required and has a default value', async () => { - appMockRender.render(<CustomFieldFlyout {...props} />); - - userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); - userEvent.paste( - await screen.findByTestId('text-custom-field-default-value'), - 'Default value' - ); - userEvent.click(await screen.findByTestId('custom-field-flyout-save')); - - await waitFor(() => { - expect(props.onSaveField).toBeCalledWith({ - key: expect.anything(), - label: 'Summary', - required: false, - type: CustomFieldTypes.TEXT, - defaultValue: 'Default value', - }); - }); - }); - - it('calls onSaveField with the correct params when a custom field is required', async () => { - appMockRender.render(<CustomFieldFlyout {...props} />); - - userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); - userEvent.click(await screen.findByTestId('text-custom-field-required')); - userEvent.paste( - await screen.findByTestId('text-custom-field-default-value'), - 'Default value' - ); - userEvent.click(await screen.findByTestId('custom-field-flyout-save')); - - await waitFor(() => { - expect(props.onSaveField).toBeCalledWith({ - key: expect.anything(), - label: 'Summary', - required: true, - type: CustomFieldTypes.TEXT, - defaultValue: 'Default value', - }); - }); - }); - - it('calls onSaveField with the correct params when a custom field is required and the defaultValue is missing', async () => { - appMockRender.render(<CustomFieldFlyout {...props} />); - - userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); - userEvent.click(await screen.findByTestId('text-custom-field-required')); - userEvent.click(await screen.findByTestId('custom-field-flyout-save')); - - await waitFor(() => { - expect(props.onSaveField).toBeCalledWith({ - key: expect.anything(), - label: 'Summary', - required: true, - type: CustomFieldTypes.TEXT, - }); - }); - }); - - it('renders flyout with the correct data when an initial customField value exists', async () => { - appMockRender.render( - <CustomFieldFlyout {...{ ...props, customField: customFieldsConfigurationMock[0] }} /> - ); - - expect(await screen.findByTestId('custom-field-label-input')).toHaveAttribute( - 'value', - customFieldsConfigurationMock[0].label - ); - expect(await screen.findByTestId('custom-field-type-selector')).toHaveAttribute('disabled'); - expect(await screen.findByTestId('text-custom-field-required')).toHaveAttribute('checked'); - expect(await screen.findByTestId('text-custom-field-default-value')).toHaveAttribute( - 'value', - customFieldsConfigurationMock[0].defaultValue - ); - }); - - it('shows an error if default value is too long', async () => { - appMockRender.render(<CustomFieldFlyout {...props} />); - - userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); - userEvent.click(await screen.findByTestId('text-custom-field-required')); - userEvent.paste( - await screen.findByTestId('text-custom-field-default-value'), - 'z'.repeat(MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH + 1) - ); - - expect( - await screen.findByText( - i18n.MAX_LENGTH_ERROR( - i18n.DEFAULT_VALUE.toLowerCase(), - MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH - ) - ) - ).toBeInTheDocument(); - }); - }); - - describe('Toggle custom field', () => { - it('calls onSaveField with correct params when a custom field is NOT required', async () => { - appMockRender.render(<CustomFieldFlyout {...props} />); - - fireEvent.change(await screen.findByTestId('custom-field-type-selector'), { - target: { value: CustomFieldTypes.TOGGLE }, - }); - - userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); - userEvent.click(await screen.findByTestId('custom-field-flyout-save')); - - await waitFor(() => { - expect(props.onSaveField).toBeCalledWith({ - key: expect.anything(), - label: 'Summary', - required: false, - type: CustomFieldTypes.TOGGLE, - defaultValue: false, - }); - }); - }); - - it('calls onSaveField with the correct default value when a custom field is required', async () => { - appMockRender.render(<CustomFieldFlyout {...props} />); - - fireEvent.change(await screen.findByTestId('custom-field-type-selector'), { - target: { value: CustomFieldTypes.TOGGLE }, - }); - - userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); - userEvent.click(await screen.findByTestId('toggle-custom-field-required')); - userEvent.click(await screen.findByTestId('custom-field-flyout-save')); - - await waitFor(() => { - expect(props.onSaveField).toBeCalledWith({ - key: expect.anything(), - label: 'Summary', - required: true, - type: CustomFieldTypes.TOGGLE, - defaultValue: false, - }); - }); - }); - - it('renders flyout with the correct data when an initial customField value exists', async () => { - appMockRender.render( - <CustomFieldFlyout {...{ ...props, customField: customFieldsConfigurationMock[1] }} /> - ); - - expect(await screen.findByTestId('custom-field-label-input')).toHaveAttribute( - 'value', - customFieldsConfigurationMock[1].label - ); - expect(await screen.findByTestId('custom-field-type-selector')).toHaveAttribute('disabled'); - expect(await screen.findByTestId('toggle-custom-field-required')).toHaveAttribute('checked'); - expect(await screen.findByTestId('toggle-custom-field-default-value')).toHaveAttribute( - 'aria-checked', - 'true' - ); - }); - }); -}); diff --git a/x-pack/plugins/cases/public/components/custom_fields/flyout.tsx b/x-pack/plugins/cases/public/components/custom_fields/flyout.tsx deleted file mode 100644 index 0be2c4ea43bcb..0000000000000 --- a/x-pack/plugins/cases/public/components/custom_fields/flyout.tsx +++ /dev/null @@ -1,105 +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, { useCallback, useState } from 'react'; -import { - EuiFlyout, - EuiFlyoutBody, - EuiFlyoutHeader, - EuiTitle, - EuiFlyoutFooter, - EuiFlexGroup, - EuiFlexItem, - EuiButtonEmpty, - EuiButton, -} from '@elastic/eui'; -import type { CustomFieldFormState } from './form'; -import { CustomFieldsForm } from './form'; -import type { CustomFieldConfiguration } from '../../../common/types/domain'; -import { CustomFieldTypes } from '../../../common/types/domain'; - -import * as i18n from './translations'; - -export interface CustomFieldFlyoutProps { - disabled: boolean; - isLoading: boolean; - onCloseFlyout: () => void; - onSaveField: (data: CustomFieldConfiguration) => void; - customField: CustomFieldConfiguration | null; -} - -const CustomFieldFlyoutComponent: React.FC<CustomFieldFlyoutProps> = ({ - onCloseFlyout, - onSaveField, - isLoading, - disabled, - customField, -}) => { - const dataTestSubj = 'custom-field-flyout'; - - const [formState, setFormState] = useState<CustomFieldFormState>({ - isValid: undefined, - submit: async () => ({ - isValid: false, - data: { key: '', label: '', type: CustomFieldTypes.TEXT, required: false }, - }), - }); - - const { submit } = formState; - - const handleSaveField = useCallback(async () => { - const { isValid, data } = await submit(); - - if (isValid) { - onSaveField(data); - } - }, [onSaveField, submit]); - - return ( - <EuiFlyout onClose={onCloseFlyout} data-test-subj={dataTestSubj}> - <EuiFlyoutHeader hasBorder data-test-subj={`${dataTestSubj}-header`}> - <EuiTitle size="s"> - <h3 id="flyoutTitle">{i18n.ADD_CUSTOM_FIELD}</h3> - </EuiTitle> - </EuiFlyoutHeader> - <EuiFlyoutBody> - <CustomFieldsForm initialValue={customField} onChange={setFormState} /> - </EuiFlyoutBody> - <EuiFlyoutFooter data-test-subj={`${dataTestSubj}-footer`}> - <EuiFlexGroup justifyContent="flexStart"> - <EuiFlexItem grow={false}> - <EuiButtonEmpty - onClick={onCloseFlyout} - data-test-subj={`${dataTestSubj}-cancel`} - disabled={disabled} - isLoading={isLoading} - > - {i18n.CANCEL} - </EuiButtonEmpty> - </EuiFlexItem> - <EuiFlexGroup justifyContent="flexEnd"> - <EuiFlexItem grow={false}> - <EuiButton - fill - onClick={handleSaveField} - data-test-subj={`${dataTestSubj}-save`} - disabled={disabled} - isLoading={isLoading} - > - {i18n.SAVE_FIELD} - </EuiButton> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexGroup> - </EuiFlyoutFooter> - </EuiFlyout> - ); -}; - -CustomFieldFlyoutComponent.displayName = 'CustomFieldFlyout'; - -export const CustomFieldFlyout = React.memo(CustomFieldFlyoutComponent); diff --git a/x-pack/plugins/cases/public/components/custom_fields/form.test.tsx b/x-pack/plugins/cases/public/components/custom_fields/form.test.tsx index ef2cbac458678..89fdca73fefbf 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/form.test.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/form.test.tsx @@ -10,13 +10,13 @@ import { screen, fireEvent, waitFor, act } from '@testing-library/react'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; -import type { CustomFieldFormState } from './form'; import { CustomFieldsForm } from './form'; import type { CustomFieldConfiguration } from '../../../common/types/domain'; import { CustomFieldTypes } from '../../../common/types/domain'; import * as i18n from './translations'; import userEvent from '@testing-library/user-event'; import { customFieldsConfigurationMock } from '../../containers/mock'; +import type { FormState } from '../configure_cases/flyout'; describe('CustomFieldsForm ', () => { let appMockRender: AppMockRenderer; @@ -68,9 +68,9 @@ describe('CustomFieldsForm ', () => { }); it('serializes the data correctly if required is selected', async () => { - let formState: CustomFieldFormState; + let formState: FormState<CustomFieldConfiguration>; - const onChangeState = (state: CustomFieldFormState) => (formState = state); + const onChangeState = (state: FormState<CustomFieldConfiguration>) => (formState = state); appMockRender.render(<CustomFieldsForm onChange={onChangeState} initialValue={null} />); @@ -96,9 +96,9 @@ describe('CustomFieldsForm ', () => { }); it('serializes the data correctly if required is selected and the text default value is not filled', async () => { - let formState: CustomFieldFormState; + let formState: FormState<CustomFieldConfiguration>; - const onChangeState = (state: CustomFieldFormState) => (formState = state); + const onChangeState = (state: FormState<CustomFieldConfiguration>) => (formState = state); appMockRender.render(<CustomFieldsForm onChange={onChangeState} initialValue={null} />); @@ -122,9 +122,9 @@ describe('CustomFieldsForm ', () => { }); it('serializes the data correctly if required is selected and the text default value is an empty string', async () => { - let formState: CustomFieldFormState; + let formState: FormState<CustomFieldConfiguration>; - const onChangeState = (state: CustomFieldFormState) => (formState = state); + const onChangeState = (state: FormState<CustomFieldConfiguration>) => (formState = state); appMockRender.render(<CustomFieldsForm onChange={onChangeState} initialValue={null} />); @@ -149,9 +149,9 @@ describe('CustomFieldsForm ', () => { }); it('serializes the data correctly if the initial default value is null', async () => { - let formState: CustomFieldFormState; + let formState: FormState<CustomFieldConfiguration>; - const onChangeState = (state: CustomFieldFormState) => (formState = state); + const onChangeState = (state: FormState<CustomFieldConfiguration>) => (formState = state); const initialValue = { required: true, @@ -190,9 +190,9 @@ describe('CustomFieldsForm ', () => { }); it('serializes the data correctly if required is not selected', async () => { - let formState: CustomFieldFormState; + let formState: FormState<CustomFieldConfiguration>; - const onChangeState = (state: CustomFieldFormState) => (formState = state); + const onChangeState = (state: FormState<CustomFieldConfiguration>) => (formState = state); appMockRender.render(<CustomFieldsForm onChange={onChangeState} initialValue={null} />); @@ -215,9 +215,9 @@ describe('CustomFieldsForm ', () => { }); it('deserializes the "type: text" custom field data correctly', async () => { - let formState: CustomFieldFormState; + let formState: FormState<CustomFieldConfiguration>; - const onChangeState = (state: CustomFieldFormState) => (formState = state); + const onChangeState = (state: FormState<CustomFieldConfiguration>) => (formState = state); appMockRender.render( <CustomFieldsForm onChange={onChangeState} initialValue={customFieldsConfigurationMock[0]} /> @@ -247,9 +247,9 @@ describe('CustomFieldsForm ', () => { }); it('deserializes the "type: toggle" custom field data correctly', async () => { - let formState: CustomFieldFormState; + let formState: FormState<CustomFieldConfiguration>; - const onChangeState = (state: CustomFieldFormState) => (formState = state); + const onChangeState = (state: FormState<CustomFieldConfiguration>) => (formState = state); appMockRender.render( <CustomFieldsForm onChange={onChangeState} initialValue={customFieldsConfigurationMock[1]} /> diff --git a/x-pack/plugins/cases/public/components/custom_fields/form.tsx b/x-pack/plugins/cases/public/components/custom_fields/form.tsx index 230b947db854d..2a2c675aac31d 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/form.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/form.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import type { FormHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { Form, useForm } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import React, { useEffect, useMemo } from 'react'; import { v4 as uuidv4 } from 'uuid'; @@ -15,14 +14,10 @@ import { FormFields } from './form_fields'; import type { CustomFieldConfiguration } from '../../../common/types/domain'; import { CustomFieldTypes } from '../../../common/types/domain'; import { customFieldSerializer } from './utils'; - -export interface CustomFieldFormState { - isValid: boolean | undefined; - submit: FormHook<CustomFieldConfiguration>['submit']; -} +import type { FormState } from '../configure_cases/flyout'; interface Props { - onChange: (state: CustomFieldFormState) => void; + onChange: (state: FormState<CustomFieldConfiguration>) => void; initialValue: CustomFieldConfiguration | null; } diff --git a/x-pack/plugins/cases/public/components/custom_fields/text/create.test.tsx b/x-pack/plugins/cases/public/components/custom_fields/text/create.test.tsx index 9db8541993057..0b62466fa6858 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/text/create.test.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/text/create.test.tsx @@ -53,6 +53,22 @@ describe('Create ', () => { ); }); + it('does not render default value when setDefaultValue is false', async () => { + render( + <FormTestComponent onSubmit={onSubmit}> + <Create + isLoading={false} + customFieldConfiguration={customFieldConfiguration} + setDefaultValue={false} + /> + </FormTestComponent> + ); + + expect( + await screen.findByTestId(`${customFieldConfiguration.key}-text-create-custom-field`) + ).toHaveValue(''); + }); + it('renders loading state correctly', async () => { render( <FormTestComponent onSubmit={onSubmit}> diff --git a/x-pack/plugins/cases/public/components/custom_fields/text/create.tsx b/x-pack/plugins/cases/public/components/custom_fields/text/create.tsx index aaab2043fb332..f735a4034f024 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/text/create.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/text/create.tsx @@ -11,16 +11,19 @@ import { TextField } from '@kbn/es-ui-shared-plugin/static/forms/components'; import type { CaseCustomFieldText } from '../../../../common/types/domain'; import type { CustomFieldType } from '../types'; import { getTextFieldConfig } from './config'; +import { OptionalFieldLabel } from '../../optional_field_label'; const CreateComponent: CustomFieldType<CaseCustomFieldText>['Create'] = ({ customFieldConfiguration, isLoading, + setAsOptional, + setDefaultValue = true, }) => { const { key, label, required, defaultValue } = customFieldConfiguration; const config = getTextFieldConfig({ - required, + required: setAsOptional ? false : required, label, - ...(defaultValue && { defaultValue: String(defaultValue) }), + ...(defaultValue && setDefaultValue && { defaultValue: String(defaultValue) }), }); return ( @@ -30,6 +33,7 @@ const CreateComponent: CustomFieldType<CaseCustomFieldText>['Create'] = ({ component={TextField} label={label} componentProps={{ + labelAppend: setAsOptional ? OptionalFieldLabel : null, euiFieldProps: { 'data-test-subj': `${key}-text-create-custom-field`, fullWidth: true, diff --git a/x-pack/plugins/cases/public/components/custom_fields/toggle/create.test.tsx b/x-pack/plugins/cases/public/components/custom_fields/toggle/create.test.tsx index 9672b3c8bb6be..8eb7c50300840 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/toggle/create.test.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/toggle/create.test.tsx @@ -36,6 +36,20 @@ describe('Create ', () => { expect(await screen.findByRole('switch')).toBeChecked(); // defaultValue true }); + it('does not render default value when setDefaultValue is false', async () => { + render( + <FormTestComponent onSubmit={onSubmit}> + <Create + isLoading={false} + customFieldConfiguration={customFieldConfiguration} + setDefaultValue={false} + /> + </FormTestComponent> + ); + + expect(await screen.findByRole('switch')).not.toBeChecked(); + }); + it('updates the value correctly', async () => { render( <FormTestComponent onSubmit={onSubmit}> diff --git a/x-pack/plugins/cases/public/components/custom_fields/toggle/create.tsx b/x-pack/plugins/cases/public/components/custom_fields/toggle/create.tsx index 2d3f51bc4f678..eb3ad2b114e57 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/toggle/create.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/toggle/create.tsx @@ -14,6 +14,7 @@ import type { CustomFieldType } from '../types'; const CreateComponent: CustomFieldType<CaseCustomFieldToggle>['Create'] = ({ customFieldConfiguration, isLoading, + setDefaultValue = true, }) => { const { key, label, defaultValue } = customFieldConfiguration; @@ -21,7 +22,7 @@ const CreateComponent: CustomFieldType<CaseCustomFieldToggle>['Create'] = ({ <UseField path={`customFields.${key}`} component={ToggleField} - config={{ defaultValue: defaultValue ? defaultValue : false }} + config={{ defaultValue: defaultValue && setDefaultValue ? defaultValue : false }} key={key} label={label} componentProps={{ diff --git a/x-pack/plugins/cases/public/components/custom_fields/types.ts b/x-pack/plugins/cases/public/components/custom_fields/types.ts index 856ff7e9e1c60..a1dcffaec6b97 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/types.ts +++ b/x-pack/plugins/cases/public/components/custom_fields/types.ts @@ -30,6 +30,8 @@ export interface CustomFieldType<T extends CaseUICustomField> { Create: React.FC<{ customFieldConfiguration: CasesConfigurationUICustomField; isLoading: boolean; + setAsOptional?: boolean; + setDefaultValue?: boolean; }>; } diff --git a/x-pack/plugins/cases/public/components/custom_fields/utils.test.ts b/x-pack/plugins/cases/public/components/custom_fields/utils.test.ts index ba629a6ea10a4..5a21319645836 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/utils.test.ts +++ b/x-pack/plugins/cases/public/components/custom_fields/utils.test.ts @@ -5,202 +5,11 @@ * 2.0. */ -import { addOrReplaceCustomField, customFieldSerializer } from './utils'; -import { customFieldsConfigurationMock, customFieldsMock } from '../../containers/mock'; +import { customFieldSerializer } from './utils'; import type { CustomFieldConfiguration } from '../../../common/types/domain'; import { CustomFieldTypes } from '../../../common/types/domain'; -import type { CaseUICustomField } from '../../../common/ui'; describe('utils ', () => { - describe('addOrReplaceCustomField ', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('adds new custom field correctly', async () => { - const fieldToAdd: CaseUICustomField = { - key: 'my_test_key', - type: CustomFieldTypes.TEXT, - value: 'my_test_value', - }; - const res = addOrReplaceCustomField(customFieldsMock, fieldToAdd); - expect(res).toMatchInlineSnapshot( - [...customFieldsMock, fieldToAdd], - ` - Array [ - Object { - "key": "test_key_1", - "type": "text", - "value": "My text test value 1", - }, - Object { - "key": "test_key_2", - "type": "toggle", - "value": true, - }, - Object { - "key": "test_key_3", - "type": "text", - "value": null, - }, - Object { - "key": "test_key_4", - "type": "toggle", - "value": null, - }, - Object { - "key": "my_test_key", - "type": "text", - "value": "my_test_value", - }, - ] - ` - ); - }); - - it('updates existing custom field correctly', async () => { - const fieldToUpdate = { - ...customFieldsMock[0], - field: { value: ['My text test value 1!!!'] }, - }; - - const res = addOrReplaceCustomField(customFieldsMock, fieldToUpdate as CaseUICustomField); - expect(res).toMatchInlineSnapshot( - [ - { ...fieldToUpdate }, - { ...customFieldsMock[1] }, - { ...customFieldsMock[2] }, - { ...customFieldsMock[3] }, - ], - ` - Array [ - Object { - "field": Object { - "value": Array [ - "My text test value 1!!!", - ], - }, - "key": "test_key_1", - "type": "text", - "value": "My text test value 1", - }, - Object { - "key": "test_key_2", - "type": "toggle", - "value": true, - }, - Object { - "key": "test_key_3", - "type": "text", - "value": null, - }, - Object { - "key": "test_key_4", - "type": "toggle", - "value": null, - }, - ] - ` - ); - }); - - it('adds new custom field configuration correctly', async () => { - const fieldToAdd = { - key: 'my_test_key', - type: CustomFieldTypes.TEXT, - label: 'my_test_label', - required: true, - }; - const res = addOrReplaceCustomField(customFieldsConfigurationMock, fieldToAdd); - expect(res).toMatchInlineSnapshot( - [...customFieldsConfigurationMock, fieldToAdd], - ` - Array [ - Object { - "defaultValue": "My default value", - "key": "test_key_1", - "label": "My test label 1", - "required": true, - "type": "text", - }, - Object { - "defaultValue": true, - "key": "test_key_2", - "label": "My test label 2", - "required": true, - "type": "toggle", - }, - Object { - "key": "test_key_3", - "label": "My test label 3", - "required": false, - "type": "text", - }, - Object { - "key": "test_key_4", - "label": "My test label 4", - "required": false, - "type": "toggle", - }, - Object { - "key": "my_test_key", - "label": "my_test_label", - "required": true, - "type": "text", - }, - ] - ` - ); - }); - - it('updates existing custom field config correctly', async () => { - const fieldToUpdate = { - ...customFieldsConfigurationMock[0], - label: `${customFieldsConfigurationMock[0].label}!!!`, - }; - - const res = addOrReplaceCustomField(customFieldsConfigurationMock, fieldToUpdate); - expect(res).toMatchInlineSnapshot( - [ - { ...fieldToUpdate }, - { ...customFieldsConfigurationMock[1] }, - { ...customFieldsConfigurationMock[2] }, - { ...customFieldsConfigurationMock[3] }, - ], - ` - Array [ - Object { - "defaultValue": "My default value", - "key": "test_key_1", - "label": "My test label 1!!!", - "required": true, - "type": "text", - }, - Object { - "defaultValue": true, - "key": "test_key_2", - "label": "My test label 2", - "required": true, - "type": "toggle", - }, - Object { - "key": "test_key_3", - "label": "My test label 3", - "required": false, - "type": "text", - }, - Object { - "key": "test_key_4", - "label": "My test label 4", - "required": false, - "type": "toggle", - }, - ] - ` - ); - }); - }); - describe('customFieldSerializer ', () => { it('serializes the data correctly if the default value is a normal string', async () => { const customField = { diff --git a/x-pack/plugins/cases/public/components/custom_fields/utils.ts b/x-pack/plugins/cases/public/components/custom_fields/utils.ts index bea01a3761bd0..3842b75b5a7ea 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/utils.ts +++ b/x-pack/plugins/cases/public/components/custom_fields/utils.ts @@ -9,27 +9,6 @@ import { isEmptyString } from '@kbn/es-ui-shared-plugin/static/validators/string import { isString } from 'lodash'; import type { CustomFieldConfiguration } from '../../../common/types/domain'; -export const addOrReplaceCustomField = <T extends { key: string }>( - customFields: T[], - customFieldToAdd: T -): T[] => { - const foundCustomFieldIndex = customFields.findIndex( - (customField) => customField.key === customFieldToAdd.key - ); - - if (foundCustomFieldIndex === -1) { - return [...customFields, customFieldToAdd]; - } - - return customFields.map((customField) => { - if (customField.key !== customFieldToAdd.key) { - return customField; - } - - return customFieldToAdd; - }); -}; - export const customFieldSerializer = ( field: CustomFieldConfiguration ): CustomFieldConfiguration => { diff --git a/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx b/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx index c939feda42e40..b1437e2e2a253 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx @@ -34,7 +34,7 @@ type MarkdownEditorFormProps = EuiMarkdownEditorProps & { bottomRightContent?: React.ReactNode; caseTitle?: string; caseTags?: string[]; - draftStorageKey: string; + draftStorageKey?: string; disabledUiPlugins?: string[]; initialValue?: string; }; @@ -59,7 +59,7 @@ export const MarkdownEditorForm = React.memo( const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); const { hasConflicts } = useMarkdownSessionStorage({ field, - sessionKey: draftStorageKey, + sessionKey: draftStorageKey ?? '', initialValue, }); const { euiTheme } = useEuiTheme(); diff --git a/x-pack/plugins/cases/public/components/markdown_editor/use_markdown_session_storage.test.tsx b/x-pack/plugins/cases/public/components/markdown_editor/use_markdown_session_storage.test.tsx index 7de2e83cf234d..e4ce68ed45237 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/use_markdown_session_storage.test.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/use_markdown_session_storage.test.tsx @@ -54,6 +54,17 @@ describe('useMarkdownSessionStorage', () => { }); }); + it('should return hasConflicts as false when sessionKey is empty', async () => { + const { result, waitFor } = renderHook(() => + useMarkdownSessionStorage({ field, sessionKey: '', initialValue }) + ); + + await waitFor(() => { + expect(field.setValue).not.toHaveBeenCalled(); + expect(result.current.hasConflicts).toBe(false); + }); + }); + it('should update the session value with field value when it is first render', async () => { const { waitFor } = renderHook<SessionStorageType, { hasConflicts: boolean }>( (props) => { diff --git a/x-pack/plugins/cases/public/components/markdown_editor/use_markdown_session_storage.tsx b/x-pack/plugins/cases/public/components/markdown_editor/use_markdown_session_storage.tsx index e33fed6729858..0a82d43cc093d 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/use_markdown_session_storage.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/use_markdown_session_storage.tsx @@ -30,7 +30,7 @@ export const useMarkdownSessionStorage = ({ const [sessionValue, setSessionValue] = useSessionStorage(sessionKey, '', true); - if (!isEmpty(sessionValue) && isFirstRender.current) { + if (!isEmpty(sessionValue) && !isEmpty(sessionKey) && isFirstRender.current) { field.setValue(sessionValue); } @@ -45,7 +45,9 @@ export const useMarkdownSessionStorage = ({ useDebounce( () => { - setSessionValue(field.value); + if (!isEmpty(sessionKey)) { + setSessionValue(field.value); + } }, STORAGE_DEBOUNCE_TIME, [field.value] diff --git a/x-pack/plugins/cases/public/components/create/optional_field_label/index.test.tsx b/x-pack/plugins/cases/public/components/optional_field_label/index.test.tsx similarity index 100% rename from x-pack/plugins/cases/public/components/create/optional_field_label/index.test.tsx rename to x-pack/plugins/cases/public/components/optional_field_label/index.test.tsx diff --git a/x-pack/plugins/cases/public/components/create/optional_field_label/index.tsx b/x-pack/plugins/cases/public/components/optional_field_label/index.tsx similarity index 89% rename from x-pack/plugins/cases/public/components/create/optional_field_label/index.tsx rename to x-pack/plugins/cases/public/components/optional_field_label/index.tsx index ea994b2219961..98c101440116a 100644 --- a/x-pack/plugins/cases/public/components/create/optional_field_label/index.tsx +++ b/x-pack/plugins/cases/public/components/optional_field_label/index.tsx @@ -8,7 +8,7 @@ import { EuiText } from '@elastic/eui'; import React from 'react'; -import * as i18n from '../../../common/translations'; +import * as i18n from '../../common/translations'; export const OptionalFieldLabel = ( <EuiText color="subdued" size="xs" data-test-subj="form-optional-field-label"> diff --git a/x-pack/plugins/cases/public/components/templates/form.test.tsx b/x-pack/plugins/cases/public/components/templates/form.test.tsx new file mode 100644 index 0000000000000..a01aa25132cb5 --- /dev/null +++ b/x-pack/plugins/cases/public/components/templates/form.test.tsx @@ -0,0 +1,790 @@ +/* + * 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 { act, screen, waitFor, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import type { AppMockRenderer } from '../../common/mock'; +import { createAppMockRenderer, mockedTestProvidersOwner } from '../../common/mock'; +import { + MAX_TAGS_PER_TEMPLATE, + MAX_TEMPLATE_DESCRIPTION_LENGTH, + MAX_TEMPLATE_NAME_LENGTH, + MAX_TEMPLATE_TAG_LENGTH, +} from '../../../common/constants'; +import { ConnectorTypes, CustomFieldTypes } from '../../../common/types/domain'; +import { + connectorsMock, + customFieldsConfigurationMock, + templatesConfigurationMock, +} from '../../containers/mock'; +import { useGetChoices } from '../connectors/servicenow/use_get_choices'; +import { useGetChoicesResponse } from '../create/mock'; +import type { FormState } from '../configure_cases/flyout'; +import { TemplateForm } from './form'; +import type { TemplateFormProps } from './types'; + +jest.mock('../connectors/servicenow/use_get_choices'); + +const useGetChoicesMock = useGetChoices as jest.Mock; + +describe('TemplateForm', () => { + let appMockRenderer: AppMockRenderer; + const defaultProps = { + connectors: connectorsMock, + currentConfiguration: { + closureType: 'close-by-user' as const, + connector: { + fields: null, + id: 'none', + name: 'none', + type: ConnectorTypes.none, + }, + customFields: [], + templates: [], + mappings: [], + version: '', + id: '', + owner: mockedTestProvidersOwner[0], + }, + onChange: jest.fn(), + initialValue: null, + }; + + beforeEach(() => { + jest.clearAllMocks(); + appMockRenderer = createAppMockRenderer(); + useGetChoicesMock.mockReturnValue(useGetChoicesResponse); + }); + + it('renders correctly', async () => { + appMockRenderer.render(<TemplateForm {...defaultProps} />); + + expect(await screen.findByTestId('template-creation-form-steps')).toBeInTheDocument(); + }); + + it('renders all default fields', async () => { + appMockRenderer.render(<TemplateForm {...defaultProps} />); + + expect(await screen.findByTestId('template-name-input')).toBeInTheDocument(); + expect(await screen.findByTestId('template-description-input')).toBeInTheDocument(); + expect(await screen.findByTestId('case-form-fields')).toBeInTheDocument(); + expect(await screen.findByTestId('caseTitle')).toBeInTheDocument(); + expect(await screen.findByTestId('caseTags')).toBeInTheDocument(); + expect(await screen.findByTestId('caseCategory')).toBeInTheDocument(); + expect(await screen.findByTestId('caseSeverity')).toBeInTheDocument(); + expect(await screen.findByTestId('caseDescription')).toBeInTheDocument(); + expect(await screen.findByTestId('caseConnectors')).toBeInTheDocument(); + }); + + it('renders all fields as per initialValue', async () => { + const newProps = { + ...defaultProps, + initialValue: { + key: 'template_key_1', + name: 'Template 1', + description: 'Sample description', + caseFields: null, + }, + }; + appMockRenderer.render(<TemplateForm {...newProps} />); + + expect(await screen.findByTestId('template-name-input')).toHaveValue('Template 1'); + expect(await screen.findByTestId('template-description-input')).toHaveValue( + 'Sample description' + ); + expect(await screen.findByTestId('case-form-fields')).toBeInTheDocument(); + expect(await screen.findByTestId('caseTitle')).toBeInTheDocument(); + expect(await screen.findByTestId('caseTags')).toBeInTheDocument(); + expect(await screen.findByTestId('caseCategory')).toBeInTheDocument(); + expect(await screen.findByTestId('caseSeverity')).toBeInTheDocument(); + expect(await screen.findByTestId('caseDescription')).toBeInTheDocument(); + expect(await screen.findByTestId('caseConnectors')).toBeInTheDocument(); + }); + + it('renders case fields as per initialValue', async () => { + const newProps = { + ...defaultProps, + initialValue: { + key: 'template_key_1', + name: 'Template 1', + description: 'Sample description', + caseFields: { + title: 'Case with template 1', + description: 'case description', + }, + }, + }; + appMockRenderer.render(<TemplateForm {...newProps} />); + + expect(await within(await screen.findByTestId('caseTitle')).findByTestId('input')).toHaveValue( + 'Case with template 1' + ); + expect( + await within(await screen.findByTestId('caseDescription')).findByTestId( + 'euiMarkdownEditorTextArea' + ) + ).toHaveValue('case description'); + }); + + it('renders case fields as optional', async () => { + appMockRenderer.render(<TemplateForm {...defaultProps} />); + + const title = await screen.findByTestId('caseTitle'); + const tags = await screen.findByTestId('caseTags'); + const category = await screen.findByTestId('caseCategory'); + const description = await screen.findByTestId('caseDescription'); + + expect(await within(title).findByTestId('form-optional-field-label')).toBeInTheDocument(); + expect(await within(tags).findByTestId('form-optional-field-label')).toBeInTheDocument(); + expect(await within(category).findByTestId('form-optional-field-label')).toBeInTheDocument(); + expect(await within(description).findByTestId('form-optional-field-label')).toBeInTheDocument(); + }); + + it('serializes the template field data correctly', async () => { + let formState: FormState<TemplateFormProps>; + + const onChangeState = (state: FormState<TemplateFormProps>) => (formState = state); + + appMockRenderer.render(<TemplateForm {...{ ...defaultProps, onChange: onChangeState }} />); + + await waitFor(() => { + expect(formState).not.toBeUndefined(); + }); + + userEvent.paste(await screen.findByTestId('template-name-input'), 'Template 1'); + + userEvent.paste( + await screen.findByTestId('template-description-input'), + 'this is a first template' + ); + + const templateTags = await screen.findByTestId('template-tags'); + + userEvent.paste(within(templateTags).getByRole('combobox'), 'foo'); + userEvent.keyboard('{enter}'); + userEvent.paste(within(templateTags).getByRole('combobox'), 'bar'); + userEvent.keyboard('{enter}'); + + await act(async () => { + const { data, isValid } = await formState!.submit(); + + expect(isValid).toBe(true); + + expect(data).toEqual({ + key: expect.anything(), + caseFields: { + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + customFields: [], + settings: { + syncAlerts: true, + }, + }, + description: 'this is a first template', + name: 'Template 1', + tags: ['foo', 'bar'], + }); + }); + }); + + it('serializes the template field data correctly with existing fields', async () => { + let formState: FormState<TemplateFormProps>; + + const onChangeState = (state: FormState<TemplateFormProps>) => (formState = state); + + const newProps = { + ...defaultProps, + initialValue: { ...templatesConfigurationMock[0], tags: ['foo', 'bar'] }, + connectors: [], + onChange: onChangeState, + isEditMode: true, + }; + + appMockRenderer.render(<TemplateForm {...newProps} />); + + await waitFor(() => { + expect(formState).not.toBeUndefined(); + }); + + await act(async () => { + const { data, isValid } = await formState!.submit(); + + expect(isValid).toBe(true); + + expect(data).toEqual({ + key: expect.anything(), + caseFields: { + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + customFields: [], + settings: { + syncAlerts: true, + }, + }, + description: 'This is a first test template', + name: 'First test template', + tags: ['foo', 'bar'], + }); + }); + }); + + it('serializes the case field data correctly', async () => { + let formState: FormState<TemplateFormProps>; + + const onChangeState = (state: FormState<TemplateFormProps>) => (formState = state); + + appMockRenderer.render( + <TemplateForm + {...{ + ...defaultProps, + initialValue: { key: 'template_1_key', name: 'Template 1', caseFields: null }, + onChange: onChangeState, + }} + /> + ); + + await waitFor(() => { + expect(formState).not.toBeUndefined(); + }); + + const caseTitle = await screen.findByTestId('caseTitle'); + userEvent.paste(within(caseTitle).getByTestId('input'), 'Case with Template 1'); + + const caseDescription = await screen.findByTestId('caseDescription'); + userEvent.paste( + within(caseDescription).getByTestId('euiMarkdownEditorTextArea'), + 'This is a case description' + ); + + const caseTags = await screen.findByTestId('caseTags'); + userEvent.paste(within(caseTags).getByRole('combobox'), 'template-1'); + userEvent.keyboard('{enter}'); + + const caseCategory = await screen.findByTestId('caseCategory'); + userEvent.type(within(caseCategory).getByRole('combobox'), 'new {enter}'); + + await act(async () => { + const { data, isValid } = await formState!.submit(); + + expect(isValid).toBe(true); + + expect(data).toEqual({ + key: expect.anything(), + caseFields: { + category: 'new', + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + customFields: [], + description: 'This is a case description', + settings: { + syncAlerts: true, + }, + tags: ['template-1'], + title: 'Case with Template 1', + }, + description: undefined, + name: 'Template 1', + tags: [], + }); + }); + }); + + it('serializes the case field data correctly with existing fields', async () => { + let formState: FormState<TemplateFormProps>; + + const onChangeState = (state: FormState<TemplateFormProps>) => (formState = state); + + const newProps = { + ...defaultProps, + initialValue: templatesConfigurationMock[3], + connectors: [], + onChange: onChangeState, + isEditMode: true, + }; + + appMockRenderer.render(<TemplateForm {...newProps} />); + + await waitFor(() => { + expect(formState).not.toBeUndefined(); + }); + + await act(async () => { + const { data, isValid } = await formState!.submit(); + + expect(isValid).toBe(true); + + expect(data).toEqual({ + key: expect.anything(), + caseFields: { + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + customFields: [], + description: 'case desc', + settings: { + syncAlerts: true, + }, + severity: 'low', + tags: ['sample-4'], + title: 'Case with sample template 4', + }, + description: 'This is a fourth test template', + name: 'Fourth test template', + tags: ['foo', 'bar'], + }); + }); + }); + + it('serializes the connector fields data correctly', async () => { + let formState: FormState<TemplateFormProps>; + + const onChangeState = (state: FormState<TemplateFormProps>) => (formState = state); + + appMockRenderer.render( + <TemplateForm + {...{ + ...defaultProps, + initialValue: { key: 'template_1_key', name: 'Template 1', caseFields: null }, + currentConfiguration: { + ...defaultProps.currentConfiguration, + connector: { + id: 'servicenow-1', + name: 'My SN connector', + type: ConnectorTypes.serviceNowITSM, + fields: null, + }, + }, + onChange: onChangeState, + }} + /> + ); + + await screen.findByTestId('caseConnectors'); + + await waitFor(() => { + expect(formState).not.toBeUndefined(); + }); + + await act(async () => { + const { data, isValid } = await formState!.submit(); + + expect(isValid).toBe(true); + + expect(data).toEqual({ + key: expect.anything(), + caseFields: { + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + customFields: [], + settings: { + syncAlerts: true, + }, + }, + description: undefined, + name: 'Template 1', + tags: [], + }); + }); + }); + + it('serializes the connector fields data correctly with existing connector', async () => { + let formState: FormState<TemplateFormProps>; + + const onChangeState = (state: FormState<TemplateFormProps>) => (formState = state); + + const newProps = { + ...defaultProps, + initialValue: { + key: 'template_1_key', + name: 'Template 1', + caseFields: { + connector: { + id: 'servicenow-1', + type: ConnectorTypes.serviceNowITSM, + name: 'my-SN-connector', + fields: null, + }, + }, + }, + connectors: connectorsMock, + currentConfiguration: { + ...defaultProps.currentConfiguration, + connector: { + id: 'resilient-2', + name: 'My Resilient connector', + type: ConnectorTypes.resilient, + fields: null, + }, + }, + onChange: onChangeState, + isEditMode: true, + }; + + appMockRenderer.render(<TemplateForm {...newProps} />); + + await waitFor(() => { + expect(formState).not.toBeUndefined(); + }); + + expect(await screen.findByTestId('connector-fields-sn-itsm')).toBeInTheDocument(); + + userEvent.selectOptions(await screen.findByTestId('categorySelect'), ['Denial of Service']); + + await act(async () => { + const { data, isValid } = await formState!.submit(); + + expect(isValid).toBe(true); + + expect(data).toEqual({ + key: expect.anything(), + caseFields: { + connector: { + fields: { + category: 'Denial of Service', + impact: null, + severity: null, + subcategory: null, + urgency: null, + }, + id: 'servicenow-1', + name: 'My SN connector', + type: '.servicenow', + }, + customFields: [], + settings: { + syncAlerts: true, + }, + }, + description: undefined, + name: 'Template 1', + tags: [], + }); + }); + }); + + it('serializes the custom fields data correctly', async () => { + let formState: FormState<TemplateFormProps>; + + const onChangeState = (state: FormState<TemplateFormProps>) => (formState = state); + + appMockRenderer.render( + <TemplateForm + {...{ + ...defaultProps, + initialValue: { + key: 'template_1_key', + name: 'Template 1', + caseFields: null, + }, + currentConfiguration: { + ...defaultProps.currentConfiguration, + customFields: customFieldsConfigurationMock, + }, + onChange: onChangeState, + }} + /> + ); + + await waitFor(() => { + expect(formState).not.toBeUndefined(); + }); + + const customFieldsElement = await screen.findByTestId('caseCustomFields'); + + expect( + await within(customFieldsElement).findAllByTestId('form-optional-field-label') + ).toHaveLength( + customFieldsConfigurationMock.filter((field) => field.type === CustomFieldTypes.TEXT).length + ); + + const textField = customFieldsConfigurationMock[0]; + const toggleField = customFieldsConfigurationMock[3]; + + const textCustomField = await screen.findByTestId( + `${textField.key}-${textField.type}-create-custom-field` + ); + + userEvent.clear(textCustomField); + + userEvent.paste(textCustomField, 'My text test value 1'); + + userEvent.click( + await screen.findByTestId(`${toggleField.key}-${toggleField.type}-create-custom-field`) + ); + + await act(async () => { + const { data, isValid } = await formState!.submit(); + + expect(isValid).toBe(true); + + expect(data).toEqual({ + key: expect.anything(), + caseFields: { + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + customFields: [ + { + key: 'test_key_1', + type: 'text', + value: 'My text test value 1', + }, + { + key: 'test_key_2', + type: 'toggle', + value: true, + }, + { + key: 'test_key_4', + type: 'toggle', + value: true, + }, + ], + settings: { + syncAlerts: true, + }, + }, + description: undefined, + name: 'Template 1', + tags: [], + }); + }); + }); + + it('serializes the custom fields data correctly with existing custom fields', async () => { + let formState: FormState<TemplateFormProps>; + + const onChangeState = (state: FormState<TemplateFormProps>) => (formState = state); + + const newProps = { + ...defaultProps, + initialValue: { + key: 'template_1_key', + name: 'Template 1', + caseFields: { + customFields: [ + { + type: CustomFieldTypes.TEXT as const, + key: 'test_key_1', + value: 'this is my first custom field value', + }, + { + type: CustomFieldTypes.TOGGLE as const, + key: 'test_key_2', + value: false, + }, + ], + }, + }, + onChange: onChangeState, + currentConfiguration: { + ...defaultProps.currentConfiguration, + customFields: customFieldsConfigurationMock, + }, + }; + appMockRenderer.render(<TemplateForm {...newProps} />); + + await waitFor(() => { + expect(formState).not.toBeUndefined(); + }); + + const toggleField = customFieldsConfigurationMock[1]; + + userEvent.click( + await screen.findByTestId(`${toggleField.key}-${toggleField.type}-create-custom-field`) + ); + + await act(async () => { + const { data, isValid } = await formState!.submit(); + + expect(isValid).toBe(true); + expect(data).toEqual({ + key: expect.anything(), + caseFields: { + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + customFields: [ + { + key: 'test_key_1', + type: 'text', + value: 'this is my first custom field value', + }, + { + key: 'test_key_2', + type: 'toggle', + value: true, + }, + { + key: 'test_key_4', + type: 'toggle', + value: false, + }, + ], + settings: { + syncAlerts: true, + }, + }, + description: undefined, + name: 'Template 1', + tags: [], + }); + }); + }); + + it('shows form state as invalid when template name missing', async () => { + let formState: FormState<TemplateFormProps>; + + const onChangeState = (state: FormState<TemplateFormProps>) => (formState = state); + + appMockRenderer.render(<TemplateForm {...{ ...defaultProps, onChange: onChangeState }} />); + + await waitFor(() => { + expect(formState).not.toBeUndefined(); + }); + + userEvent.paste(await screen.findByTestId('template-name-input'), ''); + + await act(async () => { + const { data, isValid } = await formState!.submit(); + + expect(isValid).toBe(false); + + expect(data).toEqual({}); + }); + }); + + it('shows from state as invalid when template name is too long', async () => { + let formState: FormState<TemplateFormProps>; + + const onChangeState = (state: FormState<TemplateFormProps>) => (formState = state); + + appMockRenderer.render(<TemplateForm {...{ ...defaultProps, onChange: onChangeState }} />); + + await waitFor(() => { + expect(formState).not.toBeUndefined(); + }); + + const name = 'a'.repeat(MAX_TEMPLATE_NAME_LENGTH + 1); + + userEvent.paste(await screen.findByTestId('template-name-input'), name); + + await act(async () => { + const { data, isValid } = await formState!.submit(); + + expect(isValid).toBe(false); + + expect(data).toEqual({}); + }); + }); + + it('shows from state as invalid when template description is too long', async () => { + let formState: FormState<TemplateFormProps>; + + const onChangeState = (state: FormState<TemplateFormProps>) => (formState = state); + + appMockRenderer.render(<TemplateForm {...{ ...defaultProps, onChange: onChangeState }} />); + + await waitFor(() => { + expect(formState).not.toBeUndefined(); + }); + + const description = 'a'.repeat(MAX_TEMPLATE_DESCRIPTION_LENGTH + 1); + + userEvent.paste(await screen.findByTestId('template-description-input'), description); + + await act(async () => { + const { data, isValid } = await formState!.submit(); + + expect(isValid).toBe(false); + + expect(data).toEqual({}); + }); + }); + + it('shows from state as invalid when template tags are more than 10', async () => { + let formState: FormState<TemplateFormProps>; + + const onChangeState = (state: FormState<TemplateFormProps>) => (formState = state); + + appMockRenderer.render(<TemplateForm {...{ ...defaultProps, onChange: onChangeState }} />); + + await waitFor(() => { + expect(formState).not.toBeUndefined(); + }); + + const tagsArray = Array(MAX_TAGS_PER_TEMPLATE + 1).fill('foo'); + + const templateTags = await screen.findByTestId('template-tags'); + + tagsArray.forEach((tag) => { + userEvent.paste(within(templateTags).getByRole('combobox'), 'template-1'); + userEvent.keyboard('{enter}'); + }); + + await act(async () => { + const { data, isValid } = await formState!.submit(); + + expect(isValid).toBe(false); + + expect(data).toEqual({}); + }); + }); + + it('shows from state as invalid when template tag is more than 50 characters', async () => { + let formState: FormState<TemplateFormProps>; + + const onChangeState = (state: FormState<TemplateFormProps>) => (formState = state); + + appMockRenderer.render(<TemplateForm {...{ ...defaultProps, onChange: onChangeState }} />); + + await waitFor(() => { + expect(formState).not.toBeUndefined(); + }); + + const x = 'a'.repeat(MAX_TEMPLATE_TAG_LENGTH + 1); + + const templateTags = await screen.findByTestId('template-tags'); + + userEvent.paste(within(templateTags).getByRole('combobox'), x); + userEvent.keyboard('{enter}'); + + await act(async () => { + const { data, isValid } = await formState!.submit(); + + expect(isValid).toBe(false); + + expect(data).toEqual({}); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/templates/form.tsx b/x-pack/plugins/cases/public/components/templates/form.tsx new file mode 100644 index 0000000000000..acd6855fe4706 --- /dev/null +++ b/x-pack/plugins/cases/public/components/templates/form.tsx @@ -0,0 +1,75 @@ +/* + * 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 { Form, useForm } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import React, { useEffect, useMemo } from 'react'; +import { v4 as uuidv4 } from 'uuid'; +import type { ActionConnector, TemplateConfiguration } from '../../../common/types/domain'; +import type { FormState } from '../configure_cases/flyout'; +import { schema } from './schema'; +import { FormFields } from './form_fields'; +import { templateDeserializer, templateSerializer } from './utils'; +import type { TemplateFormProps } from './types'; +import type { CasesConfigurationUI } from '../../containers/types'; + +interface Props { + onChange: (state: FormState<TemplateConfiguration, TemplateFormProps>) => void; + initialValue: TemplateConfiguration | null; + connectors: ActionConnector[]; + currentConfiguration: CasesConfigurationUI; + isEditMode?: boolean; +} + +const FormComponent: React.FC<Props> = ({ + onChange, + initialValue, + connectors, + currentConfiguration, + isEditMode = false, +}) => { + const keyDefaultValue = useMemo(() => uuidv4(), []); + + const { form } = useForm({ + defaultValue: initialValue ?? { + key: keyDefaultValue, + name: '', + description: '', + tags: [], + caseFields: { + connector: currentConfiguration.connector, + }, + }, + options: { stripEmptyFields: false }, + schema, + deserializer: templateDeserializer, + serializer: (data: TemplateFormProps) => + templateSerializer(connectors, currentConfiguration, data), + }); + + const { submit, isValid, isSubmitting } = form; + + useEffect(() => { + if (onChange) { + onChange({ isValid, submit }); + } + }, [onChange, isValid, submit]); + + return ( + <Form form={form}> + <FormFields + isSubmitting={isSubmitting} + connectors={connectors} + currentConfiguration={currentConfiguration} + isEditMode={isEditMode} + /> + </Form> + ); +}; + +FormComponent.displayName = 'TemplateForm'; + +export const TemplateForm = React.memo(FormComponent); diff --git a/x-pack/plugins/cases/public/components/templates/form_fields.test.tsx b/x-pack/plugins/cases/public/components/templates/form_fields.test.tsx new file mode 100644 index 0000000000000..814ba13efe6ed --- /dev/null +++ b/x-pack/plugins/cases/public/components/templates/form_fields.test.tsx @@ -0,0 +1,398 @@ +/* + * 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 { screen, waitFor, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import type { AppMockRenderer } from '../../common/mock'; +import { CaseSeverity, ConnectorTypes } from '../../../common/types/domain'; +import { createAppMockRenderer, mockedTestProvidersOwner } from '../../common/mock'; +import { FormTestComponent } from '../../common/test_utils'; +import { useGetChoices } from '../connectors/servicenow/use_get_choices'; +import { useGetChoicesResponse } from '../create/mock'; +import { connectorsMock, customFieldsConfigurationMock } from '../../containers/mock'; +import { TEMPLATE_FIELDS, CASE_FIELDS, CONNECTOR_FIELDS, CASE_SETTINGS } from './translations'; +import { FormFields } from './form_fields'; + +jest.mock('../connectors/servicenow/use_get_choices'); + +const useGetChoicesMock = useGetChoices as jest.Mock; + +describe('form fields', () => { + let appMockRenderer: AppMockRenderer; + const onSubmit = jest.fn(); + const formDefaultValue = { tags: [], templateTags: [] }; + const defaultProps = { + connectors: connectorsMock, + currentConfiguration: { + closureType: 'close-by-user' as const, + connector: { + fields: null, + id: 'none', + name: 'none', + type: ConnectorTypes.none, + }, + customFields: [], + templates: [], + mappings: [], + version: '', + id: '', + owner: mockedTestProvidersOwner[0], + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + appMockRenderer = createAppMockRenderer(); + useGetChoicesMock.mockReturnValue(useGetChoicesResponse); + }); + + it('renders correctly', async () => { + appMockRenderer.render( + <FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}> + <FormFields {...defaultProps} /> + </FormTestComponent> + ); + + expect(await screen.findByTestId('template-creation-form-steps')).toBeInTheDocument(); + }); + + it('renders all steps', async () => { + appMockRenderer.render( + <FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}> + <FormFields {...defaultProps} /> + </FormTestComponent> + ); + + expect(await screen.findByText(TEMPLATE_FIELDS)).toBeInTheDocument(); + expect(await screen.findByText(CASE_FIELDS)).toBeInTheDocument(); + expect(await screen.findByText(CASE_SETTINGS)).toBeInTheDocument(); + expect(await screen.findByText(CONNECTOR_FIELDS)).toBeInTheDocument(); + }); + + it('renders template fields correctly', async () => { + appMockRenderer.render( + <FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}> + <FormFields {...defaultProps} /> + </FormTestComponent> + ); + + expect(await screen.findByTestId('template-fields')).toBeInTheDocument(); + expect(await screen.findByTestId('template-name-input')).toBeInTheDocument(); + expect(await screen.findByTestId('template-tags')).toBeInTheDocument(); + expect(await screen.findByTestId('template-description-input')).toBeInTheDocument(); + }); + + it('renders case fields', async () => { + appMockRenderer.render( + <FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}> + <FormFields {...defaultProps} /> + </FormTestComponent> + ); + + expect(await screen.findByTestId('case-form-fields')).toBeInTheDocument(); + expect(await screen.findByTestId('caseTitle')).toBeInTheDocument(); + expect(await screen.findByTestId('caseTags')).toBeInTheDocument(); + expect(await screen.findByTestId('caseCategory')).toBeInTheDocument(); + expect(await screen.findByTestId('caseSeverity')).toBeInTheDocument(); + expect(await screen.findByTestId('caseDescription')).toBeInTheDocument(); + }); + + it('renders case fields with existing value', async () => { + appMockRenderer.render( + <FormTestComponent + formDefaultValue={{ + title: 'Case title', + description: 'case description', + tags: ['case-1', 'case-2'], + category: 'new', + severity: CaseSeverity.MEDIUM, + templateTags: [], + }} + onSubmit={onSubmit} + > + <FormFields {...defaultProps} /> + </FormTestComponent> + ); + + expect(await within(await screen.findByTestId('caseTitle')).findByTestId('input')).toHaveValue( + 'Case title' + ); + + const caseTags = await screen.findByTestId('caseTags'); + expect(await within(caseTags).findByTestId('comboBoxInput')).toHaveTextContent('case-1'); + expect(await within(caseTags).findByTestId('comboBoxInput')).toHaveTextContent('case-2'); + + const category = await screen.findByTestId('caseCategory'); + expect(await within(category).findByTestId('comboBoxSearchInput')).toHaveValue('new'); + expect(await screen.findByTestId('case-severity-selection-medium')).toBeInTheDocument(); + expect(await screen.findByTestId('caseDescription')).toHaveTextContent('case description'); + }); + + it('renders sync alerts correctly', async () => { + appMockRenderer.render( + <FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}> + <FormFields {...defaultProps} /> + </FormTestComponent> + ); + + expect(await screen.findByTestId('caseSyncAlerts')).toBeInTheDocument(); + }); + + it('renders custom fields correctly', async () => { + const newProps = { + ...defaultProps, + currentConfiguration: { + ...defaultProps.currentConfiguration, + customFields: customFieldsConfigurationMock, + }, + }; + + appMockRenderer.render( + <FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}> + <FormFields {...newProps} /> + </FormTestComponent> + ); + + expect(await screen.findByTestId('caseCustomFields')).toBeInTheDocument(); + }); + + it('renders default connector correctly', async () => { + appMockRenderer.render( + <FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}> + <FormFields {...defaultProps} /> + </FormTestComponent> + ); + + expect(await screen.findByTestId('caseConnectors')).toBeInTheDocument(); + }); + + it('renders connector and its fields correctly', async () => { + const newProps = { + ...defaultProps, + currentConfiguration: { + ...defaultProps.currentConfiguration, + connector: { + id: 'servicenow-1', + name: 'My SN connector', + type: ConnectorTypes.serviceNowITSM, + fields: null, + }, + }, + }; + + appMockRenderer.render( + <FormTestComponent + formDefaultValue={{ ...formDefaultValue, connectorId: 'servicenow-1' }} + onSubmit={onSubmit} + > + <FormFields {...newProps} /> + </FormTestComponent> + ); + + expect(await screen.findByTestId('caseConnectors')).toBeInTheDocument(); + expect(await screen.findByTestId('connector-fields')).toBeInTheDocument(); + expect(await screen.findByTestId('connector-fields-sn-itsm')).toBeInTheDocument(); + }); + + it('does not render sync alerts when feature is not enabled', () => { + appMockRenderer = createAppMockRenderer({ + features: { alerts: { sync: false, enabled: true } }, + }); + + appMockRenderer.render( + <FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}> + <FormFields {...defaultProps} /> + </FormTestComponent> + ); + + expect(screen.queryByTestId('caseSyncAlerts')).not.toBeInTheDocument(); + }); + + it('calls onSubmit with template fields', async () => { + appMockRenderer.render( + <FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}> + <FormFields {...defaultProps} /> + </FormTestComponent> + ); + + userEvent.paste(await screen.findByTestId('template-name-input'), 'Template 1'); + + const templateTags = await screen.findByTestId('template-tags'); + + userEvent.paste(within(templateTags).getByRole('combobox'), 'first'); + userEvent.keyboard('{enter}'); + + userEvent.paste( + await screen.findByTestId('template-description-input'), + 'this is a first template' + ); + + userEvent.click(screen.getByText('Submit')); + + await waitFor(() => { + expect(onSubmit).toBeCalledWith( + { + category: null, + connectorId: 'none', + tags: [], + syncAlerts: true, + name: 'Template 1', + templateDescription: 'this is a first template', + templateTags: ['first'], + }, + true + ); + }); + }); + + it('calls onSubmit with case fields', async () => { + appMockRenderer.render( + <FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}> + <FormFields {...defaultProps} /> + </FormTestComponent> + ); + + const caseTitle = await screen.findByTestId('caseTitle'); + userEvent.paste(within(caseTitle).getByTestId('input'), 'Case with Template 1'); + + const caseDescription = await screen.findByTestId('caseDescription'); + userEvent.paste( + within(caseDescription).getByTestId('euiMarkdownEditorTextArea'), + 'This is a case description' + ); + + const caseTags = await screen.findByTestId('caseTags'); + userEvent.paste(within(caseTags).getByRole('combobox'), 'template-1'); + userEvent.keyboard('{enter}'); + + const caseCategory = await screen.findByTestId('caseCategory'); + userEvent.type(within(caseCategory).getByRole('combobox'), 'new {enter}'); + + userEvent.click(screen.getByText('Submit')); + + await waitFor(() => { + expect(onSubmit).toBeCalledWith( + { + category: 'new', + tags: ['template-1'], + description: 'This is a case description', + title: 'Case with Template 1', + connectorId: 'none', + syncAlerts: true, + templateTags: [], + }, + true + ); + }); + }); + + it('calls onSubmit with custom fields', async () => { + const newProps = { + ...defaultProps, + currentConfiguration: { + ...defaultProps.currentConfiguration, + customFields: customFieldsConfigurationMock, + }, + }; + + appMockRenderer.render( + <FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}> + <FormFields {...newProps} /> + </FormTestComponent> + ); + + expect(await screen.findByTestId('caseCustomFields')).toBeInTheDocument(); + + const textField = customFieldsConfigurationMock[0]; + const toggleField = customFieldsConfigurationMock[1]; + + const textCustomField = await screen.findByTestId( + `${textField.key}-${textField.type}-create-custom-field` + ); + + userEvent.clear(textCustomField); + userEvent.paste(textCustomField, 'My text test value 1'); + + userEvent.click( + await screen.findByTestId(`${toggleField.key}-${toggleField.type}-create-custom-field`) + ); + + userEvent.click(screen.getByText('Submit')); + + await waitFor(() => { + expect(onSubmit).toBeCalledWith( + { + category: null, + tags: [], + connectorId: 'none', + customFields: { + test_key_1: 'My text test value 1', + test_key_2: false, + test_key_4: false, + }, + syncAlerts: true, + templateTags: [], + }, + true + ); + }); + }); + + it('calls onSubmit with connector fields', async () => { + const newProps = { + ...defaultProps, + currentConfiguration: { + ...defaultProps.currentConfiguration, + connector: { + id: 'servicenow-1', + name: 'My SN connector', + type: ConnectorTypes.serviceNowITSM, + fields: null, + }, + }, + }; + + appMockRenderer.render( + <FormTestComponent + formDefaultValue={{ ...formDefaultValue, connectorId: 'servicenow-1' }} + onSubmit={onSubmit} + > + <FormFields {...newProps} /> + </FormTestComponent> + ); + + expect(await screen.findByTestId('connector-fields-sn-itsm')).toBeInTheDocument(); + + userEvent.selectOptions(await screen.findByTestId('severitySelect'), '3'); + + userEvent.selectOptions(await screen.findByTestId('urgencySelect'), '2'); + + userEvent.selectOptions(await screen.findByTestId('categorySelect'), ['software']); + + userEvent.click(screen.getByText('Submit')); + + await waitFor(() => { + expect(onSubmit).toBeCalledWith( + { + tags: [], + category: null, + connectorId: 'servicenow-1', + fields: { + category: 'software', + severity: '3', + urgency: '2', + subcategory: null, + }, + syncAlerts: true, + templateTags: [], + }, + true + ); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/templates/form_fields.tsx b/x-pack/plugins/cases/public/components/templates/form_fields.tsx new file mode 100644 index 0000000000000..9f28f7b7179b4 --- /dev/null +++ b/x-pack/plugins/cases/public/components/templates/form_fields.tsx @@ -0,0 +1,105 @@ +/* + * 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, { memo, useMemo } from 'react'; +import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { HiddenField } from '@kbn/es-ui-shared-plugin/static/forms/components'; +import { EuiSteps } from '@elastic/eui'; +import { CaseFormFields } from '../case_form_fields'; +import * as i18n from './translations'; +import type { ActionConnector } from '../../containers/configure/types'; +import type { CasesConfigurationUI } from '../../containers/types'; +import { TemplateFields } from './template_fields'; +import { useCasesFeatures } from '../../common/use_cases_features'; +import { SyncAlertsToggle } from '../case_form_fields/sync_alerts_toggle'; +import { Connector } from '../case_form_fields/connector'; + +interface FormFieldsProps { + isSubmitting?: boolean; + connectors: ActionConnector[]; + currentConfiguration: CasesConfigurationUI; + isEditMode?: boolean; +} + +const FormFieldsComponent: React.FC<FormFieldsProps> = ({ + isSubmitting = false, + connectors, + currentConfiguration, + isEditMode, +}) => { + const { isSyncAlertsEnabled } = useCasesFeatures(); + const { customFields: configurationCustomFields, templates } = currentConfiguration; + const configurationTemplateTags = templates + .map((template) => (template?.tags?.length ? template.tags : [])) + .flat(); + + const firstStep = useMemo( + () => ({ + title: i18n.TEMPLATE_FIELDS, + children: ( + <TemplateFields + isLoading={isSubmitting} + configurationTemplateTags={configurationTemplateTags} + /> + ), + }), + [isSubmitting, configurationTemplateTags] + ); + + const secondStep = useMemo( + () => ({ + title: i18n.CASE_FIELDS, + children: ( + <CaseFormFields + configurationCustomFields={configurationCustomFields} + isLoading={isSubmitting} + setCustomFieldsOptional={true} + isEditMode={isEditMode} + /> + ), + }), + [isSubmitting, configurationCustomFields, isEditMode] + ); + + const thirdStep = useMemo( + () => ({ + title: i18n.CASE_SETTINGS, + children: <SyncAlertsToggle isLoading={isSubmitting} />, + }), + [isSubmitting] + ); + + const fourthStep = useMemo( + () => ({ + title: i18n.CONNECTOR_FIELDS, + children: ( + <Connector connectors={connectors} isLoading={isSubmitting} isLoadingConnectors={false} /> + ), + }), + [connectors, isSubmitting] + ); + + const allSteps = useMemo( + () => [firstStep, secondStep, ...(isSyncAlertsEnabled ? [thirdStep] : []), fourthStep], + [firstStep, secondStep, thirdStep, fourthStep, isSyncAlertsEnabled] + ); + + return ( + <> + <UseField path="key" component={HiddenField} /> + <EuiSteps + headingElement="h2" + steps={allSteps} + data-test-subj={'template-creation-form-steps'} + /> + </> + ); +}; + +FormFieldsComponent.displayName = 'FormFields'; + +export const FormFields = memo(FormFieldsComponent); diff --git a/x-pack/plugins/cases/public/components/templates/index.test.tsx b/x-pack/plugins/cases/public/components/templates/index.test.tsx new file mode 100644 index 0000000000000..ca4cb4c3caf83 --- /dev/null +++ b/x-pack/plugins/cases/public/components/templates/index.test.tsx @@ -0,0 +1,138 @@ +/* + * 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 userEvent from '@testing-library/user-event'; +import { screen, waitFor, within } from '@testing-library/react'; + +import type { AppMockRenderer } from '../../common/mock'; +import { createAppMockRenderer } from '../../common/mock'; + +import { MAX_TEMPLATES_LENGTH } from '../../../common/constants'; +import { Templates } from '.'; +import * as i18n from './translations'; +import { templatesConfigurationMock } from '../../containers/mock'; + +describe('Templates', () => { + let appMockRender: AppMockRenderer; + + const props = { + disabled: false, + isLoading: false, + templates: [], + onAddTemplate: jest.fn(), + onEditTemplate: jest.fn(), + onDeleteTemplate: jest.fn(), + }; + + beforeEach(() => { + appMockRender = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + it('renders correctly', async () => { + appMockRender.render(<Templates {...props} />); + + expect(await screen.findByTestId('templates-form-group')).toBeInTheDocument(); + expect(await screen.findByTestId('add-template')).toBeInTheDocument(); + }); + + it('renders empty templates correctly', async () => { + appMockRender.render(<Templates {...{ ...props, templates: [] }} />); + + expect(await screen.findByTestId('add-template')).toBeInTheDocument(); + expect(await screen.findByTestId('empty-templates')).toBeInTheDocument(); + expect(await screen.queryByTestId('templates-list')).not.toBeInTheDocument(); + }); + + it('renders templates correctly', async () => { + appMockRender.render(<Templates {...{ ...props, templates: templatesConfigurationMock }} />); + + expect(await screen.findByTestId('add-template')).toBeInTheDocument(); + expect(await screen.findByTestId('templates-list')).toBeInTheDocument(); + }); + + it('renders loading state correctly', async () => { + appMockRender.render(<Templates {...{ ...props, isLoading: true }} />); + + expect(await screen.findByRole('progressbar')).toBeInTheDocument(); + }); + + it('renders disabled state correctly', async () => { + appMockRender.render(<Templates {...{ ...props, disabled: true }} />); + + expect(await screen.findByTestId('add-template')).toHaveAttribute('disabled'); + }); + + it('calls onChange on add option click', async () => { + appMockRender.render(<Templates {...props} />); + + userEvent.click(await screen.findByTestId('add-template')); + + expect(props.onAddTemplate).toBeCalled(); + }); + + it('calls onEditTemplate correctly', async () => { + appMockRender.render(<Templates {...{ ...props, templates: templatesConfigurationMock }} />); + + const list = await screen.findByTestId('templates-list'); + + expect(list).toBeInTheDocument(); + + userEvent.click( + await within(list).findByTestId(`${templatesConfigurationMock[0].key}-template-edit`) + ); + + await waitFor(() => { + expect(props.onEditTemplate).toHaveBeenCalledWith(templatesConfigurationMock[0].key); + }); + }); + + it('calls onDeleteTemplate correctly', async () => { + appMockRender.render(<Templates {...{ ...props, templates: templatesConfigurationMock }} />); + + const list = await screen.findByTestId('templates-list'); + + userEvent.click( + await within(list).findByTestId(`${templatesConfigurationMock[0].key}-template-delete`) + ); + + expect(await screen.findByTestId('confirm-delete-modal')).toBeInTheDocument(); + + userEvent.click(await screen.findByText('Delete')); + + await waitFor(() => { + expect(props.onDeleteTemplate).toHaveBeenCalledWith(templatesConfigurationMock[0].key); + }); + }); + + it('shows the experimental badge', async () => { + appMockRender.render(<Templates {...props} />); + + expect(await screen.findByTestId('case-experimental-badge')).toBeInTheDocument(); + }); + + it('shows error when templates reaches the limit', async () => { + const mockTemplates = []; + + for (let i = 0; i < MAX_TEMPLATES_LENGTH; i++) { + mockTemplates.push({ + key: `field_key_${i + 1}`, + name: `template_${i + 1}`, + description: 'random foobar', + caseFields: null, + }); + } + + appMockRender.render(<Templates {...{ ...props, templates: mockTemplates }} />); + + userEvent.click(await screen.findByTestId('add-template')); + + expect(await screen.findByText(i18n.MAX_TEMPLATE_LIMIT(MAX_TEMPLATES_LENGTH))); + expect(await screen.findByTestId('add-template')).toHaveAttribute('disabled'); + }); +}); diff --git a/x-pack/plugins/cases/public/components/templates/index.tsx b/x-pack/plugins/cases/public/components/templates/index.tsx new file mode 100644 index 0000000000000..9671b9aee8556 --- /dev/null +++ b/x-pack/plugins/cases/public/components/templates/index.tsx @@ -0,0 +1,135 @@ +/* + * 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 { + EuiButtonEmpty, + EuiPanel, + EuiDescribedFormGroup, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + EuiText, +} from '@elastic/eui'; +import { MAX_TEMPLATES_LENGTH } from '../../../common/constants'; +import type { CasesConfigurationUITemplate } from '../../../common/ui'; +import { useCasesContext } from '../cases_context/use_cases_context'; +import { ExperimentalBadge } from '../experimental_badge/experimental_badge'; +import * as i18n from './translations'; +import { TemplatesList } from './templates_list'; + +interface Props { + disabled: boolean; + isLoading: boolean; + templates: CasesConfigurationUITemplate[]; + onAddTemplate: () => void; + onEditTemplate: (key: string) => void; + onDeleteTemplate: (key: string) => void; +} + +const TemplatesComponent: React.FC<Props> = ({ + disabled, + isLoading, + templates, + onAddTemplate, + onEditTemplate, + onDeleteTemplate, +}) => { + const { permissions } = useCasesContext(); + const canAddTemplates = permissions.create && permissions.update; + const [error, setError] = useState<boolean>(false); + + const handleAddTemplate = useCallback(() => { + if (templates.length === MAX_TEMPLATES_LENGTH && !error) { + setError(true); + return; + } + + onAddTemplate(); + setError(false); + }, [onAddTemplate, error, templates]); + + const handleEditTemplate = useCallback( + (key: string) => { + setError(false); + onEditTemplate(key); + }, + [setError, onEditTemplate] + ); + + const handleDeleteTemplate = useCallback( + (key: string) => { + setError(false); + onDeleteTemplate(key); + }, + [setError, onDeleteTemplate] + ); + + return ( + <EuiDescribedFormGroup + fullWidth + title={ + <EuiFlexGroup alignItems="center" gutterSize="none"> + <EuiFlexItem grow={false}>{i18n.TEMPLATE_TITLE}</EuiFlexItem> + <EuiFlexItem grow={false}> + <ExperimentalBadge /> + </EuiFlexItem> + </EuiFlexGroup> + } + description={<p>{i18n.TEMPLATE_DESCRIPTION}</p>} + data-test-subj="templates-form-group" + > + <EuiPanel paddingSize="s" color="subdued" hasBorder={false} hasShadow={false}> + {templates.length ? ( + <> + <TemplatesList + templates={templates} + onEditTemplate={handleEditTemplate} + onDeleteTemplate={handleDeleteTemplate} + /> + {error ? ( + <EuiFlexGroup justifyContent="center"> + <EuiFlexItem grow={false}> + <EuiText color="danger">{i18n.MAX_TEMPLATE_LIMIT(MAX_TEMPLATES_LENGTH)}</EuiText> + </EuiFlexItem> + </EuiFlexGroup> + ) : null} + </> + ) : null} + <EuiSpacer size="m" /> + {!templates.length ? ( + <EuiFlexGroup justifyContent="center"> + <EuiFlexItem grow={false} data-test-subj="empty-templates"> + {i18n.NO_TEMPLATES} + <EuiSpacer size="m" /> + </EuiFlexItem> + </EuiFlexGroup> + ) : null} + {canAddTemplates ? ( + <EuiFlexGroup justifyContent="center"> + <EuiFlexItem grow={false}> + <EuiButtonEmpty + isLoading={isLoading} + isDisabled={disabled || error} + size="s" + onClick={handleAddTemplate} + iconType="plusInCircle" + data-test-subj="add-template" + > + {i18n.ADD_TEMPLATE} + </EuiButtonEmpty> + </EuiFlexItem> + </EuiFlexGroup> + ) : null} + </EuiPanel> + </EuiDescribedFormGroup> + ); +}; + +TemplatesComponent.displayName = 'Templates'; + +export const Templates = React.memo(TemplatesComponent); diff --git a/x-pack/plugins/cases/public/components/templates/schema.test.tsx b/x-pack/plugins/cases/public/components/templates/schema.test.tsx new file mode 100644 index 0000000000000..3e572068b5fdc --- /dev/null +++ b/x-pack/plugins/cases/public/components/templates/schema.test.tsx @@ -0,0 +1,115 @@ +/* + * 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 { caseFormFieldsSchemaWithOptionalLabel } from './schema'; + +describe('Template schema', () => { + describe('caseFormFieldsSchemaWithOptionalLabel', () => { + it('has label append for each field', () => { + expect(caseFormFieldsSchemaWithOptionalLabel).toMatchInlineSnapshot(` + Object { + "assignees": Object { + "labelAppend": <EuiText + color="subdued" + data-test-subj="form-optional-field-label" + size="xs" + > + Optional + </EuiText>, + }, + "category": Object { + "labelAppend": <EuiText + color="subdued" + data-test-subj="form-optional-field-label" + size="xs" + > + Optional + </EuiText>, + }, + "connectorId": Object { + "defaultValue": "none", + "label": "External incident management system", + }, + "customFields": Object {}, + "description": Object { + "label": "Description", + "labelAppend": <EuiText + color="subdued" + data-test-subj="form-optional-field-label" + size="xs" + > + Optional + </EuiText>, + "validations": Array [ + Object { + "validator": [Function], + }, + ], + }, + "fields": Object { + "defaultValue": null, + }, + "severity": Object { + "label": "Severity", + }, + "syncAlerts": Object { + "defaultValue": true, + "helpText": "Enabling this option will sync the alert statuses with the case status.", + "labelAppend": <EuiText + color="subdued" + data-test-subj="form-optional-field-label" + size="xs" + > + Optional + </EuiText>, + }, + "tags": Object { + "helpText": "Separate tags with a line break.", + "label": "Tags", + "labelAppend": <EuiText + color="subdued" + data-test-subj="form-optional-field-label" + size="xs" + > + Optional + </EuiText>, + "validations": Array [ + Object { + "isBlocking": false, + "type": "arrayItem", + "validator": [Function], + }, + Object { + "isBlocking": false, + "type": "arrayItem", + "validator": [Function], + }, + Object { + "validator": [Function], + }, + ], + }, + "title": Object { + "label": "Name", + "labelAppend": <EuiText + color="subdued" + data-test-subj="form-optional-field-label" + size="xs" + > + Optional + </EuiText>, + "validations": Array [ + Object { + "validator": [Function], + }, + ], + }, + } + `); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/templates/schema.tsx b/x-pack/plugins/cases/public/components/templates/schema.tsx new file mode 100644 index 0000000000000..2c51bc8827b3b --- /dev/null +++ b/x-pack/plugins/cases/public/components/templates/schema.tsx @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; +import type { FormSchema } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { VALIDATION_TYPES } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { + MAX_TAGS_PER_TEMPLATE, + MAX_TEMPLATE_TAG_LENGTH, + MAX_TEMPLATE_NAME_LENGTH, + MAX_TEMPLATE_DESCRIPTION_LENGTH, +} from '../../../common/constants'; +import { OptionalFieldLabel } from '../optional_field_label'; +import * as i18n from './translations'; +import type { TemplateFormProps } from './types'; +import { + validateEmptyTags, + validateMaxLength, + validateMaxTagsLength, +} from '../case_form_fields/utils'; +import { schema as caseFormFieldsSchema } from '../case_form_fields/schema'; +const { emptyField, maxLengthField } = fieldValidators; + +const nonOptionalFields = ['connectorId', 'fields', 'severity', 'customFields']; + +// add optional label to all case form fields +export const caseFormFieldsSchemaWithOptionalLabel = Object.fromEntries( + Object.entries(caseFormFieldsSchema).map(([key, value]) => { + if (typeof value === 'object' && !nonOptionalFields.includes(key)) { + const updatedValue = { ...value, labelAppend: OptionalFieldLabel }; + return [key, updatedValue]; + } + + return [key, value]; + }) +); + +export const schema: FormSchema<TemplateFormProps> = { + key: { + validations: [ + { + validator: emptyField(i18n.REQUIRED_FIELD('key')), + }, + ], + }, + name: { + label: i18n.TEMPLATE_NAME, + validations: [ + { + validator: emptyField(i18n.REQUIRED_FIELD(i18n.TEMPLATE_NAME)), + }, + { + validator: maxLengthField({ + length: MAX_TEMPLATE_NAME_LENGTH, + message: i18n.MAX_LENGTH_ERROR('template name', MAX_TEMPLATE_NAME_LENGTH), + }), + }, + ], + }, + templateDescription: { + label: i18n.DESCRIPTION, + labelAppend: OptionalFieldLabel, + validations: [ + { + validator: maxLengthField({ + length: MAX_TEMPLATE_DESCRIPTION_LENGTH, + message: i18n.MAX_LENGTH_ERROR('template description', MAX_TEMPLATE_DESCRIPTION_LENGTH), + }), + }, + ], + }, + templateTags: { + label: i18n.TAGS, + helpText: i18n.TEMPLATE_TAGS_HELP, + labelAppend: OptionalFieldLabel, + validations: [ + { + validator: ({ value }: { value: string | string[] }) => + validateEmptyTags({ value, message: i18n.TAGS_EMPTY_ERROR }), + type: VALIDATION_TYPES.ARRAY_ITEM, + isBlocking: false, + }, + { + validator: ({ value }: { value: string | string[] }) => + validateMaxLength({ + value, + message: i18n.MAX_LENGTH_ERROR('tag', MAX_TEMPLATE_TAG_LENGTH), + limit: MAX_TEMPLATE_TAG_LENGTH, + }), + type: VALIDATION_TYPES.ARRAY_ITEM, + isBlocking: false, + }, + { + validator: ({ value }: { value: string[] }) => + validateMaxTagsLength({ + value, + message: i18n.MAX_TAGS_ERROR(MAX_TAGS_PER_TEMPLATE), + limit: MAX_TAGS_PER_TEMPLATE, + }), + }, + ], + }, + ...caseFormFieldsSchemaWithOptionalLabel, +}; diff --git a/x-pack/plugins/cases/public/components/templates/template_fields.test.tsx b/x-pack/plugins/cases/public/components/templates/template_fields.test.tsx new file mode 100644 index 0000000000000..8073c2e25fb41 --- /dev/null +++ b/x-pack/plugins/cases/public/components/templates/template_fields.test.tsx @@ -0,0 +1,141 @@ +/* + * 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 { screen, waitFor, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import type { AppMockRenderer } from '../../common/mock'; +import { createAppMockRenderer } from '../../common/mock'; +import { FormTestComponent } from '../../common/test_utils'; +import { TemplateFields } from './template_fields'; + +describe('Template fields', () => { + let appMockRenderer: AppMockRenderer; + const onSubmit = jest.fn(); + const formDefaultValue = { templateTags: [] }; + const defaultProps = { + isLoading: false, + configurationTemplateTags: [], + }; + + beforeEach(() => { + jest.clearAllMocks(); + appMockRenderer = createAppMockRenderer(); + }); + + it('renders template fields correctly', async () => { + appMockRenderer.render( + <FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}> + <TemplateFields {...defaultProps} /> + </FormTestComponent> + ); + + expect(await screen.findByTestId('template-name-input')).toBeInTheDocument(); + expect(await screen.findByTestId('template-tags')).toBeInTheDocument(); + expect(await screen.findByTestId('template-description-input')).toBeInTheDocument(); + }); + + it('renders template fields with existing value', async () => { + appMockRenderer.render( + <FormTestComponent + formDefaultValue={{ + name: 'Sample template', + templateDescription: 'This is a template description', + templateTags: ['template-1', 'template-2'], + }} + onSubmit={onSubmit} + > + <TemplateFields {...defaultProps} /> + </FormTestComponent> + ); + + expect(await screen.findByTestId('template-name-input')).toHaveValue('Sample template'); + + const templateTags = await screen.findByTestId('template-tags'); + + expect(await within(templateTags).findByTestId('comboBoxInput')).toHaveTextContent( + 'template-1' + ); + expect(await within(templateTags).findByTestId('comboBoxInput')).toHaveTextContent( + 'template-2' + ); + + expect(await screen.findByTestId('template-description-input')).toHaveTextContent( + 'This is a template description' + ); + }); + + it('calls onSubmit with template fields', async () => { + appMockRenderer.render( + <FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}> + <TemplateFields {...defaultProps} /> + </FormTestComponent> + ); + + userEvent.paste(await screen.findByTestId('template-name-input'), 'Template 1'); + + const templateTags = await screen.findByTestId('template-tags'); + + userEvent.paste(await within(templateTags).findByRole('combobox'), 'first'); + userEvent.keyboard('{enter}'); + + userEvent.paste( + await screen.findByTestId('template-description-input'), + 'this is a first template' + ); + + userEvent.click(screen.getByText('Submit')); + + await waitFor(() => { + expect(onSubmit).toBeCalledWith( + { + name: 'Template 1', + templateDescription: 'this is a first template', + templateTags: ['first'], + }, + true + ); + }); + }); + + it('calls onSubmit with updated template fields', async () => { + appMockRenderer.render( + <FormTestComponent + formDefaultValue={{ + name: 'Sample template', + templateDescription: 'This is a template description', + templateTags: ['template-1', 'template-2'], + }} + onSubmit={onSubmit} + > + <TemplateFields {...defaultProps} /> + </FormTestComponent> + ); + + userEvent.paste(await screen.findByTestId('template-name-input'), '!!'); + + const templateTags = await screen.findByTestId('template-tags'); + + userEvent.paste(await within(templateTags).findByRole('combobox'), 'first'); + userEvent.keyboard('{enter}'); + + userEvent.paste(await screen.findByTestId('template-description-input'), '..'); + + userEvent.click(screen.getByText('Submit')); + + await waitFor(() => { + expect(onSubmit).toBeCalledWith( + { + name: 'Sample template!!', + templateDescription: 'This is a template description..', + templateTags: ['template-1', 'template-2', 'first'], + }, + true + ); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/templates/template_fields.tsx b/x-pack/plugins/cases/public/components/templates/template_fields.tsx new file mode 100644 index 0000000000000..2f989201437c3 --- /dev/null +++ b/x-pack/plugins/cases/public/components/templates/template_fields.tsx @@ -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. + */ + +import React, { memo } from 'react'; +import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { TextField, TextAreaField } from '@kbn/es-ui-shared-plugin/static/forms/components'; +import { EuiFlexGroup } from '@elastic/eui'; +import { OptionalFieldLabel } from '../optional_field_label'; +import { TemplateTags } from './template_tags'; + +const TemplateFieldsComponent: React.FC<{ + isLoading: boolean; + configurationTemplateTags: string[]; +}> = ({ isLoading = false, configurationTemplateTags }) => ( + <EuiFlexGroup data-test-subj="template-fields" direction="column" gutterSize="none"> + <UseField + path="name" + component={TextField} + componentProps={{ + euiFieldProps: { + 'data-test-subj': 'template-name-input', + fullWidth: true, + autoFocus: true, + isLoading, + }, + }} + /> + <TemplateTags isLoading={isLoading} tagOptions={configurationTemplateTags} /> + <UseField + path="templateDescription" + component={TextAreaField} + componentProps={{ + labelAppend: OptionalFieldLabel, + euiFieldProps: { + 'data-test-subj': 'template-description-input', + fullWidth: true, + isLoading, + }, + }} + /> + </EuiFlexGroup> +); + +TemplateFieldsComponent.displayName = 'TemplateFields'; + +export const TemplateFields = memo(TemplateFieldsComponent); diff --git a/x-pack/plugins/cases/public/components/templates/template_tags.test.tsx b/x-pack/plugins/cases/public/components/templates/template_tags.test.tsx new file mode 100644 index 0000000000000..6a99321bb7727 --- /dev/null +++ b/x-pack/plugins/cases/public/components/templates/template_tags.test.tsx @@ -0,0 +1,128 @@ +/* + * 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 { screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import type { AppMockRenderer } from '../../common/mock'; +import { createAppMockRenderer } from '../../common/mock'; +import { FormTestComponent } from '../../common/test_utils'; +import { TemplateTags } from './template_tags'; +import { showEuiComboBoxOptions } from '@elastic/eui/lib/test/rtl'; + +describe('TemplateTags', () => { + let appMockRenderer: AppMockRenderer; + const onSubmit = jest.fn(); + const formDefaultValue = { templateTags: [] }; + + beforeEach(() => { + jest.clearAllMocks(); + appMockRenderer = createAppMockRenderer(); + }); + + it('renders template tags', async () => { + appMockRenderer.render( + <FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}> + <TemplateTags isLoading={false} tagOptions={[]} /> + </FormTestComponent> + ); + + expect(await screen.findByTestId('template-tags')).toBeInTheDocument(); + }); + + it('renders loading state', async () => { + appMockRenderer.render( + <FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}> + <TemplateTags isLoading={true} tagOptions={[]} /> + </FormTestComponent> + ); + + expect(await screen.findByRole('progressbar')).toBeInTheDocument(); + expect(await screen.findByLabelText('Loading')).toBeInTheDocument(); + }); + + it('shows template tags options', async () => { + appMockRenderer.render( + <FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}> + <TemplateTags isLoading={false} tagOptions={['foo', 'bar', 'test']} /> + </FormTestComponent> + ); + + expect(await screen.findByTestId('template-tags')).toBeInTheDocument(); + + await showEuiComboBoxOptions(); + + expect(await screen.findByText('foo')).toBeInTheDocument(); + }); + + it('shows template tags with current values', async () => { + appMockRenderer.render( + <FormTestComponent formDefaultValue={{ templateTags: ['foo', 'bar'] }} onSubmit={onSubmit}> + <TemplateTags isLoading={false} tagOptions={[]} /> + </FormTestComponent> + ); + + expect(await screen.findByTestId('template-tags')).toBeInTheDocument(); + + expect(await screen.findByText('foo')).toBeInTheDocument(); + + expect(await screen.findByText('bar')).toBeInTheDocument(); + }); + + it('adds template tag ', async () => { + appMockRenderer.render( + <FormTestComponent formDefaultValue={formDefaultValue} onSubmit={onSubmit}> + <TemplateTags isLoading={false} tagOptions={[]} /> + </FormTestComponent> + ); + + expect(await screen.findByTestId('template-tags')).toBeInTheDocument(); + + const comboBoxEle = await screen.findByRole('combobox'); + userEvent.paste(comboBoxEle, 'test'); + userEvent.keyboard('{enter}'); + userEvent.paste(comboBoxEle, 'template'); + userEvent.keyboard('{enter}'); + + userEvent.click(screen.getByText('Submit')); + + await waitFor(() => { + expect(onSubmit).toBeCalledWith( + { + templateTags: ['test', 'template'], + }, + true + ); + }); + }); + + it('adds new template tag to existing tags', async () => { + appMockRenderer.render( + <FormTestComponent formDefaultValue={{ templateTags: ['foo', 'bar'] }} onSubmit={onSubmit}> + <TemplateTags isLoading={false} tagOptions={[]} /> + </FormTestComponent> + ); + + expect(await screen.findByTestId('template-tags')).toBeInTheDocument(); + + const comboBoxEle = await screen.findByRole('combobox'); + userEvent.paste(comboBoxEle, 'test'); + userEvent.keyboard('{enter}'); + + userEvent.click(screen.getByText('Submit')); + + await waitFor(() => { + expect(onSubmit).toBeCalledWith( + { + templateTags: ['foo', 'bar', 'test'], + }, + true + ); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/templates/template_tags.tsx b/x-pack/plugins/cases/public/components/templates/template_tags.tsx new file mode 100644 index 0000000000000..92f141a73eb85 --- /dev/null +++ b/x-pack/plugins/cases/public/components/templates/template_tags.tsx @@ -0,0 +1,46 @@ +/* + * 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, { memo } from 'react'; + +import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { ComboBoxField } from '@kbn/es-ui-shared-plugin/static/forms/components'; +import * as i18n from './translations'; +interface Props { + isLoading: boolean; + tagOptions: string[]; +} + +const TemplateTagsComponent: React.FC<Props> = ({ isLoading, tagOptions }) => { + const options = tagOptions.map((label) => ({ + label, + })); + + return ( + <UseField + path="templateTags" + component={ComboBoxField} + componentProps={{ + idAria: 'template-tags', + 'data-test-subj': 'template-tags', + euiFieldProps: { + placeholder: '', + fullWidth: true, + disabled: isLoading, + isLoading, + options, + noSuggestions: false, + customOptionText: i18n.ADD_TAG_CUSTOM_OPTION_LABEL_COMBO_BOX, + }, + }} + /> + ); +}; + +TemplateTagsComponent.displayName = 'TemplateTagsComponent'; + +export const TemplateTags = memo(TemplateTagsComponent); diff --git a/x-pack/plugins/cases/public/components/templates/templates_list.test.tsx b/x-pack/plugins/cases/public/components/templates/templates_list.test.tsx new file mode 100644 index 0000000000000..61f855c427c3c --- /dev/null +++ b/x-pack/plugins/cases/public/components/templates/templates_list.test.tsx @@ -0,0 +1,145 @@ +/* + * 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 { screen, waitFor, within } from '@testing-library/react'; + +import type { AppMockRenderer } from '../../common/mock'; +import { createAppMockRenderer } from '../../common/mock'; +import { templatesConfigurationMock } from '../../containers/mock'; +import { TemplatesList } from './templates_list'; +import userEvent from '@testing-library/user-event'; + +describe('TemplatesList', () => { + let appMockRender: AppMockRenderer; + const onDeleteTemplate = jest.fn(); + const onEditTemplate = jest.fn(); + + const props = { + templates: templatesConfigurationMock, + onDeleteTemplate, + onEditTemplate, + }; + + beforeEach(() => { + jest.clearAllMocks(); + appMockRender = createAppMockRenderer(); + }); + + it('renders correctly', () => { + appMockRender.render(<TemplatesList {...props} />); + + expect(screen.getByTestId('templates-list')).toBeInTheDocument(); + }); + + it('renders all templates', async () => { + appMockRender.render( + <TemplatesList {...{ ...props, templates: templatesConfigurationMock }} /> + ); + + expect(await screen.findByTestId('templates-list')).toBeInTheDocument(); + + templatesConfigurationMock.forEach((template) => + expect(screen.getByTestId(`template-${template.key}`)).toBeInTheDocument() + ); + }); + + it('renders template details correctly', async () => { + appMockRender.render( + <TemplatesList {...{ ...props, templates: [templatesConfigurationMock[3]] }} /> + ); + + const list = await screen.findByTestId('templates-list'); + + expect(list).toBeInTheDocument(); + expect( + await screen.findByTestId(`template-${templatesConfigurationMock[3].key}`) + ).toBeInTheDocument(); + expect(await screen.findByText(`${templatesConfigurationMock[3].name}`)).toBeInTheDocument(); + + const tags = templatesConfigurationMock[3].tags; + + tags?.forEach((tag, index) => + expect( + screen.getByTestId(`${templatesConfigurationMock[3].key}-tag-${index}`) + ).toBeInTheDocument() + ); + }); + + it('renders empty state correctly', () => { + appMockRender.render(<TemplatesList {...{ ...props, templates: [] }} />); + + expect(screen.queryAllByTestId(`template-`, { exact: false })).toHaveLength(0); + }); + + it('renders edit button', async () => { + appMockRender.render( + <TemplatesList {...{ ...props, templates: [templatesConfigurationMock[0]] }} /> + ); + + expect( + await screen.findByTestId(`${templatesConfigurationMock[0].key}-template-edit`) + ).toBeInTheDocument(); + }); + + it('renders delete button', async () => { + appMockRender.render( + <TemplatesList {...{ ...props, templates: [templatesConfigurationMock[0]] }} /> + ); + + expect( + await screen.findByTestId(`${templatesConfigurationMock[0].key}-template-delete`) + ).toBeInTheDocument(); + }); + + it('renders delete modal', async () => { + appMockRender.render( + <TemplatesList {...{ ...props, templates: [templatesConfigurationMock[0]] }} /> + ); + + userEvent.click( + await screen.findByTestId(`${templatesConfigurationMock[0].key}-template-delete`) + ); + + expect(await screen.findByTestId('confirm-delete-modal')).toBeInTheDocument(); + expect(await screen.findByText('Delete')).toBeInTheDocument(); + expect(await screen.findByText('Cancel')).toBeInTheDocument(); + }); + + it('calls onEditTemplate correctly', async () => { + appMockRender.render(<TemplatesList {...props} />); + + const list = await screen.findByTestId('templates-list'); + + userEvent.click( + await within(list).findByTestId(`${templatesConfigurationMock[0].key}-template-edit`) + ); + + await waitFor(() => { + expect(props.onEditTemplate).toHaveBeenCalledWith(templatesConfigurationMock[0].key); + }); + }); + + it('calls onDeleteTemplate correctly', async () => { + appMockRender.render(<TemplatesList {...props} />); + + const list = await screen.findByTestId('templates-list'); + + userEvent.click( + await within(list).findByTestId(`${templatesConfigurationMock[0].key}-template-delete`) + ); + + expect(await screen.findByTestId('confirm-delete-modal')).toBeInTheDocument(); + + userEvent.click(await screen.findByText('Delete')); + + await waitFor(() => { + expect(screen.queryByTestId('confirm-delete-modal')).not.toBeInTheDocument(); + expect(props.onDeleteTemplate).toHaveBeenCalledWith(templatesConfigurationMock[0].key); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/templates/templates_list.tsx b/x-pack/plugins/cases/public/components/templates/templates_list.tsx new file mode 100644 index 0000000000000..ceaac643ecab3 --- /dev/null +++ b/x-pack/plugins/cases/public/components/templates/templates_list.tsx @@ -0,0 +1,133 @@ +/* + * 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 { + EuiPanel, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiText, + EuiBadge, + useEuiTheme, + EuiButtonIcon, + EuiBadgeGroup, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { TruncatedText } from '../truncated_text'; +import type { TemplateConfiguration, TemplatesConfiguration } from '../../../common/types/domain'; +import { DeleteConfirmationModal } from '../configure_cases/delete_confirmation_modal'; +import * as i18n from './translations'; +export interface Props { + templates: TemplatesConfiguration; + onDeleteTemplate: (key: string) => void; + onEditTemplate: (key: string) => void; +} + +const TemplatesListComponent: React.FC<Props> = (props) => { + const { templates, onEditTemplate, onDeleteTemplate } = props; + const { euiTheme } = useEuiTheme(); + const [itemToBeDeleted, setItemToBeDeleted] = useState<TemplateConfiguration | null>(null); + + const onConfirm = useCallback(() => { + if (itemToBeDeleted) { + onDeleteTemplate(itemToBeDeleted.key); + } + + setItemToBeDeleted(null); + }, [onDeleteTemplate, setItemToBeDeleted, itemToBeDeleted]); + + const onCancel = useCallback(() => { + setItemToBeDeleted(null); + }, []); + + const showModal = Boolean(itemToBeDeleted); + + return templates.length ? ( + <> + <EuiSpacer size="s" /> + <EuiFlexGroup justifyContent="flexStart" data-test-subj="templates-list"> + <EuiFlexItem> + {templates.map((template) => ( + <React.Fragment key={template.key}> + <EuiPanel + paddingSize="s" + data-test-subj={`template-${template.key}`} + hasShadow={false} + > + <EuiFlexGroup alignItems="center" gutterSize="s"> + <EuiFlexItem grow={true}> + <EuiFlexGroup alignItems="center" gutterSize="s"> + <EuiFlexItem grow={false}> + <EuiText> + <h4> + <TruncatedText text={template.name} /> + </h4> + </EuiText> + </EuiFlexItem> + <EuiBadgeGroup gutterSize="s"> + {template.tags?.length + ? template.tags.map((tag, index) => ( + <EuiBadge + css={css` + max-width: 100px; + `} + key={`${template.key}-tag-${index}`} + data-test-subj={`${template.key}-tag-${index}`} + color={euiTheme.colors.body} + > + {tag} + </EuiBadge> + )) + : null} + </EuiBadgeGroup> + </EuiFlexGroup> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiFlexGroup alignItems="flexEnd" gutterSize="s"> + <EuiFlexItem grow={false}> + <EuiButtonIcon + data-test-subj={`${template.key}-template-edit`} + aria-label={`${template.key}-template-edit`} + iconType="pencil" + color="primary" + onClick={() => onEditTemplate(template.key)} + /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButtonIcon + data-test-subj={`${template.key}-template-delete`} + aria-label={`${template.key}-template-delete`} + iconType="minusInCircle" + color="danger" + onClick={() => setItemToBeDeleted(template)} + /> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGroup> + </EuiPanel> + <EuiSpacer size="s" /> + </React.Fragment> + ))} + </EuiFlexItem> + {showModal && itemToBeDeleted ? ( + <DeleteConfirmationModal + title={i18n.DELETE_TITLE(itemToBeDeleted.name)} + message={i18n.DELETE_MESSAGE(itemToBeDeleted.name)} + onCancel={onCancel} + onConfirm={onConfirm} + /> + ) : null} + </EuiFlexGroup> + </> + ) : null; +}; + +TemplatesListComponent.displayName = 'TemplatesList'; + +export const TemplatesList = React.memo(TemplatesListComponent); diff --git a/x-pack/plugins/cases/public/components/templates/translations.ts b/x-pack/plugins/cases/public/components/templates/translations.ts new file mode 100644 index 0000000000000..2993070046813 --- /dev/null +++ b/x-pack/plugins/cases/public/components/templates/translations.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 { i18n } from '@kbn/i18n'; + +export * from '../../common/translations'; + +export const TEMPLATE_TITLE = i18n.translate('xpack.cases.templates.title', { + defaultMessage: 'Templates', +}); + +export const TEMPLATE_DESCRIPTION = i18n.translate('xpack.cases.templates.description', { + defaultMessage: + 'Add Case Templates to automatically define the case fields while creating a new case. A user can choose to create an empty case or based on a preset template. Templates allow to auto-populate values when creating new cases.', +}); + +export const NO_TEMPLATES = i18n.translate('xpack.cases.templates.noTemplates', { + defaultMessage: 'You do not have any templates yet', +}); + +export const ADD_TEMPLATE = i18n.translate('xpack.cases.templates.addTemplate', { + defaultMessage: 'Add template', +}); + +export const CREATE_TEMPLATE = i18n.translate('xpack.cases.templates.createTemplate', { + defaultMessage: 'Create template', +}); + +export const REQUIRED = i18n.translate('xpack.cases.templates.required', { + defaultMessage: 'Required', +}); + +export const REQUIRED_FIELD = (fieldName: string): string => + i18n.translate('xpack.cases.templates.requiredField', { + values: { fieldName }, + defaultMessage: 'A {fieldName} is required.', + }); + +export const TEMPLATE_NAME = i18n.translate('xpack.cases.templates.templateName', { + defaultMessage: 'Template name', +}); + +export const TEMPLATE_TAGS_HELP = i18n.translate('xpack.cases.templates.templateTagsHelp', { + defaultMessage: + 'Type one or more custom identifying tags for this template. Please enter after each tag to begin a new one', +}); + +export const TEMPLATE_FIELDS = i18n.translate('xpack.cases.templates.templateFields', { + defaultMessage: 'Template fields', +}); + +export const CASE_FIELDS = i18n.translate('xpack.cases.templates.caseFields', { + defaultMessage: 'Case fields', +}); + +export const CASE_SETTINGS = i18n.translate('xpack.cases.templates.caseSettings', { + defaultMessage: 'Case settings', +}); + +export const CONNECTOR_FIELDS = i18n.translate('xpack.cases.templates.connectorFields', { + defaultMessage: 'External Connector Fields', +}); + +export const DELETE_TITLE = (name: string) => + i18n.translate('xpack.cases.configuration.deleteTitle', { + values: { name }, + defaultMessage: 'Delete {name}?', + }); + +export const DELETE_MESSAGE = (name: string) => + i18n.translate('xpack.cases.configuration.deleteMessage', { + values: { name }, + defaultMessage: 'This action will permanently delete {name}.', + }); + +export const MAX_TEMPLATE_LIMIT = (maxTemplates: number) => + i18n.translate('xpack.cases.templates.maxTemplateLimit', { + values: { maxTemplates }, + defaultMessage: 'Maximum number of {maxTemplates} templates reached.', + }); diff --git a/x-pack/plugins/cases/public/components/templates/types.ts b/x-pack/plugins/cases/public/components/templates/types.ts new file mode 100644 index 0000000000000..cf1187ed64e2d --- /dev/null +++ b/x-pack/plugins/cases/public/components/templates/types.ts @@ -0,0 +1,15 @@ +/* + * 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 { TemplateConfiguration } from '../../../common/types/domain'; +import type { CaseFormFieldsSchemaProps } from '../case_form_fields/schema'; + +export type TemplateFormProps = Pick<TemplateConfiguration, 'key' | 'name'> & + Partial<CaseFormFieldsSchemaProps> & { + templateTags?: string[]; + templateDescription?: string; + }; diff --git a/x-pack/plugins/cases/public/components/templates/utils.test.ts b/x-pack/plugins/cases/public/components/templates/utils.test.ts new file mode 100644 index 0000000000000..9e3cd70c120af --- /dev/null +++ b/x-pack/plugins/cases/public/components/templates/utils.test.ts @@ -0,0 +1,389 @@ +/* + * 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 { CaseSeverity, ConnectorTypes } from '../../../common'; +import { CustomFieldTypes } from '../../../common/types/domain'; +import { casesConfigurationsMock } from '../../containers/configure/mock'; +import { connectorsMock, customFieldsConfigurationMock } from '../../containers/mock'; +import type { CaseUI } from '../../containers/types'; +import { userProfiles } from '../../containers/user_profiles/api.mock'; +import { + convertTemplateCustomFields, + removeEmptyFields, + templateDeserializer, + templateSerializer, +} from './utils'; + +describe('utils', () => { + describe('getTemplateSerializedData', () => { + it('serializes empty fields correctly', () => { + const res = templateSerializer(connectorsMock, casesConfigurationsMock, { + key: '', + name: '', + templateDescription: '', + title: '', + description: '', + templateTags: [], + tags: [], + fields: null, + category: null, + }); + + expect(res).toEqual({ + caseFields: { + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + customFields: [], + settings: { + syncAlerts: false, + }, + }, + description: undefined, + key: '', + name: '', + tags: [], + }); + }); + + it('serializes connectors fields correctly', () => { + const res = templateSerializer(connectorsMock, casesConfigurationsMock, { + key: '', + name: '', + templateDescription: '', + fields: null, + }); + + expect(res).toEqual({ + caseFields: { + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + customFields: [], + settings: { + syncAlerts: false, + }, + }, + description: undefined, + key: '', + name: '', + tags: [], + }); + }); + + it('serializes non empty fields correctly', () => { + const res = templateSerializer(connectorsMock, casesConfigurationsMock, { + key: 'key_1', + name: 'template 1', + templateDescription: 'description 1', + templateTags: ['sample'], + category: 'new', + }); + + expect(res).toEqual({ + caseFields: { + category: 'new', + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + customFields: [], + settings: { + syncAlerts: false, + }, + }, + description: 'description 1', + key: 'key_1', + name: 'template 1', + tags: ['sample'], + }); + }); + + it('serializes custom fields correctly', () => { + const res = templateSerializer(connectorsMock, casesConfigurationsMock, { + key: 'key_1', + name: 'template 1', + templateDescription: '', + customFields: { + custom_field_1: 'foobar', + custom_fields_2: '', + custom_field_3: true, + }, + }); + + expect(res).toEqual({ + caseFields: { + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + customFields: [], + settings: { + syncAlerts: false, + }, + }, + description: undefined, + key: 'key_1', + name: 'template 1', + tags: [], + }); + }); + + it('serializes connector fields correctly', () => { + const res = templateSerializer(connectorsMock, casesConfigurationsMock, { + key: 'key_1', + name: 'template 1', + templateDescription: '', + fields: { + impact: 'high', + severity: 'low', + category: null, + urgency: null, + subcategory: null, + }, + }); + + expect(res).toEqual({ + caseFields: { + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + customFields: [], + settings: { + syncAlerts: false, + }, + }, + description: undefined, + key: 'key_1', + name: 'template 1', + tags: [], + }); + }); + }); + + describe('removeEmptyFields', () => { + it('removes empty fields', () => { + const res = removeEmptyFields({ + key: '', + name: '', + templateDescription: '', + title: '', + description: '', + templateTags: [], + tags: [], + fields: null, + }); + + expect(res).toEqual({}); + }); + + it('does not remove not empty fields', () => { + const res = removeEmptyFields({ + key: 'key_1', + name: 'template 1', + templateDescription: 'description 1', + }); + + expect(res).toEqual({ + key: 'key_1', + name: 'template 1', + templateDescription: 'description 1', + }); + }); + }); + + describe('templateDeserializer', () => { + it('deserialzies initial data correctly', () => { + const res = templateDeserializer({ key: 'temlate_1', name: 'Template 1', caseFields: null }); + + expect(res).toEqual({ + key: 'temlate_1', + name: 'Template 1', + templateDescription: '', + templateTags: [], + tags: [], + connectorId: 'none', + customFields: {}, + fields: null, + }); + }); + + it('deserialzies template data correctly', () => { + const res = templateDeserializer({ + key: 'temlate_1', + name: 'Template 1', + description: 'This is first template', + tags: ['t1', 't2'], + caseFields: null, + }); + + expect(res).toEqual({ + key: 'temlate_1', + name: 'Template 1', + templateDescription: 'This is first template', + templateTags: ['t1', 't2'], + tags: [], + connectorId: 'none', + customFields: {}, + fields: null, + }); + }); + + it('deserialzies case fields data correctly', () => { + const res = templateDeserializer({ + key: 'temlate_1', + name: 'Template 1', + caseFields: { + title: 'Case title', + description: 'This is test case', + category: null, + tags: ['foo', 'bar'], + severity: CaseSeverity.LOW, + assignees: [{ uid: userProfiles[0].uid }], + }, + }); + + expect(res).toEqual({ + key: 'temlate_1', + name: 'Template 1', + templateDescription: '', + templateTags: [], + title: 'Case title', + description: 'This is test case', + category: null, + tags: ['foo', 'bar'], + severity: CaseSeverity.LOW, + assignees: [{ uid: userProfiles[0].uid }], + connectorId: 'none', + customFields: {}, + fields: null, + }); + }); + + it('deserialzies custom fields data correctly', () => { + const res = templateDeserializer({ + key: 'temlate_1', + name: 'Template 1', + caseFields: { + customFields: [ + { + key: customFieldsConfigurationMock[0].key, + type: CustomFieldTypes.TEXT, + value: 'this is first custom field value', + }, + { + key: customFieldsConfigurationMock[1].key, + type: CustomFieldTypes.TOGGLE, + value: true, + }, + ], + }, + }); + + expect(res).toEqual({ + key: 'temlate_1', + name: 'Template 1', + templateDescription: '', + templateTags: [], + tags: [], + connectorId: 'none', + customFields: { + [customFieldsConfigurationMock[0].key]: 'this is first custom field value', + [customFieldsConfigurationMock[1].key]: true, + }, + fields: null, + }); + }); + + it('deserialzies connector data correctly', () => { + const res = templateDeserializer({ + key: 'temlate_1', + name: 'Template 1', + caseFields: { + connector: { + id: 'servicenow-1', + name: 'My SN connector', + type: ConnectorTypes.serviceNowITSM, + fields: { + category: 'software', + urgency: '1', + severity: null, + impact: null, + subcategory: null, + }, + }, + }, + }); + + expect(res).toEqual({ + key: 'temlate_1', + name: 'Template 1', + templateDescription: '', + templateTags: [], + tags: [], + connectorId: 'servicenow-1', + customFields: {}, + fields: { + category: 'software', + impact: undefined, + severity: undefined, + subcategory: undefined, + urgency: '1', + }, + }); + }); + }); + + describe('convertTemplateCustomFields', () => { + it('converts data correctly', () => { + const data = [ + { + key: customFieldsConfigurationMock[0].key, + type: CustomFieldTypes.TEXT, + value: 'this is first custom field value', + }, + { + key: customFieldsConfigurationMock[1].key, + type: CustomFieldTypes.TOGGLE, + value: true, + }, + ] as CaseUI['customFields']; + + const res = convertTemplateCustomFields(data); + + expect(res).toEqual({ + [customFieldsConfigurationMock[0].key]: 'this is first custom field value', + [customFieldsConfigurationMock[1].key]: true, + }); + }); + + it('returns null when customFields empty', () => { + const res = convertTemplateCustomFields([]); + + expect(res).toEqual(null); + }); + + it('returns null when customFields undefined', () => { + const res = convertTemplateCustomFields(undefined); + + expect(res).toEqual(null); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/templates/utils.ts b/x-pack/plugins/cases/public/components/templates/utils.ts new file mode 100644 index 0000000000000..3ee3002388e2d --- /dev/null +++ b/x-pack/plugins/cases/public/components/templates/utils.ts @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isEmpty } from 'lodash'; +import type { ActionConnector, TemplateConfiguration } from '../../../common/types/domain'; +import type { CasesConfigurationUI, CaseUI } from '../../containers/types'; +import { normalizeActionConnector, getNoneConnector } from '../configure_cases/utils'; +import { + customFieldsFormDeserializer, + customFieldsFormSerializer, + getConnectorById, + getConnectorsFormDeserializer, + getConnectorsFormSerializer, +} from '../utils'; +import type { TemplateFormProps } from './types'; + +export function removeEmptyFields<T extends Record<string, unknown>>(obj: T): Partial<T> { + return Object.fromEntries( + Object.entries(obj) + .filter(([_, value]) => !isEmpty(value) || typeof value === 'boolean') + .map(([key, value]) => [ + key, + value === Object(value) && !Array.isArray(value) + ? removeEmptyFields(value as Record<string, unknown>) + : value, + ]) + ) as T; +} + +export const convertTemplateCustomFields = ( + customFields?: CaseUI['customFields'] +): Record<string, string | boolean> | null => { + if (!customFields || !customFields.length) { + return null; + } + + return customFields.reduce((acc, customField) => { + const initial = { + [customField.key]: customField.value, + }; + + return { ...acc, ...initial }; + }, {}); +}; + +export const templateDeserializer = (data: TemplateConfiguration): TemplateFormProps => { + if (data == null) { + return data; + } + + const { key, name, description, tags: templateTags, caseFields } = data; + const { connector, customFields, settings, tags, ...rest } = caseFields ?? {}; + const connectorFields = getConnectorsFormDeserializer({ fields: connector?.fields ?? null }); + const convertedCustomFields = customFieldsFormDeserializer(customFields); + + return { + key, + name, + templateDescription: description ?? '', + templateTags: templateTags ?? [], + connectorId: connector?.id ?? 'none', + fields: connectorFields.fields ?? null, + customFields: convertedCustomFields ?? {}, + tags: tags ?? [], + ...rest, + }; +}; + +export const templateSerializer = ( + connectors: ActionConnector[], + currentConfiguration: CasesConfigurationUI, + data: TemplateFormProps +): TemplateConfiguration => { + if (data == null) { + return data; + } + + const { fields: connectorFields = null, key, name, ...rest } = data; + + const serializedConnectorFields = getConnectorsFormSerializer({ fields: connectorFields }); + const nonEmptyFields = removeEmptyFields({ ...rest }); + + const { + connectorId, + customFields: templateCustomFields, + syncAlerts = false, + templateTags, + templateDescription, + ...otherCaseFields + } = nonEmptyFields; + + const transformedCustomFields = templateCustomFields + ? customFieldsFormSerializer(templateCustomFields, currentConfiguration.customFields) + : []; + + const templateConnector = connectorId ? getConnectorById(connectorId, connectors) : null; + + const transformedConnector = templateConnector + ? normalizeActionConnector(templateConnector, serializedConnectorFields.fields) + : getNoneConnector(); + + const transformedData: TemplateConfiguration = { + key, + name, + description: templateDescription, + tags: templateTags ?? [], + caseFields: { + ...otherCaseFields, + connector: transformedConnector, + customFields: transformedCustomFields, + settings: { syncAlerts }, + }, + }; + + return transformedData; +}; diff --git a/x-pack/plugins/cases/public/components/utils.test.ts b/x-pack/plugins/cases/public/components/utils.test.ts index 0e7cd9fb03b35..005f15b78b3d7 100644 --- a/x-pack/plugins/cases/public/components/utils.test.ts +++ b/x-pack/plugins/cases/public/components/utils.test.ts @@ -7,7 +7,14 @@ import { actionTypeRegistryMock } from '@kbn/triggers-actions-ui-plugin/public/application/action_type_registry.mock'; import { triggersActionsUiMock } from '@kbn/triggers-actions-ui-plugin/public/mocks'; -import { elasticUser, getCaseUsersMockResponse } from '../containers/mock'; +import { + customFieldsConfigurationMock, + customFieldsMock, + elasticUser, + getCaseUsersMockResponse, +} from '../containers/mock'; +import type { CaseUICustomField } from '../containers/types'; +import { CustomFieldTypes } from '../../common/types/domain/custom_field/v1'; import { connectorDeprecationValidator, convertEmptyValuesToNull, @@ -21,6 +28,9 @@ import { stringifyToURL, parseCaseUsers, convertCustomFieldValue, + addOrReplaceField, + removeEmptyFields, + customFieldsFormSerializer, } from './utils'; describe('Utils', () => { @@ -528,4 +538,274 @@ describe('Utils', () => { expect(convertCustomFieldValue(false)).toMatchInlineSnapshot('false'); }); }); + + describe('addOrReplaceField ', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('adds new custom field correctly', async () => { + const fieldToAdd: CaseUICustomField = { + key: 'my_test_key', + type: CustomFieldTypes.TEXT, + value: 'my_test_value', + }; + const res = addOrReplaceField(customFieldsMock, fieldToAdd); + expect(res).toMatchInlineSnapshot( + [...customFieldsMock, fieldToAdd], + ` + Array [ + Object { + "key": "test_key_1", + "type": "text", + "value": "My text test value 1", + }, + Object { + "key": "test_key_2", + "type": "toggle", + "value": true, + }, + Object { + "key": "test_key_3", + "type": "text", + "value": null, + }, + Object { + "key": "test_key_4", + "type": "toggle", + "value": null, + }, + Object { + "key": "my_test_key", + "type": "text", + "value": "my_test_value", + }, + ] + ` + ); + }); + + it('updates existing custom field correctly', async () => { + const fieldToUpdate = { + ...customFieldsMock[0], + field: { value: ['My text test value 1!!!'] }, + }; + + const res = addOrReplaceField(customFieldsMock, fieldToUpdate as CaseUICustomField); + expect(res).toMatchInlineSnapshot( + [ + { ...fieldToUpdate }, + { ...customFieldsMock[1] }, + { ...customFieldsMock[2] }, + { ...customFieldsMock[3] }, + ], + ` + Array [ + Object { + "field": Object { + "value": Array [ + "My text test value 1!!!", + ], + }, + "key": "test_key_1", + "type": "text", + "value": "My text test value 1", + }, + Object { + "key": "test_key_2", + "type": "toggle", + "value": true, + }, + Object { + "key": "test_key_3", + "type": "text", + "value": null, + }, + Object { + "key": "test_key_4", + "type": "toggle", + "value": null, + }, + ] + ` + ); + }); + + it('adds new custom field configuration correctly', async () => { + const fieldToAdd = { + key: 'my_test_key', + type: CustomFieldTypes.TEXT, + label: 'my_test_label', + required: true, + }; + const res = addOrReplaceField(customFieldsConfigurationMock, fieldToAdd); + expect(res).toMatchInlineSnapshot( + [...customFieldsConfigurationMock, fieldToAdd], + ` + Array [ + Object { + "defaultValue": "My default value", + "key": "test_key_1", + "label": "My test label 1", + "required": true, + "type": "text", + }, + Object { + "defaultValue": true, + "key": "test_key_2", + "label": "My test label 2", + "required": true, + "type": "toggle", + }, + Object { + "key": "test_key_3", + "label": "My test label 3", + "required": false, + "type": "text", + }, + Object { + "key": "test_key_4", + "label": "My test label 4", + "required": false, + "type": "toggle", + }, + Object { + "key": "my_test_key", + "label": "my_test_label", + "required": true, + "type": "text", + }, + ] + ` + ); + }); + + it('updates existing custom field config correctly', async () => { + const fieldToUpdate = { + ...customFieldsConfigurationMock[0], + label: `${customFieldsConfigurationMock[0].label}!!!`, + }; + + const res = addOrReplaceField(customFieldsConfigurationMock, fieldToUpdate); + expect(res).toMatchInlineSnapshot( + [ + { ...fieldToUpdate }, + { ...customFieldsConfigurationMock[1] }, + { ...customFieldsConfigurationMock[2] }, + { ...customFieldsConfigurationMock[3] }, + ], + ` + Array [ + Object { + "defaultValue": "My default value", + "key": "test_key_1", + "label": "My test label 1!!!", + "required": true, + "type": "text", + }, + Object { + "defaultValue": true, + "key": "test_key_2", + "label": "My test label 2", + "required": true, + "type": "toggle", + }, + Object { + "key": "test_key_3", + "label": "My test label 3", + "required": false, + "type": "text", + }, + Object { + "key": "test_key_4", + "label": "My test label 4", + "required": false, + "type": "toggle", + }, + ] + ` + ); + }); + }); + + describe('removeEmptyFields', () => { + it('removes empty fields', () => { + const res = removeEmptyFields({ + key: '', + name: '', + templateDescription: '', + title: '', + description: '', + templateTags: [], + tags: [], + fields: null, + }); + + expect(res).toEqual({}); + }); + + it('does not remove not empty fields', () => { + const res = removeEmptyFields({ + key: 'key_1', + name: 'template 1', + templateDescription: 'description 1', + }); + + expect(res).toEqual({ + key: 'key_1', + name: 'template 1', + templateDescription: 'description 1', + }); + }); + }); + + describe('customFieldsFormSerializer', () => { + it('transforms customFields correctly', () => { + const customFields = { + test_key_1: 'first value', + test_key_2: true, + test_key_3: 'second value', + }; + + expect(customFieldsFormSerializer(customFields, customFieldsConfigurationMock)).toEqual([ + { + key: 'test_key_1', + type: 'text', + value: 'first value', + }, + { + key: 'test_key_2', + type: 'toggle', + value: true, + }, + { + key: 'test_key_3', + type: 'text', + value: 'second value', + }, + ]); + }); + + it('returns empty array when custom fields are empty', () => { + expect(customFieldsFormSerializer({}, customFieldsConfigurationMock)).toEqual([]); + }); + + it('returns empty array when not custom fields in the configuration', () => { + const customFields = { + test_key_1: 'first value', + test_key_2: true, + test_key_3: 'second value', + }; + + expect(customFieldsFormSerializer(customFields, [])).toEqual([]); + }); + + it('returns empty array when custom fields do not match with configuration', () => { + const customFields = { + random_key: 'first value', + }; + + expect(customFieldsFormSerializer(customFields, customFieldsConfigurationMock)).toEqual([]); + }); + }); }); diff --git a/x-pack/plugins/cases/public/components/utils.ts b/x-pack/plugins/cases/public/components/utils.ts index 13bff3b48fdc9..7e1aa54554f50 100644 --- a/x-pack/plugins/cases/public/components/utils.ts +++ b/x-pack/plugins/cases/public/components/utils.ts @@ -17,7 +17,13 @@ import { ConnectorTypes } from '../../common/types/domain'; import type { CasesPublicStartDependencies } from '../types'; import { connectorValidator as swimlaneConnectorValidator } from './connectors/swimlane/validator'; import type { CaseActionConnector } from './types'; -import type { CaseUser, CaseUsers } from '../../common/ui/types'; +import type { + CasesConfigurationUI, + CaseUI, + CaseUICustomField, + CaseUser, + CaseUsers, +} from '../../common/ui/types'; import { convertToCaseUserWithProfileInfo } from './user_profiles/user_converter'; import type { CaseUserWithProfileInfo } from './user_profiles/types'; @@ -235,3 +241,72 @@ export const convertCustomFieldValue = (value: string | boolean) => { return value; }; + +export const addOrReplaceField = <T extends { key: string }>(fields: T[], fieldToAdd: T): T[] => { + const foundFieldIndex = fields.findIndex((field) => field.key === fieldToAdd.key); + + if (foundFieldIndex === -1) { + return [...fields, fieldToAdd]; + } + + return fields.map((field) => { + if (field.key !== fieldToAdd.key) { + return field; + } + + return fieldToAdd; + }); +}; + +export function removeEmptyFields<T extends Record<string, unknown>>(obj: T): Partial<T> { + return Object.fromEntries( + Object.entries(obj) + .filter(([_, value]) => !isEmpty(value) || typeof value === 'boolean') + .map(([key, value]) => [ + key, + value === Object(value) && !Array.isArray(value) + ? removeEmptyFields(value as Record<string, unknown>) + : value, + ]) + ) as T; +} + +export const customFieldsFormDeserializer = ( + customFields?: CaseUI['customFields'] +): Record<string, string | boolean> | null => { + if (!customFields || !customFields.length) { + return null; + } + + return customFields.reduce((acc, customField) => { + const initial = { + [customField.key]: customField.value, + }; + + return { ...acc, ...initial }; + }, {}); +}; + +export const customFieldsFormSerializer = ( + customFields: Record<string, string | boolean>, + selectedCustomFieldsConfiguration: CasesConfigurationUI['customFields'] +): CaseUI['customFields'] => { + const transformedCustomFields: CaseUI['customFields'] = []; + + if (!customFields || !selectedCustomFieldsConfiguration.length) { + return []; + } + + for (const [key, value] of Object.entries(customFields)) { + const configCustomField = selectedCustomFieldsConfiguration.find((item) => item.key === key); + if (configCustomField) { + transformedCustomFields.push({ + key: configCustomField.key, + type: configCustomField.type, + value: convertCustomFieldValue(value), + } as CaseUICustomField); + } + } + + return transformedCustomFields; +}; diff --git a/x-pack/plugins/cases/public/containers/configure/api.ts b/x-pack/plugins/cases/public/containers/configure/api.ts index ae72d839d3ac5..b67e8f53f2268 100644 --- a/x-pack/plugins/cases/public/containers/configure/api.ts +++ b/x-pack/plugins/cases/public/containers/configure/api.ts @@ -115,7 +115,8 @@ export const fetchActionTypes = async ({ signal }: ApiProps): Promise<ActionType const convertConfigureResponseToCasesConfigure = ( configuration: SnakeToCamelCase<Configuration> ): CasesConfigurationUI => { - const { id, version, mappings, customFields, closureType, connector, owner } = configuration; + const { id, version, mappings, customFields, templates, closureType, connector, owner } = + configuration; - return { id, version, mappings, customFields, closureType, connector, owner }; + return { id, version, mappings, customFields, templates, closureType, connector, owner }; }; diff --git a/x-pack/plugins/cases/public/containers/configure/mock.ts b/x-pack/plugins/cases/public/containers/configure/mock.ts index a5946ca319641..1124283e5aa94 100644 --- a/x-pack/plugins/cases/public/containers/configure/mock.ts +++ b/x-pack/plugins/cases/public/containers/configure/mock.ts @@ -11,7 +11,7 @@ import { ConnectorTypes } from '../../../common/types/domain'; import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import type { CaseConnectorMapping } from './types'; import type { CasesConfigurationUI } from '../types'; -import { customFieldsConfigurationMock } from '../mock'; +import { customFieldsConfigurationMock, templatesConfigurationMock } from '../mock'; export const mappings: CaseConnectorMapping[] = [ { @@ -49,6 +49,7 @@ export const caseConfigurationResponseMock: Configuration = { owner: SECURITY_SOLUTION_OWNER, version: 'WzHJ12', customFields: customFieldsConfigurationMock, + templates: templatesConfigurationMock, }; export const caseConfigurationRequest: ConfigurationRequest = { @@ -74,5 +75,6 @@ export const casesConfigurationsMock: CasesConfigurationUI = { mappings: [], version: 'WzHJ12', customFields: customFieldsConfigurationMock, + templates: templatesConfigurationMock, owner: 'securitySolution', }; diff --git a/x-pack/plugins/cases/public/containers/configure/use_get_all_case_configurations.test.ts b/x-pack/plugins/cases/public/containers/configure/use_get_all_case_configurations.test.ts index fdd46d640e5fc..cd9e44d1bdaae 100644 --- a/x-pack/plugins/cases/public/containers/configure/use_get_all_case_configurations.test.ts +++ b/x-pack/plugins/cases/public/containers/configure/use_get_all_case_configurations.test.ts @@ -48,6 +48,7 @@ describe('Use get all case configurations hook', () => { closureType: 'close-by-user', connector: { fields: null, id: 'none', name: 'none', type: '.none' }, customFields: [], + templates: [], id: '', mappings: [], version: '', @@ -86,6 +87,7 @@ describe('Use get all case configurations hook', () => { closureType: 'close-by-user', connector: { fields: null, id: 'none', name: 'none', type: '.none' }, customFields: [], + templates: [], id: '', mappings: [], version: '', diff --git a/x-pack/plugins/cases/public/containers/configure/use_get_supported_action_connectors.tsx b/x-pack/plugins/cases/public/containers/configure/use_get_supported_action_connectors.tsx index e98d63debce4b..0fd0ca642baf2 100644 --- a/x-pack/plugins/cases/public/containers/configure/use_get_supported_action_connectors.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_get_supported_action_connectors.tsx @@ -27,6 +27,7 @@ export function useGetSupportedActionConnectors() { return getSupportedActionConnectors({ signal }); }, { + staleTime: 60 * 1000, // one minute onError: (error: ServerError) => { if (error.name !== 'AbortError') { toasts.addError( diff --git a/x-pack/plugins/cases/public/containers/configure/use_persist_configuration.test.tsx b/x-pack/plugins/cases/public/containers/configure/use_persist_configuration.test.tsx index 509b0e72cd1fc..4fab35fd5ce5f 100644 --- a/x-pack/plugins/cases/public/containers/configure/use_persist_configuration.test.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_persist_configuration.test.tsx @@ -14,13 +14,14 @@ import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; import { ConnectorTypes } from '../../../common'; import { casesQueriesKeys } from '../constants'; +import { customFieldsConfigurationMock, templatesConfigurationMock } from '../mock'; jest.mock('./api'); jest.mock('../../common/lib/kibana'); const useToastMock = useToasts as jest.Mock; -describe('useCreateAttachments', () => { +describe('usePersistConfiguration', () => { const addError = jest.fn(); const addSuccess = jest.fn(); @@ -38,6 +39,7 @@ describe('useCreateAttachments', () => { type: ConnectorTypes.none, }, customFields: [], + templates: [], version: '', id: '', }; @@ -53,7 +55,7 @@ describe('useCreateAttachments', () => { const spyPost = jest.spyOn(api, 'postCaseConfigure'); const spyPatch = jest.spyOn(api, 'patchCaseConfigure'); - const { waitForNextUpdate, result } = renderHook(() => usePersistConfiguration(), { + const { waitFor, result } = renderHook(() => usePersistConfiguration(), { wrapper: appMockRender.AppWrapper, }); @@ -61,22 +63,24 @@ describe('useCreateAttachments', () => { result.current.mutate({ ...request, version: 'test' }); }); - await waitForNextUpdate(); + await waitFor(() => { + expect(spyPost).toHaveBeenCalledWith({ + closure_type: 'close-by-user', + connector: { fields: null, id: 'none', name: 'none', type: '.none' }, + customFields: [], + owner: 'securitySolution', + templates: [], + }); + }); expect(spyPatch).not.toHaveBeenCalled(); - expect(spyPost).toHaveBeenCalledWith({ - closure_type: 'close-by-user', - connector: { fields: null, id: 'none', name: 'none', type: '.none' }, - customFields: [], - owner: 'securitySolution', - }); }); it('calls postCaseConfigure when the version is empty', async () => { const spyPost = jest.spyOn(api, 'postCaseConfigure'); const spyPatch = jest.spyOn(api, 'patchCaseConfigure'); - const { waitForNextUpdate, result } = renderHook(() => usePersistConfiguration(), { + const { waitFor, result } = renderHook(() => usePersistConfiguration(), { wrapper: appMockRender.AppWrapper, }); @@ -84,14 +88,44 @@ describe('useCreateAttachments', () => { result.current.mutate({ ...request, id: 'test' }); }); - await waitForNextUpdate(); + await waitFor(() => { + expect(spyPost).toHaveBeenCalledWith({ + closure_type: 'close-by-user', + connector: { fields: null, id: 'none', name: 'none', type: '.none' }, + customFields: [], + templates: [], + owner: 'securitySolution', + }); + }); expect(spyPatch).not.toHaveBeenCalled(); - expect(spyPost).toHaveBeenCalledWith({ - closure_type: 'close-by-user', - connector: { fields: null, id: 'none', name: 'none', type: '.none' }, - customFields: [], - owner: 'securitySolution', + }); + + it('calls postCaseConfigure with correct data', async () => { + const spyPost = jest.spyOn(api, 'postCaseConfigure'); + + const { waitFor, result } = renderHook(() => usePersistConfiguration(), { + wrapper: appMockRender.AppWrapper, + }); + + const newRequest = { + ...request, + customFields: customFieldsConfigurationMock, + templates: templatesConfigurationMock, + }; + + act(() => { + result.current.mutate({ ...newRequest, id: 'test-id' }); + }); + + await waitFor(() => { + expect(spyPost).toHaveBeenCalledWith({ + closure_type: 'close-by-user', + connector: { fields: null, id: 'none', name: 'none', type: '.none' }, + customFields: customFieldsConfigurationMock, + templates: templatesConfigurationMock, + owner: 'securitySolution', + }); }); }); @@ -99,7 +133,7 @@ describe('useCreateAttachments', () => { const spyPost = jest.spyOn(api, 'postCaseConfigure'); const spyPatch = jest.spyOn(api, 'patchCaseConfigure'); - const { waitForNextUpdate, result } = renderHook(() => usePersistConfiguration(), { + const { waitFor, result } = renderHook(() => usePersistConfiguration(), { wrapper: appMockRender.AppWrapper, }); @@ -107,20 +141,50 @@ describe('useCreateAttachments', () => { result.current.mutate({ ...request, id: 'test-id', version: 'test-version' }); }); - await waitForNextUpdate(); + await waitFor(() => { + expect(spyPatch).toHaveBeenCalledWith('test-id', { + closure_type: 'close-by-user', + connector: { fields: null, id: 'none', name: 'none', type: '.none' }, + customFields: [], + templates: [], + version: 'test-version', + }); + }); expect(spyPost).not.toHaveBeenCalled(); - expect(spyPatch).toHaveBeenCalledWith('test-id', { - closure_type: 'close-by-user', - connector: { fields: null, id: 'none', name: 'none', type: '.none' }, - customFields: [], - version: 'test-version', + }); + + it('calls patchCaseConfigure with correct data', async () => { + const spyPatch = jest.spyOn(api, 'patchCaseConfigure'); + + const { waitFor, result } = renderHook(() => usePersistConfiguration(), { + wrapper: appMockRender.AppWrapper, + }); + + const newRequest = { + ...request, + customFields: customFieldsConfigurationMock, + templates: templatesConfigurationMock, + }; + + act(() => { + result.current.mutate({ ...newRequest, id: 'test-id', version: 'test-version' }); + }); + + await waitFor(() => { + expect(spyPatch).toHaveBeenCalledWith('test-id', { + closure_type: 'close-by-user', + connector: { fields: null, id: 'none', name: 'none', type: '.none' }, + customFields: customFieldsConfigurationMock, + templates: templatesConfigurationMock, + version: 'test-version', + }); }); }); it('invalidates the queries correctly', async () => { const queryClientSpy = jest.spyOn(appMockRender.queryClient, 'invalidateQueries'); - const { waitForNextUpdate, result } = renderHook(() => usePersistConfiguration(), { + const { waitFor, result } = renderHook(() => usePersistConfiguration(), { wrapper: appMockRender.AppWrapper, }); @@ -128,13 +192,13 @@ describe('useCreateAttachments', () => { result.current.mutate(request); }); - await waitForNextUpdate(); - - expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.configuration({})); + await waitFor(() => { + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.configuration({})); + }); }); it('shows the success toaster', async () => { - const { waitForNextUpdate, result } = renderHook(() => usePersistConfiguration(), { + const { waitFor, result } = renderHook(() => usePersistConfiguration(), { wrapper: appMockRender.AppWrapper, }); @@ -142,9 +206,9 @@ describe('useCreateAttachments', () => { result.current.mutate(request); }); - await waitForNextUpdate(); - - expect(addSuccess).toHaveBeenCalled(); + await waitFor(() => { + expect(addSuccess).toHaveBeenCalled(); + }); }); it('shows a toast error when the api return an error', async () => { @@ -152,7 +216,7 @@ describe('useCreateAttachments', () => { .spyOn(api, 'postCaseConfigure') .mockRejectedValue(new Error('useCreateAttachments: Test error')); - const { waitForNextUpdate, result } = renderHook(() => usePersistConfiguration(), { + const { waitFor, result } = renderHook(() => usePersistConfiguration(), { wrapper: appMockRender.AppWrapper, }); @@ -160,8 +224,8 @@ describe('useCreateAttachments', () => { result.current.mutate(request); }); - await waitForNextUpdate(); - - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(addError).toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/cases/public/containers/configure/use_persist_configuration.tsx b/x-pack/plugins/cases/public/containers/configure/use_persist_configuration.tsx index 95162d23aa391..dc9bed95d1df8 100644 --- a/x-pack/plugins/cases/public/containers/configure/use_persist_configuration.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_persist_configuration.tsx @@ -27,12 +27,13 @@ export const usePersistConfiguration = () => { const { showErrorToast, showSuccessToast } = useCasesToast(); return useMutation( - ({ id, version, closureType, customFields, connector }: Request) => { + ({ id, version, closureType, customFields, templates, connector }: Request) => { if (isEmpty(id) || isEmpty(version)) { return postCaseConfigure({ closure_type: closureType, connector, customFields: customFields ?? [], + templates: templates ?? [], owner: owner[0], }); } @@ -42,6 +43,7 @@ export const usePersistConfiguration = () => { closure_type: closureType, connector, customFields: customFields ?? [], + templates: templates ?? [], }); }, { diff --git a/x-pack/plugins/cases/public/containers/configure/utils.ts b/x-pack/plugins/cases/public/containers/configure/utils.ts index 164b9c0f94945..e4416beb5ce57 100644 --- a/x-pack/plugins/cases/public/containers/configure/utils.ts +++ b/x-pack/plugins/cases/public/containers/configure/utils.ts @@ -16,6 +16,7 @@ export const initialConfiguration: CasesConfigurationUI = { type: ConnectorTypes.none, }, customFields: [], + templates: [], mappings: [], version: '', id: '', diff --git a/x-pack/plugins/cases/public/containers/mock.ts b/x-pack/plugins/cases/public/containers/mock.ts index 63fac9c816955..8d2feca6b9be0 100644 --- a/x-pack/plugins/cases/public/containers/mock.ts +++ b/x-pack/plugins/cases/public/containers/mock.ts @@ -45,6 +45,7 @@ import type { AttachmentUI, CaseUICustomField, CasesConfigurationUICustomField, + CasesConfigurationUITemplate, } from '../../common/ui/types'; import { CaseMetricsFeature } from '../../common/types/api'; import { SECURITY_SOLUTION_OWNER } from '../../common/constants'; @@ -1177,3 +1178,84 @@ export const customFieldsConfigurationMock: CasesConfigurationUICustomField[] = { type: CustomFieldTypes.TEXT, key: 'test_key_3', label: 'My test label 3', required: false }, { type: CustomFieldTypes.TOGGLE, key: 'test_key_4', label: 'My test label 4', required: false }, ]; + +export const templatesConfigurationMock: CasesConfigurationUITemplate[] = [ + { + key: 'test_template_1', + name: 'First test template', + description: 'This is a first test template', + caseFields: null, + }, + { + key: 'test_template_2', + name: 'Second test template', + description: 'This is a second test template', + tags: [], + caseFields: {}, + }, + { + key: 'test_template_3', + name: 'Third test template', + description: 'This is a third test template with few case fields', + tags: ['foo'], + caseFields: { + title: 'This is case title using a test template', + severity: CaseSeverity.MEDIUM, + tags: ['third-template', 'medium'], + }, + }, + { + key: 'test_template_4', + name: 'Fourth test template', + description: 'This is a fourth test template', + tags: ['foo', 'bar'], + caseFields: { + title: 'Case with sample template 4', + description: 'case desc', + severity: CaseSeverity.LOW, + category: null, + tags: ['sample-4'], + assignees: [{ uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0' }], + customFields: [ + { + key: 'first_custom_field_key', + type: CustomFieldTypes.TEXT, + value: 'this is a text field value', + }, + ], + connector: { + id: 'none', + name: 'My Connector', + type: ConnectorTypes.none, + fields: null, + }, + }, + }, + { + key: 'test_template_5', + name: 'Fifth test template', + description: 'This is a fifth test template', + tags: ['foo', 'bar'], + caseFields: { + title: 'Case with sample template 5', + description: 'case desc', + severity: CaseSeverity.HIGH, + category: 'my category', + tags: ['sample-4'], + assignees: [{ uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0' }], + customFields: [ + { + key: 'first_custom_field_key', + type: CustomFieldTypes.TEXT, + value: 'this is a text field value', + }, + ], + connector: { + id: 'jira-1', + name: 'Jira', + type: ConnectorTypes.jira, + fields: { issueType: 'Task', priority: 'Low', parent: null }, + }, + }, + }, +]; diff --git a/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.test.ts b/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.test.ts index 5732085d99c8e..4edc105b8d349 100644 --- a/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.test.ts +++ b/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.test.ts @@ -114,4 +114,14 @@ describe.skip('useBulkGetUserProfiles', () => { expect(addError).toHaveBeenCalled(); }); + + it('does not call the bulkGetUserProfiles if the array of uids is empty', async () => { + const spyOnBulkGetUserProfiles = jest.spyOn(api, 'bulkGetUserProfiles'); + + renderHook(() => useBulkGetUserProfiles({ uids: [] }), { + wrapper: appMockRender.AppWrapper, + }); + + expect(spyOnBulkGetUserProfiles).not.toHaveBeenCalled(); + }); }); diff --git a/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.ts b/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.ts index a9e60f3e854a9..8b1b9580ca84f 100644 --- a/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.ts +++ b/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.ts @@ -34,6 +34,7 @@ export const useBulkGetUserProfiles = ({ uids }: { uids: string[] }) => { select: profilesToMap, retry: false, keepPreviousData: true, + staleTime: 60 * 1000, // one minute onError: (error: ServerError) => { if (error.name !== 'AbortError') { toasts.addError( @@ -44,6 +45,7 @@ export const useBulkGetUserProfiles = ({ uids }: { uids: string[] }) => { ); } }, + enabled: uids.length > 0, } ); }; diff --git a/x-pack/plugins/cases/server/client/cases/bulk_create.test.ts b/x-pack/plugins/cases/server/client/cases/bulk_create.test.ts index 1f6c9b307fe6e..c7f047aa6b385 100644 --- a/x-pack/plugins/cases/server/client/cases/bulk_create.test.ts +++ b/x-pack/plugins/cases/server/client/cases/bulk_create.test.ts @@ -977,7 +977,7 @@ describe('bulkCreate', () => { casesClient ) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to bulk create cases: Error: Invalid duplicated custom field keys in request: duplicated_key"` + `"Failed to bulk create cases: Error: Invalid duplicated customFields keys in request: duplicated_key"` ); }); diff --git a/x-pack/plugins/cases/server/client/cases/bulk_update.test.ts b/x-pack/plugins/cases/server/client/cases/bulk_update.test.ts index a3fc842dfe3e1..0109e6eda8808 100644 --- a/x-pack/plugins/cases/server/client/cases/bulk_update.test.ts +++ b/x-pack/plugins/cases/server/client/cases/bulk_update.test.ts @@ -1309,7 +1309,7 @@ describe('update', () => { casesClient ) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to update case, ids: [{\\"id\\":\\"mock-id-1\\",\\"version\\":\\"WzAsMV0=\\"}]: Error: Invalid duplicated custom field keys in request: duplicated_key"` + `"Failed to update case, ids: [{\\"id\\":\\"mock-id-1\\",\\"version\\":\\"WzAsMV0=\\"}]: Error: Invalid duplicated customFields keys in request: duplicated_key"` ); }); diff --git a/x-pack/plugins/cases/server/client/cases/create.test.ts b/x-pack/plugins/cases/server/client/cases/create.test.ts index 315ee14834574..8b24c79c530b0 100644 --- a/x-pack/plugins/cases/server/client/cases/create.test.ts +++ b/x-pack/plugins/cases/server/client/cases/create.test.ts @@ -632,7 +632,7 @@ describe('create', () => { casesClient ) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to create case: Error: Invalid duplicated custom field keys in request: duplicated_key"` + `"Failed to create case: Error: Invalid duplicated customFields keys in request: duplicated_key"` ); }); diff --git a/x-pack/plugins/cases/server/client/cases/validators.ts b/x-pack/plugins/cases/server/client/cases/validators.ts index eeebbc8c13ca0..b2e286d48c4b1 100644 --- a/x-pack/plugins/cases/server/client/cases/validators.ts +++ b/x-pack/plugins/cases/server/client/cases/validators.ts @@ -9,7 +9,7 @@ import { differenceWith, intersectionWith, isEmpty } from 'lodash'; import Boom from '@hapi/boom'; import type { CustomFieldsConfiguration } from '../../../common/types/domain'; import type { CaseRequestCustomFields, CasesSearchRequest } from '../../../common/types/api'; -import { validateDuplicatedCustomFieldKeysInRequest } from '../validators'; +import { validateDuplicatedKeysInRequest } from '../validators'; import type { ICasesCustomField } from '../../custom_fields'; import { casesCustomFields } from '../../custom_fields'; import { MAX_CUSTOM_FIELDS_PER_CASE } from '../../../common/constants'; @@ -20,7 +20,10 @@ interface CustomFieldValidationParams { } export const validateCustomFields = (params: CustomFieldValidationParams) => { - validateDuplicatedCustomFieldKeysInRequest(params); + validateDuplicatedKeysInRequest({ + requestFields: params.requestCustomFields, + fieldName: 'customFields', + }); validateCustomFieldKeysAgainstConfiguration(params); validateRequiredCustomFields(params); validateCustomFieldTypesInRequest(params); diff --git a/x-pack/plugins/cases/server/client/configure/client.test.ts b/x-pack/plugins/cases/server/client/configure/client.test.ts index b5958c44de080..8b312d2d957a2 100644 --- a/x-pack/plugins/cases/server/client/configure/client.test.ts +++ b/x-pack/plugins/cases/server/client/configure/client.test.ts @@ -15,8 +15,10 @@ import { createCasesClientInternalMock, createCasesClientMockArgs } from '../moc import { MAX_CUSTOM_FIELDS_PER_CASE, MAX_SUPPORTED_CONNECTORS_RETURNED, + MAX_TEMPLATES_LENGTH, } from '../../../common/constants'; import { ConnectorTypes } from '../../../common'; +import type { TemplatesConfiguration } from '../../../common/types/domain'; import { CustomFieldTypes } from '../../../common/types/domain'; import type { ConfigurationRequest } from '../../../common/types/api'; @@ -306,7 +308,7 @@ describe('client', () => { casesClientInternal ) ).rejects.toThrow( - 'Failed to get patch configure in route: Error: Invalid duplicated custom field keys in request: duplicated_key' + 'Failed to get patch configure in route: Error: Invalid duplicated customFields keys in request: duplicated_key' ); }); @@ -346,6 +348,618 @@ describe('client', () => { 'Failed to get patch configure in route: Error: Invalid custom field types in request for the following labels: "text label"' ); }); + + describe('templates', () => { + it(`does not throw error when trying to update templates`, async () => { + clientArgs.services.caseConfigureService.get.mockResolvedValue({ + // @ts-ignore: these are all the attributes needed for the test + attributes: { + customFields: [], + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + closure_type: 'close-by-user', + owner: 'cases', + templates: [], + }, + version: 'test-version', + }); + + clientArgs.services.caseConfigureService.patch.mockResolvedValue({ + id: 'test-id', + type: 'cases-configure', + version: 'test-version', + namespaces: ['default'], + references: [], + attributes: { + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'test', + tags: ['foo', 'bar'], + caseFields: { + title: 'Case title', + description: 'This is test desc', + tags: ['sample-1'], + assignees: [], + customFields: [], + category: null, + }, + }, + ], + created_at: '2019-11-25T21:54:48.952Z', + created_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + updated_at: '2019-11-25T21:54:48.952Z', + updated_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + }, + }); + + await expect( + update( + 'test-id', + { + version: 'test-version', + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'test', + tags: ['foo', 'bar'], + caseFields: { + title: 'Case title', + description: 'This is test desc', + tags: ['sample-1'], + assignees: [], + customFields: [], + category: null, + }, + }, + ], + }, + clientArgs, + casesClientInternal + ) + ).resolves.not.toThrow(); + }); + + it(`does not throw error when trying to update to empty templates`, async () => { + clientArgs.services.caseConfigureService.get.mockResolvedValue({ + // @ts-ignore: these are all the attributes needed for the test + attributes: { + customFields: [], + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + closure_type: 'close-by-user', + owner: 'cases', + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'test', + tags: ['foo', 'bar'], + caseFields: { + title: 'Case title', + description: 'This is test desc', + tags: ['sample-1'], + assignees: [], + customFields: [], + category: null, + }, + }, + ], + }, + version: 'test-version', + }); + + clientArgs.services.caseConfigureService.patch.mockResolvedValue({ + id: 'test-id', + type: 'cases-configure', + version: 'test-version', + namespaces: ['default'], + references: [], + attributes: { + templates: [], + created_at: '2019-11-25T21:54:48.952Z', + created_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + updated_at: '2019-11-25T21:54:48.952Z', + updated_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + }, + }); + + await expect( + update( + 'test-id', + { + version: 'test-version', + templates: [], + }, + clientArgs, + casesClientInternal + ) + ).resolves.not.toThrow(); + }); + + it(`throws when trying to update more than ${MAX_TEMPLATES_LENGTH} templates`, async () => { + await expect( + update( + 'test-id', + { + version: 'test-version', + templates: new Array(MAX_TEMPLATES_LENGTH + 1).fill({ + key: 'template_1', + name: 'template 1', + caseFields: null, + }), + }, + clientArgs, + casesClientInternal + ) + ).rejects.toThrow( + `Failed to get patch configure in route: Error: The length of the field templates is too long. Array must be of length <= ${MAX_TEMPLATES_LENGTH}.` + ); + }); + + it('throws when there are duplicated template keys in the request', async () => { + await expect( + update( + 'test-id', + { + version: 'test-version', + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'test', + tags: ['foo', 'bar'], + caseFields: null, + }, + { + key: 'template_1', + name: 'template 2', + tags: [], + caseFields: { + title: 'Case title', + }, + }, + ], + }, + clientArgs, + casesClientInternal + ) + ).rejects.toThrow( + 'Failed to get patch configure in route: Error: Invalid duplicated templates keys in request: template_1' + ); + }); + + describe('customFields', () => { + it('throws when there are no customFields in configure and template has customField in the request', async () => { + clientArgs.services.caseConfigureService.get.mockResolvedValue({ + // @ts-ignore: these are all the attributes needed for the test + attributes: { + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'this is test description', + caseFields: null, + }, + ], + }, + }); + + await expect( + update( + 'test-id', + { + version: 'test-version', + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'this is test description', + caseFields: { + customFields: [ + { + key: 'custom_field_key_1', + type: CustomFieldTypes.TEXT, + value: 'custom field value 1', + }, + ], + }, + }, + ], + }, + clientArgs, + casesClientInternal + ) + ).rejects.toThrow( + 'Failed to get patch configure in route: Error: No custom fields configured.' + ); + }); + + it('throws when template has duplicated custom field keys in the request', async () => { + clientArgs.services.caseConfigureService.get.mockResolvedValue({ + // @ts-ignore: these are all the attributes needed for the test + attributes: { + customFields: [ + { + key: 'custom_field_key_1', + label: 'text label', + type: CustomFieldTypes.TEXT, + required: false, + }, + ], + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'this is test description', + caseFields: { + customFields: [ + { + key: 'custom_field_key_1', + type: CustomFieldTypes.TEXT, + value: 'custom field value 1', + }, + ], + }, + }, + ], + }, + }); + + await expect( + update( + 'test-id', + { + version: 'test-version', + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'test', + caseFields: { + customFields: [ + { + key: 'custom_field_key_1', + type: CustomFieldTypes.TEXT, + value: 'custom field value 1', + }, + { + key: 'custom_field_key_1', + type: CustomFieldTypes.TEXT, + value: 'custom field value 2', + }, + ], + }, + }, + ], + }, + clientArgs, + casesClientInternal + ) + ).rejects.toThrow( + `Failed to get patch configure in route: Error: Invalid duplicated templates[0]'s customFields keys in request: custom_field_key_1` + ); + }); + + it('throws when there are invalid customField keys in the request', async () => { + clientArgs.services.caseConfigureService.get.mockResolvedValue({ + // @ts-ignore: these are all the attributes needed for the test + attributes: { + customFields: [ + { + key: 'custom_field_key_1', + label: 'text label', + type: CustomFieldTypes.TEXT, + required: false, + }, + ], + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'this is test description', + caseFields: { + customFields: [ + { + key: 'custom_field_key_1', + type: CustomFieldTypes.TEXT, + value: 'custom field value 1', + }, + ], + }, + }, + ], + }, + }); + + await expect( + update( + 'test-id', + { + version: 'test-version', + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'this is test description', + caseFields: { + customFields: [ + { + key: 'custom_field_key_2', + type: CustomFieldTypes.TEXT, + value: 'custom field value 1', + }, + ], + }, + }, + ], + }, + clientArgs, + casesClientInternal + ) + ).rejects.toThrow( + 'Failed to get patch configure in route: Error: Invalid custom field keys: custom_field_key_2' + ); + }); + + it('throws when template has customField with invalid type in the request', async () => { + clientArgs.services.caseConfigureService.get.mockResolvedValue({ + // @ts-ignore: these are all the attributes needed for the test + attributes: { + customFields: [ + { + key: 'custom_field_key_1', + label: 'text label', + type: CustomFieldTypes.TEXT, + required: false, + }, + ], + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'this is test description', + caseFields: { + customFields: [ + { + key: 'custom_field_key_1', + type: CustomFieldTypes.TEXT, + value: 'custom field value 1', + }, + ], + }, + }, + ], + }, + }); + + await expect( + update( + 'test-id', + { + version: 'test-version', + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'this is test description', + caseFields: { + customFields: [ + { + key: 'custom_field_key_1', + type: CustomFieldTypes.TOGGLE, + value: true, + }, + ], + }, + }, + ], + }, + clientArgs, + casesClientInternal + ) + ).rejects.toThrow( + 'Failed to get patch configure in route: Error: The following custom fields have the wrong type in the request: "text label"' + ); + }); + + it('removes deleted custom field from template correctly', async () => { + clientArgs.services.caseConfigureService.get.mockResolvedValue({ + // @ts-ignore: these are all the attributes needed for the test + attributes: { + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + customFields: [ + { + key: 'custom_field_key_1', + label: 'text label', + type: CustomFieldTypes.TEXT, + required: false, + }, + ], + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'this is test description', + caseFields: { + customFields: [ + { + key: 'custom_field_key_1', + type: CustomFieldTypes.TEXT, + value: 'custom field value 1', + }, + ], + }, + }, + ], + closure_type: 'close-by-user', + owner: 'cases', + }, + id: 'test-id', + version: 'test-version', + }); + + await update( + 'test-id', + { + version: 'test-version', + customFields: [], + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'this is test description', + caseFields: { + customFields: [ + { + key: 'custom_field_key_1', + type: CustomFieldTypes.TEXT, + value: 'updated value', + }, + ], + }, + }, + ], + }, + clientArgs, + casesClientInternal + ); + + expect(clientArgs.services.caseConfigureService.patch).toHaveBeenCalledWith({ + configurationId: 'test-id', + originalConfiguration: { + attributes: { + closure_type: 'close-by-user', + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + customFields: [ + { + key: 'custom_field_key_1', + label: 'text label', + required: false, + type: 'text', + }, + ], + owner: 'cases', + templates: [ + { + caseFields: { + customFields: [ + { + key: 'custom_field_key_1', + type: 'text', + value: 'custom field value 1', + }, + ], + }, + description: 'this is test description', + key: 'template_1', + name: 'template 1', + }, + ], + }, + id: 'test-id', + version: 'test-version', + }, + unsecuredSavedObjectsClient: expect.anything(), + updatedAttributes: { + customFields: [], + templates: [ + { + caseFields: { + customFields: [], + }, + description: 'this is test description', + key: 'template_1', + name: 'template 1', + }, + ], + updated_at: expect.anything(), + updated_by: expect.anything(), + }, + }); + }); + }); + + describe('assignees', () => { + it('throws if the user does not have the correct license while adding assignees in template ', async () => { + clientArgs.services.licensingService.isAtLeastPlatinum.mockResolvedValue(false); + clientArgs.services.caseConfigureService.get.mockResolvedValue({ + // @ts-ignore: these are all the attributes needed for the test + attributes: { + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'this is test description', + tags: ['foo', 'bar'], + caseFields: null, + }, + ], + }, + }); + + await expect( + update( + 'test-id', + { + version: 'test-version', + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'this is test description', + tags: ['foo', 'bar'], + caseFields: { + assignees: [{ uid: '1' }], + }, + }, + ], + }, + clientArgs, + casesClientInternal + ) + ).rejects.toThrow( + 'Failed to get patch configure in route: Error: In order to assign users to cases, you must be subscribed to an Elastic Platinum license' + ); + }); + }); + }); }); describe('create', () => { @@ -404,8 +1018,334 @@ describe('client', () => { casesClientInternal ) ).rejects.toThrow( - 'Failed to create case configuration: Error: Invalid duplicated custom field keys in request: duplicated_key' + 'Failed to create case configuration: Error: Invalid duplicated customFields keys in request: duplicated_key' ); }); + + describe('templates', () => { + it(`throws when trying to create more than ${MAX_TEMPLATES_LENGTH} templates`, async () => { + await expect( + create( + { + ...baseRequest, + templates: new Array(MAX_TEMPLATES_LENGTH + 1).fill({ + key: 'template_1', + name: 'template 1', + description: 'test', + caseFields: null, + }), + }, + clientArgs, + casesClientInternal + ) + ).rejects.toThrow( + `Failed to create case configuration: Error: The length of the field templates is too long. Array must be of length <= ${MAX_TEMPLATES_LENGTH}.` + ); + }); + + it('throws when there are duplicated template keys in the request', async () => { + await expect( + create( + { + ...baseRequest, + templates: [ + { + key: 'duplicated_key', + name: 'template 1', + description: 'test', + caseFields: null, + }, + { + key: 'duplicated_key', + name: 'template 2', + description: 'test', + tags: [], + caseFields: { + title: 'Case title', + }, + }, + ], + }, + clientArgs, + casesClientInternal + ) + ).rejects.toThrow( + 'Failed to create case configuration: Error: Invalid duplicated templates keys in request: duplicated_key' + ); + }); + + describe('customFields', () => { + it('does not throw error when creating template with correct custom fields', async () => { + const customFields = [ + { + key: 'custom_field_key_1', + type: CustomFieldTypes.TEXT, + label: 'custom field 1', + required: true, + }, + ]; + const templates: TemplatesConfiguration = [ + { + key: 'template_1', + name: 'template 1', + description: 'test', + tags: ['foo', 'bar'], + caseFields: { + customFields: [ + { + key: 'custom_field_key_1', + type: CustomFieldTypes.TEXT, + value: 'custom field value 1', + }, + ], + }, + }, + ]; + + clientArgs.services.caseConfigureService.find.mockResolvedValueOnce({ + page: 1, + per_page: 20, + total: 1, + saved_objects: [ + { + id: 'test-id', + type: 'cases-configure', + version: 'test-version', + namespaces: ['default'], + references: [], + attributes: { + ...baseRequest, + customFields, + templates, + created_at: '2019-11-25T21:54:48.952Z', + created_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + updated_at: null, + updated_by: null, + }, + score: 0, + }, + ], + pit_id: undefined, + }); + + clientArgs.services.caseConfigureService.post.mockResolvedValue({ + id: 'test-id', + type: 'cases-configure', + version: 'test-version', + namespaces: ['default'], + references: [], + attributes: { + ...baseRequest, + customFields, + templates, + created_at: '2019-11-25T21:54:48.952Z', + created_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + updated_at: null, + updated_by: null, + }, + }); + + await expect( + create( + { + ...baseRequest, + customFields, + templates, + }, + clientArgs, + casesClientInternal + ) + ).resolves.not.toThrow(); + }); + + it('throws when there are no customFields in configure and template has customField in the request', async () => { + await expect( + create( + { + ...baseRequest, + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'this is test description', + tags: ['foo', 'bar'], + caseFields: { + customFields: [ + { + key: 'custom_field_key_1', + type: CustomFieldTypes.TEXT, + value: 'custom field value 1', + }, + ], + }, + }, + ], + }, + clientArgs, + casesClientInternal + ) + ).rejects.toThrow( + 'Failed to create case configuration: Error: No custom fields configured.' + ); + }); + + it('throws when template has duplicated custom field keys in the request', async () => { + await expect( + create( + { + ...baseRequest, + customFields: [ + { + key: 'custom_field_key_1', + type: CustomFieldTypes.TEXT, + label: 'custom field 1', + required: true, + }, + ], + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'test', + tags: ['foo', 'bar'], + caseFields: { + customFields: [ + { + key: 'custom_field_key_1', + type: CustomFieldTypes.TEXT, + value: 'custom field value 1', + }, + { + key: 'custom_field_key_1', + type: CustomFieldTypes.TEXT, + value: 'custom field value 2', + }, + ], + }, + }, + ], + }, + clientArgs, + casesClientInternal + ) + ).rejects.toThrow( + `Failed to create case configuration: Error: Invalid duplicated templates[0]'s customFields keys in request: custom_field_key_1` + ); + }); + + it('throws when there are invalid customField keys in the request', async () => { + await expect( + create( + { + ...baseRequest, + customFields: [ + { + key: 'custom_field_key_1', + type: CustomFieldTypes.TEXT, + label: 'custom field 1', + required: true, + }, + ], + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'this is test description', + caseFields: { + customFields: [ + { + key: 'custom_field_key_2', + type: CustomFieldTypes.TEXT, + value: 'custom field value 1', + }, + ], + }, + }, + ], + }, + clientArgs, + casesClientInternal + ) + ).rejects.toThrow( + 'Failed to create case configuration: Error: Invalid custom field keys: custom_field_key_2' + ); + }); + + it('throws when template has customField with invalid type in the request', async () => { + await expect( + create( + { + ...baseRequest, + customFields: [ + { + key: 'custom_field_key_1', + type: CustomFieldTypes.TEXT, + label: 'custom field 1', + required: true, + }, + ], + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'this is test description', + caseFields: { + customFields: [ + { + key: 'custom_field_key_1', + type: CustomFieldTypes.TOGGLE, + value: true, + }, + ], + }, + }, + ], + }, + clientArgs, + casesClientInternal + ) + ).rejects.toThrow( + 'Failed to create case configuration: Error: The following custom fields have the wrong type in the request: "custom field 1"' + ); + }); + }); + + describe('assignees', () => { + it('throws if the user does not have the correct license while adding assignees in template ', async () => { + clientArgs.services.licensingService.isAtLeastPlatinum.mockResolvedValue(false); + + await expect( + create( + { + ...baseRequest, + templates: [ + { + key: 'template_1', + name: 'template 1', + description: 'this is test description', + tags: [], + caseFields: { + assignees: [{ uid: '1' }], + }, + }, + ], + }, + clientArgs, + casesClientInternal + ) + ).rejects.toThrow( + 'Failed to create case configuration: Error: In order to assign users to cases, you must be subscribed to an Elastic Platinum license' + ); + }); + }); + }); }); }); diff --git a/x-pack/plugins/cases/server/client/configure/client.ts b/x-pack/plugins/cases/server/client/configure/client.ts index 2fc0cc3e72590..68db617af8bc2 100644 --- a/x-pack/plugins/cases/server/client/configure/client.ts +++ b/x-pack/plugins/cases/server/client/configure/client.ts @@ -18,6 +18,8 @@ import type { ConfigurationAttributes, Configurations, ConnectorMappings, + CustomFieldsConfiguration, + TemplatesConfiguration, } from '../../../common/types/domain'; import type { ConfigurationPatchRequest, @@ -42,13 +44,17 @@ import type { CasesClientArgs } from '../types'; import { getMappings } from './get_mappings'; import { Operations } from '../../authorization'; -import { combineAuthorizedAndOwnerFilter } from '../utils'; +import { combineAuthorizedAndOwnerFilter, removeCustomFieldFromTemplates } from '../utils'; import type { MappingsArgs, CreateMappingsArgs, UpdateMappingsArgs } from './types'; import { createMappings } from './create_mappings'; import { updateMappings } from './update_mappings'; import { ConfigurationRt, ConfigurationsRt } from '../../../common/types/domain'; -import { validateDuplicatedCustomFieldKeysInRequest } from '../validators'; -import { validateCustomFieldTypesInRequest } from './validators'; +import { validateDuplicatedKeysInRequest } from '../validators'; +import { + validateCustomFieldTypesInRequest, + validateTemplatesCustomFieldsInRequest, +} from './validators'; +import { LICENSING_CASE_ASSIGNMENT_FEATURE } from '../../common/constants'; /** * Defines the internal helper functions. @@ -91,6 +97,52 @@ export interface ConfigureSubClient { create(configuration: ConfigurationRequest): Promise<Configuration>; } +/** + * validate templates in configuration + */ +const validateTemplates = async ({ + templates, + clientArgs, + customFields, +}: { + templates: TemplatesConfiguration | undefined; + clientArgs: CasesClientArgs; + customFields: CustomFieldsConfiguration | undefined; +}) => { + const { licensingService } = clientArgs.services; + + validateDuplicatedKeysInRequest({ + requestFields: templates, + fieldName: 'templates', + }); + + if (templates && templates.length) { + /** + * Assign users to a template is only available to Platinum+ + */ + const hasAssigneesInTemplate = templates.some((template) => + Boolean(template.caseFields?.assignees && template.caseFields?.assignees.length > 0) + ); + + const hasPlatinumLicenseOrGreater = await licensingService.isAtLeastPlatinum(); + + if (hasAssigneesInTemplate && !hasPlatinumLicenseOrGreater) { + throw Boom.forbidden( + 'In order to assign users to cases, you must be subscribed to an Elastic Platinum license' + ); + } + + if (hasAssigneesInTemplate) { + licensingService.notifyUsage(LICENSING_CASE_ASSIGNMENT_FEATURE); + } + + validateTemplatesCustomFieldsInRequest({ + templates, + customFieldsConfiguration: customFields, + }); + } +}; + /** * These functions should not be exposed on the plugin contract. They are for internal use to support the CRUD of * configurations. @@ -251,9 +303,12 @@ export async function update( try { const request = decodeWithExcessOrThrow(ConfigurationPatchRequestRt)(req); - validateDuplicatedCustomFieldKeysInRequest({ requestCustomFields: request.customFields }); + validateDuplicatedKeysInRequest({ + requestFields: request.customFields, + fieldName: 'customFields', + }); - const { version, ...queryWithoutVersion } = request; + const { version, templates, ...queryWithoutVersion } = request; const configuration = await caseConfigureService.get({ unsecuredSavedObjectsClient, @@ -265,6 +320,17 @@ export async function update( originalCustomFields: configuration.attributes.customFields, }); + await validateTemplates({ + templates, + clientArgs, + customFields: configuration.attributes.customFields, + }); + + const updatedTemplates = removeCustomFieldFromTemplates({ + templates, + customFields: request.customFields, + }); + await authorization.ensureAuthorized({ operation: Operations.updateConfiguration, entities: [{ owner: configuration.attributes.owner, id: configuration.id }], @@ -320,6 +386,7 @@ export async function update( configurationId: configuration.id, updatedAttributes: { ...queryWithoutVersionAndConnector, + ...(updatedTemplates && { templates: updatedTemplates }), ...(connector != null && { connector }), updated_at: updateDate, updated_by: user, @@ -364,8 +431,15 @@ export async function create( const validatedConfigurationRequest = decodeWithExcessOrThrow(ConfigurationRequestRt)(configRequest); - validateDuplicatedCustomFieldKeysInRequest({ - requestCustomFields: validatedConfigurationRequest.customFields, + validateDuplicatedKeysInRequest({ + requestFields: validatedConfigurationRequest.customFields, + fieldName: 'customFields', + }); + + await validateTemplates({ + templates: validatedConfigurationRequest.templates, + clientArgs, + customFields: validatedConfigurationRequest.customFields, }); let error = null; @@ -441,6 +515,7 @@ export async function create( attributes: { ...validatedConfigurationRequest, customFields: validatedConfigurationRequest.customFields ?? [], + templates: validatedConfigurationRequest.templates ?? [], connector: validatedConfigurationRequest.connector, created_at: creationDate, created_by: user, diff --git a/x-pack/plugins/cases/server/client/configure/validators.test.ts b/x-pack/plugins/cases/server/client/configure/validators.test.ts index 0f8e20505fb39..ca81926519d37 100644 --- a/x-pack/plugins/cases/server/client/configure/validators.test.ts +++ b/x-pack/plugins/cases/server/client/configure/validators.test.ts @@ -6,10 +6,16 @@ */ import { CustomFieldTypes } from '../../../common/types/domain'; -import { validateCustomFieldTypesInRequest } from './validators'; +import { + validateCustomFieldTypesInRequest, + validateTemplatesCustomFieldsInRequest, +} from './validators'; describe('validators', () => { describe('validateCustomFieldTypesInRequest', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); it('throws an error with the keys of customFields in request that have invalid types', () => { expect(() => validateCustomFieldTypesInRequest({ @@ -69,4 +75,303 @@ describe('validators', () => { ).not.toThrow(); }); }); + + describe('validateTemplatesCustomFieldsInRequest', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('does not throw if all custom fields types in request match the configuration', () => { + expect(() => + validateTemplatesCustomFieldsInRequest({ + templates: [ + { + key: 'template_key_1', + name: 'first template', + description: 'this is a first template value', + caseFields: { + customFields: [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + value: 'this is a text field value', + }, + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + value: null, + }, + ], + }, + }, + { + key: 'template_key_2', + name: 'second template', + description: 'this is a second template value', + caseFields: { + title: 'Case title with template 2', + customFields: [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + value: 'this is a text field value', + }, + ], + }, + }, + ], + customFieldsConfiguration: [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + label: 'foo', + required: false, + }, + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + label: 'foo', + required: false, + }, + ], + }) + ).not.toThrow(); + }); + + it('does not throw if no custom fields are in request', () => { + expect(() => + validateTemplatesCustomFieldsInRequest({ + customFieldsConfiguration: undefined, + templates: [ + { + key: 'template_key_1', + name: 'first template', + description: 'this is a first template value', + caseFields: { + tags: ['first-template'], + }, + }, + { + key: 'template_key_2', + name: 'second template', + description: 'this is a second template value', + caseFields: null, + }, + ], + }) + ).not.toThrow(); + }); + + it('does not throw if no configuration found but no templates are in request', () => { + expect(() => + validateTemplatesCustomFieldsInRequest({ + customFieldsConfiguration: undefined, + templates: [], + }) + ).not.toThrow(); + }); + + it('does not throw if the configuration is undefined but no custom fields are in request', () => { + expect(() => validateTemplatesCustomFieldsInRequest({})).not.toThrow(); + }); + + it('throws if configuration is missing and template has custom fields', () => { + expect(() => + validateTemplatesCustomFieldsInRequest({ + templates: [ + { + key: 'template_key_1', + name: 'first template', + description: 'this is a first template value', + caseFields: { + customFields: [ + { + key: 'first_key', + type: CustomFieldTypes.TOGGLE, + value: null, + }, + ], + }, + }, + ], + }) + ).toThrowErrorMatchingInlineSnapshot(`"No custom fields configured."`); + }); + + it('throws for a single invalid type', () => { + expect(() => + validateTemplatesCustomFieldsInRequest({ + templates: [ + { + key: 'template_key_1', + name: 'first template', + description: 'this is a first template value', + caseFields: { + customFields: [ + { + key: 'first_key', + type: CustomFieldTypes.TOGGLE, + value: null, + }, + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + value: true, + }, + ], + }, + }, + ], + customFieldsConfiguration: [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + label: 'first label', + required: false, + }, + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + label: 'foo', + required: false, + }, + ], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"The following custom fields have the wrong type in the request: \\"first label\\""` + ); + }); + + it('throws for multiple custom fields with invalid types', () => { + expect(() => + validateTemplatesCustomFieldsInRequest({ + templates: [ + { + key: 'template_key_1', + name: 'first template', + description: 'this is a first template value', + caseFields: { + customFields: [ + { + key: 'first_key', + type: CustomFieldTypes.TOGGLE, + value: null, + }, + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + value: true, + }, + { + key: 'third_key', + type: CustomFieldTypes.TEXT, + value: 'abc', + }, + ], + }, + }, + ], + + customFieldsConfiguration: [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + label: 'first label', + required: false, + }, + { + key: 'second_key', + type: CustomFieldTypes.TEXT, + label: 'second label', + required: false, + }, + { + key: 'third_key', + type: CustomFieldTypes.TOGGLE, + label: 'third label', + required: false, + }, + ], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"The following custom fields have the wrong type in the request: \\"first label\\", \\"second label\\", \\"third label\\""` + ); + }); + + it('throws if there are invalid custom field keys', () => { + expect(() => + validateTemplatesCustomFieldsInRequest({ + templates: [ + { + key: 'template_key_1', + name: 'first template', + description: 'this is a first template value', + caseFields: { + customFields: [ + { + key: 'invalid_key', + type: CustomFieldTypes.TOGGLE, + value: null, + }, + ], + }, + }, + ], + customFieldsConfiguration: [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + label: 'foo', + required: false, + }, + ], + }) + ).toThrowErrorMatchingInlineSnapshot(`"Invalid custom field keys: invalid_key"`); + }); + + it('throws if template has duplicated custom field keys', () => { + expect(() => + validateTemplatesCustomFieldsInRequest({ + templates: [ + { + key: 'template_key_1', + name: 'first template', + description: 'this is a first template value', + caseFields: { + customFields: [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + value: 'this is a text field value', + }, + { + key: 'first_key', + type: CustomFieldTypes.TOGGLE, + value: null, + }, + ], + }, + }, + ], + + customFieldsConfiguration: [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + label: 'foo', + required: false, + }, + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + label: 'foo', + required: false, + }, + ], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid duplicated templates[0]'s customFields keys in request: first_key"` + ); + }); + }); }); diff --git a/x-pack/plugins/cases/server/client/configure/validators.ts b/x-pack/plugins/cases/server/client/configure/validators.ts index c5929065c631b..1dec647561ab8 100644 --- a/x-pack/plugins/cases/server/client/configure/validators.ts +++ b/x-pack/plugins/cases/server/client/configure/validators.ts @@ -6,7 +6,16 @@ */ import Boom from '@hapi/boom'; -import type { CustomFieldTypes } from '../../../common/types/domain'; +import type { + CustomFieldsConfiguration, + CustomFieldTypes, + TemplatesConfiguration, +} from '../../../common/types/domain'; +import { validateDuplicatedKeysInRequest } from '../validators'; +import { + validateCustomFieldKeysAgainstConfiguration, + validateCustomFieldTypesInRequest as validateCaseCustomFieldTypesInRequest, +} from '../cases/validators'; /** * Throws an error if the request tries to change the type of existing custom fields. @@ -38,3 +47,41 @@ export const validateCustomFieldTypesInRequest = ({ ); } }; + +export const validateTemplatesCustomFieldsInRequest = ({ + templates, + customFieldsConfiguration, +}: { + templates?: TemplatesConfiguration; + customFieldsConfiguration?: CustomFieldsConfiguration; +}) => { + if (!Array.isArray(templates) || !templates.length) { + return; + } + + templates.forEach((template, index) => { + if ( + !template.caseFields || + !template.caseFields.customFields || + !template.caseFields.customFields.length + ) { + return; + } + + if (customFieldsConfiguration === undefined) { + throw Boom.badRequest('No custom fields configured.'); + } + + const params = { + requestCustomFields: template.caseFields.customFields, + customFieldsConfiguration, + }; + + validateDuplicatedKeysInRequest({ + requestFields: params.requestCustomFields, + fieldName: `templates[${index}]'s customFields`, + }); + validateCustomFieldKeysAgainstConfiguration(params); + validateCaseCustomFieldTypesInRequest(params); + }); +}; diff --git a/x-pack/plugins/cases/server/client/utils.test.ts b/x-pack/plugins/cases/server/client/utils.test.ts index 8f9e8648a1269..56615189d1d5e 100644 --- a/x-pack/plugins/cases/server/client/utils.test.ts +++ b/x-pack/plugins/cases/server/client/utils.test.ts @@ -19,6 +19,7 @@ import { constructQueryOptions, constructSearch, convertSortField, + removeCustomFieldFromTemplates, } from './utils'; import { CasePersistedSeverity, CasePersistedStatus } from '../common/types/case'; import type { CustomFieldsConfiguration } from '../../common/types/domain'; @@ -1130,4 +1131,289 @@ describe('utils', () => { ); }); }); + + describe('removeCustomFieldFromTemplates', () => { + const customFields = [ + { + type: CustomFieldTypes.TEXT as const, + key: 'test_key_1', + label: 'My test label 1', + required: true, + defaultValue: 'My default value', + }, + { + type: CustomFieldTypes.TOGGLE as const, + key: 'test_key_2', + label: 'My test label 2', + required: true, + defaultValue: true, + }, + { + type: CustomFieldTypes.TEXT as const, + key: 'test_key_3', + label: 'My test label 3', + required: false, + }, + ]; + + const templates = [ + { + key: 'test_template_1', + name: 'First test template', + description: 'This is a first test template', + caseFields: { + customFields: [ + { + type: CustomFieldTypes.TEXT as const, + key: 'test_key_1', + value: 'My default value', + }, + { + type: CustomFieldTypes.TOGGLE as const, + key: 'test_key_2', + value: false, + }, + { + type: CustomFieldTypes.TEXT as const, + key: 'test_key_3', + value: 'Test custom field', + }, + ], + }, + }, + { + key: 'test_template_2', + name: 'Second test template', + description: 'This is a second test template', + tags: [], + caseFields: { + customFields: [ + { + type: CustomFieldTypes.TEXT as const, + key: 'test_key_1', + value: 'My value', + }, + { + type: CustomFieldTypes.TOGGLE as const, + key: 'test_key_2', + value: true, + }, + ], + }, + }, + ]; + + it('removes custom field from template correctly', () => { + const res = removeCustomFieldFromTemplates({ + templates, + customFields: [customFields[0], customFields[1]], + }); + + expect(res).toEqual([ + { + caseFields: { + customFields: [ + { + key: 'test_key_1', + type: 'text', + value: 'My default value', + }, + { + key: 'test_key_2', + type: 'toggle', + value: false, + }, + ], + }, + description: 'This is a first test template', + key: 'test_template_1', + name: 'First test template', + }, + { + description: 'This is a second test template', + key: 'test_template_2', + name: 'Second test template', + tags: [], + caseFields: { + customFields: [ + { + key: 'test_key_1', + type: 'text', + value: 'My value', + }, + { + key: 'test_key_2', + type: 'toggle', + value: true, + }, + ], + }, + }, + ]); + }); + + it('removes multiple custom fields from template correctly', () => { + const res = removeCustomFieldFromTemplates({ + templates, + customFields: [customFields[0]], + }); + + expect(res).toEqual([ + { + caseFields: { + customFields: [ + { + key: 'test_key_1', + type: 'text', + value: 'My default value', + }, + ], + }, + description: 'This is a first test template', + key: 'test_template_1', + name: 'First test template', + }, + { + description: 'This is a second test template', + key: 'test_template_2', + name: 'Second test template', + tags: [], + caseFields: { + customFields: [ + { + key: 'test_key_1', + type: 'text', + value: 'My value', + }, + ], + }, + }, + ]); + }); + + it('removes all custom fields from templates when custom fields are empty', () => { + const res = removeCustomFieldFromTemplates({ + templates, + customFields: [], + }); + + expect(res).toEqual([ + { + caseFields: { + customFields: [], + }, + description: 'This is a first test template', + key: 'test_template_1', + name: 'First test template', + }, + { + description: 'This is a second test template', + key: 'test_template_2', + name: 'Second test template', + tags: [], + caseFields: { + customFields: [], + }, + }, + ]); + }); + + it('removes all custom fields from templates when custom fields are undefined', () => { + const res = removeCustomFieldFromTemplates({ + templates, + customFields: undefined, + }); + + expect(res).toEqual([ + { ...templates[0], caseFields: { customFields: [] } }, + { ...templates[1], caseFields: { ...templates[1].caseFields, customFields: [] } }, + ]); + }); + + it('does not remove custom field when templates do not have custom fields', () => { + const res = removeCustomFieldFromTemplates({ + templates: [ + { + key: 'test_template_1', + name: 'First test template', + description: 'This is a first test template', + caseFields: null, + }, + { + key: 'test_template_2', + name: 'Second test template', + caseFields: { + title: 'Test title', + description: 'this is test', + }, + }, + ], + customFields: [customFields[0], customFields[1]], + }); + + expect(res).toEqual([ + { + caseFields: null, + description: 'This is a first test template', + key: 'test_template_1', + name: 'First test template', + }, + { + key: 'test_template_2', + name: 'Second test template', + caseFields: { + description: 'this is test', + title: 'Test title', + }, + }, + ]); + }); + + it('does not remove custom field when templates have empty custom fields', () => { + const res = removeCustomFieldFromTemplates({ + templates: [ + { + key: 'test_template_2', + name: 'Second test template', + caseFields: { + title: 'Test title', + description: 'this is test', + customFields: [], + }, + }, + ], + customFields: [customFields[0], customFields[1]], + }); + + expect(res).toEqual([ + { + key: 'test_template_2', + name: 'Second test template', + caseFields: { + title: 'Test title', + description: 'this is test', + customFields: [], + }, + }, + ]); + }); + + it('does not remove custom field from empty templates', () => { + const res = removeCustomFieldFromTemplates({ + templates: [], + customFields: [customFields[0], customFields[1]], + }); + + expect(res).toEqual([]); + }); + + it('returns empty array when templates are undefined', () => { + const res = removeCustomFieldFromTemplates({ + templates: undefined, + customFields: [customFields[0], customFields[1]], + }); + + expect(res).toEqual([]); + }); + }); }); diff --git a/x-pack/plugins/cases/server/client/utils.ts b/x-pack/plugins/cases/server/client/utils.ts index 0ce4da8bcc21b..258761a563fd3 100644 --- a/x-pack/plugins/cases/server/client/utils.ts +++ b/x-pack/plugins/cases/server/client/utils.ts @@ -21,6 +21,7 @@ import type { CaseStatuses, CustomFieldsConfiguration, ExternalReferenceAttachmentPayload, + TemplatesConfiguration, } from '../../common/types/domain'; import { ActionsAttachmentPayloadRt, @@ -604,3 +605,37 @@ export const constructSearch = ( return { search }; }; + +/** + * remove deleted custom field from template + */ +export const removeCustomFieldFromTemplates = ({ + templates, + customFields, +}: { + templates?: TemplatesConfiguration; + customFields?: CustomFieldsConfiguration; +}): TemplatesConfiguration => { + if (!templates || !templates.length) { + return []; + } + + return templates.map((template) => { + if (!template.caseFields?.customFields || !template.caseFields?.customFields.length) { + return template; + } + + if (!customFields || !customFields?.length) { + return { ...template, caseFields: { ...template.caseFields, customFields: [] } }; + } + + const templateCustomFields = template.caseFields.customFields.filter((templateCustomField) => + customFields?.find((customField) => customField.key === templateCustomField.key) + ); + + return { + ...template, + caseFields: { ...template.caseFields, customFields: templateCustomFields }, + }; + }); +}; diff --git a/x-pack/plugins/cases/server/client/validators.test.ts b/x-pack/plugins/cases/server/client/validators.test.ts index 8d6caa218f932..77867aedbcb4a 100644 --- a/x-pack/plugins/cases/server/client/validators.test.ts +++ b/x-pack/plugins/cases/server/client/validators.test.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { validateDuplicatedCustomFieldKeysInRequest } from './validators'; +import { validateDuplicatedKeysInRequest } from './validators'; describe('validators', () => { - describe('validateDuplicatedCustomFieldKeysInRequest', () => { - it('returns customFields in request that have duplicated keys', () => { + describe('validateDuplicatedKeysInRequest', () => { + it('returns fields in request that have duplicated keys', () => { expect(() => - validateDuplicatedCustomFieldKeysInRequest({ - requestCustomFields: [ + validateDuplicatedKeysInRequest({ + requestFields: [ { key: 'triplicated_key', }, @@ -29,16 +29,18 @@ describe('validators', () => { key: 'duplicated_key', }, ], + + fieldName: 'foobar', }) ).toThrowErrorMatchingInlineSnapshot( - `"Invalid duplicated custom field keys in request: triplicated_key,duplicated_key"` + `"Invalid duplicated foobar keys in request: triplicated_key,duplicated_key"` ); }); - it('does not throw if no customFields in request have duplicated keys', () => { + it('does not throw if no fields in request have duplicated keys', () => { expect(() => - validateDuplicatedCustomFieldKeysInRequest({ - requestCustomFields: [ + validateDuplicatedKeysInRequest({ + requestFields: [ { key: '1', }, @@ -46,6 +48,7 @@ describe('validators', () => { key: '2', }, ], + fieldName: 'foobar', }) ).not.toThrow(); }); diff --git a/x-pack/plugins/cases/server/client/validators.ts b/x-pack/plugins/cases/server/client/validators.ts index 88b62640cee88..24527ac81155b 100644 --- a/x-pack/plugins/cases/server/client/validators.ts +++ b/x-pack/plugins/cases/server/client/validators.ts @@ -10,15 +10,17 @@ import Boom from '@hapi/boom'; /** * Throws an error if the request has custom fields with duplicated keys. */ -export const validateDuplicatedCustomFieldKeysInRequest = ({ - requestCustomFields = [], +export const validateDuplicatedKeysInRequest = ({ + requestFields = [], + fieldName, }: { - requestCustomFields?: Array<{ key: string }>; + requestFields?: Array<{ key: string }>; + fieldName: string; }) => { const uniqueKeys = new Set<string>(); const duplicatedKeys = new Set<string>(); - requestCustomFields.forEach((item) => { + requestFields.forEach((item) => { if (uniqueKeys.has(item.key)) { duplicatedKeys.add(item.key); } else { @@ -28,7 +30,7 @@ export const validateDuplicatedCustomFieldKeysInRequest = ({ if (duplicatedKeys.size > 0) { throw Boom.badRequest( - `Invalid duplicated custom field keys in request: ${Array.from(duplicatedKeys.values())}` + `Invalid duplicated ${fieldName} keys in request: ${Array.from(duplicatedKeys.values())}` ); } }; diff --git a/x-pack/plugins/cases/server/common/types/configure.ts b/x-pack/plugins/cases/server/common/types/configure.ts index 94dcaf0a9ce19..faf2517fbe173 100644 --- a/x-pack/plugins/cases/server/common/types/configure.ts +++ b/x-pack/plugins/cases/server/common/types/configure.ts @@ -8,14 +8,19 @@ import * as rt from 'io-ts'; import type { SavedObject } from '@kbn/core/server'; -import type { ConfigurationAttributes } from '../../../common/types/domain'; +import type { + CaseConnector, + CaseCustomFields, + CaseSeverity, + ConfigurationAttributes, +} from '../../../common/types/domain'; import { ConfigurationActivityFieldsRt, ConfigurationAttributesRt, ConfigurationBasicWithoutOwnerRt, } from '../../../common/types/domain'; import type { ConnectorPersisted } from './connectors'; -import type { User } from './user'; +import type { User, UserProfile } from './user'; export interface ConfigurationPersistedAttributes { connector: ConnectorPersisted; @@ -26,6 +31,7 @@ export interface ConfigurationPersistedAttributes { updated_at: string | null; updated_by: User | null; customFields?: PersistedCustomFieldsConfiguration; + templates?: PersistedTemplatesConfiguration; } type PersistedCustomFieldsConfiguration = Array<{ @@ -36,6 +42,26 @@ type PersistedCustomFieldsConfiguration = Array<{ defaultValue?: string | boolean | null; }>; +type PersistedTemplatesConfiguration = Array<{ + key: string; + name: string; + description?: string; + tags?: string[]; + caseFields?: CaseFieldsAttributes | null; +}>; + +export interface CaseFieldsAttributes { + title?: string; + assignees?: UserProfile[]; + connector?: CaseConnector; + description?: string; + severity?: CaseSeverity; + tags?: string[]; + category?: string | null; + customFields?: CaseCustomFields; + settings?: { syncAlerts: boolean }; +} + export type ConfigurationTransformedAttributes = ConfigurationAttributes; export type ConfigurationSavedObjectTransformed = SavedObject<ConfigurationTransformedAttributes>; diff --git a/x-pack/plugins/cases/server/services/configure/index.test.ts b/x-pack/plugins/cases/server/services/configure/index.test.ts index d1a79cc1a8d6e..627263de50849 100644 --- a/x-pack/plugins/cases/server/services/configure/index.test.ts +++ b/x-pack/plugins/cases/server/services/configure/index.test.ts @@ -5,8 +5,12 @@ * 2.0. */ -import type { CaseConnector, ConfigurationAttributes } from '../../../common/types/domain'; -import { CustomFieldTypes, ConnectorTypes } from '../../../common/types/domain'; +import type { + CaseConnector, + CaseCustomFields, + ConfigurationAttributes, +} from '../../../common/types/domain'; +import { CustomFieldTypes, ConnectorTypes, CaseSeverity } from '../../../common/types/domain'; import { CASE_CONFIGURE_SAVED_OBJECT, SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import type { @@ -59,6 +63,40 @@ const basicConfigFields = { defaultValue: 'foobar', }, ], + templates: [ + { + key: 'test_template_1', + name: 'First test template', + description: 'This is a first test template', + caseFields: null, + }, + { + key: 'test_template_4', + name: 'Fourth test template', + description: 'This is a fourth test template', + caseFields: { + title: 'Case with sample template 4', + description: 'case desc', + severity: CaseSeverity.LOW, + category: null, + tags: ['sample-4'], + assignees: [{ uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0' }], + customFields: [ + { + key: 'first_custom_field_key', + type: CustomFieldTypes.TEXT, + value: 'this is a text field value', + }, + ] as CaseCustomFields, + connector: { + id: 'none', + name: 'My Connector', + type: ConnectorTypes.none, + fields: null, + }, + }, + }, + ], }; const createConfigUpdateParams = (connector?: CaseConnector): Partial<ConfigurationAttributes> => ({ @@ -204,6 +242,46 @@ describe('CaseConfigureService', () => { }, ], "owner": "securitySolution", + "templates": Array [ + Object { + "caseFields": null, + "description": "This is a first test template", + "key": "test_template_1", + "name": "First test template", + }, + Object { + "caseFields": Object { + "assignees": Array [ + Object { + "uid": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0", + }, + ], + "category": null, + "connector": Object { + "fields": null, + "id": "none", + "name": "My Connector", + "type": ".none", + }, + "customFields": Array [ + Object { + "key": "first_custom_field_key", + "type": "text", + "value": "this is a text field value", + }, + ], + "description": "case desc", + "severity": "low", + "tags": Array [ + "sample-4", + ], + "title": "Case with sample template 4", + }, + "description": "This is a fourth test template", + "key": "test_template_4", + "name": "Fourth test template", + }, + ], "updated_at": "2020-04-09T09:43:51.778Z", "updated_by": Object { "email": "testemail@elastic.co", @@ -490,6 +568,46 @@ describe('CaseConfigureService', () => { }, ], "owner": "securitySolution", + "templates": Array [ + Object { + "caseFields": null, + "description": "This is a first test template", + "key": "test_template_1", + "name": "First test template", + }, + Object { + "caseFields": Object { + "assignees": Array [ + Object { + "uid": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0", + }, + ], + "category": null, + "connector": Object { + "fields": null, + "id": "none", + "name": "My Connector", + "type": ".none", + }, + "customFields": Array [ + Object { + "key": "first_custom_field_key", + "type": "text", + "value": "this is a text field value", + }, + ], + "description": "case desc", + "severity": "low", + "tags": Array [ + "sample-4", + ], + "title": "Case with sample template 4", + }, + "description": "This is a fourth test template", + "key": "test_template_4", + "name": "Fourth test template", + }, + ], "updated_at": "2020-04-09T09:43:51.778Z", "updated_by": Object { "email": "testemail@elastic.co", diff --git a/x-pack/plugins/cases/server/services/configure/index.ts b/x-pack/plugins/cases/server/services/configure/index.ts index 6c367d9a96848..f50ac271bc4ff 100644 --- a/x-pack/plugins/cases/server/services/configure/index.ts +++ b/x-pack/plugins/cases/server/services/configure/index.ts @@ -228,12 +228,17 @@ function transformToExternalModel( ? [] : (configuration.attributes.customFields as ConfigurationTransformedAttributes['customFields']); + const templates = !configuration.attributes.templates + ? [] + : (configuration.attributes.templates as ConfigurationTransformedAttributes['templates']); + return { ...configuration, attributes: { ...castedAttributes, connector, customFields, + templates, }, }; } diff --git a/x-pack/test/cases_api_integration/common/lib/api/configuration.ts b/x-pack/test/cases_api_integration/common/lib/api/configuration.ts index c74c6be78da82..99479082f6559 100644 --- a/x-pack/test/cases_api_integration/common/lib/api/configuration.ts +++ b/x-pack/test/cases_api_integration/common/lib/api/configuration.ts @@ -43,6 +43,7 @@ export const getConfigurationRequest = ({ closure_type: 'close-by-user', owner: 'securitySolutionFixture', customFields: [], + templates: [], ...overrides, }; }; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/get_configure.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/get_configure.ts index 4a9e99016c801..5ccf9015839c7 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/get_configure.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/get_configure.ts @@ -5,7 +5,11 @@ * 2.0. */ -import { CustomFieldTypes } from '@kbn/cases-plugin/common/types/domain'; +import { + CaseSeverity, + ConnectorTypes, + CustomFieldTypes, +} from '@kbn/cases-plugin/common/types/domain'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; @@ -80,6 +84,61 @@ export default ({ getService }: FtrProviderContext): void => { expect(data).to.eql(getConfigurationOutput(false, customFields)); }); + it('should return a configuration with templates', async () => { + const templates = { + templates: [ + { + key: 'test_template_1', + name: 'First test template', + description: 'This is a first test template', + tags: [], + caseFields: null, + }, + { + key: 'test_template_2', + name: 'Second test template', + description: 'This is a second test template', + tags: ['foobar'], + caseFields: { + title: 'Case with sample template 2', + description: 'case desc', + severity: CaseSeverity.LOW, + category: null, + tags: ['sample-4'], + assignees: [], + customFields: [], + connector: { + id: 'none', + name: 'My Connector', + type: ConnectorTypes.none, + fields: null, + }, + }, + }, + { + key: 'test_template_3', + name: 'Third test template', + description: 'This is a third test template', + caseFields: { + title: 'Case with sample template 3', + tags: ['sample-3'], + }, + }, + ], + }; + + await createConfiguration( + supertest, + getConfigurationRequest({ + overrides: templates, + }) + ); + const configuration = await getConfiguration({ supertest }); + + const data = removeServerGeneratedPropertiesFromSavedObject(configuration[0]); + expect(data).to.eql(getConfigurationOutput(false, templates)); + }); + it('should get a single configuration', async () => { await createConfiguration(supertest, getConfigurationRequest({ id: 'connector-2' })); await createConfiguration(supertest); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/patch_configure.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/patch_configure.ts index c8e0f092edf3a..114182a1ad20d 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/patch_configure.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/patch_configure.ts @@ -6,7 +6,11 @@ */ import expect from '@kbn/expect'; -import { ConnectorTypes, CustomFieldTypes } from '@kbn/cases-plugin/common/types/domain'; +import { + CaseSeverity, + ConnectorTypes, + CustomFieldTypes, +} from '@kbn/cases-plugin/common/types/domain'; import { ConfigurationPatchRequest } from '@kbn/cases-plugin/common/types/api'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { ObjectRemover as ActionsRemover } from '../../../../../alerting_api_integration/common/lib'; @@ -127,6 +131,163 @@ export default ({ getService }: FtrProviderContext): void => { ]); }); + it('should patch a configuration with templates', async () => { + const customFieldsConfiguration = [ + { + key: 'text_field_1', + type: CustomFieldTypes.TEXT, + label: 'Text field 1', + required: true, + }, + { + key: 'toggle_field_1', + label: '#2', + type: CustomFieldTypes.TOGGLE, + required: false, + }, + ]; + + const templates = [ + { + key: 'test_template_1', + name: 'First test template', + description: 'This is a first test template', + tags: ['foo', 'bar'], + caseFields: null, + }, + { + key: 'test_template_2', + name: 'Second test template', + description: 'This is a second test template', + caseFields: { + title: 'Case with sample template 2', + description: 'case desc', + severity: CaseSeverity.LOW, + category: null, + tags: ['sample-4'], + assignees: [], + customFields: [ + { + key: 'text_field_1', + type: CustomFieldTypes.TEXT, + value: 'this is a text field value', + }, + { + key: 'toggle_field_1', + value: true, + type: CustomFieldTypes.TOGGLE, + }, + ], + connector: { + id: 'none', + name: 'My Connector', + type: ConnectorTypes.none, + fields: null, + }, + }, + }, + { + key: 'test_template_3', + name: 'Third test template', + description: 'This is a third test template', + tags: [], + caseFields: { + title: 'Case with sample template 3', + tags: ['sample-3'], + }, + }, + ] as ConfigurationPatchRequest['templates']; + + const configuration = await createConfiguration(supertest, { + ...getConfigurationRequest(), + customFields: customFieldsConfiguration as ConfigurationPatchRequest['customFields'], + }); + const newConfiguration = await updateConfiguration(supertest, configuration.id, { + version: configuration.version, + customFields: customFieldsConfiguration, + templates, + }); + + const data = removeServerGeneratedPropertiesFromSavedObject(newConfiguration); + expect(data).to.eql({ + ...getConfigurationOutput(true), + customFields: customFieldsConfiguration as ConfigurationPatchRequest['customFields'], + templates, + }); + }); + + it('should remove custom fields from templates', async () => { + const customFieldsConfiguration = [ + { + key: 'text_field_1', + type: CustomFieldTypes.TEXT, + label: 'Text field 1', + required: true, + }, + { + key: 'toggle_field_1', + label: '#2', + type: CustomFieldTypes.TOGGLE, + required: false, + }, + ]; + + const templates = [ + { + key: 'test_template_2', + name: 'Second test template', + description: 'This is a second test template', + caseFields: { + title: 'Case with sample template 2', + description: 'case desc', + severity: CaseSeverity.LOW, + category: null, + tags: ['sample-4'], + assignees: [], + customFields: [ + { + key: 'text_field_1', + type: CustomFieldTypes.TEXT, + value: 'this is a text field value', + }, + { + key: 'toggle_field_1', + value: true, + type: CustomFieldTypes.TOGGLE, + }, + ], + connector: { + id: 'none', + name: 'My Connector', + type: ConnectorTypes.none, + fields: null, + }, + }, + }, + ]; + + const configuration = await createConfiguration(supertest, { + ...getConfigurationRequest(), + customFields: customFieldsConfiguration as ConfigurationPatchRequest['customFields'], + }); + + // delete custom fields + const newConfiguration = await updateConfiguration(supertest, configuration.id, { + version: configuration.version, + customFields: [], + templates: templates as ConfigurationPatchRequest['templates'], + }); + + const data = removeServerGeneratedPropertiesFromSavedObject(newConfiguration); + expect(data).to.eql({ + ...getConfigurationOutput(true), + customFields: [], + templates: [ + { ...templates[0], caseFields: { ...templates[0].caseFields, customFields: [] } }, + ], + }); + }); + describe('validation', () => { it('should not patch a configuration with unsupported connector type', async () => { const configuration = await createConfiguration(supertest); @@ -270,6 +431,64 @@ export default ({ getService }: FtrProviderContext): void => { 400 ); }); + + it("should not update a configuration with templates with custom fields that don't exist in the configuration", async () => { + const configuration = await createConfiguration(supertest); + + await updateConfiguration( + supertest, + configuration.id, + { + version: configuration.version, + templates: [ + { + key: 'test_template_1', + name: 'First test template', + description: 'This is a first test template', + caseFields: { + customFields: [ + { + key: 'random_key', + type: CustomFieldTypes.TEXT, + value: 'Test', + }, + ], + }, + }, + ], + }, + 400 + ); + }); + + it('should not patch a configuration with duplicated template keys', async () => { + const configuration = await createConfiguration(supertest); + await updateConfiguration( + supertest, + configuration.id, + { + version: configuration.version, + templates: [ + { + key: 'test_template_1', + name: 'First test template', + description: 'This is a first test template', + caseFields: null, + }, + { + key: 'test_template_1', + name: 'Third test template', + description: 'This is a third test template', + caseFields: { + title: 'Case with sample template 3', + tags: ['sample-3'], + }, + }, + ] as ConfigurationPatchRequest['templates'], + }, + 400 + ); + }); }); describe('rbac', () => { diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/post_configure.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/post_configure.ts index a7461d5f1fc18..8a81214f009d6 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/post_configure.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/post_configure.ts @@ -6,7 +6,11 @@ */ import expect from '@kbn/expect'; -import { ConnectorTypes, CustomFieldTypes } from '@kbn/cases-plugin/common/types/domain'; +import { + CaseSeverity, + ConnectorTypes, + CustomFieldTypes, +} from '@kbn/cases-plugin/common/types/domain'; import { MAX_CUSTOM_FIELD_LABEL_LENGTH } from '@kbn/cases-plugin/common/constants'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { ObjectRemover as ActionsRemover } from '../../../../../alerting_api_integration/common/lib'; @@ -98,6 +102,84 @@ export default ({ getService }: FtrProviderContext): void => { expect(data).to.eql(getConfigurationOutput(false, customFields)); }); + it('should create a configuration with templates', async () => { + const customFields = [ + { + key: 'text_field_1', + type: CustomFieldTypes.TEXT, + label: 'Text field 1', + required: true, + }, + { + key: 'toggle_field_1', + label: '#2', + type: CustomFieldTypes.TOGGLE, + required: false, + }, + ]; + + const templates = [ + { + key: 'test_template_1', + name: 'First test template', + description: 'This is a first test template', + caseFields: null, + }, + { + key: 'test_template_2', + name: 'Second test template', + description: 'This is a second test template', + tags: ['foo', 'bar'], + caseFields: { + title: 'Case with sample template 2', + description: 'case desc', + severity: CaseSeverity.LOW, + category: null, + tags: ['sample-4'], + assignees: [], + customFields: [ + { + key: 'text_field_1', + type: CustomFieldTypes.TEXT, + value: 'this is a text field value', + }, + { + key: 'toggle_field_1', + value: true, + type: CustomFieldTypes.TOGGLE, + }, + ], + connector: { + id: 'none', + name: 'My Connector', + type: ConnectorTypes.none, + fields: null, + }, + }, + }, + { + key: 'test_template_3', + name: 'Third test template', + description: 'This is a third test template', + tags: ['foobar'], + caseFields: { + title: 'Case with sample template 3', + tags: ['sample-3'], + }, + }, + ]; + + const configuration = await createConfiguration( + supertest, + getConfigurationRequest({ + overrides: { customFields, templates }, + }) + ); + + const data = removeServerGeneratedPropertiesFromSavedObject(configuration); + expect(data).to.eql({ ...getConfigurationOutput(false), customFields, templates }); + }); + it('should keep only the latest configuration', async () => { await createConfiguration(supertest, getConfigurationRequest({ id: 'connector-2' })); await createConfiguration(supertest); @@ -410,6 +492,61 @@ export default ({ getService }: FtrProviderContext): void => { 400 ); }); + + it("should not create a configuration with templates with custom fields that don't exist in the configuration", async () => { + await createConfiguration( + supertest, + getConfigurationRequest({ + overrides: { + templates: [ + { + key: 'test_template_1', + name: 'First test template', + description: 'This is a first test template', + caseFields: { + customFields: [ + { + key: 'random_key', + type: CustomFieldTypes.TEXT, + value: 'Test', + }, + ], + }, + }, + ], + }, + }), + 400 + ); + }); + + it('should not create a configuration with duplicated template keys', async () => { + await createConfiguration( + supertest, + getConfigurationRequest({ + overrides: { + templates: [ + { + key: 'test_template_1', + name: 'First test template', + description: 'This is a first test template', + caseFields: null, + }, + { + key: 'test_template_1', + name: 'Third test template', + description: 'This is a third test template', + caseFields: { + title: 'Case with sample template 3', + tags: ['sample-3'], + }, + }, + ], + }, + }), + 400 + ); + }); }); describe('rbac', () => { diff --git a/x-pack/test/functional/services/cases/api.ts b/x-pack/test/functional/services/cases/api.ts index 72a65bc98cb61..7a1d4f52108d1 100644 --- a/x-pack/test/functional/services/cases/api.ts +++ b/x-pack/test/functional/services/cases/api.ts @@ -161,5 +161,23 @@ export function CasesAPIServiceProvider({ getService }: FtrProviderContext) { }) ); }, + + async createConfigWithTemplates({ + templates, + owner, + }: { + templates: Configuration['templates']; + owner: string; + }) { + return createConfiguration( + kbnSupertest, + getConfigurationRequest({ + overrides: { + templates, + owner, + }, + }) + ); + }, }; } diff --git a/x-pack/test/functional/services/cases/create.ts b/x-pack/test/functional/services/cases/create.ts index fb018615dd194..3f7b6e1e65f94 100644 --- a/x-pack/test/functional/services/cases/create.ts +++ b/x-pack/test/functional/services/cases/create.ts @@ -58,6 +58,10 @@ export function CasesCreateViewServiceProvider( category, owner, }: CreateCaseParams) { + if (owner) { + await this.setSolution(owner); + } + await this.setTitle(title); await this.setDescription(description); await this.setTags(tag); @@ -70,10 +74,6 @@ export function CasesCreateViewServiceProvider( await this.setSeverity(severity); } - if (owner) { - await this.setSolution(owner); - } - await this.submitCase(); }, @@ -96,7 +96,8 @@ export function CasesCreateViewServiceProvider( }, async setSolution(owner: string) { - await testSubjects.click(`${owner}RadioButton`); + await testSubjects.click('caseOwnerSuperSelect'); + await testSubjects.click(`${owner}OwnerOption`); }, async setSeverity(severity: CaseSeverity) { diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/group1/create_case_form.ts b/x-pack/test/functional_with_es_ssl/apps/cases/group1/create_case_form.ts index fcb1e23d6f9bb..c9a16b6e45983 100644 --- a/x-pack/test/functional_with_es_ssl/apps/cases/group1/create_case_form.ts +++ b/x-pack/test/functional_with_es_ssl/apps/cases/group1/create_case_form.ts @@ -93,7 +93,7 @@ export default ({ getService, getPageObject }: FtrProviderContext) => { 'The length of the tag is too long. The maximum length is 256 characters.' ); - const category = await testSubjects.find('case-create-form-category'); + const category = await testSubjects.find('caseCategory'); expect(await category.getVisibleText()).contain( 'The length of the category is too long. The maximum length is 50 characters.' ); @@ -150,7 +150,7 @@ export default ({ getService, getPageObject }: FtrProviderContext) => { await cases.create.openCreateCasePage(); // verify custom fields on create case page - await testSubjects.existOrFail('create-case-custom-fields'); + await testSubjects.existOrFail('caseCustomFields'); await cases.create.setTitle(caseTitle); await cases.create.setDescription('this is a test description'); @@ -207,7 +207,7 @@ export default ({ getService, getPageObject }: FtrProviderContext) => { await cases.create.openCreateCasePage(); // verify custom fields on create case page - await testSubjects.existOrFail('create-case-custom-fields'); + await testSubjects.existOrFail('caseCustomFields'); await cases.create.setTitle(caseTitle); await cases.create.setDescription('this is a test description'); diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/group2/attachment_framework.ts b/x-pack/test/functional_with_es_ssl/apps/cases/group2/attachment_framework.ts index 8c4dd47532255..c714cdba25637 100644 --- a/x-pack/test/functional_with_es_ssl/apps/cases/group2/attachment_framework.ts +++ b/x-pack/test/functional_with_es_ssl/apps/cases/group2/attachment_framework.ts @@ -235,8 +235,10 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { it('renders solutions selection', async () => { await openFlyout(); + await testSubjects.click('caseOwnerSelector'); + for (const owner of TOTAL_OWNERS) { - await testSubjects.existOrFail(`${owner}RadioButton`); + await testSubjects.existOrFail(`${owner}OwnerOption`); } await closeFlyout(); diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/group2/configure.ts b/x-pack/test/functional_with_es_ssl/apps/cases/group2/configure.ts index 29eb8c991952a..ee013b882c487 100644 --- a/x-pack/test/functional_with_es_ssl/apps/cases/group2/configure.ts +++ b/x-pack/test/functional_with_es_ssl/apps/cases/group2/configure.ts @@ -15,7 +15,9 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const cases = getService('cases'); const toasts = getService('toasts'); const header = getPageObject('header'); + const comboBox = getService('comboBox'); const find = getService('find'); + const retry = getService('retry'); describe('Configure', function () { before(async () => { @@ -81,13 +83,13 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { it('adds a custom field', async () => { await testSubjects.existOrFail('custom-fields-form-group'); - await common.clickAndValidate('add-custom-field', 'custom-field-flyout'); + await common.clickAndValidate('add-custom-field', 'common-flyout'); await testSubjects.setValue('custom-field-label-input', 'Summary'); await testSubjects.setCheckbox('text-custom-field-required-wrapper', 'check'); - await testSubjects.click('custom-field-flyout-save'); + await testSubjects.click('common-flyout-save'); expect(await testSubjects.exists('euiFlyoutCloseButton')).to.be(false); await testSubjects.existOrFail('custom-fields-list'); @@ -105,7 +107,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { await input.type('!!!'); - await testSubjects.click('custom-field-flyout-save'); + await testSubjects.click('common-flyout-save'); expect(await testSubjects.exists('euiFlyoutCloseButton')).to.be(false); await testSubjects.existOrFail('custom-fields-list'); @@ -119,12 +121,111 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { await deleteButton.click(); - await testSubjects.existOrFail('confirm-delete-custom-field-modal'); + await testSubjects.existOrFail('confirm-delete-modal'); await testSubjects.click('confirmModalConfirmButton'); await testSubjects.missingOrFail('custom-fields-list'); }); }); + + describe('Templates', function () { + before(async () => { + await cases.api.createConfigWithTemplates({ + templates: [ + { + key: 'o11y_template', + name: 'My template 1', + description: 'this is my first template', + tags: ['foo'], + caseFields: null, + }, + ], + owner: 'observability', + }); + }); + + it('existing configurations do not interfere', async () => { + // A configuration created in o11y should not be visible in stack + expect(await testSubjects.getVisibleText('empty-templates')).to.be( + 'You do not have any templates yet' + ); + }); + + it('adds a template', async () => { + await testSubjects.existOrFail('templates-form-group'); + await common.clickAndValidate('add-template', 'common-flyout'); + + await testSubjects.setValue('template-name-input', 'Template name'); + await comboBox.setCustom('template-tags', 'tag-t1'); + await testSubjects.setValue('template-description-input', 'Template description'); + + const caseTitle = await find.byCssSelector( + `[data-test-subj="input"][aria-describedby="caseTitle"]` + ); + await caseTitle.focus(); + await caseTitle.type('case with template'); + + await cases.create.setDescription('test description'); + + await cases.create.setTags('tagme'); + await cases.create.setCategory('new'); + + await testSubjects.click('common-flyout-save'); + expect(await testSubjects.exists('euiFlyoutCloseButton')).to.be(false); + + await retry.waitFor('templates-list', async () => { + return await testSubjects.exists('templates-list'); + }); + + expect(await testSubjects.getVisibleText('templates-list')).to.be('Template name\ntag-t1'); + }); + + it('updates a template', async () => { + await testSubjects.existOrFail('templates-form-group'); + const editButton = await find.byCssSelector('[data-test-subj*="-template-edit"]'); + + await editButton.click(); + + await testSubjects.setValue('template-name-input', 'Updated template name!'); + await comboBox.setCustom('template-tags', 'tag-t1'); + await testSubjects.setValue('template-description-input', 'Template description updated'); + + const caseTitle = await find.byCssSelector( + `[data-test-subj="input"][aria-describedby="caseTitle"]` + ); + await caseTitle.focus(); + await caseTitle.type('!!'); + + await cases.create.setDescription('test description!!'); + + await cases.create.setTags('case-tag'); + await cases.create.setCategory('new!'); + + await testSubjects.click('common-flyout-save'); + expect(await testSubjects.exists('euiFlyoutCloseButton')).to.be(false); + + await retry.waitFor('templates-list', async () => { + return await testSubjects.exists('templates-list'); + }); + + expect(await testSubjects.getVisibleText('templates-list')).to.be( + 'Updated template name!\ntag-t1' + ); + }); + + it('deletes a template', async () => { + await testSubjects.existOrFail('templates-form-group'); + const deleteButton = await find.byCssSelector('[data-test-subj*="-template-delete"]'); + + await deleteButton.click(); + + await testSubjects.existOrFail('confirm-delete-modal'); + + await testSubjects.click('confirmModalConfirmButton'); + + await testSubjects.missingOrFail('template-list'); + }); + }); }); }; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/connectors.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/connectors.cy.ts index 6506b0985ee20..9c84a9067fbfe 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/connectors.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/connectors.cy.ts @@ -33,6 +33,7 @@ describe('Cases connectors', { tags: ['@ess', '@serverless'] }, () => { updated_at: null, updated_by: null, customFields: [], + templates: [], mappings: [ { source: 'title', target: 'summary', action_type: 'overwrite' }, { source: 'description', target: 'description', action_type: 'overwrite' }, diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/create_new_case.ts b/x-pack/test/security_solution_cypress/cypress/tasks/create_new_case.ts index aa154cd15b036..6f99331f91813 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/create_new_case.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/create_new_case.ts @@ -65,6 +65,7 @@ export const attachTimeline = (newCase: TestCase) => { cy.get('body').type('{esc}'); cy.get(INSERT_TIMELINE_BTN).click(); cy.get(LOADING_INDICATOR).should('not.exist'); + cy.get('[data-test-subj="selectable-input"]').click(); cy.get(TIMELINE_SEARCHBOX).should('exist'); cy.get(TIMELINE_SEARCHBOX).should('be.visible'); diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts index 44cce6cfb520d..d7d0f30da7502 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts @@ -20,6 +20,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const toasts = getService('toasts'); const retry = getService('retry'); const find = getService('find'); + const comboBox = getService('comboBox'); describe('Configure Case', function () { before(async () => { @@ -75,13 +76,13 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { describe('Custom fields', function () { it('adds a custom field', async () => { await testSubjects.existOrFail('custom-fields-form-group'); - await common.clickAndValidate('add-custom-field', 'custom-field-flyout'); + await common.clickAndValidate('add-custom-field', 'common-flyout'); await testSubjects.setValue('custom-field-label-input', 'Summary'); await testSubjects.setCheckbox('text-custom-field-required-wrapper', 'check'); - await testSubjects.click('custom-field-flyout-save'); + await testSubjects.click('common-flyout-save'); expect(await testSubjects.exists('euiFlyoutCloseButton')).to.be(false); await testSubjects.existOrFail('custom-fields-list'); @@ -99,7 +100,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { await input.type('!!!'); - await testSubjects.click('custom-field-flyout-save'); + await testSubjects.click('common-flyout-save'); expect(await testSubjects.exists('euiFlyoutCloseButton')).to.be(false); await testSubjects.existOrFail('custom-fields-list'); @@ -113,12 +114,89 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { await deleteButton.click(); - await testSubjects.existOrFail('confirm-delete-custom-field-modal'); + await testSubjects.existOrFail('confirm-delete-modal'); await testSubjects.click('confirmModalConfirmButton'); await testSubjects.missingOrFail('custom-fields-list'); }); }); + + describe('Templates', function () { + it('adds a template', async () => { + await testSubjects.existOrFail('templates-form-group'); + await common.clickAndValidate('add-template', 'common-flyout'); + + await testSubjects.setValue('template-name-input', 'Template name'); + await comboBox.setCustom('template-tags', 'tag-t1'); + await testSubjects.setValue('template-description-input', 'Template description'); + + const caseTitle = await find.byCssSelector( + `[data-test-subj="input"][aria-describedby="caseTitle"]` + ); + await caseTitle.focus(); + await caseTitle.type('case with template'); + + await cases.create.setDescription('test description'); + + await cases.create.setTags('tagme'); + await cases.create.setCategory('new'); + + await testSubjects.click('common-flyout-save'); + expect(await testSubjects.exists('euiFlyoutCloseButton')).to.be(false); + + await retry.waitFor('templates-list', async () => { + return await testSubjects.exists('templates-list'); + }); + + expect(await testSubjects.getVisibleText('templates-list')).to.be('Template name\ntag-t1'); + }); + + it('updates a template', async () => { + await testSubjects.existOrFail('templates-form-group'); + const editButton = await find.byCssSelector('[data-test-subj*="-template-edit"]'); + + await editButton.click(); + + await testSubjects.setValue('template-name-input', 'Updated template name!'); + await comboBox.setCustom('template-tags', 'tag-t1'); + await testSubjects.setValue('template-description-input', 'Template description updated'); + + const caseTitle = await find.byCssSelector( + `[data-test-subj="input"][aria-describedby="caseTitle"]` + ); + await caseTitle.focus(); + await caseTitle.type('!!'); + + await cases.create.setDescription('test description!!'); + + await cases.create.setTags('case-tag'); + await cases.create.setCategory('new!'); + + await testSubjects.click('common-flyout-save'); + expect(await testSubjects.exists('euiFlyoutCloseButton')).to.be(false); + + await retry.waitFor('templates-list', async () => { + return await testSubjects.exists('templates-list'); + }); + + expect(await testSubjects.getVisibleText('templates-list')).to.be( + 'Updated template name!\ntag-t1' + ); + }); + + it('deletes a template', async () => { + await testSubjects.existOrFail('templates-form-group'); + const deleteButton = await find.byCssSelector('[data-test-subj*="-template-delete"]'); + + await deleteButton.click(); + + await testSubjects.existOrFail('confirm-delete-modal'); + + await testSubjects.click('confirmModalConfirmButton'); + + await testSubjects.missingOrFail('template-list'); + }); + }); }); }; diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts index 67b7cd1f3dfb3..9377238535b40 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts @@ -96,7 +96,7 @@ export default ({ getService, getPageObject }: FtrProviderContext) => { await cases.create.openCreateCasePage(); // verify custom fields on create case page - await testSubjects.existOrFail('create-case-custom-fields'); + await testSubjects.existOrFail('caseCustomFields'); await cases.create.setTitle(caseTitle); await cases.create.setDescription('this is a test description'); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts index bd36f8f7a8ea1..478cb6d78f775 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts @@ -22,6 +22,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const toasts = getService('toasts'); const retry = getService('retry'); const find = getService('find'); + const comboBox = getService('comboBox'); describe('Configure Case', function () { before(async () => { @@ -76,13 +77,13 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { describe('Custom fields', function () { it('adds a custom field', async () => { await testSubjects.existOrFail('custom-fields-form-group'); - await common.clickAndValidate('add-custom-field', 'custom-field-flyout'); + await common.clickAndValidate('add-custom-field', 'common-flyout'); await testSubjects.setValue('custom-field-label-input', 'Summary'); await testSubjects.setCheckbox('text-custom-field-required-wrapper', 'check'); - await testSubjects.click('custom-field-flyout-save'); + await testSubjects.click('common-flyout-save'); expect(await testSubjects.exists('euiFlyoutCloseButton')).to.be(false); await testSubjects.existOrFail('custom-fields-list'); @@ -100,7 +101,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { await input.type('!!!'); - await testSubjects.click('custom-field-flyout-save'); + await testSubjects.click('common-flyout-save'); expect(await testSubjects.exists('euiFlyoutCloseButton')).to.be(false); await testSubjects.existOrFail('custom-fields-list'); @@ -114,12 +115,89 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { await deleteButton.click(); - await testSubjects.existOrFail('confirm-delete-custom-field-modal'); + await testSubjects.existOrFail('confirm-delete-modal'); await testSubjects.click('confirmModalConfirmButton'); await testSubjects.missingOrFail('custom-fields-list'); }); }); + + describe('Templates', function () { + it('adds a template', async () => { + await testSubjects.existOrFail('templates-form-group'); + await common.clickAndValidate('add-template', 'common-flyout'); + + await testSubjects.setValue('template-name-input', 'Template name'); + await comboBox.setCustom('template-tags', 'tag-t1'); + await testSubjects.setValue('template-description-input', 'Template description'); + + const caseTitle = await find.byCssSelector( + `[data-test-subj="input"][aria-describedby="caseTitle"]` + ); + await caseTitle.focus(); + await caseTitle.type('case with template'); + + await cases.create.setDescription('test description'); + + await cases.create.setTags('tagme'); + await cases.create.setCategory('new'); + + await testSubjects.click('common-flyout-save'); + expect(await testSubjects.exists('euiFlyoutCloseButton')).to.be(false); + + await retry.waitFor('templates-list', async () => { + return await testSubjects.exists('templates-list'); + }); + + expect(await testSubjects.getVisibleText('templates-list')).to.be('Template name\ntag-t1'); + }); + + it('updates a template', async () => { + await testSubjects.existOrFail('templates-form-group'); + const editButton = await find.byCssSelector('[data-test-subj*="-template-edit"]'); + + await editButton.click(); + + await testSubjects.setValue('template-name-input', 'Updated template name!'); + await comboBox.setCustom('template-tags', 'tag-t1'); + await testSubjects.setValue('template-description-input', 'Template description updated'); + + const caseTitle = await find.byCssSelector( + `[data-test-subj="input"][aria-describedby="caseTitle"]` + ); + await caseTitle.focus(); + await caseTitle.type('!!'); + + await cases.create.setDescription('test description!!'); + + await cases.create.setTags('case-tag'); + await cases.create.setCategory('new!'); + + await testSubjects.click('common-flyout-save'); + expect(await testSubjects.exists('euiFlyoutCloseButton')).to.be(false); + + await retry.waitFor('templates-list', async () => { + return await testSubjects.exists('templates-list'); + }); + + expect(await testSubjects.getVisibleText('templates-list')).to.be( + 'Updated template name!\ntag-t1' + ); + }); + + it('deletes a template', async () => { + await testSubjects.existOrFail('templates-form-group'); + const deleteButton = await find.byCssSelector('[data-test-subj*="-template-delete"]'); + + await deleteButton.click(); + + await testSubjects.existOrFail('confirm-delete-modal'); + + await testSubjects.click('confirmModalConfirmButton'); + + await testSubjects.missingOrFail('template-list'); + }); + }); }); }; diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/create_case_form.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/create_case_form.ts index 27e4fda20f5ec..4662e96c401f2 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/create_case_form.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/create_case_form.ts @@ -97,7 +97,7 @@ export default ({ getService, getPageObject }: FtrProviderContext) => { await cases.create.openCreateCasePage(); // verify custom fields on create case page - await testSubjects.existOrFail('create-case-custom-fields'); + await testSubjects.existOrFail('caseCustomFields'); await cases.create.setTitle(caseTitle); await cases.create.setDescription('this is a test description'); From 013bda4e60f05cdaba62629c228334d4f4497227 Mon Sep 17 00:00:00 2001 From: Robert Oskamp <robert.oskamp@elastic.co> Date: Tue, 2 Jul 2024 13:15:31 +0200 Subject: [PATCH 065/128] Add pipeline for serverless emergency release quality gate (#187251) ## Summary This PR adds separately quality gate pipelines for the emergency release process. More details in the original PR #186833, which is split into the creation of the new pipeline (this PR) and moving existing pipelines from `catalog-info.yaml` to `.buildkite/pipeline-resource-definitions` (#187253). --- ...ana-serverless-quality-gates-emergency.yml | 33 ++++++++++++++ .../locations.yml | 1 + .../pipeline.emergency.kibana-tests.yaml | 33 ++++++++++++++ .../emergency/pipeline.tests-production.yaml | 37 +++++++++++++++ .../emergency/pipeline.tests-qa.yaml | 12 +++++ .../emergency/pipeline.tests-staging.yaml | 45 +++++++++++++++++++ .../quality-gates/pipeline.tests-staging.yaml | 2 +- 7 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 .buildkite/pipeline-resource-definitions/kibana-serverless-quality-gates-emergency.yml create mode 100644 .buildkite/pipelines/quality-gates/emergency/pipeline.emergency.kibana-tests.yaml create mode 100644 .buildkite/pipelines/quality-gates/emergency/pipeline.tests-production.yaml create mode 100644 .buildkite/pipelines/quality-gates/emergency/pipeline.tests-qa.yaml create mode 100644 .buildkite/pipelines/quality-gates/emergency/pipeline.tests-staging.yaml diff --git a/.buildkite/pipeline-resource-definitions/kibana-serverless-quality-gates-emergency.yml b/.buildkite/pipeline-resource-definitions/kibana-serverless-quality-gates-emergency.yml new file mode 100644 index 0000000000000..ba053d7c44da6 --- /dev/null +++ b/.buildkite/pipeline-resource-definitions/kibana-serverless-quality-gates-emergency.yml @@ -0,0 +1,33 @@ +# yaml-language-server: $schema=https://gist.githubusercontent.com/elasticmachine/988b80dae436cafea07d9a4a460a011d/raw/rre.schema.json +apiVersion: backstage.io/v1alpha1 +kind: Resource +metadata: + name: kibana-tests-emergency-pipeline + description: Definition of the kibana pipeline + links: + - title: Pipeline + url: https://buildkite.com/elastic/kibana-tests-emergency +spec: + type: buildkite-pipeline + owner: group:kibana-tech-leads + system: buildkite + implementation: + apiVersion: buildkite.elastic.dev/v1 + kind: Pipeline + metadata: + name: kibana-tests-emergency + description: Pipeline that tests the service integration in various environments + spec: + repository: elastic/kibana + pipeline_file: ./.buildkite/pipelines/quality-gates/emergency/pipeline.emergency.kibana-tests.yaml + provider_settings: + trigger_mode: none + teams: + kibana-operations: + access_level: MANAGE_BUILD_AND_READ + kibana-release-operators: + access_level: BUILD_AND_READ + cloud-tooling: + access_level: BUILD_AND_READ + everyone: + access_level: READ_ONLY diff --git a/.buildkite/pipeline-resource-definitions/locations.yml b/.buildkite/pipeline-resource-definitions/locations.yml index 55af40868bd4a..ccbc41c60ece1 100644 --- a/.buildkite/pipeline-resource-definitions/locations.yml +++ b/.buildkite/pipeline-resource-definitions/locations.yml @@ -27,6 +27,7 @@ spec: - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/kibana-performance-data-set-extraction-daily.yml - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/kibana-pr.yml - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/kibana-purge-cloud-deployments.yml + - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/kibana-serverless-quality-gates-emergency.yml - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/kibana-serverless-release-testing.yml - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/kibana-serverless-release.yml - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/scalability_testing-daily.yml diff --git a/.buildkite/pipelines/quality-gates/emergency/pipeline.emergency.kibana-tests.yaml b/.buildkite/pipelines/quality-gates/emergency/pipeline.emergency.kibana-tests.yaml new file mode 100644 index 0000000000000..60ede2aae2b91 --- /dev/null +++ b/.buildkite/pipelines/quality-gates/emergency/pipeline.emergency.kibana-tests.yaml @@ -0,0 +1,33 @@ +# This pipeline serves as the entry point for your service's quality gates definitions. When +# properly configured, it will be invoked automatically as part of the automated +# promotion process once a new version was rolled out in one of the various cloud stages. +# +# The updated environment is provided via ENVIRONMENT variable. The seedling +# step will branch and execute pipeline snippets at the following location: +# pipeline.tests-qa.yaml +# pipeline.tests-staging.yaml +# pipeline.tests-production.yaml +# +# Docs: https://docs.elastic.dev/serverless/qualitygates + +agents: + cpu: 2 + ephemeralStorage: "20G" + memory: "8G" + +env: + SKIP_NODE_SETUP: true + TEAM_CHANNEL: "#kibana-mission-control" + ENVIRONMENT: ${ENVIRONMENT?} + +steps: + - label: ":pipeline::grey_question::seedling: Trigger Kibana Tests for ${ENVIRONMENT}" + env: + QG_PIPELINE_LOCATION: ".buildkite/pipelines/quality-gates/emergency" + command: "make -C /agent run-environment-tests" # will trigger https://buildkite.com/elastic/kibana-tests-emergency + agents: + image: "docker.elastic.co/ci-agent-images/quality-gate-seedling:0.0.4" + +notify: + - slack: "${TEAM_CHANNEL?}" + if: build.branch == "main" && build.state == "failed" diff --git a/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-production.yaml b/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-production.yaml new file mode 100644 index 0000000000000..a1de7f41a2100 --- /dev/null +++ b/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-production.yaml @@ -0,0 +1,37 @@ +# These pipeline steps constitute the quality gate for your service within the production environment. +# Incorporate any necessary additional logic to validate the service's integrity. +# A failure in this pipeline build will prevent further progression to the subsequent stage. + +steps: + - label: ":kibana: SLO check" + trigger: "serverless-quality-gates" # https://buildkite.com/elastic/serverless-quality-gates + build: + message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-production.yaml)" + env: + TARGET_ENV: production + CHECK_SLO: true + CHECK_SLO_TAG: kibana + CHECK_SLO_WAITING_PERIOD: 15m + CHECK_SLO_BURN_RATE_THRESHOLD: 0.1 + soft_fail: true + + - label: ":rocket: control-plane e2e tests" + if: build.env("ENVIRONMENT") == "production-canary" + trigger: "ess-k8s-production-e2e-tests" # https://buildkite.com/elastic/ess-k8s-production-e2e-tests + build: + env: + REGION_ID: aws-us-east-1 + NAME_PREFIX: ci_test_kibana-promotion_ + message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-production.yaml)" + + - label: ":cookie: 24h bake time before continuing promotion" + if: build.env("ENVIRONMENT") == "production-canary" + command: "sleep 86400" + soft_fail: + # A manual cancel of that step produces return code 255. + # We're treating this case as a soft fail to allow manual bake time skipping. + # To stop the promotion entirely, instead click the "Cancel" button at the top of the page + - exit_status: 255 + agents: + # How long can this agent live for in minutes - 25 hours + instanceMaxAge: 1500 diff --git a/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-qa.yaml b/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-qa.yaml new file mode 100644 index 0000000000000..1c0e69ef7a7b4 --- /dev/null +++ b/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-qa.yaml @@ -0,0 +1,12 @@ +# These pipeline steps constitute the quality gate for your service within the QA environment. +# Incorporate any necessary additional logic to validate the service's integrity. +# A failure in this pipeline build will prevent further progression to the subsequent stage. + +steps: + - label: ":rocket: control-plane e2e tests" + trigger: "ess-k8s-qa-e2e-tests-daily" # https://buildkite.com/elastic/ess-k8s-qa-e2e-tests-daily + build: + env: + REGION_ID: aws-eu-west-1 + NAME_PREFIX: ci_test_kibana-promotion_ + message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-qa.yaml)" diff --git a/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-staging.yaml b/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-staging.yaml new file mode 100644 index 0000000000000..febb61c12c5f1 --- /dev/null +++ b/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-staging.yaml @@ -0,0 +1,45 @@ +# These pipeline steps constitute the quality gate for your service within the staging environment. +# Incorporate any necessary additional logic to validate the service's integrity. +# A failure in this pipeline build will prevent further progression to the subsequent stage. + +steps: + - label: ":rocket: control-plane e2e tests" + trigger: "ess-k8s-staging-e2e-tests" # https://buildkite.com/elastic/ess-k8s-staging-e2e-tests + build: + env: + REGION_ID: aws-us-east-1 + NAME_PREFIX: ci_test_kibana-promotion_ + message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-staging.yaml)" + + - label: ":kibana: Kibana Serverless Tests for ${ENVIRONMENT}" + trigger: appex-qa-serverless-kibana-ftr-tests # https://buildkite.com/elastic/appex-qa-serverless-kibana-ftr-tests + soft_fail: true # Remove when tests stabilize + build: + env: + ENVIRONMENT: ${ENVIRONMENT} + EC_ENV: staging + EC_REGION: aws-us-east-1 + RETRY_TESTS_ON_FAIL: "true" + message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-staging.yaml)" + + - label: ":rocket: Fleet synthetic monitor to check the long standing project" + trigger: "serverless-quality-gates" + build: + message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-staging.yaml)" + env: + TARGET_ENV: staging + CHECK_SYNTHETICS: true + CHECK_SYNTHETICS_TAG: "fleet" + CHECK_SYNTHETICS_MINIMUM_RUNS: 3 + MAX_FAILURES: 2 + CHECK_SYNTHETIC_MAX_POLL: 50 + soft_fail: true + + - wait: ~ + + - group: "Kibana Release Manager" + steps: + - label: ":judge::seedling: Trigger Manual Tests Phase" + command: "make -C /agent trigger-manual-verification-phase" + agents: + image: "docker.elastic.co/ci-agent-images/manual-verification-agent:0.0.6" diff --git a/.buildkite/pipelines/quality-gates/pipeline.tests-staging.yaml b/.buildkite/pipelines/quality-gates/pipeline.tests-staging.yaml index 837234fc51441..febb61c12c5f1 100644 --- a/.buildkite/pipelines/quality-gates/pipeline.tests-staging.yaml +++ b/.buildkite/pipelines/quality-gates/pipeline.tests-staging.yaml @@ -21,7 +21,7 @@ steps: EC_REGION: aws-us-east-1 RETRY_TESTS_ON_FAIL: "true" message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-staging.yaml)" - + - label: ":rocket: Fleet synthetic monitor to check the long standing project" trigger: "serverless-quality-gates" build: From 369fb6028c6ea2f4f03fa2363949bfa42f2a2b4c Mon Sep 17 00:00:00 2001 From: Anton Dosov <anton.dosov@elastic.co> Date: Tue, 2 Jul 2024 14:03:30 +0200 Subject: [PATCH 066/128] Improve fatal error message (#186609) ## Summary Close https://github.com/elastic/kibana-team/issues/948 Makes the error message when Kibana fails to load less scary ![Screenshot 2024-06-21 at 12 13 45](https://github.com/elastic/kibana/assets/7784120/bdb09e35-f782-43c1-912d-89ed9933eb6f) --- .../render_template.test.ts.snap | 41 +++++++++++++++---- .../src/bootstrap/render_template.ts | 41 +++++++++++++++---- .../src/views/template.tsx | 10 ++++- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 6 files changed, 72 insertions(+), 23 deletions(-) diff --git a/packages/core/rendering/core-rendering-server-internal/src/bootstrap/__snapshots__/render_template.test.ts.snap b/packages/core/rendering/core-rendering-server-internal/src/bootstrap/__snapshots__/render_template.test.ts.snap index f7e28eebd1a61..af1808d9a2019 100644 --- a/packages/core/rendering/core-rendering-server-internal/src/bootstrap/__snapshots__/render_template.test.ts.snap +++ b/packages/core/rendering/core-rendering-server-internal/src/bootstrap/__snapshots__/render_template.test.ts.snap @@ -52,15 +52,38 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { // make subsequent calls to failure() noop failure = function () {}; - var err = document.createElement('h1'); - err.style['color'] = 'white'; - err.style['font-family'] = 'monospace'; - err.style['text-align'] = 'center'; - err.style['background'] = '#F44336'; - err.style['padding'] = '25px'; - err.innerText = document.querySelector('[data-error-message]').dataset.errorMessage; - - document.body.innerHTML = err.outerHTML; + var errorTitle = document.querySelector('[data-error-message-title]').dataset.errorMessageTitle; + var errorText = document.querySelector('[data-error-message-text]').dataset.errorMessageText; + var errorReload = document.querySelector('[data-error-message-reload]').dataset.errorMessageReload; + + var err = document.createElement('div'); + err.style.textAlign = 'center'; + err.style.padding = '120px 20px'; + err.style.fontFamily = 'Inter, BlinkMacSystemFont, Helvetica, Arial, sans-serif'; + + var errorTitleEl = document.createElement('h1'); + errorTitleEl.innerText = errorTitle; + errorTitleEl.style.margin = '20px'; + + var errorTextEl = document.createElement('p'); + errorTextEl.innerText = errorText; + errorTextEl.style.margin = '20px'; + + var errorReloadEl = document.createElement('button'); + errorReloadEl.innerText = errorReload; + errorReloadEl.onclick = function () { + location.reload(); + }; + errorReloadEl.setAttribute('style', + 'cursor: pointer; padding-inline: 12px; block-size: 40px; font-size: 1rem; line-height: 1.4286rem; border-radius: 6px; min-inline-size: 112px; color: rgb(255, 255, 255); background-color: rgb(0, 119, 204); outline-color: rgb(0, 0, 0); border:none' + ); + + err.appendChild(errorTitleEl); + err.appendChild(errorTextEl); + err.appendChild(errorReloadEl); + + document.body.innerHTML = ''; + document.body.appendChild(err); } var stylesheetTarget = document.querySelector('head meta[name=\\"add-styles-here\\"]') diff --git a/packages/core/rendering/core-rendering-server-internal/src/bootstrap/render_template.ts b/packages/core/rendering/core-rendering-server-internal/src/bootstrap/render_template.ts index 996aacd5e3ede..fbb7a4290bf14 100644 --- a/packages/core/rendering/core-rendering-server-internal/src/bootstrap/render_template.ts +++ b/packages/core/rendering/core-rendering-server-internal/src/bootstrap/render_template.ts @@ -68,15 +68,38 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { // make subsequent calls to failure() noop failure = function () {}; - var err = document.createElement('h1'); - err.style['color'] = 'white'; - err.style['font-family'] = 'monospace'; - err.style['text-align'] = 'center'; - err.style['background'] = '#F44336'; - err.style['padding'] = '25px'; - err.innerText = document.querySelector('[data-error-message]').dataset.errorMessage; - - document.body.innerHTML = err.outerHTML; + var errorTitle = document.querySelector('[data-error-message-title]').dataset.errorMessageTitle; + var errorText = document.querySelector('[data-error-message-text]').dataset.errorMessageText; + var errorReload = document.querySelector('[data-error-message-reload]').dataset.errorMessageReload; + + var err = document.createElement('div'); + err.style.textAlign = 'center'; + err.style.padding = '120px 20px'; + err.style.fontFamily = 'Inter, BlinkMacSystemFont, Helvetica, Arial, sans-serif'; + + var errorTitleEl = document.createElement('h1'); + errorTitleEl.innerText = errorTitle; + errorTitleEl.style.margin = '20px'; + + var errorTextEl = document.createElement('p'); + errorTextEl.innerText = errorText; + errorTextEl.style.margin = '20px'; + + var errorReloadEl = document.createElement('button'); + errorReloadEl.innerText = errorReload; + errorReloadEl.onclick = function () { + location.reload(); + }; + errorReloadEl.setAttribute('style', + 'cursor: pointer; padding-inline: 12px; block-size: 40px; font-size: 1rem; line-height: 1.4286rem; border-radius: 6px; min-inline-size: 112px; color: rgb(255, 255, 255); background-color: rgb(0, 119, 204); outline-color: rgb(0, 0, 0); border:none' + ); + + err.appendChild(errorTitleEl); + err.appendChild(errorTextEl); + err.appendChild(errorReloadEl); + + document.body.innerHTML = ''; + document.body.appendChild(err); } var stylesheetTarget = document.querySelector('head meta[name="add-styles-here"]') diff --git a/packages/core/rendering/core-rendering-server-internal/src/views/template.tsx b/packages/core/rendering/core-rendering-server-internal/src/views/template.tsx index 358cd267f653a..f1612e4adbe54 100644 --- a/packages/core/rendering/core-rendering-server-internal/src/views/template.tsx +++ b/packages/core/rendering/core-rendering-server-internal/src/views/template.tsx @@ -80,9 +80,15 @@ export const Template: FunctionComponent<Props> = ({ {logo} <div className="kbnWelcomeText" - data-error-message={i18n('core.ui.welcomeErrorMessage', { + data-error-message-title={i18n('core.ui.welcomeErrorMessageTitle', { + defaultMessage: 'Elastic did not load properly', + })} + data-error-message-text={i18n('core.ui.welcomeErrorMessageText', { defaultMessage: - 'Elastic did not load properly. Check the server output for more information.', + 'Please reload this page. If the issue persists, check the browser console and server logs.', + })} + data-error-message-reload={i18n('core.ui.welcomeErrorReloadButton', { + defaultMessage: 'Reload', })} > {i18n('core.ui.welcomeMessage', { diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 53e56dc0c09c1..8516ca3a3d33b 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -1069,7 +1069,6 @@ "core.ui.searchNavList.label": "Recherche", "core.ui.securityNavList.label": "Sécurité", "core.ui.skipToMainButton": "Passer au contenu principal", - "core.ui.welcomeErrorMessage": "Elastic ne s'est pas chargé correctement. Vérifiez la sortie du serveur pour plus d'informations.", "core.ui.welcomeMessage": "Chargement d'Elastic", "customIntegrations.components.replacementAccordion.recommendationDescription": "Les intégrations d'Elastic Agent sont recommandées, mais vous pouvez également utiliser Beats. Pour plus de détails, consultez notre {link}.", "customIntegrations.languageClients.DotnetElasticsearch.readme.connectingText": "Vous pouvez vous connecter à Elastic Cloud à l'aide d'une {api_key} et d'un {cloud_id} :", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 64c1ec3017aaf..8600d6d980144 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1069,7 +1069,6 @@ "core.ui.searchNavList.label": "検索", "core.ui.securityNavList.label": "セキュリティ", "core.ui.skipToMainButton": "メインコンテンツに移動", - "core.ui.welcomeErrorMessage": "Elasticが正常に読み込まれませんでした。詳細はサーバーアウトプットを確認してください。", "core.ui.welcomeMessage": "Elastic の読み込み中", "customIntegrations.components.replacementAccordion.recommendationDescription": "Elasticエージェント統合が推奨されますが、Beatsも使用できます。詳細については、{link}。", "customIntegrations.languageClients.DotnetElasticsearch.readme.connectingText": "{api_key}と{cloud_id}を使用して、Elastic Cloudに接続できます。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index e2b4c8ab1f602..6290890e7c6c4 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1071,7 +1071,6 @@ "core.ui.searchNavList.label": "搜索", "core.ui.securityNavList.label": "安全", "core.ui.skipToMainButton": "跳到主要内容", - "core.ui.welcomeErrorMessage": "Elastic 未正确加载。检查服务器输出以了解详情。", "core.ui.welcomeMessage": "正在加载 Elastic", "customIntegrations.components.replacementAccordion.recommendationDescription": "建议使用 Elastic 代理集成,但也可以使用 Beats。有关更多详情,请访问 {link}。", "customIntegrations.languageClients.DotnetElasticsearch.readme.connectingText": "您可以使用 {api_key} 和 {cloud_id} 连接到 Elastic Cloud:", From 813fc21ae4c2b13355ae2c7d5baccc822916ded3 Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Tue, 2 Jul 2024 14:21:10 +0200 Subject: [PATCH 067/128] [Fleet] Added modal to manage agent policies of an integration policy (#186987) ## Summary Closes https://github.com/elastic/kibana/issues/182112 Added modal to manage agent policies. To verify: - go to an integration where the integration policies are listed - click on the `+` button in the agent policies column - click on `Manage agent policies` in the popover - add/remove agent policies in the modal - click submit, the integration policy should be updated to be linked to the updated agent policies <img width="1614" alt="image" src="https://github.com/elastic/kibana/assets/90178898/33101f7e-8563-4990-9a52-74e5448c21da"> Made a change to the table column display to show a `+` button even if there is only one policy. Previously the popover could only be accessed if there are at least 2 agent policies. Also restored the agent policy link, lock icon and revision display (instead of a badge) if there are multiple agent policies. @simosilvestri Let me know if you have any UX recommendation as it differs slightly from the prototype. <img width="982" alt="image" src="https://github.com/elastic/kibana/assets/90178898/51aff39c-3f84-4861-8614-c0e16b64f4bf"> <img width="1533" alt="image" src="https://github.com/elastic/kibana/assets/90178898/f08de084-d4c2-47c6-b532-f875ceaf10ef"> EDIT: after discussing with Simona, removed the + button in case of a single agent policy assigned to the integration policy. <img width="1391" alt="image" src="https://github.com/elastic/kibana/assets/90178898/9ce5e98a-3ea2-4b79-a073-62047012db03"> Disabling `Manage agent policies` button if the current user doesn't have at least write integration policies and write agent policies privilege. This is how it looks with read privileges: <img width="2294" alt="image" src="https://github.com/elastic/kibana/assets/90178898/625843fb-8a50-4a06-b3a3-dc95a9fc2654"> ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../components/agent_policy_multi_select.tsx | 26 ++- .../steps/components/agent_policy_options.tsx | 177 ++++++++++++++ .../steps/step_select_agent_policy.test.tsx | 2 +- .../steps/step_select_agent_policy.tsx | 198 ++-------------- .../components/steps/step_select_hosts.tsx | 2 +- .../edit_package_policy_page/index.test.tsx | 76 +++++- .../detail/policies/package_policies.tsx | 14 +- .../manage_agent_policies_modal.test.tsx | 161 +++++++++++++ .../manage_agent_policies_modal.tsx | 218 ++++++++++++++++++ ...ultiple_agent_policy_summary_line.test.tsx | 22 +- .../multiple_agent_policy_summary_line.tsx | 191 ++++++++++----- 11 files changed, 823 insertions(+), 264 deletions(-) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/agent_policy_options.tsx create mode 100644 x-pack/plugins/fleet/public/components/manage_agent_policies_modal.test.tsx create mode 100644 x-pack/plugins/fleet/public/components/manage_agent_policies_modal.tsx diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/agent_policy_multi_select.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/agent_policy_multi_select.tsx index 4b10b2e2fc9ac..63d49ab4dffe5 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/agent_policy_multi_select.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/agent_policy_multi_select.tsx @@ -9,10 +9,11 @@ import type { EuiComboBoxOptionOption } from '@elastic/eui'; import { EuiComboBox } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { uniq } from 'lodash'; -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; -import type { PackageInfo } from '../../../../../../../../../common'; +import type { AgentPolicy, PackageInfo } from '../../../../../../../../../common'; export interface Props { isLoading: boolean; @@ -20,6 +21,7 @@ export interface Props { selectedPolicyIds: string[]; setSelectedPolicyIds: (policyIds: string[]) => void; packageInfo?: PackageInfo; + selectedAgentPolicies: AgentPolicy[]; } export const AgentPolicyMultiSelect: React.FunctionComponent<Props> = ({ @@ -27,11 +29,25 @@ export const AgentPolicyMultiSelect: React.FunctionComponent<Props> = ({ agentPolicyMultiOptions, selectedPolicyIds, setSelectedPolicyIds, + selectedAgentPolicies, }) => { const selectedOptions = useMemo(() => { return agentPolicyMultiOptions.filter((option) => selectedPolicyIds.includes(option.key!)); }, [agentPolicyMultiOptions, selectedPolicyIds]); + // managed policies cannot be removed + const updateSelectedPolicyIds = useCallback( + (ids: string[]) => { + setSelectedPolicyIds( + uniq([ + ...selectedAgentPolicies.filter((policy) => policy.is_managed).map((policy) => policy.id), + ...ids, + ]) + ); + }, + [selectedAgentPolicies, setSelectedPolicyIds] + ); + return ( <EuiComboBox aria-label="Select Multiple Agent Policies" @@ -44,9 +60,9 @@ export const AgentPolicyMultiSelect: React.FunctionComponent<Props> = ({ )} options={agentPolicyMultiOptions} selectedOptions={selectedOptions} - onChange={(newOptions) => { - setSelectedPolicyIds(newOptions.map((option: any) => option.key)); - }} + onChange={(newOptions) => + updateSelectedPolicyIds(newOptions.map((option: any) => option.key)) + } isClearable={true} isLoading={isLoading} /> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/agent_policy_options.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/agent_policy_options.tsx new file mode 100644 index 0000000000000..c39466d779548 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/agent_policy_options.tsx @@ -0,0 +1,177 @@ +/* + * 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, { useMemo } from 'react'; +import type { EuiComboBoxOptionOption, EuiSuperSelectOption } from '@elastic/eui'; +import { EuiIcon, EuiSpacer, EuiText, EuiToolTip } from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n-react'; + +import type { AgentPolicy, Output, PackageInfo } from '../../../../../../../../../common'; +import { + FLEET_APM_PACKAGE, + PACKAGE_POLICY_SAVED_OBJECT_TYPE, + SO_SEARCH_LIMIT, +} from '../../../../../../../../../common'; +import { outputType } from '../../../../../../../../../common/constants'; +import { isPackageLimited } from '../../../../../../../../../common/services'; +import { useGetAgentPolicies, useGetOutputs, useGetPackagePolicies } from '../../../../../../hooks'; + +export function useAgentPoliciesOptions(packageInfo?: PackageInfo) { + // Fetch agent policies info + const { + data: agentPoliciesData, + error: agentPoliciesError, + isLoading: isAgentPoliciesLoading, + } = useGetAgentPolicies({ + page: 1, + perPage: SO_SEARCH_LIMIT, + sortField: 'name', + sortOrder: 'asc', + noAgentCount: true, // agentPolicy.agents will always be 0 + full: false, // package_policies will always be empty + }); + const agentPolicies = useMemo( + () => agentPoliciesData?.items.filter((policy) => !policy.is_managed) || [], + [agentPoliciesData?.items] + ); + + const { data: outputsData, isLoading: isOutputLoading } = useGetOutputs(); + + // get all package policies with apm integration or the current integration + const { data: packagePoliciesForThisPackage, isLoading: isLoadingPackagePolicies } = + useGetPackagePolicies({ + page: 1, + perPage: SO_SEARCH_LIMIT, + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: ${packageInfo?.name}`, + }); + + const packagePoliciesForThisPackageByAgentPolicyId = useMemo( + () => + packagePoliciesForThisPackage?.items.reduce( + (acc: { [key: string]: boolean }, packagePolicy) => { + packagePolicy.policy_ids.forEach((policyId) => { + acc[policyId] = true; + }); + return acc; + }, + {} + ), + [packagePoliciesForThisPackage?.items] + ); + + const { getDataOutputForPolicy } = useMemo(() => { + const defaultOutput = (outputsData?.items ?? []).find((output) => output.is_default); + const outputsById = (outputsData?.items ?? []).reduce( + (acc: { [key: string]: Output }, output) => { + acc[output.id] = output; + return acc; + }, + {} + ); + + return { + getDataOutputForPolicy: (policy: Pick<AgentPolicy, 'data_output_id'>) => { + return policy.data_output_id ? outputsById[policy.data_output_id] : defaultOutput; + }, + }; + }, [outputsData]); + + const agentPolicyOptions: Array<EuiSuperSelectOption<string>> = useMemo( + () => + packageInfo + ? agentPolicies.map((policy) => { + const isLimitedPackageAlreadyInPolicy = + isPackageLimited(packageInfo!) && + packagePoliciesForThisPackageByAgentPolicyId?.[policy.id]; + + const isAPMPackageAndDataOutputIsLogstash = + packageInfo?.name === FLEET_APM_PACKAGE && + getDataOutputForPolicy(policy)?.type === outputType.Logstash; + + return { + inputDisplay: ( + <> + <EuiText size="s">{policy.name}</EuiText> + {isAPMPackageAndDataOutputIsLogstash && ( + <> + <EuiSpacer size="xs" /> + <EuiText size="s"> + <FormattedMessage + id="xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyDisabledAPMLogstashOuputText" + defaultMessage="Logstash output for integrations is not supported with APM" + /> + </EuiText> + </> + )} + </> + ), + value: policy.id, + disabled: isLimitedPackageAlreadyInPolicy || isAPMPackageAndDataOutputIsLogstash, + 'data-test-subj': 'agentPolicyItem', + }; + }) + : [], + [ + packageInfo, + agentPolicies, + packagePoliciesForThisPackageByAgentPolicyId, + getDataOutputForPolicy, + ] + ); + + const agentPolicyMultiOptions: Array<EuiComboBoxOptionOption<string>> = useMemo( + () => + packageInfo && !isOutputLoading && !isAgentPoliciesLoading && !isLoadingPackagePolicies + ? agentPolicies.map((policy) => { + const isLimitedPackageAlreadyInPolicy = + isPackageLimited(packageInfo!) && + packagePoliciesForThisPackageByAgentPolicyId?.[policy.id]; + + const isAPMPackageAndDataOutputIsLogstash = + packageInfo?.name === FLEET_APM_PACKAGE && + getDataOutputForPolicy(policy)?.type === outputType.Logstash; + + return { + append: isAPMPackageAndDataOutputIsLogstash ? ( + <EuiToolTip + content={ + <FormattedMessage + id="xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyDisabledAPMLogstashOuputText" + defaultMessage="Logstash output for integrations is not supported with APM" + /> + } + > + <EuiIcon size="s" type="warningFilled" /> + </EuiToolTip> + ) : null, + key: policy.id, + label: policy.name, + disabled: isLimitedPackageAlreadyInPolicy || isAPMPackageAndDataOutputIsLogstash, + 'data-test-subj': 'agentPolicyMultiItem', + }; + }) + : [], + [ + packageInfo, + agentPolicies, + packagePoliciesForThisPackageByAgentPolicyId, + getDataOutputForPolicy, + isOutputLoading, + isAgentPoliciesLoading, + isLoadingPackagePolicies, + ] + ); + + return { + agentPoliciesError, + isLoading: isOutputLoading || isAgentPoliciesLoading || isLoadingPackagePolicies, + agentPolicyOptions, + agentPolicies, + agentPolicyMultiOptions, + }; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.test.tsx index 33ff461f7efd5..30688c7a99b11 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.test.tsx @@ -76,7 +76,7 @@ describe('step select agent policy', () => { agentPolicies={[]} updateAgentPolicies={updateAgentPoliciesMock} setHasAgentPolicyError={mockSetHasAgentPolicyError} - selectedAgentPolicyIds={selectedAgentPolicyIds} + initialSelectedAgentPolicyIds={selectedAgentPolicyIds} /> )); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.tsx index f28593d84ef9a..6238a2cc62a07 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.tsx @@ -5,12 +5,10 @@ * 2.0. */ -import React, { useEffect, useState, useMemo, useCallback } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { EuiComboBoxOptionOption, EuiSuperSelectOption } from '@elastic/eui'; -import { EuiIcon, EuiToolTip } from '@elastic/eui'; import { EuiSuperSelect } from '@elastic/eui'; import { EuiFlexGroup, @@ -19,29 +17,18 @@ import { EuiDescribedFormGroup, EuiTitle, EuiText, - EuiSpacer, } from '@elastic/eui'; import { Error } from '../../../../../components'; -import type { AgentPolicy, Output, PackageInfo } from '../../../../../types'; + +import type { AgentPolicy, PackageInfo } from '../../../../../types'; import { isPackageLimited, doesAgentPolicyAlreadyIncludePackage } from '../../../../../services'; -import { - useGetAgentPolicies, - useGetOutputs, - useFleetStatus, - useGetPackagePolicies, - sendBulkGetAgentPolicies, -} from '../../../../../hooks'; -import { - FLEET_APM_PACKAGE, - SO_SEARCH_LIMIT, - outputType, - PACKAGE_POLICY_SAVED_OBJECT_TYPE, -} from '../../../../../../../../common/constants'; +import { useFleetStatus, sendBulkGetAgentPolicies } from '../../../../../hooks'; import { useMultipleAgentPolicies } from '../../../../../hooks'; import { AgentPolicyMultiSelect } from './components/agent_policy_multi_select'; +import { useAgentPoliciesOptions } from './components/agent_policy_options'; const AgentPolicyFormRow = styled(EuiFormRow)` .euiFormRow__label { @@ -49,161 +36,6 @@ const AgentPolicyFormRow = styled(EuiFormRow)` } `; -function useAgentPoliciesOptions(packageInfo?: PackageInfo) { - // Fetch agent policies info - const { - data: agentPoliciesData, - error: agentPoliciesError, - isLoading: isAgentPoliciesLoading, - } = useGetAgentPolicies({ - page: 1, - perPage: SO_SEARCH_LIMIT, - sortField: 'name', - sortOrder: 'asc', - noAgentCount: true, // agentPolicy.agents will always be 0 - full: false, // package_policies will always be empty - }); - const agentPolicies = useMemo( - () => agentPoliciesData?.items.filter((policy) => !policy.is_managed) || [], - [agentPoliciesData?.items] - ); - - const { data: outputsData, isLoading: isOutputLoading } = useGetOutputs(); - - // get all package policies with apm integration or the current integration - const { data: packagePoliciesForThisPackage, isLoading: isLoadingPackagePolicies } = - useGetPackagePolicies({ - page: 1, - perPage: SO_SEARCH_LIMIT, - kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: ${packageInfo?.name}`, - }); - - const packagePoliciesForThisPackageByAgentPolicyId = useMemo( - () => - packagePoliciesForThisPackage?.items.reduce( - (acc: { [key: string]: boolean }, packagePolicy) => { - packagePolicy.policy_ids.forEach((policyId) => { - acc[policyId] = true; - }); - return acc; - }, - {} - ), - [packagePoliciesForThisPackage?.items] - ); - - const { getDataOutputForPolicy } = useMemo(() => { - const defaultOutput = (outputsData?.items ?? []).find((output) => output.is_default); - const outputsById = (outputsData?.items ?? []).reduce( - (acc: { [key: string]: Output }, output) => { - acc[output.id] = output; - return acc; - }, - {} - ); - - return { - getDataOutputForPolicy: (policy: Pick<AgentPolicy, 'data_output_id'>) => { - return policy.data_output_id ? outputsById[policy.data_output_id] : defaultOutput; - }, - }; - }, [outputsData]); - - const agentPolicyOptions: Array<EuiSuperSelectOption<string>> = useMemo( - () => - packageInfo - ? agentPolicies.map((policy) => { - const isLimitedPackageAlreadyInPolicy = - isPackageLimited(packageInfo) && - packagePoliciesForThisPackageByAgentPolicyId?.[policy.id]; - - const isAPMPackageAndDataOutputIsLogstash = - packageInfo?.name === FLEET_APM_PACKAGE && - getDataOutputForPolicy(policy)?.type === outputType.Logstash; - - return { - inputDisplay: ( - <> - <EuiText size="s">{policy.name}</EuiText> - {isAPMPackageAndDataOutputIsLogstash && ( - <> - <EuiSpacer size="xs" /> - <EuiText size="s"> - <FormattedMessage - id="xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyDisabledAPMLogstashOuputText" - defaultMessage="Logstash output for integrations is not supported with APM" - /> - </EuiText> - </> - )} - </> - ), - value: policy.id, - disabled: isLimitedPackageAlreadyInPolicy || isAPMPackageAndDataOutputIsLogstash, - 'data-test-subj': 'agentPolicyItem', - }; - }) - : [], - [ - packageInfo, - agentPolicies, - packagePoliciesForThisPackageByAgentPolicyId, - getDataOutputForPolicy, - ] - ); - - const agentPolicyMultiOptions: Array<EuiComboBoxOptionOption<string>> = useMemo( - () => - packageInfo && !isOutputLoading && !isAgentPoliciesLoading && !isLoadingPackagePolicies - ? agentPolicies.map((policy) => { - const isLimitedPackageAlreadyInPolicy = - isPackageLimited(packageInfo) && - packagePoliciesForThisPackageByAgentPolicyId?.[policy.id]; - - const isAPMPackageAndDataOutputIsLogstash = - packageInfo?.name === FLEET_APM_PACKAGE && - getDataOutputForPolicy(policy)?.type === outputType.Logstash; - - return { - append: isAPMPackageAndDataOutputIsLogstash ? ( - <EuiToolTip - content={ - <FormattedMessage - id="xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyDisabledAPMLogstashOuputText" - defaultMessage="Logstash output for integrations is not supported with APM" - /> - } - > - <EuiIcon size="s" type="warningFilled" /> - </EuiToolTip> - ) : null, - key: policy.id, - label: policy.name, - disabled: isLimitedPackageAlreadyInPolicy || isAPMPackageAndDataOutputIsLogstash, - 'data-test-subj': 'agentPolicyMultiItem', - }; - }) - : [], - [ - packageInfo, - agentPolicies, - packagePoliciesForThisPackageByAgentPolicyId, - getDataOutputForPolicy, - isOutputLoading, - isAgentPoliciesLoading, - isLoadingPackagePolicies, - ] - ); - - return { - agentPoliciesError, - isLoading: isOutputLoading || isAgentPoliciesLoading || isLoadingPackagePolicies, - agentPolicyOptions, - agentPolicies, - agentPolicyMultiOptions, - }; -} - function doesAgentPolicyHaveLimitedPackage(policy: AgentPolicy, pkgInfo: PackageInfo) { return policy ? isPackageLimited(pkgInfo) && doesAgentPolicyAlreadyIncludePackage(policy, pkgInfo.name) @@ -215,13 +47,13 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{ agentPolicies: AgentPolicy[]; updateAgentPolicies: (agentPolicies: AgentPolicy[]) => void; setHasAgentPolicyError: (hasError: boolean) => void; - selectedAgentPolicyIds: string[]; + initialSelectedAgentPolicyIds: string[]; }> = ({ packageInfo, agentPolicies, updateAgentPolicies: updateSelectedAgentPolicies, setHasAgentPolicyError, - selectedAgentPolicyIds, + initialSelectedAgentPolicyIds, }) => { const { isReady: isFleetReady } = useFleetStatus(); @@ -239,7 +71,6 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{ const [selectedPolicyIds, setSelectedPolicyIds] = useState<string[]>([]); const [isFirstLoad, setIsFirstLoad] = useState<boolean>(true); - const [isLoadingSelectedAgentPolicies, setIsLoadingSelectedAgentPolicies] = useState<boolean>(false); const [selectedAgentPolicies, setSelectedAgentPolicies] = useState<AgentPolicy[]>(agentPolicies); @@ -292,17 +123,17 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{ setIsFirstLoad(false); if (canUseMultipleAgentPolicies) { const enabledOptions = agentPolicyMultiOptions.filter((option) => !option.disabled); - if (enabledOptions.length === 1) { + if (enabledOptions.length === 1 && initialSelectedAgentPolicyIds.length === 0) { setSelectedPolicyIds([enabledOptions[0].key!]); - } else if (selectedAgentPolicyIds.length > 0) { - setSelectedPolicyIds(selectedAgentPolicyIds); + } else if (initialSelectedAgentPolicyIds.length > 0) { + setSelectedPolicyIds(initialSelectedAgentPolicyIds); } } else { const enabledOptions = agentPolicyOptions.filter((option) => !option.disabled); if (enabledOptions.length === 1) { setSelectedPolicyIds([enabledOptions[0].value]); - } else if (selectedAgentPolicyIds.length > 0) { - setSelectedPolicyIds(selectedAgentPolicyIds); + } else if (initialSelectedAgentPolicyIds.length > 0) { + setSelectedPolicyIds(initialSelectedAgentPolicyIds); } } } @@ -310,7 +141,7 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{ agentPolicyOptions, agentPolicyMultiOptions, canUseMultipleAgentPolicies, - selectedAgentPolicyIds, + initialSelectedAgentPolicyIds, selectedPolicyIds, existingAgentPolicies, isFirstLoad, @@ -346,7 +177,7 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{ const someNewAgentPoliciesHaveLimitedPackage = !packageInfo || selectedAgentPolicies - .filter((policy) => !selectedAgentPolicyIds.find((id) => policy.id === id)) + .filter((policy) => !initialSelectedAgentPolicyIds.find((id) => policy.id === id)) .some((selectedAgentPolicy) => doesAgentPolicyHaveLimitedPackage(selectedAgentPolicy, packageInfo) ); @@ -426,6 +257,7 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{ selectedPolicyIds={selectedPolicyIds} setSelectedPolicyIds={setSelectedPolicyIds} agentPolicyMultiOptions={agentPolicyMultiOptions} + selectedAgentPolicies={agentPolicies} /> ) : ( <EuiSuperSelect diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.tsx index 73671e97e95db..4416b7340ef36 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.tsx @@ -112,7 +112,7 @@ export const StepSelectHosts: React.FunctionComponent<Props> = ({ agentPolicies={agentPolicies} updateAgentPolicies={updateAgentPolicies} setHasAgentPolicyError={setHasAgentPolicyError} - selectedAgentPolicyIds={selectedAgentPolicyIds} + initialSelectedAgentPolicyIds={selectedAgentPolicyIds} /> ), }, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.test.tsx index 7d50d3e494dbb..8e52ced483d72 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.test.tsx @@ -23,6 +23,7 @@ import { sendBulkGetAgentPolicies, useGetAgentPolicies, useMultipleAgentPolicies, + useGetPackagePolicies, } from '../../../hooks'; import { useGetOnePackagePolicy } from '../../../../integrations/hooks'; @@ -134,6 +135,18 @@ jest.mock('../../../hooks', () => { sendCreateAgentPolicy: jest.fn(), sendBulkGetAgentPolicies: jest.fn(), sendBulkInstallPackages: jest.fn(), + useGetPackagePolicies: jest.fn(), + useGetOutputs: jest.fn().mockReturnValue({ + data: { + items: [ + { + id: 'logstash-1', + type: 'logstash', + }, + ], + }, + isLoading: false, + }), }; }); @@ -223,8 +236,11 @@ describe('edit package policy page', () => { item: mockPackagePolicy, }, }); - (sendGetOneAgentPolicy as MockFn).mockResolvedValue({ - data: { item: { id: 'agent-policy-1', name: 'Agent policy 1', namespace: 'default' } }, + (useGetPackagePolicies as MockFn).mockReturnValue({ + data: { + items: [mockPackagePolicy], + }, + isLoading: false, }); (sendUpgradePackagePolicyDryRun as MockFn).mockResolvedValue({ data: [ @@ -496,6 +512,7 @@ describe('edit package policy page', () => { (sendGetAgentStatus as jest.MockedFunction<any>).mockResolvedValue({ data: { results: { total: 0 } }, }); + jest.clearAllMocks(); }); it('should create agent policy with sys monitoring when new hosts is selected', async () => { @@ -539,5 +556,60 @@ describe('edit package policy page', () => { }) ); }); + + it('should not remove managed policy when policies are modified', async () => { + (sendBulkGetAgentPolicies as MockFn).mockImplementation((ids: string[]) => { + const items = []; + if (ids.includes('agent-policy-1')) { + items.push({ id: 'agent-policy-1', name: 'Agent policy 1', is_managed: true }); + } + if (ids.includes('fleet-server-policy')) { + items.push({ id: 'fleet-server-policy', name: 'Fleet Server Policy' }); + } + return Promise.resolve({ + data: { + items, + }, + }); + }); + (useGetAgentPolicies as MockFn).mockReturnValue({ + data: { + items: [ + { id: 'agent-policy-1', name: 'Agent policy 1', is_managed: true }, + { id: 'fleet-server-policy', name: 'Fleet Server Policy' }, + ], + }, + isLoading: false, + }); + + await act(async () => { + render(); + }); + expect(renderResult.getByTestId('agentPolicyMultiSelect')).toBeInTheDocument(); + + await act(async () => { + renderResult.getByTestId('comboBoxToggleListButton').click(); + }); + + expect(renderResult.queryByText('Agent policy 1')).toBeNull(); + + await act(async () => { + fireEvent.click(renderResult.getByText('Fleet Server Policy')); + }); + + await act(async () => { + fireEvent.click(renderResult.getByText(/Save integration/).closest('button')!); + }); + await act(async () => { + fireEvent.click(renderResult.getAllByText(/Save and deploy changes/)[1].closest('button')!); + }); + + expect(sendUpdatePackagePolicy).toHaveBeenCalledWith( + 'nginx-1', + expect.objectContaining({ + policy_ids: ['agent-policy-1', 'fleet-server-policy'], + }) + ); + }); }); }); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx index 50b5b6e89a3ef..cc91af6a873a8 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx @@ -31,6 +31,7 @@ import { AgentPolicyRefreshContext, useIsPackagePolicyUpgradable, useAuthz, + useMultipleAgentPolicies, } from '../../../../../hooks'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants'; import { @@ -41,8 +42,6 @@ import { } from '../../../../../components'; import { SideBarColumn } from '../../../components/side_bar_column'; -import { useMultipleAgentPolicies } from '../../../../../hooks'; - import { PackagePolicyAgentsCell } from './components/package_policy_agents_cell'; import { usePackagePoliciesWithAgentPolicy } from './use_package_policies_with_agent_policy'; import { Persona } from './persona'; @@ -234,10 +233,14 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps defaultMessage: 'Agent policy', }), truncateText: true, - render(id, { agentPolicies }) { + render(id, { agentPolicies, packagePolicy }) { return agentPolicies.length > 0 ? ( - canShowMultiplePoliciesCell && agentPolicies.length > 1 ? ( - <MultipleAgentPoliciesSummaryLine policies={agentPolicies} /> + canShowMultiplePoliciesCell ? ( + <MultipleAgentPoliciesSummaryLine + policies={agentPolicies} + packagePolicyId={packagePolicy.id} + onAgentPoliciesChange={refreshPolicies} + /> ) : ( <AgentPolicySummaryLine policy={agentPolicies[0]} /> ) @@ -328,6 +331,7 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps canAddFleetServers, canAddAgents, showAddAgentHelpForPackagePolicyId, + refreshPolicies, ] ); diff --git a/x-pack/plugins/fleet/public/components/manage_agent_policies_modal.test.tsx b/x-pack/plugins/fleet/public/components/manage_agent_policies_modal.test.tsx new file mode 100644 index 0000000000000..fb550a157d8b6 --- /dev/null +++ b/x-pack/plugins/fleet/public/components/manage_agent_policies_modal.test.tsx @@ -0,0 +1,161 @@ +/* + * 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 { act } from '@testing-library/react'; + +import type { TestRenderer } from '../mock'; +import { createFleetTestRendererMock } from '../mock'; +import type { AgentPolicy } from '../types'; + +import { usePackagePolicyWithRelatedData } from '../applications/fleet/sections/agent_policy/edit_package_policy_page/hooks'; + +import { useGetAgentPolicies } from '../hooks'; + +import { ManageAgentPoliciesModal } from './manage_agent_policies_modal'; + +jest.mock('../applications/fleet/sections/agent_policy/edit_package_policy_page/hooks', () => ({ + ...jest.requireActual( + '../applications/fleet/sections/agent_policy/edit_package_policy_page/hooks' + ), + usePackagePolicyWithRelatedData: jest.fn().mockReturnValue({ + packageInfo: {}, + packagePolicy: { name: 'Integration 1' }, + savePackagePolicy: jest.fn().mockResolvedValue({ error: undefined }), + }), +})); + +jest.mock('../hooks', () => ({ + ...jest.requireActual('../hooks'), + useStartServices: jest.fn().mockReturnValue({ + notifications: { + toasts: { + addSuccess: jest.fn(), + addError: jest.fn(), + }, + }, + }), + useGetAgentPolicies: jest.fn(), + useGetPackagePolicies: jest.fn().mockReturnValue({ + data: { + items: [{ name: 'Integration 1', revision: 2, id: 'integration1', policy_ids: ['policy1'] }], + }, + isLoading: false, + }), + useGetOutputs: jest.fn().mockReturnValue({ + data: { + items: [ + { + id: 'logstash-1', + type: 'logstash', + }, + ], + }, + isLoading: false, + }), +})); + +describe('ManageAgentPoliciesModal', () => { + let testRenderer: TestRenderer; + const mockOnClose = jest.fn(); + const mockPolicies = [{ name: 'Test policy', revision: 2, id: 'policy1' }] as AgentPolicy[]; + + const render = (policies?: AgentPolicy[]) => + testRenderer.render( + <ManageAgentPoliciesModal + selectedAgentPolicies={policies || mockPolicies} + packagePolicyId="integration1" + onClose={mockOnClose} + onAgentPoliciesChange={jest.fn()} + /> + ); + + beforeEach(() => { + testRenderer = createFleetTestRendererMock(); + + (useGetAgentPolicies as jest.Mock).mockReturnValue({ + data: { + items: [ + { name: 'Test policy', revision: 2, id: 'policy1' }, + { name: 'Test policy 2', revision: 1, id: 'policy2' }, + ] as AgentPolicy[], + }, + isLoading: false, + }); + }); + + it('should update policy on submit', async () => { + const results = render(); + + expect(results.queryByTestId('manageAgentPoliciesModal')).toBeInTheDocument(); + expect(results.getByTestId('integrationNameText').textContent).toEqual( + 'Integration: Integration 1' + ); + + await act(async () => { + results.getByTestId('comboBoxToggleListButton').click(); + }); + await act(async () => { + results.getByText('Test policy 2').click(); + }); + expect(results.getByText('Confirm').getAttribute('disabled')).toBeNull(); + await act(async () => { + results.getByText('Confirm').click(); + }); + expect(usePackagePolicyWithRelatedData('', {}).savePackagePolicy).toHaveBeenCalledWith({ + policy_ids: ['policy1', 'policy2'], + }); + }); + + it('should keep managed policy when policies are changed', async () => { + (useGetAgentPolicies as jest.Mock).mockReturnValue({ + data: { + items: [ + { name: 'Test policy', revision: 2, id: 'policy1', is_managed: true }, + { name: 'Test policy 2', revision: 1, id: 'policy2' }, + ] as AgentPolicy[], + }, + isLoading: false, + }); + const results = render([ + { name: 'Test policy', revision: 2, id: 'policy1', is_managed: true }, + ] as AgentPolicy[]); + + expect(results.queryByTestId('manageAgentPoliciesModal')).toBeInTheDocument(); + expect(results.getByTestId('integrationNameText').textContent).toEqual( + 'Integration: Integration 1' + ); + + await act(async () => { + results.getByTestId('comboBoxToggleListButton').click(); + }); + expect(results.queryByText('Test policy')).toBeNull(); + await act(async () => { + results.getByText('Test policy 2').click(); + }); + expect(results.getByText('Confirm').getAttribute('disabled')).toBeNull(); + await act(async () => { + results.getByText('Confirm').click(); + }); + expect(usePackagePolicyWithRelatedData('', {}).savePackagePolicy).toHaveBeenCalledWith({ + policy_ids: ['policy1', 'policy2'], + }); + }); + + it('should display callout and disable confirm if policy is removed', async () => { + const results = render(); + + await act(async () => { + results.getByTestId('comboBoxClearButton').click(); + }); + expect(results.getByText('Confirm').getAttribute('disabled')).toBeDefined(); + expect(results.getByTestId('confirmRemovePoliciesCallout')).toBeInTheDocument(); + expect(results.getByTestId('confirmRemovePoliciesCallout').textContent).toContain( + 'Test policy will no longer use this integration.' + ); + }); +}); diff --git a/x-pack/plugins/fleet/public/components/manage_agent_policies_modal.tsx b/x-pack/plugins/fleet/public/components/manage_agent_policies_modal.tsx new file mode 100644 index 0000000000000..26e9421ed86fe --- /dev/null +++ b/x-pack/plugins/fleet/public/components/manage_agent_policies_modal.tsx @@ -0,0 +1,218 @@ +/* + * 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 { + EuiCallOut, + EuiConfirmModal, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiText, +} from '@elastic/eui'; +import React, { useState, useMemo } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { i18n } from '@kbn/i18n'; + +import { isEqual } from 'lodash'; +import styled from 'styled-components'; + +import { AgentPolicyMultiSelect } from '../applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/agent_policy_multi_select'; +import { useAgentPoliciesOptions } from '../applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/agent_policy_options'; +import type { AgentPolicy } from '../types'; +import { usePackagePolicyWithRelatedData } from '../applications/fleet/sections/agent_policy/edit_package_policy_page/hooks'; +import { useStartServices } from '../hooks'; + +const StyledEuiConfirmModal = styled(EuiConfirmModal)` + min-width: 448px; +`; + +interface Props { + onClose: () => void; + selectedAgentPolicies: AgentPolicy[]; + packagePolicyId: string; + onAgentPoliciesChange: () => void; +} + +export const ManageAgentPoliciesModal: React.FunctionComponent<Props> = ({ + onClose, + selectedAgentPolicies, + packagePolicyId, + onAgentPoliciesChange, +}) => { + const initialPolicyIds = selectedAgentPolicies.map((policy) => policy.id); + + const [selectedPolicyIds, setSelectedPolicyIds] = useState<string[]>(initialPolicyIds); + const [isSubmitting, setIsSubmitting] = useState<boolean>(false); + const { notifications } = useStartServices(); + const { packageInfo, packagePolicy, savePackagePolicy } = usePackagePolicyWithRelatedData( + packagePolicyId, + {} + ); + + const removedPolicies = useMemo( + () => + selectedAgentPolicies + .filter((policy) => !selectedPolicyIds.find((id) => policy.id === id)) + .map((policy) => policy.name), + [selectedAgentPolicies, selectedPolicyIds] + ); + + const onCancel = () => { + onClose(); + }; + + const onConfirm = async () => { + setIsSubmitting(true); + const { error } = await savePackagePolicy({ + policy_ids: selectedPolicyIds, + }); + setIsSubmitting(false); + if (!error) { + onAgentPoliciesChange(); + notifications.toasts.addSuccess({ + title: i18n.translate('xpack.fleet.manageAgentPolicies.updatedNotificationTitle', { + defaultMessage: `Successfully updated ''{packagePolicyName}''`, + values: { + packagePolicyName: packagePolicy.name, + }, + }), + 'data-test-subj': 'policyUpdateSuccessToast', + }); + } else { + if (error.statusCode === 409) { + notifications.toasts.addError(error, { + title: i18n.translate('xpack.fleet.manageAgentPolicies.failedNotificationTitle', { + defaultMessage: `Error updating ''{packagePolicyName}''`, + values: { + packagePolicyName: packagePolicy.name, + }, + }), + toastMessage: i18n.translate( + 'xpack.fleet.manageAgentPolicies.failedConflictNotificationMessage', + { + defaultMessage: `Data is out of date. Refresh the page to get the latest policy.`, + } + ), + }); + } else { + notifications.toasts.addError(error, { + title: i18n.translate('xpack.fleet.manageAgentPolicies.failedNotificationTitle', { + defaultMessage: `Error updating ''{packagePolicyName}''`, + values: { + packagePolicyName: packagePolicy.name, + }, + }), + }); + } + } + onClose(); + }; + + const { agentPolicyMultiOptions, isLoading } = useAgentPoliciesOptions(packageInfo); + + return ( + <StyledEuiConfirmModal + title={ + <FormattedMessage + id="xpack.fleet.manageAgentPolicies.confirmModalTitle" + defaultMessage="Manage agent policies" + /> + } + onCancel={onCancel} + onConfirm={onConfirm} + cancelButtonText={ + <FormattedMessage + id="xpack.fleet.manageAgentPolicies.confirmModalCancelButtonLabel" + defaultMessage="Cancel" + /> + } + confirmButtonText={ + <FormattedMessage + id="xpack.fleet.manageAgentPolicies.confirmModalConfirmButtonLabel" + defaultMessage="Confirm" + /> + } + buttonColor="primary" + confirmButtonDisabled={ + selectedPolicyIds.length === 0 || + isSubmitting || + isEqual(initialPolicyIds, selectedPolicyIds) + } + data-test-subj="manageAgentPoliciesModal" + > + <EuiFlexGroup direction="column" gutterSize="m"> + <EuiFlexItem> + <EuiText> + <FormattedMessage + id="xpack.fleet.manageAgentPolicies.confirmModalDescription" + defaultMessage="Agent policies sharing this integration" + /> + </EuiText> + </EuiFlexItem> + <EuiFlexItem> + <EuiText data-test-subj="integrationNameText"> + <b> + <FormattedMessage + id="xpack.fleet.manageAgentPolicies.integrationName" + defaultMessage="Integration: " + /> + </b> + {packagePolicy.name} + </EuiText> + </EuiFlexItem> + <EuiFlexItem> + <EuiFormRow + label={ + <FormattedMessage + id="xpack.fleet.manageAgentPolicies.agentPoliciesLabel" + defaultMessage="Agent policies" + /> + } + > + <AgentPolicyMultiSelect + isLoading={isLoading} + selectedPolicyIds={selectedPolicyIds} + setSelectedPolicyIds={setSelectedPolicyIds} + agentPolicyMultiOptions={agentPolicyMultiOptions} + selectedAgentPolicies={selectedAgentPolicies} + /> + </EuiFormRow> + </EuiFlexItem> + {removedPolicies.length > 0 && ( + <EuiFlexItem> + <EuiCallOut + data-test-subj="confirmRemovePoliciesCallout" + title={ + <FormattedMessage + id="xpack.fleet.manageAgentPolicies.calloutTitle" + defaultMessage="This action will update this integration" + /> + } + > + <EuiText size="s"> + <FormattedMessage + id="xpack.fleet.manageAgentPolicies.calloutBody" + defaultMessage="{removedPolicies} will no longer use this integration." + values={{ removedPolicies: <b>{removedPolicies.join(', ')}</b> }} + /> + </EuiText> + </EuiCallOut> + </EuiFlexItem> + )} + <EuiFlexItem> + <EuiText> + <FormattedMessage + id="xpack.fleet.manageAgentPolicies.confirmText" + defaultMessage="Are you sure you wish to continue?" + /> + </EuiText> + </EuiFlexItem> + </EuiFlexGroup> + </StyledEuiConfirmModal> + ); +}; diff --git a/x-pack/plugins/fleet/public/components/multiple_agent_policy_summary_line.test.tsx b/x-pack/plugins/fleet/public/components/multiple_agent_policy_summary_line.test.tsx index 0d88dcc4b44b7..100a6f67ad838 100644 --- a/x-pack/plugins/fleet/public/components/multiple_agent_policy_summary_line.test.tsx +++ b/x-pack/plugins/fleet/public/components/multiple_agent_policy_summary_line.test.tsx @@ -19,16 +19,22 @@ describe('MultipleAgentPolicySummaryLine', () => { let testRenderer: TestRenderer; const render = (agentPolicies: AgentPolicy[]) => - testRenderer.render(<MultipleAgentPoliciesSummaryLine policies={agentPolicies} />); + testRenderer.render( + <MultipleAgentPoliciesSummaryLine + policies={agentPolicies} + packagePolicyId="policy1" + onAgentPoliciesChange={jest.fn()} + /> + ); beforeEach(() => { testRenderer = createFleetTestRendererMock(); }); - test('it should render only the policy name when there is only one policy', async () => { + test('it should only render the policy name when there is only one policy', async () => { const results = render([{ name: 'Test policy', revision: 2 }] as AgentPolicy[]); - expect(results.container.textContent).toBe('Test policy'); - expect(results.queryByTestId('agentPolicyNameBadge')).toBeInTheDocument(); + expect(results.container.textContent).toBe('Test policyrev. 2'); + expect(results.queryByTestId('agentPolicyNameLink')).toBeInTheDocument(); expect(results.queryByTestId('agentPoliciesNumberBadge')).not.toBeInTheDocument(); }); @@ -38,7 +44,7 @@ describe('MultipleAgentPolicySummaryLine', () => { { name: 'Test policy 2', id: '0002' }, { name: 'Test policy 3', id: '0003' }, ] as AgentPolicy[]); - expect(results.queryByTestId('agentPolicyNameBadge')).toBeInTheDocument(); + expect(results.queryByTestId('agentPolicyNameLink')).toBeInTheDocument(); expect(results.queryByTestId('agentPoliciesNumberBadge')).toBeInTheDocument(); expect(results.container.textContent).toBe('Test policy 1+2'); @@ -50,5 +56,11 @@ describe('MultipleAgentPolicySummaryLine', () => { expect(results.queryByTestId('policy-0001')).toBeInTheDocument(); expect(results.queryByTestId('policy-0002')).toBeInTheDocument(); expect(results.queryByTestId('policy-0003')).toBeInTheDocument(); + + await act(async () => { + fireEvent.click(results.getByTestId('agentPoliciesPopoverButton')); + }); + + expect(results.queryByTestId('manageAgentPoliciesModal')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/fleet/public/components/multiple_agent_policy_summary_line.tsx b/x-pack/plugins/fleet/public/components/multiple_agent_policy_summary_line.tsx index 2a869f12bd817..0280989fb6eda 100644 --- a/x-pack/plugins/fleet/public/components/multiple_agent_policy_summary_line.tsx +++ b/x-pack/plugins/fleet/public/components/multiple_agent_policy_summary_line.tsx @@ -15,27 +15,41 @@ import { EuiButton, EuiListGroup, type EuiListGroupItemProps, + EuiLink, + EuiIconTip, + EuiText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { CSSProperties } from 'react'; import { useMemo } from 'react'; import React, { memo, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; + import type { AgentPolicy } from '../../common/types'; -import { useLink } from '../hooks'; +import { useAuthz, useLink } from '../hooks'; + +import { ManageAgentPoliciesModal } from './manage_agent_policies_modal'; const MIN_WIDTH: CSSProperties = { minWidth: 0 }; +const NO_WRAP_WHITE_SPACE: CSSProperties = { whiteSpace: 'nowrap' }; export const MultipleAgentPoliciesSummaryLine = memo<{ policies: AgentPolicy[]; direction?: 'column' | 'row'; -}>(({ policies, direction = 'row' }) => { + packagePolicyId: string; + onAgentPoliciesChange: () => void; +}>(({ policies, direction = 'row', packagePolicyId, onAgentPoliciesChange }) => { const { getHref } = useLink(); const [isPopoverOpen, setIsPopoverOpen] = useState(false); const closePopover = () => setIsPopoverOpen(false); + const [policiesModalEnabled, setPoliciesModalEnabled] = useState(false); + const authz = useAuthz(); + const canManageAgentPolicies = + authz.integrations.writeIntegrationPolicies && authz.fleet.allAgentPolicies; // as default, show only the first policy const policy = policies[0]; - const { name, id } = policy; + const { name, id, is_managed: isManaged, revision } = policy; const listItems: EuiListGroupItemProps[] = useMemo(() => { return policies.map((p) => { @@ -61,67 +75,120 @@ export const MultipleAgentPoliciesSummaryLine = memo<{ }, [getHref, policies]); return ( - <EuiFlexGroup direction="column" gutterSize="xs"> - <EuiFlexItem> - <EuiFlexGroup - direction={direction} - gutterSize={direction === 'column' ? 'none' : 's'} - alignItems="baseline" - style={MIN_WIDTH} - responsive={false} - justifyContent={'flexStart'} - > - <EuiFlexItem grow={false} className="eui-textTruncate"> - <EuiFlexGroup style={MIN_WIDTH} gutterSize="s" alignItems="baseline" responsive={false}> - <EuiFlexItem grow={false} className="eui-textTruncate"> - <EuiBadge color="default" data-test-subj="agentPolicyNameBadge"> - {name || id} - </EuiBadge> - </EuiFlexItem> - {policies.length > 1 && ( - <EuiFlexItem grow={false}> - <EuiBadge - color="hollow" - data-test-subj="agentPoliciesNumberBadge" - onClick={() => setIsPopoverOpen(!isPopoverOpen)} - onClickAriaLabel="Open agent policies popover" - > - {`+${policies.length - 1}`} - </EuiBadge> - <EuiPopover - data-test-subj="agentPoliciesPopover" - isOpen={isPopoverOpen} - closePopover={closePopover} - anchorPosition="downCenter" + <> + <EuiFlexGroup direction="column" gutterSize="xs"> + <EuiFlexItem> + <EuiFlexGroup + direction={direction} + gutterSize={direction === 'column' ? 'none' : 's'} + alignItems="baseline" + style={MIN_WIDTH} + responsive={false} + justifyContent={'flexStart'} + > + <EuiFlexItem grow={false} className="eui-textTruncate"> + <EuiFlexGroup + style={MIN_WIDTH} + gutterSize="s" + alignItems="baseline" + responsive={false} + > + <EuiFlexItem grow={false} className="eui-textTruncate"> + <EuiLink + className={`eui-textTruncate`} + href={getHref('policy_details', { policyId: id })} + title={name || id} + data-test-subj="agentPolicyNameLink" > - <EuiPopoverTitle> - {i18n.translate('xpack.fleet.agentPolicySummaryLine.popover.title', { - defaultMessage: 'This integration is shared by', - })} - </EuiPopoverTitle> - <div style={{ width: '280px' }}> - <EuiListGroup - listItems={listItems} - color="primary" - size="s" - gutterSize="none" + {name || id} + </EuiLink> + </EuiFlexItem> + {isManaged && ( + <EuiFlexItem grow={false}> + <EuiIconTip + title="Hosted agent policy" + content={i18n.translate( + 'xpack.fleet.agentPolicySummaryLine.hostedPolicyTooltip', + { + defaultMessage: + 'This policy is managed outside of Fleet. Most actions related to this policy are unavailable.', + } + )} + type="lock" + size="m" + color="subdued" + /> + </EuiFlexItem> + )} + {revision && ( + <EuiFlexItem grow={false}> + <EuiText color="subdued" size="xs" style={NO_WRAP_WHITE_SPACE}> + <FormattedMessage + id="xpack.fleet.agentPolicySummaryLine.revisionNumber" + defaultMessage="rev. {revNumber}" + values={{ revNumber: revision }} /> - </div> - <EuiPopoverFooter> - {/* TODO: implement missing onClick function */} - <EuiButton fullWidth size="s" data-test-subj="agentPoliciesPopoverButton"> - {i18n.translate('xpack.fleet.agentPolicySummaryLine.popover.button', { - defaultMessage: 'Manage agent policies', + </EuiText> + </EuiFlexItem> + )} + {policies.length > 1 && ( + <EuiFlexItem grow={false}> + <EuiBadge + color="hollow" + data-test-subj="agentPoliciesNumberBadge" + onClick={() => setIsPopoverOpen(!isPopoverOpen)} + onClickAriaLabel="Open agent policies popover" + > + +{policies.length - 1} + </EuiBadge> + <EuiPopover + data-test-subj="agentPoliciesPopover" + isOpen={isPopoverOpen} + closePopover={closePopover} + anchorPosition="downCenter" + > + <EuiPopoverTitle> + {i18n.translate('xpack.fleet.agentPolicySummaryLine.popover.title', { + defaultMessage: 'This integration is shared by', })} - </EuiButton> - </EuiPopoverFooter> - </EuiPopover> - </EuiFlexItem> - )} - </EuiFlexGroup> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexItem> - </EuiFlexGroup> + </EuiPopoverTitle> + <div style={{ width: '280px' }}> + <EuiListGroup + listItems={listItems} + color="primary" + size="s" + gutterSize="none" + /> + </div> + <EuiPopoverFooter> + <EuiButton + fullWidth + size="s" + data-test-subj="agentPoliciesPopoverButton" + onClick={() => setPoliciesModalEnabled(true)} + isDisabled={!canManageAgentPolicies} + > + {i18n.translate('xpack.fleet.agentPolicySummaryLine.popover.button', { + defaultMessage: 'Manage agent policies', + })} + </EuiButton> + </EuiPopoverFooter> + </EuiPopover> + </EuiFlexItem> + )} + </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGroup> + {policiesModalEnabled && ( + <ManageAgentPoliciesModal + onClose={() => setPoliciesModalEnabled(false)} + onAgentPoliciesChange={onAgentPoliciesChange} + selectedAgentPolicies={policies} + packagePolicyId={packagePolicyId} + /> + )} + </> ); }); From 5020721fddb1355b2b572372ef08d53a5fd84137 Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Tue, 2 Jul 2024 14:21:24 +0200 Subject: [PATCH 068/128] Add browser confirm to unsaved changes hook (#187011) ## Summary This adds a browser event-based event listener to the `useUnsavedChangesPrompt` hook, so that we also show an unsaved changes prompt when the user closes the browser or navigates outside our SPA. Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../unsaved_changes_prompt.test.tsx | 35 ++++++++++++++++++- .../unsaved_changes_prompt.tsx | 14 ++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.test.tsx b/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.test.tsx index bbd14526a0ac3..ea740880c4490 100644 --- a/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.test.tsx +++ b/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.test.tsx @@ -7,7 +7,7 @@ */ import { createMemoryHistory } from 'history'; -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act, cleanup } from '@testing-library/react-hooks'; import { coreMock } from '@kbn/core/public/mocks'; import { CoreScopedHistory } from '@kbn/core/public'; @@ -23,6 +23,20 @@ const navigateToUrl = jest.fn().mockImplementation(async (url) => { }); describe('useUnsavedChangesPrompt', () => { + let addSpy: jest.SpiedFunction<Window['addEventListener']>; + let removeSpy: jest.SpiedFunction<Window['removeEventListener']>; + + beforeEach(() => { + addSpy = jest.spyOn(window, 'addEventListener'); + removeSpy = jest.spyOn(window, 'removeEventListener'); + }); + + afterEach(() => { + addSpy.mockRestore(); + removeSpy.mockRestore(); + jest.resetAllMocks(); + }); + it('should not block if not edited', () => { renderHook(() => useUnsavedChangesPrompt({ @@ -39,6 +53,7 @@ describe('useUnsavedChangesPrompt', () => { expect(history.location.pathname).toBe('/test'); expect(history.location.search).toBe(''); expect(coreStart.overlays.openConfirm).not.toBeCalled(); + expect(addSpy).not.toBeCalledWith('beforeunload', expect.anything()); }); it('should block if edited', async () => { @@ -61,5 +76,23 @@ describe('useUnsavedChangesPrompt', () => { expect(navigateToUrl).toBeCalledWith('/mock/test', expect.anything()); expect(coreStart.overlays.openConfirm).toBeCalled(); + expect(addSpy).toBeCalledWith('beforeunload', expect.anything()); + }); + + it('beforeunload event should be cleaned up', async () => { + coreStart.overlays.openConfirm.mockResolvedValue(true); + + renderHook(() => + useUnsavedChangesPrompt({ + hasUnsavedChanges: true, + http: coreStart.http, + openConfirm: coreStart.overlays.openConfirm, + history, + navigateToUrl, + }) + ); + cleanup(); + expect(addSpy).toBeCalledWith('beforeunload', expect.anything()); + expect(removeSpy).toBeCalledWith('beforeunload', expect.anything()); }); }); diff --git a/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.tsx b/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.tsx index 20d815cffd8b3..f424e0b27e01f 100644 --- a/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.tsx +++ b/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.tsx @@ -51,6 +51,20 @@ export const useUnsavedChangesPrompt = ({ confirmButtonText = DEFAULT_CONFIRM_BUTTON, cancelButtonText = DEFAULT_CANCEL_BUTTON, }: Props) => { + useEffect(() => { + if (hasUnsavedChanges) { + const handler = (event: BeforeUnloadEvent) => { + // These 2 lines of code are the recommendation from MDN for triggering a browser prompt for confirming + // whether or not a user wants to leave the current site. + event.preventDefault(); + event.returnValue = ''; + }; + // Adding this handler will prompt users if they are navigating to a new page, outside of the Kibana SPA + window.addEventListener('beforeunload', handler); + return () => window.removeEventListener('beforeunload', handler); + } + }, [hasUnsavedChanges]); + useEffect(() => { if (!hasUnsavedChanges) { return; From 69c085873192562e31190e1d2d21453a4cd0a057 Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Tue, 2 Jul 2024 14:21:52 +0200 Subject: [PATCH 069/128] [Index Management/ML] Fix broken retries on inference id creation (#186961) ## Summary This fixes the inference endpoint creation API being called multiple times on error. The call will often time out because downloading/deploying the model takes longer than the Kibana request timeout limit. Setting the timeout limit higher would still be fragile, so ignoring the timeout error makes more sense. This PR also contains a few small language fixes and variable renames for clarity. --- .../field_parameters/select_inference_id.tsx | 4 +-- .../semantic_text/use_semantic_text.test.ts | 2 +- .../semantic_text/use_semantic_text.ts | 15 +++++------ .../details_page_mappings_content.tsx | 2 +- .../public/application/services/api.ts | 4 +-- .../public/application/services/index.ts | 2 +- ...ils_page_mappings_model_management.test.ts | 2 +- ..._details_page_mappings_model_management.ts | 4 +-- .../hooks/use_ml_model_status_toasts.ts | 7 +++-- .../model_management/models_provider.ts | 27 +++++++++++++++---- 10 files changed, 44 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx index 24b99ef9e7c08..e9a4387f91206 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx @@ -40,7 +40,7 @@ import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_ import { getFieldConfig } from '../../../lib'; import { useAppContext } from '../../../../../app_context'; import { Form, UseField, useForm } from '../../../shared_imports'; -import { useLoadInferenceModels } from '../../../../../services/api'; +import { useLoadInferenceEndpoints } from '../../../../../services/api'; import { getTrainedModelStats } from '../../../../../../hooks/use_details_page_mappings_model_management'; import { InferenceToModelIdMap } from '../fields'; import { useMLModelNotificationToasts } from '../../../../../../hooks/use_ml_model_status_toasts'; @@ -134,7 +134,7 @@ export const SelectInferenceId = ({ ]; }, []); - const { isLoading, data: models } = useLoadInferenceModels(); + const { isLoading, data: models } = useLoadInferenceEndpoints(); const [options, setOptions] = useState<EuiSelectableOption[]>([...defaultInferenceIds]); const inferenceIdOptionsFromModels = useMemo(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.test.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.test.ts index 4833562e58e31..f9bc12a9022fd 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.test.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.test.ts @@ -104,7 +104,7 @@ jest.mock('../../../../../../component_templates/component_templates_context', ( })); jest.mock('../../../../../../../services/api', () => ({ - getInferenceModels: jest.fn().mockResolvedValue({ + getInferenceEndpoints: jest.fn().mockResolvedValue({ data: [ { model_id: 'e5', diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.ts index 01a37275a54dd..72be2636329a2 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.ts @@ -17,7 +17,7 @@ import { FormHook } from '../../../../../shared_imports'; import { CustomInferenceEndpointConfig, DefaultInferenceModels, Field } from '../../../../../types'; import { useMLModelNotificationToasts } from '../../../../../../../../hooks/use_ml_model_status_toasts'; -import { getInferenceModels } from '../../../../../../../services/api'; +import { getInferenceEndpoints } from '../../../../../../../services/api'; interface UseSemanticTextProps { form: FormHook<Field, Field>; ml?: MlPluginStart; @@ -83,7 +83,7 @@ export function useSemanticText(props: UseSemanticTextProps) { if (data.inferenceId === undefined) { throw new Error( i18n.translate('xpack.idxMgmt.mappingsEditor.createField.undefinedInferenceIdError', { - defaultMessage: 'InferenceId is undefined while creating the inference endpoint.', + defaultMessage: 'Inference ID is undefined', }) ); } @@ -138,18 +138,17 @@ export function useSemanticText(props: UseSemanticTextProps) { dispatch({ type: 'field.addSemanticText', value: data }); try { - // if model exists already, do not create inference endpoint - const inferenceModels = await getInferenceModels(); + // if inference endpoint exists already, do not create inference endpoint + const inferenceModels = await getInferenceEndpoints(); const inferenceModel: InferenceAPIConfigResponse[] = inferenceModels.data.some( (e: InferenceAPIConfigResponse) => e.model_id === inferenceValue ); if (inferenceModel) { return; } - - if (trainedModelId) { - // show toasts only if it's elastic models - showSuccessToasts(); + // Only show toast if it's an internal Elastic model that hasn't been deployed yet + if (trainedModelId && inferenceData.isDeployable && !inferenceData.isDeployed) { + showSuccessToasts(trainedModelId); } await createInferenceEndpoint(trainedModelId, data, customInferenceEndpointConfig); diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx index effa53f717cde..1a7bf5a2ff337 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx @@ -227,7 +227,7 @@ export const DetailsPageMappingsContent: FunctionComponent<{ if (!error) { notificationService.showSuccessToast( i18n.translate('xpack.idxMgmt.indexDetails.mappings.successfullyUpdatedIndexMappings', { - defaultMessage: 'Index Mapping was successfully updated', + defaultMessage: 'Updated index mapping', }) ); refetchMapping(); diff --git a/x-pack/plugins/index_management/public/application/services/api.ts b/x-pack/plugins/index_management/public/application/services/api.ts index ce6907219930c..e071e0bf3c68c 100644 --- a/x-pack/plugins/index_management/public/application/services/api.ts +++ b/x-pack/plugins/index_management/public/application/services/api.ts @@ -442,14 +442,14 @@ export function updateIndexMappings(indexName: string, newFields: Fields) { }); } -export function getInferenceModels() { +export function getInferenceEndpoints() { return sendRequest({ path: `${API_BASE_PATH}/inference/all`, method: 'get', }); } -export function useLoadInferenceModels() { +export function useLoadInferenceEndpoints() { return useRequest<InferenceAPIConfigResponse[]>({ path: `${API_BASE_PATH}/inference/all`, method: 'get', diff --git a/x-pack/plugins/index_management/public/application/services/index.ts b/x-pack/plugins/index_management/public/application/services/index.ts index 34e1d8cedf784..5c34d83186c6a 100644 --- a/x-pack/plugins/index_management/public/application/services/index.ts +++ b/x-pack/plugins/index_management/public/application/services/index.ts @@ -28,7 +28,7 @@ export { loadIndexStatistics, useLoadIndexSettings, createIndex, - useLoadInferenceModels, + useLoadInferenceEndpoints, } from './api'; export { sortTable } from './sort_table'; diff --git a/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.test.ts b/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.test.ts index 1517b9664f3ea..c3d74c21ec528 100644 --- a/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.test.ts +++ b/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.test.ts @@ -36,7 +36,7 @@ jest.mock('../application/app_context', () => ({ })); jest.mock('../application/services/api', () => ({ - getInferenceModels: jest.fn().mockResolvedValue({ + getInferenceEndpoints: jest.fn().mockResolvedValue({ data: [ { model_id: 'e5', diff --git a/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.ts b/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.ts index 38cf20b9bf534..125892bdf6979 100644 --- a/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.ts +++ b/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.ts @@ -18,7 +18,7 @@ import { DeploymentState, NormalizedFields, } from '../application/components/mappings_editor/types'; -import { getInferenceModels } from '../application/services/api'; +import { getInferenceEndpoints } from '../application/services/api'; interface InferenceModel { data: InferenceAPIConfigResponse[]; @@ -91,7 +91,7 @@ export const useDetailsPageMappingsModelManagement = ( const dispatch = useDispatch(); const fetchInferenceModelsAndTrainedModelStats = useCallback(async () => { - const inferenceModels = await getInferenceModels(); + const inferenceModels = await getInferenceEndpoints(); const trainedModelStats = await ml?.mlApi?.trainedModels.getTrainedModelStats(); diff --git a/x-pack/plugins/index_management/public/hooks/use_ml_model_status_toasts.ts b/x-pack/plugins/index_management/public/hooks/use_ml_model_status_toasts.ts index c9a0c37a37fc9..cba440186a1d0 100644 --- a/x-pack/plugins/index_management/public/hooks/use_ml_model_status_toasts.ts +++ b/x-pack/plugins/index_management/public/hooks/use_ml_model_status_toasts.ts @@ -11,7 +11,7 @@ import { useComponentTemplatesContext } from '../application/components/componen export function useMLModelNotificationToasts() { const { toasts } = useComponentTemplatesContext(); - const showSuccessToasts = () => { + const showSuccessToasts = (modelName: string) => { return toasts.addSuccess({ title: i18n.translate( 'xpack.idxMgmt.mappingsEditor.createField.modelDeploymentStartedNotification', @@ -20,7 +20,10 @@ export function useMLModelNotificationToasts() { } ), text: i18n.translate('xpack.idxMgmt.mappingsEditor.createField.modelDeploymentNotification', { - defaultMessage: '1 model is being deployed on your ml_node.', + defaultMessage: 'Model {modelName} is being deployed on your machine learning node.', + values: { + modelName, + }, }), }); }; diff --git a/x-pack/plugins/ml/server/models/model_management/models_provider.ts b/x-pack/plugins/ml/server/models/model_management/models_provider.ts index c56e6ba599d0d..e76fa13dc8f6f 100644 --- a/x-pack/plugins/ml/server/models/model_management/models_provider.ts +++ b/x-pack/plugins/ml/server/models/model_management/models_provider.ts @@ -597,11 +597,28 @@ export class ModelsProvider { taskType: InferenceTaskType, modelConfig: InferenceModelConfig ) { - return await this._client.asCurrentUser.inference.putModel({ - inference_id: inferenceId, - task_type: taskType, - model_config: modelConfig, - }); + try { + const result = await this._client.asCurrentUser.inference.putModel( + { + inference_id: inferenceId, + task_type: taskType, + model_config: modelConfig, + }, + { maxRetries: 0 } + ); + return result; + } catch (error) { + // Request timeouts will usually occur when the model is being downloaded/deployed + // Erroring out is misleading in these cases, so we return the model_id and task_type + if (error.name === 'TimeoutError') { + return { + model_id: modelConfig.service, + task_type: taskType, + }; + } else { + throw error; + } + } } async getModelsDownloadStatus() { From 75cabd283a9df7cacf0cdb9d4f75c326d918c8c7 Mon Sep 17 00:00:00 2001 From: mohamedhamed-ahmed <mohamed.ahmed@elastic.co> Date: Tue, 2 Jul 2024 15:22:33 +0300 Subject: [PATCH 070/128] [Dataset Quality] Fix DS naming scheme link (#187321) closes https://github.com/elastic/kibana/issues/187320 --- .../components/dataset_quality/header.tsx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/header.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/header.tsx index da0e2e0c74626..4ec200b1b98c8 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/header.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/header.tsx @@ -11,16 +11,11 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { DEFAULT_LOGS_DATA_VIEW } from '../../../common/constants'; -import { useKibanaContextForPlugin } from '../../utils'; import { datasetQualityAppTitle } from '../../../common/translations'; // Allow for lazy loading // eslint-disable-next-line import/no-default-export export default function Header() { - const { - services: { docLinks }, - } = useKibanaContextForPlugin(); - return ( <EuiPageHeader bottomBorder @@ -38,19 +33,19 @@ export default function Header() { description={ <FormattedMessage id="xpack.datasetQuality.appDescription" - defaultMessage="Monitor the data set quality for {logsPattern} data streams that follow the {ecsNamingSchemeLink}." + defaultMessage="Monitor the data set quality for {logsPattern} data streams that follow the {dsNamingSchemeLink}." values={{ logsPattern: <EuiCode>{DEFAULT_LOGS_DATA_VIEW}</EuiCode>, - ecsNamingSchemeLink: ( + dsNamingSchemeLink: ( <EuiLink - data-test-subj="datasetQualityAppDescriptionEcsNamingSchemeLink" - href={docLinks.links.ecs.dataStreams} + data-test-subj="datasetQualityAppDescriptionDsNamingSchemeLink" + href="https://ela.st/data-stream-naming-scheme" target="_blank" rel="noopener" > <FormattedMessage - id="xpack.datasetQuality.appDescription.ecsNamingSchemeLinkText" - defaultMessage="ECS naming scheme" + id="xpack.datasetQuality.appDescription.dsNamingSchemeLinkText" + defaultMessage="Data stream naming scheme" /> </EuiLink> ), From 29a7141bf509bf13b34826373d1307bc223e1fad Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Tue, 2 Jul 2024 14:26:32 +0200 Subject: [PATCH 071/128] [Index Management] Add unsaved changes prompt (#186982) ## Summary This adds a prompt when users try to leave the mappings editor page with unsaved changes. <img width="1311" alt="Screenshot 2024-06-28 at 17 13 18" src="https://github.com/elastic/kibana/assets/94373878/50aac235-0671-4479-bb27-b2ebde7e6920"> ## Testing Go to the index management index details page Open the mappings tab Add a field to your mappings but don't hit save Try to navigate away within Kibana and you should see the above dismissable callout --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../helpers/setup_environment.tsx | 3 +++ .../public/application/app_context.tsx | 2 ++ .../public/application/index.tsx | 2 +- .../application/mount_management_section.ts | 1 + .../details_page_mappings_content.tsx | 22 ++++++++++++++++++- x-pack/plugins/index_management/tsconfig.json | 1 + 6 files changed, 29 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx index 020750cdd8b93..19490125e8840 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx @@ -88,6 +88,9 @@ const appDependencies = { enableTogglingDataRetention: true, enableSemanticText: false, }, + overlays: { + openConfirm: jest.fn(), + }, } as any; export const kibanaVersion = new SemVer(MAJOR_VERSION); diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx index f19575811725a..9cc0a426f47e3 100644 --- a/x-pack/plugins/index_management/public/application/app_context.tsx +++ b/x-pack/plugins/index_management/public/application/app_context.tsx @@ -18,6 +18,7 @@ import { ExecutionContextStart, HttpSetup, IUiSettingsClient, + OverlayStart, } from '@kbn/core/public'; import type { MlPluginStart } from '@kbn/ml-plugin/public'; import type { SharePluginStart } from '@kbn/share-plugin/public'; @@ -75,6 +76,7 @@ export interface AppDependencies { url: SharePluginStart['url']; docLinks: DocLinksStart; kibanaVersion: SemVer; + overlays: OverlayStart; } export const AppContextProvider = ({ diff --git a/x-pack/plugins/index_management/public/application/index.tsx b/x-pack/plugins/index_management/public/application/index.tsx index 48a19f58bbfa5..72821d6a194ae 100644 --- a/x-pack/plugins/index_management/public/application/index.tsx +++ b/x-pack/plugins/index_management/public/application/index.tsx @@ -80,7 +80,7 @@ export const IndexManagementAppContext: React.FC<IndexManagementAppContextProps> <KibanaRenderContextProvider {...core}> <KibanaReactContextProvider> <Provider store={indexManagementStore(services)}> - <AppContextProvider value={dependencies}> + <AppContextProvider value={{ ...dependencies, overlays }}> <MappingsEditorProvider> <ComponentTemplatesProvider value={componentTemplateProviderValues}> <GlobalFlyoutProvider>{children}</GlobalFlyoutProvider> diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts index d0cd5b07eab0f..da17bc4706c02 100644 --- a/x-pack/plugins/index_management/public/application/mount_management_section.ts +++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts @@ -99,6 +99,7 @@ export function getIndexManagementDependencies({ url, docLinks, kibanaVersion, + overlays: core.overlays, }; } diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx index 1a7bf5a2ff337..88902ff517c35 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx @@ -28,6 +28,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'; import { ILicense } from '@kbn/licensing-plugin/public'; +import { useUnsavedChangesPrompt } from '@kbn/unsaved-changes-prompt'; import { Index } from '../../../../../../common'; import { useDetailsPageMappingsModelManagement } from '../../../../../hooks/use_details_page_mappings_model_management'; import { useAppContext } from '../../../../app_context'; @@ -68,11 +69,14 @@ export const DetailsPageMappingsContent: FunctionComponent<{ services: { extensionsService }, core: { getUrlForApp, - application: { capabilities }, + application: { capabilities, navigateToUrl }, + http, }, plugins: { ml, licensing }, url, config, + overlays, + history, } = useAppContext(); const [isPlatinumLicense, setIsPlatinumLicense] = useState<boolean>(false); @@ -108,6 +112,22 @@ export const DetailsPageMappingsContent: FunctionComponent<{ }); const [isAddingFields, setAddingFields] = useState<boolean>(false); + + useUnsavedChangesPrompt({ + titleText: i18n.translate('xpack.idxMgmt.indexDetails.mappings.unsavedChangesPromptTitle', { + defaultMessage: 'Exit without saving changes?', + }), + messageText: i18n.translate('xpack.idxMgmt.indexDetails.mappings.unsavedChangesPromptMessage', { + defaultMessage: + 'Your changes will be lost if you leave this page without saving the mapping.', + }), + hasUnsavedChanges: isAddingFields, + openConfirm: overlays.openConfirm, + history, + http, + navigateToUrl, + }); + const newFieldsLength = useMemo(() => { return Object.keys(state.fields.byId).length; }, [state.fields.byId]); diff --git a/x-pack/plugins/index_management/tsconfig.json b/x-pack/plugins/index_management/tsconfig.json index e5d24269ba476..c734e465c003c 100644 --- a/x-pack/plugins/index_management/tsconfig.json +++ b/x-pack/plugins/index_management/tsconfig.json @@ -53,6 +53,7 @@ "@kbn/react-kibana-mount", "@kbn/rollup", "@kbn/ml-error-utils", + "@kbn/unsaved-changes-prompt", ], "exclude": ["target/**/*"] } From 73b695d8933711805574d84c2442e8086e201417 Mon Sep 17 00:00:00 2001 From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com> Date: Tue, 2 Jul 2024 08:28:09 -0400 Subject: [PATCH 072/128] [Security Solution] Adds diff algorithm and unit tests for scalar array values (#186323) ## Summary Related ticket: https://github.com/elastic/kibana/issues/180162 Adds the diff algorithm for arrays of scalar values (right now we only have fields of strings) and unit tests to cover all the base cases of what the algorithm can return. ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../three_way_diff/three_way_diff_outcome.ts | 46 ++- .../diff/calculation/algorithms/index.ts | 1 + .../algorithms/number_diff_algorithm.test.ts | 14 +- .../scalar_array_diff_algorithm.test.ts | 333 ++++++++++++++++++ .../algorithms/scalar_array_diff_algorithm.ts | 123 +++++++ .../single_line_string_diff_algorithm.test.ts | 14 +- 6 files changed, 516 insertions(+), 15 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/scalar_array_diff_algorithm.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/scalar_array_diff_algorithm.ts diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts index fd36a69dbde3c..afa63c01744e1 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts @@ -38,7 +38,51 @@ export const determineDiffOutcome = <TValue>( const baseEqlTarget = isEqual(baseVersion, targetVersion); const currentEqlTarget = isEqual(currentVersion, targetVersion); - if (baseVersion === MissingVersion) { + return getThreeWayDiffOutcome({ + baseEqlCurrent, + baseEqlTarget, + currentEqlTarget, + hasBaseVersion: baseVersion !== MissingVersion, + }); +}; + +/** + * Determines diff outcomes of array fields that do not care about order (e.g. `[1, 2 , 3] === [3, 2, 1]`) + */ +export const determineOrderAgnosticDiffOutcome = <TValue>( + baseVersion: TValue[] | MissingVersion, + currentVersion: TValue[], + targetVersion: TValue[] +): ThreeWayDiffOutcome => { + const baseSet = baseVersion === MissingVersion ? MissingVersion : new Set<TValue>(baseVersion); + const currentSet = new Set<TValue>(currentVersion); + const targetSet = new Set<TValue>(targetVersion); + const baseEqlCurrent = isEqual(baseSet, currentSet); + const baseEqlTarget = isEqual(baseSet, targetSet); + const currentEqlTarget = isEqual(currentSet, targetSet); + + return getThreeWayDiffOutcome({ + baseEqlCurrent, + baseEqlTarget, + currentEqlTarget, + hasBaseVersion: baseVersion !== MissingVersion, + }); +}; + +interface DetermineDiffOutcomeProps { + baseEqlCurrent: boolean; + baseEqlTarget: boolean; + currentEqlTarget: boolean; + hasBaseVersion: boolean; +} + +const getThreeWayDiffOutcome = ({ + baseEqlCurrent, + baseEqlTarget, + currentEqlTarget, + hasBaseVersion, +}: DetermineDiffOutcomeProps): ThreeWayDiffOutcome => { + if (!hasBaseVersion) { /** * We couldn't find the base version of the rule in the package so further * version comparison is not possible. We assume that the rule is not diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/index.ts index 57dd3553cb4c7..f2ffb98ad5432 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/index.ts @@ -7,4 +7,5 @@ export { numberDiffAlgorithm } from './number_diff_algorithm'; export { singleLineStringDiffAlgorithm } from './single_line_string_diff_algorithm'; +export { scalarArrayDiffAlgorithm } from './scalar_array_diff_algorithm'; export { simpleDiffAlgorithm } from './simple_diff_algorithm'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.test.ts index 43f6c9ed97e9d..ddefb27a397da 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.test.ts @@ -14,7 +14,7 @@ import { import { numberDiffAlgorithm } from './number_diff_algorithm'; describe('numberDiffAlgorithm', () => { - it('returns current_version as merged output if there is no update', () => { + it('returns current_version as merged output if there is no update - scenario AAA', () => { const mockVersions: ThreeVersionsOf<number> = { base_version: 1, current_version: 1, @@ -33,7 +33,7 @@ describe('numberDiffAlgorithm', () => { ); }); - it('returns current_version as merged output if current_version is different and there is no update', () => { + it('returns current_version as merged output if current_version is different and there is no update - scenario ABA', () => { const mockVersions: ThreeVersionsOf<number> = { base_version: 1, current_version: 2, @@ -52,7 +52,7 @@ describe('numberDiffAlgorithm', () => { ); }); - it('returns target_version as merged output if current_version is the same and there is an update', () => { + it('returns target_version as merged output if current_version is the same and there is an update - scenario AAB', () => { const mockVersions: ThreeVersionsOf<number> = { base_version: 1, current_version: 1, @@ -71,7 +71,7 @@ describe('numberDiffAlgorithm', () => { ); }); - it('returns current_version as merged output if current version is different but it matches the update', () => { + it('returns current_version as merged output if current version is different but it matches the update - scenario ABB', () => { const mockVersions: ThreeVersionsOf<number> = { base_version: 1, current_version: 2, @@ -90,7 +90,7 @@ describe('numberDiffAlgorithm', () => { ); }); - it('returns current_version as merged output if all three versions are different', () => { + it('returns current_version as merged output if all three versions are different - scenario ABC', () => { const mockVersions: ThreeVersionsOf<number> = { base_version: 1, current_version: 2, @@ -110,7 +110,7 @@ describe('numberDiffAlgorithm', () => { }); describe('if base_version is missing', () => { - it('returns current_version as merged output if current_version and target_version are the same', () => { + it('returns current_version as merged output if current_version and target_version are the same - scenario -AA', () => { const mockVersions: ThreeVersionsOf<number> = { base_version: MissingVersion, current_version: 1, @@ -129,7 +129,7 @@ describe('numberDiffAlgorithm', () => { ); }); - it('returns target_version as merged output if current_version and target_version are different', () => { + it('returns target_version as merged output if current_version and target_version are different - scenario -AB', () => { const mockVersions: ThreeVersionsOf<number> = { base_version: MissingVersion, current_version: 1, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/scalar_array_diff_algorithm.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/scalar_array_diff_algorithm.test.ts new file mode 100644 index 0000000000000..81f3c0272ac6e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/scalar_array_diff_algorithm.test.ts @@ -0,0 +1,333 @@ +/* + * 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 { ThreeVersionsOf } from '../../../../../../../../common/api/detection_engine'; +import { + ThreeWayDiffOutcome, + ThreeWayMergeOutcome, + MissingVersion, +} from '../../../../../../../../common/api/detection_engine'; +import { scalarArrayDiffAlgorithm } from './scalar_array_diff_algorithm'; + +describe('scalarArrayDiffAlgorithm', () => { + it('returns current_version as merged output if there is no update - scenario AAA', () => { + const mockVersions: ThreeVersionsOf<string[]> = { + base_version: ['one', 'two', 'three'], + current_version: ['one', 'two', 'three'], + target_version: ['one', 'two', 'three'], + }; + + const result = scalarArrayDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.StockValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_conflict: false, + }) + ); + }); + + it('returns current_version as merged output if current_version is different and there is no update - scenario ABA', () => { + const mockVersions: ThreeVersionsOf<string[]> = { + base_version: ['one', 'two', 'three'], + current_version: ['one', 'three', 'four'], + target_version: ['one', 'two', 'three'], + }; + + const result = scalarArrayDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_conflict: false, + }) + ); + }); + + it('returns target_version as merged output if current_version is the same and there is an update - scenario AAB', () => { + const mockVersions: ThreeVersionsOf<string[]> = { + base_version: ['one', 'two', 'three'], + current_version: ['one', 'two', 'three'], + target_version: ['one', 'four', 'three'], + }; + + const result = scalarArrayDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.target_version, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + }) + ); + }); + + it('returns current_version as merged output if current version is different but it matches the update - scenario ABB', () => { + const mockVersions: ThreeVersionsOf<string[]> = { + base_version: ['one', 'two', 'three'], + current_version: ['one', 'three', 'four'], + target_version: ['one', 'four', 'three'], + }; + + const result = scalarArrayDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_conflict: false, + }) + ); + }); + + it('returns custom merged version as merged output if all three versions are different - scenario ABC', () => { + const mockVersions: ThreeVersionsOf<string[]> = { + base_version: ['one', 'two', 'three'], + current_version: ['two', 'three', 'four', 'five'], + target_version: ['one', 'three', 'four', 'six'], + }; + const expectedMergedVersion = ['three', 'four', 'five', 'six']; + + const result = scalarArrayDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: expectedMergedVersion, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Merged, + has_conflict: false, + }) + ); + }); + + describe('if base_version is missing', () => { + it('returns current_version as merged output if current_version and target_version are the same - scenario -AA', () => { + const mockVersions: ThreeVersionsOf<string[]> = { + base_version: MissingVersion, + current_version: ['one', 'two', 'three'], + target_version: ['one', 'two', 'three'], + }; + + const result = scalarArrayDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.StockValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_conflict: false, + }) + ); + }); + + it('returns target_version as merged output if current_version and target_version are different - scenario -AB', () => { + const mockVersions: ThreeVersionsOf<string[]> = { + base_version: MissingVersion, + current_version: ['one', 'two', 'three'], + target_version: ['one', 'four', 'three'], + }; + + const result = scalarArrayDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.target_version, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + }) + ); + }); + }); + + describe('edge cases', () => { + it('compares arrays agnostic of order', () => { + const mockVersions: ThreeVersionsOf<string[]> = { + base_version: ['one', 'two', 'three'], + current_version: ['one', 'three', 'two'], + target_version: ['three', 'one', 'two'], + }; + + const result = scalarArrayDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.StockValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_conflict: false, + }) + ); + }); + + describe('compares arrays deduplicated', () => { + it('when values duplicated in base version', () => { + const mockVersions: ThreeVersionsOf<string[]> = { + base_version: ['one', 'two', 'two'], + current_version: ['one', 'two'], + target_version: ['one', 'two'], + }; + const expectedMergedVersion = ['one', 'two']; + + const result = scalarArrayDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: expectedMergedVersion, + diff_outcome: ThreeWayDiffOutcome.StockValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_conflict: false, + }) + ); + }); + + it('when values are duplicated in current version', () => { + const mockVersions: ThreeVersionsOf<string[]> = { + base_version: ['one', 'two'], + current_version: ['one', 'two', 'two'], + target_version: ['one', 'two'], + }; + const expectedMergedVersion = ['one', 'two']; + + const result = scalarArrayDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: expectedMergedVersion, + diff_outcome: ThreeWayDiffOutcome.StockValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_conflict: false, + }) + ); + }); + + it('when values are duplicated in target version', () => { + const mockVersions: ThreeVersionsOf<string[]> = { + base_version: ['one', 'two'], + current_version: ['one', 'two'], + target_version: ['one', 'two', 'two'], + }; + const expectedMergedVersion = ['one', 'two']; + + const result = scalarArrayDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: expectedMergedVersion, + diff_outcome: ThreeWayDiffOutcome.StockValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_conflict: false, + }) + ); + }); + + it('when values are duplicated in all versions', () => { + const mockVersions: ThreeVersionsOf<string[]> = { + base_version: ['one', 'two', 'two'], + current_version: ['two', 'two', 'three'], + target_version: ['one', 'one', 'three', 'three'], + }; + const expectedMergedVersion = ['three']; + + const result = scalarArrayDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: expectedMergedVersion, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Merged, + has_conflict: false, + }) + ); + }); + }); + + describe('compares empty arrays', () => { + it('when base version is empty', () => { + const mockVersions: ThreeVersionsOf<string[]> = { + base_version: [], + current_version: ['one', 'two'], + target_version: ['one', 'two'], + }; + + const result = scalarArrayDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_conflict: false, + }) + ); + }); + + it('when current version is empty', () => { + const mockVersions: ThreeVersionsOf<string[]> = { + base_version: ['one', 'two'], + current_version: [], + target_version: ['one', 'two'], + }; + + const result = scalarArrayDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_conflict: false, + }) + ); + }); + + it('when target version is empty', () => { + const mockVersions: ThreeVersionsOf<string[]> = { + base_version: ['one', 'two'], + current_version: ['one', 'two'], + target_version: [], + }; + + const result = scalarArrayDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.target_version, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + }) + ); + }); + + it('when all versions are empty', () => { + const mockVersions: ThreeVersionsOf<string[]> = { + base_version: [], + current_version: [], + target_version: [], + }; + + const result = scalarArrayDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: [], + diff_outcome: ThreeWayDiffOutcome.StockValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_conflict: false, + }) + ); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/scalar_array_diff_algorithm.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/scalar_array_diff_algorithm.ts new file mode 100644 index 0000000000000..18cf7f4f8b2cd --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/scalar_array_diff_algorithm.ts @@ -0,0 +1,123 @@ +/* + * 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 { difference, union, uniq } from 'lodash'; +import { assertUnreachable } from '../../../../../../../../common/utility_types'; +import type { + ThreeVersionsOf, + ThreeWayDiff, +} from '../../../../../../../../common/api/detection_engine/prebuilt_rules'; +import { + determineOrderAgnosticDiffOutcome, + determineIfValueCanUpdate, + ThreeWayDiffOutcome, + ThreeWayMergeOutcome, + MissingVersion, +} from '../../../../../../../../common/api/detection_engine/prebuilt_rules'; + +/** + * Diff algorithm used for arrays of scalar values (eg. numbers, strings, booleans, etc.) + * + * NOTE: Diffing logic will be agnostic to array order + */ +export const scalarArrayDiffAlgorithm = <TValue>( + versions: ThreeVersionsOf<TValue[]> +): ThreeWayDiff<TValue[]> => { + const { + base_version: baseVersion, + current_version: currentVersion, + target_version: targetVersion, + } = versions; + + const diffOutcome = determineOrderAgnosticDiffOutcome(baseVersion, currentVersion, targetVersion); + const valueCanUpdate = determineIfValueCanUpdate(diffOutcome); + + const { mergeOutcome, mergedVersion } = mergeVersions({ + baseVersion, + currentVersion, + targetVersion, + diffOutcome, + }); + + return { + base_version: baseVersion, + current_version: currentVersion, + target_version: targetVersion, + merged_version: mergedVersion, + + diff_outcome: diffOutcome, + merge_outcome: mergeOutcome, + has_update: valueCanUpdate, + has_conflict: mergeOutcome === ThreeWayMergeOutcome.Conflict, + }; +}; + +interface MergeResult<TValue> { + mergeOutcome: ThreeWayMergeOutcome; + mergedVersion: TValue[]; +} + +interface MergeArgs<TValue> { + baseVersion: TValue[] | MissingVersion; + currentVersion: TValue[]; + targetVersion: TValue[]; + diffOutcome: ThreeWayDiffOutcome; +} + +const mergeVersions = <TValue>({ + baseVersion, + currentVersion, + targetVersion, + diffOutcome, +}: MergeArgs<TValue>): MergeResult<TValue> => { + const dedupedBaseVersion = baseVersion !== MissingVersion ? uniq(baseVersion) : MissingVersion; + const dedupedCurrentVersion = uniq(currentVersion); + const dedupedTargetVersion = uniq(targetVersion); + + switch (diffOutcome) { + case ThreeWayDiffOutcome.StockValueNoUpdate: + case ThreeWayDiffOutcome.CustomizedValueNoUpdate: + case ThreeWayDiffOutcome.CustomizedValueSameUpdate: { + return { + mergeOutcome: ThreeWayMergeOutcome.Current, + mergedVersion: dedupedCurrentVersion, + }; + } + case ThreeWayDiffOutcome.StockValueCanUpdate: { + return { + mergeOutcome: ThreeWayMergeOutcome.Target, + mergedVersion: dedupedTargetVersion, + }; + } + case ThreeWayDiffOutcome.CustomizedValueCanUpdate: { + if (dedupedBaseVersion === MissingVersion) { + return { + mergeOutcome: ThreeWayMergeOutcome.Merged, + mergedVersion: union(currentVersion, targetVersion), + }; + } + + const addedCurrent = difference(dedupedCurrentVersion, dedupedBaseVersion); + const removedCurrent = difference(dedupedBaseVersion, dedupedCurrentVersion); + + const addedTarget = difference(dedupedTargetVersion, dedupedBaseVersion); + const removedTarget = difference(dedupedBaseVersion, dedupedTargetVersion); + + const bothAdded = union(addedCurrent, addedTarget); + const bothRemoved = union(removedCurrent, removedTarget); + + const merged = difference(union(dedupedBaseVersion, bothAdded), bothRemoved); + + return { + mergeOutcome: ThreeWayMergeOutcome.Merged, + mergedVersion: merged, + }; + } + default: + return assertUnreachable(diffOutcome); + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.test.ts index a4f5197979db4..427b592985e07 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.test.ts @@ -14,7 +14,7 @@ import { import { singleLineStringDiffAlgorithm } from './single_line_string_diff_algorithm'; describe('singleLineStringDiffAlgorithm', () => { - it('returns current_version as merged output if there is no update', () => { + it('returns current_version as merged output if there is no update - scenario AAA', () => { const mockVersions: ThreeVersionsOf<string> = { base_version: 'A', current_version: 'A', @@ -33,7 +33,7 @@ describe('singleLineStringDiffAlgorithm', () => { ); }); - it('returns current_version as merged output if current_version is different and there is no update', () => { + it('returns current_version as merged output if current_version is different and there is no update - scenario ABA', () => { const mockVersions: ThreeVersionsOf<string> = { base_version: 'A', current_version: 'B', @@ -52,7 +52,7 @@ describe('singleLineStringDiffAlgorithm', () => { ); }); - it('returns target_version as merged output if current_version is the same and there is an update', () => { + it('returns target_version as merged output if current_version is the same and there is an update - scenario AAB', () => { const mockVersions: ThreeVersionsOf<string> = { base_version: 'A', current_version: 'A', @@ -71,7 +71,7 @@ describe('singleLineStringDiffAlgorithm', () => { ); }); - it('returns current_version as merged output if current version is different but it matches the update', () => { + it('returns current_version as merged output if current version is different but it matches the update - scenario ABB', () => { const mockVersions: ThreeVersionsOf<string> = { base_version: 'A', current_version: 'B', @@ -90,7 +90,7 @@ describe('singleLineStringDiffAlgorithm', () => { ); }); - it('returns current_version as merged output if all three versions are different', () => { + it('returns current_version as merged output if all three versions are different - scenario ABC', () => { const mockVersions: ThreeVersionsOf<string> = { base_version: 'A', current_version: 'B', @@ -110,7 +110,7 @@ describe('singleLineStringDiffAlgorithm', () => { }); describe('if base_version is missing', () => { - it('returns current_version as merged output if current_version and target_version are the same', () => { + it('returns current_version as merged output if current_version and target_version are the same - scenario -AA', () => { const mockVersions: ThreeVersionsOf<string> = { base_version: MissingVersion, current_version: 'A', @@ -129,7 +129,7 @@ describe('singleLineStringDiffAlgorithm', () => { ); }); - it('returns target_version as merged output if current_version and target_version are different', () => { + it('returns target_version as merged output if current_version and target_version are different - scenario -AB', () => { const mockVersions: ThreeVersionsOf<string> = { base_version: MissingVersion, current_version: 'A', From 49d849f267c7afabeb0119a9efeca6c23928ee07 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria <jatin.kathuria@elastic.co> Date: Tue, 2 Jul 2024 15:00:53 +0200 Subject: [PATCH 073/128] [Threat Hunting] Include Threat hunting API tests in Quality Gate Peroidic pipeline (#186613) ## Summary Fixes #181684 This PR creates scripts required to run threat hunting tests API tests in ESS and Serverless mode for Quality Gate periodic run pipeline. --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Gloria Hornero <gloria.hornero@elastic.co> --- .../package.json | 52 ++++++++++++++++++- .../scripts/index.js | 30 ++++++++++- .../configs/serverless.config.ts | 8 +-- 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/x-pack/test/security_solution_api_integration/package.json b/x-pack/test/security_solution_api_integration/package.json index 52ff9a233b477..ffba287fb07cd 100644 --- a/x-pack/test/security_solution_api_integration/package.json +++ b/x-pack/test/security_solution_api_integration/package.json @@ -35,6 +35,12 @@ "initialize-server:lists:complete": "node ./scripts/index.js server lists_and_exception_lists trial_license_complete_tier", "run-tests:lists:complete": "node ./scripts/index.js runner lists_and_exception_lists trial_license_complete_tier", + "initialize-server:investigations": "node scripts/index.js server investigation trial_license_complete_tier", + "run-tests:investigations": "node scripts/index.js runner investigation trial_license_complete_tier", + + "intialize-server:explore": "node scripts/index.js server explore trial_license_complete_tier", + "run-tests:explore": "node scripts/index.js runner explore trial_license_complete_tier", + "genai:server:serverless": "npm run initialize-server:genai:trial_complete invoke_ai serverless", "genai:runner:serverless": "npm run run-tests:genai:trial_complete invoke_ai serverless serverlessEnv", "genai:qa:serverless": "npm run run-tests:genai:trial_complete invoke_ai serverless qaPeriodicEnv", @@ -302,6 +308,48 @@ "rules_management:essentials:qa:serverless": "npm run run-tests:rm:basic_essentials rule_management serverless qaPeriodicEnv", "rules_management:essentials:qa:serverless:release": "npm run run-tests:rm:basic_essentials rule_management serverless qaEnv", "rules_management:basic:server:ess": "npm run initialize-server:rm:basic_essentials rule_management ess", - "rules_management:basic:runner:ess": "npm run run-tests:rm:basic_essentials rule_management ess essEnv" + "rules_management:basic:runner:ess": "npm run run-tests:rm:basic_essentials rule_management ess essEnv", + + "investigations:timeline:server:serverless": "npm run initialize-server:investigations timeline serverless", + "investigations:timeline:runner:serverless": "npm run run-tests:investigations timeline serverless serverlessEnv", + "investigations:timeline:runner:qa:serverless": "npm run run-tests:investigations timeline serverless qaPeriodicEnv", + "investigations:timeline:runner:qa:serverless:release": "npm run run-tests:investigations timeline serverless qaEnv", + "investigations:timeline:server:ess": "npm run initialize-server:investigations timeline ess", + "investigations:timeline:runner:ess": "npm run run-tests:investigations timeline ess essEnv", + + "investigations:saved-objects:server:serverless": "npm run initialize-server:investigations saved_objects serverless", + "investigations:saved-objects:runner:serverless": "npm run run-tests:investigations saved_objects serverless serverlessEnv", + "investigations:saved-objects:runner:qa:serverless": "npm run run-tests:investigations saved_objects serverless qaPeriodicEnv", + "investigations:saved-objects:runner:qa:serverless:release": "npm run run-tests:investigations saved_objects serverless qaEnv", + "investigations:saved-objects:server:ess": "npm run initialize-server:investigations saved_objects ess", + "investigations:saved-objects:runner:ess": "npm run run-tests:investigations saved_objects ess essEnv", + + "explore:hosts:server:serverless": "npm run intialize-server:explore hosts serverless", + "explore:hosts:runner:serverless": "npm run run-tests:explore hosts serverless serverlessEnv", + "explore:hosts:runner:qa:serverless": "npm run run-tests:explore hosts serverless qaPeriodicEnv", + "explore:hosts:runner:qa:serverless:release": "npm run run-tests:explore hosts serverless qaEnv", + "explore:hosts:server:ess": "npm run intialize-server:explore hosts ess", + "explore:hosts:runner:ess": "npm run run-tests:explore hosts ess essEnv", + + "explore:network:server:serverless": "npm run intialize-server:explore network serverless", + "explore:network:runner:serverless": "npm run run-tests:explore network serverless serverlessEnv", + "explore:network:runner:qa:serverless": "npm run run-tests:explore network serverless qaPeriodicEnv", + "explore:network:runner:qa:serverless:release": "npm run run-tests:explore network serverless qaEnv", + "explore:network:server:ess": "npm run intialize-server:explore network ess", + "explore:network:runner:ess": "npm run run-tests:explore network ess essEnv", + + "explore:overview:server:serverless": "npm run intialize-server:explore overview serverless", + "explore:overview:runner:serverless": "npm run run-tests:explore overview serverless serverlessEnv", + "explore:overview:runner:qa:serverless": "npm run run-tests:explore overview serverless qaPeriodicEnv", + "explore:overview:runner:qa:serverless:release": "npm run run-tests:explore overview serverless qaEnv", + "explore:overview:server:ess": "npm run intialize-server:explore overview ess", + "explore:overview:runner:ess": "npm run run-tests:explore overview ess essEnv", + + "explore:users:server:serverless": "npm run intialize-server:explore users serverless", + "explore:users:runner:serverless": "npm run run-tests:explore users serverless serverlessEnv", + "explore:users:runner:qa:serverless": "npm run run-tests:explore users serverless qaPeriodicEnv", + "explore:users:runner:qa:serverless:release": "npm run run-tests:explore users serverless qaEnv", + "explore:users:server:ess": "npm run intialize-server:explore users ess", + "explore:users:runner:ess": "npm run run-tests:explore users ess essEnv" } -} +} \ No newline at end of file diff --git a/x-pack/test/security_solution_api_integration/scripts/index.js b/x-pack/test/security_solution_api_integration/scripts/index.js index 52f4964aa5111..c3f4637d2b137 100644 --- a/x-pack/test/security_solution_api_integration/scripts/index.js +++ b/x-pack/test/security_solution_api_integration/scripts/index.js @@ -9,6 +9,21 @@ const { spawn } = require('child_process'); const [, , type, area, licenseFolder, domain, projectType, environment, ...args] = process.argv; +const commandUsage = ` +Usage: node index.js <type> <area> <licenseFolder> <domain> <projectType> <environment> [args] + +Arguments: + type: server | runner + environment: serverlessEnv | essEnv | qaPeriodicEnv | qaEnv. Mandatory for runner type + +area, domain, licenseFolder, projectType, environment are required arguments to locate the config file with below path + : ./test_suites/<area>/<domain>/<licenseFolder>/configs/<projectType>.config.ts +`; + +if (!type || !area || !licenseFolder || !domain || !projectType) { + console.error(commandUsage); +} + const configPath = `./test_suites/${area}/${domain}/${licenseFolder}/configs/${projectType}.config.ts`; const command = @@ -37,11 +52,24 @@ if (type !== 'server') { break; default: - console.error(`Unsupported environment: ${environment}`); + console.error( + `Unsupported environment: ${environment}. + ${commandUsage} + ` + ); process.exit(1); } } +console.log( + "Spawning child process with command: 'node',", + command, + '--config', + configPath, + ...grepArgs, + ...args +); + const child = spawn('node', [command, '--config', configPath, ...grepArgs, ...args], { stdio: 'inherit', }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/configs/serverless.config.ts index b7d273add372b..a5d28b90c8dc9 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/configs/serverless.config.ts @@ -11,13 +11,13 @@ export default createTestConfig({ kbnTestServerArgs: [ `--xpack.securitySolution.enableExperimental=${JSON.stringify([])}`, `--xpack.securitySolutionServerless.productTypes=${JSON.stringify([ - { product_line: 'security', product_tier: 'essentials' }, - { product_line: 'endpoint', product_tier: 'essentials' }, - { product_line: 'cloud', product_tier: 'essentials' }, + { product_line: 'security', product_tier: 'complete' }, + { product_line: 'endpoint', product_tier: 'complete' }, + { product_line: 'cloud', product_tier: 'complete' }, ])}`, ], testFiles: [require.resolve('..')], junit: { - reportName: 'Saved Objects Integration Tests - Serverless Env - Essentials Tier', + reportName: 'Saved Objects Integration Tests - Serverless Env - Complete Tier', }, }); From 6b8819e2d4280f4d7178b43d02be77e347caeb70 Mon Sep 17 00:00:00 2001 From: Sherry Ger <sherry.ger@elastic.co> Date: Tue, 2 Jul 2024 06:15:37 -0700 Subject: [PATCH 074/128] Update java_settings.ts (#187305) Fix type - Cirtcuit -> Circuit ## Summary Summarize your PR. If it involves visual changes include a screenshot or gif. ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../agent_configuration/setting_definitions/java_settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_solution/apm/common/agent_configuration/setting_definitions/java_settings.ts b/x-pack/plugins/observability_solution/apm/common/agent_configuration/setting_definitions/java_settings.ts index b9f50ec9e85f6..efba2ca061e73 100644 --- a/x-pack/plugins/observability_solution/apm/common/agent_configuration/setting_definitions/java_settings.ts +++ b/x-pack/plugins/observability_solution/apm/common/agent_configuration/setting_definitions/java_settings.ts @@ -49,7 +49,7 @@ export const javaSettings: RawSettingDefinition[] = [ { key: 'circuit_breaker_enabled', label: i18n.translate('xpack.apm.agentConfig.circuitBreakerEnabled.label', { - defaultMessage: 'Cirtcuit breaker enabled', + defaultMessage: 'Circuit breaker enabled', }), type: 'boolean', category: 'Circuit-Breaker', From 307c03e9b982670132c02074f5b1f5ee5aae4300 Mon Sep 17 00:00:00 2001 From: Cristina Amico <criamico@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:16:59 +0200 Subject: [PATCH 075/128] [Fleet] Show a shared label in integration table (#187073) Closes https://github.com/elastic/kibana/issues/182219 ## Summary Show a `shared` label with a tooltip in integration table when an integration policy is shared by multiple policies. ![Screenshot 2024-06-27 at 16 46 03](https://github.com/elastic/kibana/assets/16084106/eb83b103-5540-4305-bf32-78d9d9508a0e) ![Screenshot 2024-06-27 at 16 46 08](https://github.com/elastic/kibana/assets/16084106/89785679-895e-4a68-84b7-37687ffb5896) ![Screenshot 2024-06-27 at 16 46 15](https://github.com/elastic/kibana/assets/16084106/5e4ca46c-077b-4093-ba9b-906e290d03b8) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../package_policies_table.tsx | 98 ++++++++++++++----- 1 file changed, 73 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx index a121c797757d7..e9bbde2520837 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { EuiInMemoryTableProps } from '@elastic/eui'; @@ -36,6 +36,7 @@ import { useIsPackagePolicyUpgradable, usePermissionCheck, useStartServices, + useMultipleAgentPolicies, } from '../../../../../hooks'; import { pkgKeyFromPackageInfo } from '../../../../../services'; @@ -63,9 +64,11 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({ const { application } = useStartServices(); const authz = useAuthz(); const canWriteIntegrationPolicies = authz.integrations.writeIntegrationPolicies; + const canReadAgentPolicies = authz.fleet.readAgentPolicies; const canReadIntegrationPolicies = authz.integrations.readIntegrationPolicies; const { isPackagePolicyUpgradable } = useIsPackagePolicyUpgradable(); const { getHref } = useLink(); + const { canUseMultipleAgentPolicies } = useMultipleAgentPolicies(); const permissionCheck = usePermissionCheck(); const missingSecurityConfiguration = @@ -99,6 +102,10 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({ return [mappedPackagePolicies, namespaceFilterOptions]; }, [originalPackagePolicies, isPackagePolicyUpgradable]); + const getSharedPoliciesNumber = useCallback((packagePolicy: PackagePolicy) => { + return packagePolicy.policy_ids.length || 0; + }, []); + const columns = useMemo( (): EuiInMemoryTableProps<InMemoryPackagePolicy>['columns'] => [ { @@ -109,29 +116,62 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({ defaultMessage: 'Name', }), render: (value: string, packagePolicy: InMemoryPackagePolicy) => ( - <EuiLink - title={value} - {...(canReadIntegrationPolicies - ? { - href: getHref('edit_integration', { - policyId: agentPolicy.id, - packagePolicyId: packagePolicy.id, - }), - } - : { disabled: true })} - > - <span className="eui-textTruncate" title={value}> - {value} - </span> - {packagePolicy.description ? ( - <span> -   - <EuiToolTip content={packagePolicy.description}> - <EuiIcon type="help" /> - </EuiToolTip> - </span> - ) : null} - </EuiLink> + <EuiFlexGroup gutterSize="s" alignItems="center"> + <EuiFlexItem data-test-subj="PackagePoliciesTableName" grow={false}> + <EuiLink + title={value} + {...(canReadIntegrationPolicies + ? { + href: getHref('edit_integration', { + policyId: agentPolicy.id, + packagePolicyId: packagePolicy.id, + }), + } + : { disabled: true })} + > + <span className="eui-textTruncate" title={value}> + {value} + </span> + {packagePolicy.description ? ( + <span> +   + <EuiToolTip content={packagePolicy.description}> + <EuiIcon type="help" /> + </EuiToolTip> + </span> + ) : null} + </EuiLink> + </EuiFlexItem> + {canUseMultipleAgentPolicies && + canReadAgentPolicies && + canReadIntegrationPolicies && + getSharedPoliciesNumber(packagePolicy) > 1 && ( + <EuiFlexItem grow={false}> + <EuiToolTip + content={ + <FormattedMessage + id="xpack.fleet.agentPolicyList.agentsColumn.sharedTooltip" + defaultMessage="This integration is shared by {numberShared} agent policies" + values={{ numberShared: getSharedPoliciesNumber(packagePolicy) }} + /> + } + > + <EuiText + data-test-subj="PackagePoliciesTableSharedLabel" + color="subdued" + size="xs" + className="eui-textNoWrap" + > + <FormattedMessage + id="xpack.fleet.agentPolicyList.agentsColumn.sharedText" + defaultMessage="Shared" + />{' '} + <EuiIcon type="iInCircle" /> + </EuiText> + </EuiToolTip> + </EuiFlexItem> + )} + </EuiFlexGroup> ), }, { @@ -265,7 +305,15 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({ ], }, ], - [agentPolicy, getHref, canWriteIntegrationPolicies, canReadIntegrationPolicies] + [ + canReadIntegrationPolicies, + getHref, + agentPolicy, + canUseMultipleAgentPolicies, + canReadAgentPolicies, + canWriteIntegrationPolicies, + getSharedPoliciesNumber, + ] ); return ( From 3a2c3f2c75f3883387c9282079d6e6741811edb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=BCrkan=20YALAMAN?= <efeguerkan.yalaman@elastic.co> Date: Tue, 2 Jul 2024 15:45:22 +0200 Subject: [PATCH 076/128] [Search] New Connector Configuration flows (#186541) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Changed connector configuration flow to include more user friendly deployment sections. See screenshots and recordings. https://github.com/elastic/kibana/assets/1410658/3c6a882b-1fff-45cb-ad5d-c90c0d1275c4 <img width="1279" alt="Screenshot 2024-07-01 at 15 01 30" src="https://github.com/elastic/kibana/assets/1410658/fa1b4fa6-df60-4a68-b231-35fc1d8b5f51"> <img width="1282" alt="Screenshot 2024-07-01 at 15 01 37" src="https://github.com/elastic/kibana/assets/1410658/af3103e2-8ffd-4d98-a5b0-d23acfd99448"> <img width="1239" alt="Screenshot 2024-07-01 at 15 02 31" src="https://github.com/elastic/kibana/assets/1410658/388fe8dd-7fbc-4e28-84dd-57b78aa73e95"> <img width="1229" alt="Screenshot 2024-07-01 at 15 02 36" src="https://github.com/elastic/kibana/assets/1410658/0f2de0f4-6df9-45cd-a140-64167bbfb870"> <img width="1229" alt="Screenshot 2024-07-01 at 15 02 42" src="https://github.com/elastic/kibana/assets/1410658/f369248b-176e-4e9d-84e7-ea64a6320538"> ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: José Luis González <joseluisgj@gmail.com> --- .../components/code_box.tsx | 130 ++-- .../lib/create_connector.ts | 8 + .../enterprise_search/common/constants.ts | 2 + .../common/types/error_codes.ts | 1 + .../api_key/get_api_key_by_id_api_logic.ts | 18 + .../generate_connector_api_key_api_logic.ts | 17 +- .../generate_connector_config_api_logic.ts | 23 + .../generate_api_key_logic.ts | 15 +- .../advanced_config_override_callout.tsx | 46 ++ .../components/configuration_skeleton.tsx | 31 + .../components/connector_linked.tsx | 32 + .../components/docker_instructions_step.tsx | 130 ++++ .../components/example_config_callout.tsx | 36 ++ .../components/generate_config_button.tsx | 46 ++ .../components/generated_config_fields.tsx | 282 +++++++++ .../components/run_from_source_step.tsx | 149 +++++ .../components/run_options_buttons.tsx | 88 +++ .../components/waiting_for_connector_step.tsx | 88 +++ .../components/whats_next_box.tsx | 133 ++++ .../connector_configuration.tsx | 566 ++++-------------- .../connector_detail/connector_view_logic.ts | 56 +- .../connector_detail/deployment.tsx | 250 ++++++++ .../connector_detail/deployment_logic.ts | 58 ++ .../native_connector_configuration.tsx | 359 ++++------- .../connectors/delete_connector_modal.tsx | 8 + .../connector/api_key_configuration.tsx | 19 +- .../search_index/connector/constants.ts | 15 + .../convert_connector.tsx | 10 +- .../native_connector_configuration_config.tsx | 27 +- .../research_configuration.tsx | 80 ++- .../header_actions/syncs_context_menu.tsx | 7 +- .../utils/connector_helpers.ts | 39 ++ .../shared/layout/page_template.tsx | 1 - .../server/lib/connectors/generate_config.ts | 48 ++ .../server/lib/indices/generate_index_name.ts | 31 + .../routes/enterprise_search/api_keys.ts | 34 ++ .../routes/enterprise_search/connectors.ts | 65 ++ .../translations/translations/fr-FR.json | 28 - .../translations/translations/ja-JP.json | 28 - .../translations/translations/zh-CN.json | 28 - 40 files changed, 2084 insertions(+), 948 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/api_key/get_api_key_by_id_api_logic.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/generate_connector_config_api_logic.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/advanced_config_override_callout.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/configuration_skeleton.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/connector_linked.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/docker_instructions_step.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/example_config_callout.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/generate_config_button.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/generated_config_fields.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/run_from_source_step.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/run_options_buttons.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/waiting_for_connector_step.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/whats_next_box.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment_logic.ts create mode 100644 x-pack/plugins/enterprise_search/server/lib/connectors/generate_config.ts create mode 100644 x-pack/plugins/enterprise_search/server/lib/indices/generate_index_name.ts diff --git a/packages/kbn-search-api-panels/components/code_box.tsx b/packages/kbn-search-api-panels/components/code_box.tsx index 57483af87e796..11bf2bea83318 100644 --- a/packages/kbn-search-api-panels/components/code_box.tsx +++ b/packages/kbn-search-api-panels/components/code_box.tsx @@ -31,17 +31,18 @@ import { LanguageDefinition } from '../types'; import './code_box.scss'; interface CodeBoxProps { - languages: LanguageDefinition[]; + languages?: LanguageDefinition[]; codeSnippet: string; // overrides the language type for syntax highlighting languageType?: string; - selectedLanguage: LanguageDefinition; - setSelectedLanguage: (language: LanguageDefinition) => void; - assetBasePath: string; + selectedLanguage?: LanguageDefinition; + setSelectedLanguage?: (language: LanguageDefinition) => void; + assetBasePath?: string; application?: ApplicationStart; consolePlugin?: ConsolePluginStart; - sharePlugin: SharePluginStart; + sharePlugin?: SharePluginStart; consoleRequest?: string; + showTopBar?: boolean; } export const CodeBox: React.FC<CodeBoxProps> = ({ @@ -55,23 +56,28 @@ export const CodeBox: React.FC<CodeBoxProps> = ({ setSelectedLanguage, sharePlugin, consoleRequest, + showTopBar = true, }) => { const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false); - const items = languages.map((language) => ( - <EuiContextMenuItem - key={language.id} - icon={`${assetBasePath}/${language.iconType}`} - onClick={() => { - setSelectedLanguage(language); - setIsPopoverOpen(false); - }} - > - {language.name} - </EuiContextMenuItem> - )); + const items = languages + ? languages.map((language) => ( + <EuiContextMenuItem + key={language.id} + icon={`${assetBasePath}/${language.iconType}`} + onClick={() => { + if (setSelectedLanguage) { + setSelectedLanguage(language); + setIsPopoverOpen(false); + } + }} + > + {language.name} + </EuiContextMenuItem> + )) + : []; - const button = ( + const button = selectedLanguage ? ( <EuiThemeProvider colorMode="dark"> <EuiButtonEmpty aria-label={i18n.translate('searchApiPanels.welcomeBanner.codeBox.selectAriaLabel', { @@ -85,52 +91,64 @@ export const CodeBox: React.FC<CodeBoxProps> = ({ {selectedLanguage.name} </EuiButtonEmpty> </EuiThemeProvider> - ); + ) : null; return ( <EuiThemeProvider colorMode="dark"> <EuiPanel paddingSize="xs" className="codeBoxPanel" data-test-subj="codeBlockControlsPanel"> - <EuiFlexGroup alignItems="center" responsive={false} gutterSize="s"> - <EuiFlexItem> - <EuiThemeProvider colorMode="light"> - <EuiPopover - button={button} - isOpen={isPopoverOpen} - closePopover={() => setIsPopoverOpen(false)} - panelPaddingSize="none" - anchorPosition="downLeft" - > - <EuiContextMenuPanel items={items} size="s" /> - </EuiPopover> - </EuiThemeProvider> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiCopy textToCopy={codeSnippet}> - {(copy) => ( - <EuiButtonEmpty color="text" iconType="copyClipboard" size="s" onClick={copy}> - {i18n.translate('searchApiPanels.welcomeBanner.codeBox.copyButtonLabel', { - defaultMessage: 'Copy', - })} - </EuiButtonEmpty> + {showTopBar && ( + <> + <EuiFlexGroup + alignItems="center" + responsive={false} + gutterSize="s" + justifyContent={languages && languages.length !== 0 ? 'spaceBetween' : 'flexEnd'} + > + {languages && button && ( + <EuiFlexItem> + <EuiThemeProvider colorMode="light"> + <EuiPopover + button={button} + isOpen={isPopoverOpen} + closePopover={() => setIsPopoverOpen(false)} + panelPaddingSize="none" + anchorPosition="downLeft" + > + <EuiContextMenuPanel items={items} size="s" /> + </EuiPopover> + </EuiThemeProvider> + </EuiFlexItem> + )} + <EuiFlexItem grow={false}> + <EuiCopy textToCopy={codeSnippet}> + {(copy) => ( + <EuiButtonEmpty color="text" iconType="copyClipboard" size="s" onClick={copy}> + {i18n.translate('searchApiPanels.welcomeBanner.codeBox.copyButtonLabel', { + defaultMessage: 'Copy', + })} + </EuiButtonEmpty> + )} + </EuiCopy> + </EuiFlexItem> + {consoleRequest !== undefined && sharePlugin && ( + <EuiFlexItem grow={false}> + <TryInConsoleButton + request={consoleRequest} + application={application} + consolePlugin={consolePlugin} + sharePlugin={sharePlugin} + /> + </EuiFlexItem> )} - </EuiCopy> - </EuiFlexItem> - {consoleRequest !== undefined && ( - <EuiFlexItem grow={false}> - <TryInConsoleButton - request={consoleRequest} - application={application} - consolePlugin={consolePlugin} - sharePlugin={sharePlugin} - /> - </EuiFlexItem> - )} - </EuiFlexGroup> - <EuiHorizontalRule margin="none" /> + </EuiFlexGroup> + <EuiHorizontalRule margin="none" /> + </> + )} <EuiCodeBlock + isCopyable={!showTopBar} transparentBackground fontSize="m" - language={languageType || selectedLanguage.languageStyling || selectedLanguage.id} + language={languageType || selectedLanguage?.languageStyling || selectedLanguage?.id} overflowHeight={500} className="codeBoxCodeBlock" > diff --git a/packages/kbn-search-connectors/lib/create_connector.ts b/packages/kbn-search-connectors/lib/create_connector.ts index 666011e50f341..5aaf8d2610dd0 100644 --- a/packages/kbn-search-connectors/lib/create_connector.ts +++ b/packages/kbn-search-connectors/lib/create_connector.ts @@ -53,6 +53,14 @@ export const createConnector = async ( }); } + if (input.configuration) { + await client.transport.request({ + method: 'PUT', + path: `/_connector/${connectorId}/_configuration`, + body: { configuration: input.configuration }, + }); + } + // createConnector function expects to return a Connector doc, so we fetch it from the index const connector = await fetchConnectorById(client, connectorId); diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index 9830f66a441ed..47c4741e41afc 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -214,6 +214,8 @@ export const ENTERPRISE_SEARCH_ELASTICSEARCH_URL = '/app/enterprise_search/elast export const WORKPLACE_SEARCH_URL = '/app/enterprise_search/workplace_search'; export const CREATE_NEW_INDEX_URL = '/search_indices/new_index'; +export const MANAGE_API_KEYS_URL = '/app/management/security/api_keys'; + export const ENTERPRISE_SEARCH_DOCUMENTS_DEFAULT_DOC_COUNT = 25; export const ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE = 'elastic-crawler'; diff --git a/x-pack/plugins/enterprise_search/common/types/error_codes.ts b/x-pack/plugins/enterprise_search/common/types/error_codes.ts index 1fe2d557d15c9..251cbbe27d05c 100644 --- a/x-pack/plugins/enterprise_search/common/types/error_codes.ts +++ b/x-pack/plugins/enterprise_search/common/types/error_codes.ts @@ -28,4 +28,5 @@ export enum ErrorCode { STATUS_TRANSITION_ERROR = 'status_transition_error', UNAUTHORIZED = 'unauthorized', UNCAUGHT_EXCEPTION = 'uncaught_exception', + GENERATE_INDEX_NAME_ERROR = 'generate_index_name_error', } diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/api_key/get_api_key_by_id_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/api_key/get_api_key_by_id_api_logic.ts new file mode 100644 index 0000000000000..ee4402cd393cc --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/api_key/get_api_key_by_id_api_logic.ts @@ -0,0 +1,18 @@ +/* + * 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 { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; +import { APIKeyResponse } from '../generate_api_key/generate_api_key_logic'; + +export const getApiKeyById = async (id: string) => { + const route = `/internal/enterprise_search/api_keys/${id}`; + + return await HttpLogic.values.http.get<APIKeyResponse>(route); +}; + +export const GetApiKeyByIdLogic = createApiLogic(['get_api_key_by_id_logic'], getApiKeyById); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/generate_connector_api_key_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/generate_connector_api_key_api_logic.ts index 7b67f21f05da7..cb3c512f660db 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/generate_connector_api_key_api_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/generate_connector_api_key_api_logic.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic'; import { HttpLogic } from '../../../shared/http'; export interface ApiKey { @@ -14,14 +14,12 @@ export interface ApiKey { id: string; name: string; } - -export const generateApiKey = async ({ - indexName, - isNative, -}: { +export interface GenerateConnectorApiKeyApiArgs { indexName: string; isNative: boolean; -}) => { +} + +export const generateApiKey = async ({ indexName, isNative }: GenerateConnectorApiKeyApiArgs) => { const route = `/internal/enterprise_search/indices/${indexName}/api_key`; const params = { is_native: isNative, @@ -35,3 +33,8 @@ export const GenerateConnectorApiKeyApiLogic = createApiLogic( ['generate_connector_api_key_api_logic'], generateApiKey ); + +export type GenerateConnectorApiKeyApiLogicActions = Actions< + GenerateConnectorApiKeyApiArgs, + ApiKey +>; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/generate_connector_config_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/generate_connector_config_api_logic.ts new file mode 100644 index 0000000000000..21edf734bc230 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/generate_connector_config_api_logic.ts @@ -0,0 +1,23 @@ +/* + * 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 { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export interface GenerateConfigApiArgs { + connectorId: string; +} + +export const generateConnectorConfig = async ({ connectorId }: GenerateConfigApiArgs) => { + const route = `/internal/enterprise_search/connectors/${connectorId}/generate_config`; + return await HttpLogic.values.http.post(route); +}; + +export const GenerateConfigApiLogic = createApiLogic( + ['generate_config_api_logic'], + generateConnectorConfig +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/generate_api_key/generate_api_key_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/generate_api_key/generate_api_key_logic.ts index d5d0f6c691dd2..26fe3476f642e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/generate_api_key/generate_api_key_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/generate_api_key/generate_api_key_logic.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic'; import { HttpLogic } from '../../../shared/http'; -interface APIKeyResponse { +export interface APIKeyResponse { apiKey: { api_key: string; encoded: string; @@ -17,13 +17,12 @@ interface APIKeyResponse { }; } -export const generateApiKey = async ({ - indexName, - keyName, -}: { +export interface GenerateApiKeyApiArgs { indexName: string; keyName: string; -}) => { +} + +export const generateApiKey = async ({ indexName, keyName }: GenerateApiKeyApiArgs) => { const route = `/internal/enterprise_search/${indexName}/api_keys`; return await HttpLogic.values.http.post<APIKeyResponse>(route, { @@ -34,3 +33,5 @@ export const generateApiKey = async ({ }; export const GenerateApiKeyLogic = createApiLogic(['generate_api_key_logic'], generateApiKey); + +export type GenerateApiKeyApiLogicActions = Actions<GenerateApiKeyApiArgs, APIKeyResponse>; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/advanced_config_override_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/advanced_config_override_callout.tsx new file mode 100644 index 0000000000000..d92571057b120 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/advanced_config_override_callout.tsx @@ -0,0 +1,46 @@ +/* + * 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 { EuiCallOut, EuiLink } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { docLinks } from '../../../../shared/doc_links'; + +export const AdvancedConfigOverrideCallout: React.FC = () => ( + <EuiCallOut + title={i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.advancedRulesCallout', + { defaultMessage: 'Configuration warning' } + )} + iconType="iInCircle" + color="warning" + > + <FormattedMessage + id="xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.advancedRulesCallout.description" + defaultMessage="{advancedSyncRulesDocs} can override some configuration fields." + values={{ + advancedSyncRulesDocs: ( + <EuiLink + data-test-subj="entSearchContent-connector-configuration-advancedSyncRulesDocsLink" + data-telemetry-id="entSearchContent-connector-configuration-advancedSyncRulesDocsLink" + href={docLinks.syncRules} + target="_blank" + > + {i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.advancedSyncRulesDocs', + { defaultMessage: 'Advanced Sync Rules' } + )} + </EuiLink> + ), + }} + /> + </EuiCallOut> +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/configuration_skeleton.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/configuration_skeleton.tsx new file mode 100644 index 0000000000000..85011b74c85cc --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/configuration_skeleton.tsx @@ -0,0 +1,31 @@ +/* + * 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 { EuiSkeletonTitle, EuiSpacer } from '@elastic/eui'; + +export const ConfigurationSkeleton: React.FC = () => ( + <> + <EuiSkeletonTitle size="m" /> + <EuiSpacer size="m" /> + <EuiSkeletonTitle size="xxs" /> + <EuiSpacer size="xs" /> + <EuiSkeletonTitle size="xxs" /> + <EuiSpacer size="xs" /> + <EuiSkeletonTitle size="xxs" /> + <EuiSpacer size="l" /> + <EuiSkeletonTitle size="m" /> + <EuiSpacer size="m" /> + <EuiSkeletonTitle size="xxs" /> + <EuiSpacer size="xs" /> + <EuiSkeletonTitle size="xxs" /> + <EuiSpacer size="xs" /> + <EuiSkeletonTitle size="xxs" /> + <EuiSpacer size="xs" /> + </> +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/connector_linked.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/connector_linked.tsx new file mode 100644 index 0000000000000..1f2359664144c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/connector_linked.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 React from 'react'; + +import { EuiCallOut } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export const ConnectorLinked: React.FC = () => { + return ( + <EuiCallOut + color="success" + title={i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.connectorLinked.callout.title', + { + defaultMessage: 'Connector connected', + } + )} + iconType="check" + > + {i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.connectorLinked.callout.description', + { + defaultMessage: 'Congratulations. Looks like your connector is deployed and connected.', + } + )} + </EuiCallOut> + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/docker_instructions_step.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/docker_instructions_step.tsx new file mode 100644 index 0000000000000..c3415b781c471 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/docker_instructions_step.tsx @@ -0,0 +1,130 @@ +/* + * 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, { useEffect } from 'react'; + +import { EuiAccordion, EuiAccordionProps, EuiCode, EuiSpacer, EuiText } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { CodeBox } from '@kbn/search-api-panels'; + +import { useCloudDetails } from '../../../../shared/cloud_details/cloud_details'; + +import { ApiKey } from '../../../api/connector/generate_connector_api_key_api_logic'; +import { + getConnectorTemplate, + getRunFromDockerSnippet, +} from '../../search_index/connector/constants'; + +export interface DockerInstructionsStepProps { + apiKeyData?: ApiKey; + connectorId: string; + hasApiKey: boolean; + isWaitingForConnector: boolean; + serviceType: string; +} +export const DockerInstructionsStep: React.FC<DockerInstructionsStepProps> = ({ + connectorId, + isWaitingForConnector, + serviceType, + apiKeyData, +}) => { + const [isOpen, setIsOpen] = React.useState<EuiAccordionProps['forceState']>('open'); + const { elasticsearchUrl } = useCloudDetails(); + + useEffect(() => { + if (!isWaitingForConnector) { + setIsOpen('closed'); + } + }, [isWaitingForConnector]); + + return ( + <> + <EuiAccordion + id="collapsibleDocker" + onToggle={() => setIsOpen(isOpen === 'closed' ? 'open' : 'closed')} + forceState={isOpen} + buttonContent={ + <EuiText size="s"> + <p> + {i18n.translate( + 'xpack.enterpriseSearch.connectorDeployment.p.downloadConfigurationLabel', + { + defaultMessage: + 'You can either download the configuration file manually or run the following command', + } + )} + </p> + </EuiText> + } + > + <EuiSpacer /> + <CodeBox + showTopBar={false} + languageType="bash" + codeSnippet={ + 'curl https://raw.githubusercontent.com/elastic/connectors/main/config.yml.example --output </absolute/path/to>/connectors' + } + /> + <EuiSpacer /> + <EuiText size="s"> + <p> + <FormattedMessage + id="xpack.enterpriseSearch.connectorDeployment.p.changeOutputPathLabel" + defaultMessage="Change the {output} argument value to the path where you want to save the configuration file." + values={{ + output: <EuiCode>--output</EuiCode>, + }} + /> + </p> + </EuiText> + <EuiSpacer /> + <FormattedMessage + id="xpack.enterpriseSearch.connectorDeployment.p.editConfigYamlLabel" + defaultMessage="Edit the {configYaml} file and provide the next credentials" + values={{ + configYaml: <EuiCode>config.yml</EuiCode>, + }} + /> + <EuiSpacer /> + <CodeBox + showTopBar={false} + languageType="yaml" + codeSnippet={getConnectorTemplate({ + apiKeyData, + connectorData: { + id: connectorId ?? '', + service_type: serviceType ?? '', + }, + host: elasticsearchUrl, + })} + /> + <EuiSpacer /> + <EuiText size="m"> + <p> + {i18n.translate( + 'xpack.enterpriseSearch.connectorDeployment.p.runTheFollowingCommandLabel', + { + defaultMessage: + 'Run the following command in your terminal. Make sure you have Docker installed on your machine', + } + )} + </p> + </EuiText> + <EuiSpacer /> + <CodeBox + showTopBar={false} + languageType="bash" + codeSnippet={getRunFromDockerSnippet({ + version: '8.15.0', + })} + /> + </EuiAccordion> + </> + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/example_config_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/example_config_callout.tsx new file mode 100644 index 0000000000000..a21dba1cbb19f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/example_config_callout.tsx @@ -0,0 +1,36 @@ +/* + * 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 { EuiCallOut, EuiSpacer, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +export const ExampleConfigCallout: React.FC = () => ( + <> + <EuiCallOut + iconType="iInCircle" + color="warning" + title={i18n.translate( + 'xpack.enterpriseSearch.content.connectors.overview.connectorUnsupportedCallOut.title', + { + defaultMessage: 'Example connector', + } + )} + > + <EuiSpacer size="s" /> + <EuiText size="s"> + <FormattedMessage + id="xpack.enterpriseSearch.content.connectors.overview.connectorUnsupportedCallOut.description" + defaultMessage="This is an example connector that serves as a building block for customizations. The design and code is being provided as-is with no warranties. This is not subject to the SLA of supported features." + /> + </EuiText> + </EuiCallOut> + <EuiSpacer /> + </> +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/generate_config_button.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/generate_config_button.tsx new file mode 100644 index 0000000000000..bb34d652ee74d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/generate_config_button.tsx @@ -0,0 +1,46 @@ +/* + * 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 { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export interface GenerateConfigButtonProps { + connectorId: string; + generateConfiguration: (params: { connectorId: string }) => void; + isGenerateLoading: boolean; +} +export const GenerateConfigButton: React.FC<GenerateConfigButtonProps> = ({ + connectorId, + generateConfiguration, + isGenerateLoading, +}) => { + return ( + <EuiFlexGroup direction="row" gutterSize="xs" responsive={false} alignItems="center"> + <EuiFlexItem grow={false}> + <EuiButton + data-test-subj="entSearchContent-connector-configuration-generateConfigButton" + data-telemetry-id="entSearchContent-connector-configuration-generateConfigButton" + fill + iconType="sparkles" + isLoading={isGenerateLoading} + onClick={() => { + generateConfiguration({ connectorId }); + }} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.generateApiKey.button.label', + { + defaultMessage: 'Generate configuration', + } + )} + </EuiButton> + </EuiFlexItem> + </EuiFlexGroup> + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/generated_config_fields.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/generated_config_fields.tsx new file mode 100644 index 0000000000000..acf6ae42c03bc --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/generated_config_fields.tsx @@ -0,0 +1,282 @@ +/* + * 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 } from 'react'; + +import { + EuiButtonIcon, + EuiCallOut, + EuiCode, + EuiConfirmModal, + EuiCopy, + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiSpacer, + EuiText, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { Connector } from '@kbn/search-connectors'; + +import { MANAGE_API_KEYS_URL } from '../../../../../../common/constants'; +import { generateEncodedPath } from '../../../../shared/encode_path_params'; +import { EuiLinkTo } from '../../../../shared/react_router_helpers'; + +import { ApiKey } from '../../../api/connector/generate_connector_api_key_api_logic'; +import { CONNECTOR_DETAIL_PATH, SEARCH_INDEX_PATH } from '../../../routes'; + +export interface GeneratedConfigFieldsProps { + apiKey?: ApiKey; + connector: Connector; + generateApiKey: () => void; + isGenerateLoading: boolean; +} + +const ConfirmModal: React.FC<{ + onCancel: () => void; + onConfirm: () => void; +}> = ({ onCancel, onConfirm }) => ( + <EuiConfirmModal + title={i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.title', + { + defaultMessage: 'Generate an Elasticsearch API key', + } + )} + onCancel={onCancel} + onConfirm={onConfirm} + cancelButtonText={i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.cancelButton.label', + { + defaultMessage: 'Cancel', + } + )} + confirmButtonText={i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.confirmButton.label', + { + defaultMessage: 'Generate API key', + } + )} + defaultFocusedButton="confirm" + > + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.description', + { + defaultMessage: + 'Generating a new API key will invalidate the previous key. Are you sure you want to generate a new API key? This can not be undone.', + } + )} + </EuiConfirmModal> +); + +export const GeneratedConfigFields: React.FC<GeneratedConfigFieldsProps> = ({ + apiKey, + connector, + generateApiKey, + isGenerateLoading, +}) => { + const [isModalVisible, setIsModalVisible] = useState(false); + + const refreshButtonClick = () => { + setIsModalVisible(true); + }; + const onCancel = () => { + setIsModalVisible(false); + }; + + const onConfirm = () => { + generateApiKey(); + setIsModalVisible(false); + }; + + return ( + <> + {isModalVisible && <ConfirmModal onCancel={onCancel} onConfirm={onConfirm} />} + <> + <EuiFlexGrid columns={3} alignItems="center" gutterSize="s"> + <EuiFlexItem> + <EuiFlexGroup responsive={false} gutterSize="xs"> + <EuiFlexItem grow={false}> + <EuiIcon type="check" /> + </EuiFlexItem> + <EuiFlexItem> + <EuiText size="s"> + <p> + {i18n.translate( + 'xpack.enterpriseSearch.connectorDeployment.connectorCreatedFlexItemLabel', + { defaultMessage: 'Connector created' } + )} + </p> + </EuiText> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + <EuiFlexItem> + <EuiLinkTo + to={generateEncodedPath(CONNECTOR_DETAIL_PATH, { + connectorId: connector.id, + })} + > + {connector.name} + </EuiLinkTo> + </EuiFlexItem> + <EuiFlexItem> + <EuiFlexGroup + responsive={false} + gutterSize="xs" + justifyContent="flexEnd" + alignItems="center" + > + <EuiFlexItem grow={false}> + <EuiLinkTo + to={generateEncodedPath(CONNECTOR_DETAIL_PATH, { + connectorId: connector.id, + })} + > + {connector.id} + </EuiLinkTo> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiCopy textToCopy={connector.id}> + {(copy) => ( + <EuiButtonIcon + size="xs" + data-test-subj="enterpriseSearchConnectorDeploymentButton" + iconType="copyClipboard" + onClick={copy} + /> + )} + </EuiCopy> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + <EuiFlexItem> + <EuiFlexGroup responsive={false} gutterSize="xs"> + <EuiFlexItem grow={false}> + <EuiIcon type="check" /> + </EuiFlexItem> + <EuiFlexItem> + {i18n.translate( + 'xpack.enterpriseSearch.connectorDeployment.indexCreatedFlexItemLabel', + { defaultMessage: 'Index created' } + )} + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + <EuiFlexItem> + {connector.index_name && ( + <EuiLinkTo + to={generateEncodedPath(SEARCH_INDEX_PATH, { + indexName: connector.index_name, + })} + > + {connector.index_name} + </EuiLinkTo> + )} + </EuiFlexItem> + <EuiFlexItem /> + <EuiFlexItem> + <EuiFlexGroup responsive={false} gutterSize="xs"> + <EuiFlexItem grow={false}> + <EuiIcon type="check" /> + </EuiFlexItem> + <EuiFlexItem> + {i18n.translate( + 'xpack.enterpriseSearch.connectorDeployment.apiKeyCreatedFlexItemLabel', + { defaultMessage: 'API key created' } + )} + {apiKey?.encoded && ` *`} + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiLink + data-test-subj="enterpriseSearchConnectorDeploymentLink" + href={generateEncodedPath(MANAGE_API_KEYS_URL, {})} + external + target="_blank" + > + {apiKey?.name} + </EuiLink> + </EuiFlexItem> + <EuiFlexItem> + <EuiFlexGroup + responsive={false} + gutterSize="xs" + justifyContent="flexEnd" + alignItems="center" + > + {apiKey?.encoded ? ( + <EuiFlexItem> + <EuiCopy textToCopy={apiKey?.encoded}> + {(copy) => ( + <EuiFlexGroup responsive={false} alignItems="center" gutterSize="xs"> + <EuiFlexItem> + <EuiCode>{apiKey?.encoded}</EuiCode> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButtonIcon + data-test-subj="enterpriseSearchGeneratedConfigFieldsButton" + size="xs" + iconType="refresh" + isLoading={isGenerateLoading} + onClick={refreshButtonClick} + disabled={!connector.index_name} + /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButtonIcon + size="xs" + data-test-subj="enterpriseSearchConnectorDeploymentButton" + iconType="copyClipboard" + onClick={copy} + /> + </EuiFlexItem> + </EuiFlexGroup> + )} + </EuiCopy> + </EuiFlexItem> + ) : ( + <EuiFlexItem grow={false}> + <EuiButtonIcon + data-test-subj="enterpriseSearchGeneratedConfigFieldsButton" + size="xs" + iconType="refresh" + isLoading={isGenerateLoading} + onClick={refreshButtonClick} + disabled={!connector.index_name} + /> + </EuiFlexItem> + )} + </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGrid> + + {apiKey?.encoded && ( + <> + <EuiSpacer size="m" /> + <EuiCallOut + color="success" + size="s" + title={i18n.translate( + 'xpack.enterpriseSearch.connectorDeployment.generatedConfigCallout', + { + defaultMessage: `You'll only see this API key once, so save it somewhere safe. We don't store your API keys, so if you lose a key you'll need to generate a replacement`, + } + )} + iconType="asterisk" + /> + </> + )} + </> + </> + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/run_from_source_step.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/run_from_source_step.tsx new file mode 100644 index 0000000000000..07df59597fa75 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/run_from_source_step.tsx @@ -0,0 +1,149 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; + +import dedent from 'dedent'; + +import { + EuiAccordion, + EuiAccordionProps, + EuiButton, + EuiCode, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiSpacer, + EuiText, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { CodeBox } from '@kbn/search-api-panels'; + +import { useCloudDetails } from '../../../../shared/cloud_details/cloud_details'; + +import { ApiKey } from '../../../api/connector/generate_connector_api_key_api_logic'; +import { getConnectorTemplate } from '../../search_index/connector/constants'; + +export interface RunFromSourceStepProps { + apiKeyData?: ApiKey; + connectorId?: string; + isWaitingForConnector: boolean; + serviceType: string; +} + +export const RunFromSourceStep: React.FC<RunFromSourceStepProps> = ({ + apiKeyData, + connectorId, + isWaitingForConnector, + serviceType, +}) => { + const [isOpen, setIsOpen] = React.useState<EuiAccordionProps['forceState']>('open'); + useEffect(() => { + if (!isWaitingForConnector) { + setIsOpen('closed'); + } + }, [isWaitingForConnector]); + + const { elasticsearchUrl } = useCloudDetails(); + + return ( + <> + <EuiText size="m"> + <p> + {i18n.translate( + 'xpack.enterpriseSearch.connectorDeployment.p.addTheFollowingConfigurationLabel', + { + defaultMessage: 'Clone or download the repo to your local machine', + } + )} + </p> + </EuiText> + <EuiSpacer size="s" /> + <EuiCode>git clone https://github.com/elastic/connectors</EuiCode>    + {i18n.translate('xpack.enterpriseSearch.connectorDeployment.orLabel', { + defaultMessage: 'or', + })} +     + <EuiButton + data-test-subj="enterpriseSearchConnectorDeploymentGoToSourceButton" + iconType="logoGithub" + href="https://github.com/elastic/connectors" + target="_blank" + > + <EuiFlexGroup responsive={false} gutterSize="xs"> + <EuiFlexItem> + {i18n.translate('xpack.enterpriseSearch.connectorDeployment.goToSourceButtonLabel', { + defaultMessage: 'Go to Source', + })} + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiIcon type="popout" /> + </EuiFlexItem> + </EuiFlexGroup> + </EuiButton> + <EuiSpacer size="s" /> + <EuiAccordion + id="collapsibleAccordion" + onToggle={() => setIsOpen(isOpen === 'closed' ? 'open' : 'closed')} + forceState={isOpen} + buttonContent={ + <EuiText size="s"> + <p> + <FormattedMessage + id="xpack.enterpriseSearch.connectorDeployment.p.editConfigLabel" + defaultMessage="Edit the {configYaml} file and provide the following configuration" + values={{ + configYaml: ( + <EuiCode> + {i18n.translate( + 'xpack.enterpriseSearch.connectorDeployment.configYamlCodeBlockLabel', + { defaultMessage: 'config.yml' } + )} + </EuiCode> + ), + }} + /> + </p> + </EuiText> + } + > + <EuiSpacer size="s" /> + <CodeBox + showTopBar={false} + languageType="yaml" + codeSnippet={getConnectorTemplate({ + apiKeyData, + connectorData: { + id: connectorId ?? '', + service_type: serviceType, + }, + host: elasticsearchUrl, + })} + /> + <EuiSpacer /> + <EuiText size="s"> + <p> + {i18n.translate('xpack.enterpriseSearch.connectorDeployment.p.compileAndRunLabel', { + defaultMessage: 'Compile and run', + })} + </p> + </EuiText> + <EuiSpacer /> + <CodeBox + showTopBar={false} + languageType="bash" + codeSnippet={dedent` + make install + make run + `} + /> + </EuiAccordion> + </> + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/run_options_buttons.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/run_options_buttons.tsx new file mode 100644 index 0000000000000..c0dd0ff23622d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/run_options_buttons.tsx @@ -0,0 +1,88 @@ +/* + * 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 { + EuiCheckableCard, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +export interface RunOptionsButtonsProps { + selectDeploymentMethod: (method: 'docker' | 'source') => void; + selectedDeploymentMethod: 'docker' | 'source' | null; +} + +export const RunOptionsButtons: React.FC<RunOptionsButtonsProps> = ({ + selectDeploymentMethod, + selectedDeploymentMethod, +}) => { + return ( + <> + <EuiSpacer size="s" /> + <EuiText size="s"> + <FormattedMessage + id="xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.description" + defaultMessage="The connector service is a Python package that you host on your own infrastructure. You can deploy with Docker or, optionally, run from source." + /> + <EuiSpacer /> + <EuiFlexGroup direction="row"> + <EuiFlexItem> + <EuiCheckableCard + onChange={() => selectDeploymentMethod('docker')} + id="xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.runConnectorService.docker" + checked={selectedDeploymentMethod === 'docker'} + label={ + <EuiFlexGroup responsive={false} gutterSize="s" alignItems="center"> + <EuiFlexItem grow={false}> + <EuiIcon type="logoDocker" size="l" /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiText> + {i18n.translate( + 'xpack.enterpriseSearch.connectorConfiguration.dockerTextLabel', + { defaultMessage: 'Run with Docker' } + )} + </EuiText> + </EuiFlexItem> + </EuiFlexGroup> + } + /> + </EuiFlexItem> + <EuiFlexItem> + <EuiCheckableCard + onChange={() => selectDeploymentMethod('source')} + id="xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.runConnectorService.source" + checked={selectedDeploymentMethod === 'source'} + label={ + <EuiFlexGroup responsive={false} gutterSize="s" alignItems="center"> + <EuiFlexItem grow={false}> + <EuiIcon type="logoGithub" size="l" /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiText> + {i18n.translate( + 'xpack.enterpriseSearch.connectorConfiguration.sourceTextLabel', + { defaultMessage: 'Run from source' } + )} + </EuiText> + </EuiFlexItem> + </EuiFlexGroup> + } + /> + </EuiFlexItem> + </EuiFlexGroup> + </EuiText> + </> + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/waiting_for_connector_step.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/waiting_for_connector_step.tsx new file mode 100644 index 0000000000000..eb22897e65072 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/waiting_for_connector_step.tsx @@ -0,0 +1,88 @@ +/* + * 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 { EuiButton, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export interface WaitingForConnectorStepProps { + isLoading: boolean; + isRecheckDisabled: boolean; + recheck: () => void; + showFinishLaterButton?: boolean; +} +export const WaitingForConnectorStep: React.FC<WaitingForConnectorStepProps> = ({ + recheck, + isLoading, + isRecheckDisabled, + showFinishLaterButton = false, +}) => { + return ( + <> + <EuiSpacer /> + <EuiCallOut + color="warning" + title={i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.callout.title', + { + defaultMessage: 'Waiting for your connector', + } + )} + iconType="iInCircle" + > + {i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.callout.description', + { + defaultMessage: + 'Your connector has not connected to Search. Troubleshoot your configuration and refresh the page.', + } + )} + <EuiSpacer size="s" /> + <EuiFlexGroup direction="row" responsive={false}> + <EuiFlexItem grow={false}> + <EuiButton + color="warning" + fill + disabled={isRecheckDisabled} + data-test-subj="entSearchContent-connector-waitingForConnector-callout-recheckNow" + data-telemetry-id="entSearchContent-connector-waitingForConnector-callout-recheckNow" + iconType="refresh" + onClick={recheck} + isLoading={isLoading} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.callout.button.label', + { + defaultMessage: 'Recheck now', + } + )} + </EuiButton> + </EuiFlexItem> + {showFinishLaterButton && ( + <EuiFlexItem grow={false}> + <EuiButton + color="warning" + data-test-subj="entSearchContent-connector-waitingForConnector-callout-finishLaterButton" + data-telemetry-id="entSearchContent-connector-waitingForConnector-callout-finishLaterButton" + iconType="save" + onClick={() => {}} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.callout.finishLaterButton.label', + { + defaultMessage: 'Finish deployment later', + } + )} + </EuiButton> + </EuiFlexItem> + )} + </EuiFlexGroup> + </EuiCallOut> + </> + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/whats_next_box.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/whats_next_box.tsx new file mode 100644 index 0000000000000..2e04c094e7d7e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/whats_next_box.tsx @@ -0,0 +1,133 @@ +/* + * 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 { useValues } from 'kea'; + +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiProgress, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { ConnectorStatus } from '@kbn/search-connectors'; + +import { APPLICATIONS_PLUGIN } from '../../../../../../common/constants'; + +import { PLAYGROUND_PATH } from '../../../../applications/routes'; +import { generateEncodedPath } from '../../../../shared/encode_path_params'; +import { KibanaLogic } from '../../../../shared/kibana'; +import { EuiButtonTo } from '../../../../shared/react_router_helpers'; +import { CONNECTOR_DETAIL_TAB_PATH } from '../../../routes'; +import { SyncsContextMenu } from '../../shared/header_actions/syncs_context_menu'; + +import { ConnectorDetailTabId } from '../connector_detail'; + +export interface WhatsNextBoxProps { + connectorId: string; + connectorIndex?: string; + connectorStatus: ConnectorStatus; + disabled?: boolean; + isSyncing?: boolean; + isWaitingForConnector?: boolean; +} + +export const WhatsNextBox: React.FC<WhatsNextBoxProps> = ({ + connectorId, + connectorIndex, + connectorStatus, + disabled = false, + isSyncing = false, + isWaitingForConnector = false, +}) => { + const { navigateToUrl } = useValues(KibanaLogic); + const isConfigured = !( + connectorStatus === ConnectorStatus.NEEDS_CONFIGURATION || + connectorStatus === ConnectorStatus.CREATED + ); + return ( + <EuiPanel hasBorder style={{ position: 'relative' }}> + {isSyncing && <EuiProgress size="xs" position="absolute" />} + <EuiTitle size="s"> + <h3> + {i18n.translate('xpack.enterpriseSearch.whatsNextBox.whatsNextPanelLabel', { + defaultMessage: "What's next?", + })} + </h3> + </EuiTitle> + <EuiSpacer /> + <EuiText> + <p> + {i18n.translate('xpack.enterpriseSearch.whatsNextBox.whatsNextPanelDescription', { + defaultMessage: + 'You can manually sync your data, schedule a recurring sync or see your documents.', + })} + </p> + </EuiText> + <EuiSpacer /> + <EuiFlexGroup responsive={false} gutterSize="s"> + <EuiFlexItem grow={false}> + <EuiButton + data-test-subj="enterpriseSearchWhatsNextBoxSearchPlaygroundButton" + iconType="sparkles" + disabled={!connectorIndex || disabled} + onClick={() => { + navigateToUrl( + `${APPLICATIONS_PLUGIN.URL}${PLAYGROUND_PATH}?default-index=${connectorIndex}`, + { + shouldNotCreateHref: true, + } + ); + }} + > + <FormattedMessage + id="xpack.enterpriseSearch.whatsNextBox.searchPlaygroundButtonLabel" + defaultMessage="Search Playground" + /> + </EuiButton> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButtonTo + data-test-subj="entSearchContent-connector-configuration-setScheduleAndSync" + data-telemetry-id="entSearchContent-connector-configuration-setScheduleAndSync" + isDisabled={isWaitingForConnector || !connectorIndex || !isConfigured} + to={`${generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, { + connectorId, + tabId: ConnectorDetailTabId.SCHEDULING, + })}`} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.schedule.button.label', + { + defaultMessage: 'Set schedule and sync', + } + )} + </EuiButtonTo> + </EuiFlexItem> + + <EuiFlexItem> + <EuiFlexGroup responsive={false} gutterSize="xs"> + <EuiFlexItem grow={false}> + <SyncsContextMenu + disabled={isWaitingForConnector || !connectorIndex || !isConfigured} + /> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGroup> + </EuiPanel> + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_configuration.tsx index 70c5c46902b69..e0aa934f17954 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_configuration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_configuration.tsx @@ -10,73 +10,54 @@ import React, { useMemo } from 'react'; import { useActions, useValues } from 'kea'; import { - EuiText, + EuiBadge, EuiFlexGroup, EuiFlexItem, - EuiLink, + EuiIcon, EuiPanel, + EuiSkeletonLoading, EuiSpacer, - EuiSteps, - EuiCodeBlock, - EuiCallOut, - EuiButton, + EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - import { ConnectorConfigurationComponent, ConnectorStatus } from '@kbn/search-connectors'; -import { EXAMPLE_CONNECTOR_SERVICE_TYPES } from '../../../../../common/constants'; - import { Status } from '../../../../../common/types/api'; -import { BetaConnectorCallout } from '../../../shared/beta/beta_connector_callout'; -import { useCloudDetails } from '../../../shared/cloud_details/cloud_details'; import { docLinks } from '../../../shared/doc_links'; -import { generateEncodedPath } from '../../../shared/encode_path_params'; import { HttpLogic } from '../../../shared/http'; import { KibanaLogic } from '../../../shared/kibana'; import { LicensingLogic } from '../../../shared/licensing'; -import { EuiButtonTo, EuiLinkTo } from '../../../shared/react_router_helpers'; -import { GenerateConnectorApiKeyApiLogic } from '../../api/connector/generate_connector_api_key_api_logic'; -import { CONNECTOR_DETAIL_TAB_PATH } from '../../routes'; -import { isLastSeenOld } from '../../utils/connector_status_helpers'; -import { isAdvancedSyncRuleSnippetEmpty } from '../../utils/sync_rules_helpers'; -import { ApiKeyConfig } from '../search_index/connector/api_key_configuration'; - -import { getConnectorTemplate } from '../search_index/connector/constants'; +import { hasNonEmptyAdvancedSnippet, isExampleConnector } from '../../utils/connector_helpers'; import { ConnectorFilteringLogic } from '../search_index/connector/sync_rules/connector_filtering_logic'; -import { SyncsContextMenu } from '../shared/header_actions/syncs_context_menu'; + +import { IndexViewLogic } from '../search_index/index_view_logic'; import { AttachIndexBox } from './attach_index_box'; -import { ConnectorDetailTabId } from './connector_detail'; +import { AdvancedConfigOverrideCallout } from './components/advanced_config_override_callout'; +import { ConfigurationSkeleton } from './components/configuration_skeleton'; +import { ExampleConfigCallout } from './components/example_config_callout'; +import { WhatsNextBox } from './components/whats_next_box'; import { ConnectorViewLogic } from './connector_view_logic'; +import { ConnectorDeployment } from './deployment'; import { NativeConnectorConfiguration } from './native_connector_configuration'; export const ConnectorConfiguration: React.FC = () => { - const { data: apiKeyData } = useValues(GenerateConnectorApiKeyApiLogic); - const { - index, - isLoading, - connector, - updateConnectorConfigurationStatus, - hasAdvancedFilteringFeature, - } = useValues(ConnectorViewLogic); - const cloudContext = useCloudDetails(); + const { connector, updateConnectorConfigurationStatus } = useValues(ConnectorViewLogic); + const { connectorTypes: connectors } = useValues(KibanaLogic); + const { isSyncing, isWaitingForSync } = useValues(IndexViewLogic); const { hasPlatinumLicense } = useValues(LicensingLogic); - const { errorConnectingMessage, http } = useValues(HttpLogic); + const { http } = useValues(HttpLogic); const { advancedSnippet } = useValues(ConnectorFilteringLogic); - const isAdvancedSnippetEmpty = isAdvancedSyncRuleSnippetEmpty(advancedSnippet); - const { connectorTypes } = useValues(KibanaLogic); - const BETA_CONNECTORS = useMemo( - () => connectorTypes.filter(({ isBeta }) => isBeta), - [connectorTypes] + const NATIVE_CONNECTORS = useMemo( + () => connectors.filter(({ isNative }) => isNative), + [connectors] ); - const { fetchConnector, updateConnectorConfiguration } = useActions(ConnectorViewLogic); + const { updateConnectorConfiguration } = useActions(ConnectorViewLogic); if (!connector) { return <></>; @@ -86,435 +67,112 @@ export const ConnectorConfiguration: React.FC = () => { return <NativeConnectorConfiguration />; } - const hasApiKey = !!(connector.api_key_id ?? apiKeyData); - const docsUrl = connectorTypes.find( - ({ serviceType }) => serviceType === connector.service_type - )?.docsUrl; + const isWaitingForConnector = !connector.status || connector.status === ConnectorStatus.CREATED; - const isBeta = Boolean( - BETA_CONNECTORS.find(({ serviceType }) => serviceType === connector.service_type) - ); + const nativeConnector = NATIVE_CONNECTORS.find( + (connectorDefinition) => connectorDefinition.serviceType === connector.service_type + ) || { + docsUrl: '', + externalAuthDocsUrl: '', + externalDocsUrl: '', + iconPath: 'custom.svg', + isBeta: true, + isNative: false, + keywords: [], + name: connector.name, + serviceType: connector.service_type ?? '', + }; + + const iconPath = nativeConnector.iconPath; return ( <> - <EuiSpacer /> { // TODO remove this callout when example status is removed - connector && - connector.service_type && - EXAMPLE_CONNECTOR_SERVICE_TYPES.includes(connector.service_type) && ( - <> - <EuiCallOut - iconType="iInCircle" - color="warning" - title={i18n.translate( - 'xpack.enterpriseSearch.content.connectors.overview.connectorUnsupportedCallOut.title', - { - defaultMessage: 'Example connector', - } - )} - > - <EuiSpacer size="s" /> - <EuiText size="s"> - <FormattedMessage - id="xpack.enterpriseSearch.content.connectors.overview.connectorUnsupportedCallOut.description" - defaultMessage="This is an example connector that serves as a building block for customizations. The design and code is being provided as-is with no warranties. This is not subject to the SLA of supported features." - /> - </EuiText> - </EuiCallOut> - <EuiSpacer /> - </> - ) + isExampleConnector(connector) && <ExampleConfigCallout /> } <EuiFlexGroup> - <EuiFlexItem grow={2}> - <EuiPanel hasShadow={false} hasBorder> - { - <> - <EuiSpacer /> - <AttachIndexBox connector={connector} /> - </> - } - {connector.index_name && ( - <> - <EuiSpacer /> - <EuiSteps - steps={[ - { - children: ( - <ApiKeyConfig - indexName={connector.index_name} - hasApiKey={!!connector.api_key_id} - isNative={false} - /> - ), - status: hasApiKey ? 'complete' : 'incomplete', - title: i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.generateApiKey.title', - { - defaultMessage: 'Generate an API key', - } - ), - titleSize: 'xs', - }, - { - children: ( - <> - <EuiSpacer /> - <EuiText size="s"> - <FormattedMessage - id="xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.description.thirdParagraph" - defaultMessage="In this step, you will need the API key and connector ID values for your config.yml file. Here's an {exampleLink}." - values={{ - exampleLink: ( - <EuiLink - data-test-subj="entSearchContent-connector-configuration-exampleConfigFileLink" - data-telemetry-id="entSearchContent-connector-configuration-exampleConfigFileLink" - href="https://github.com/elastic/connectors-python/blob/main/config.yml.example" - target="_blank" - external - > - {i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.configurationFileLink', - { defaultMessage: 'example config file' } - )} - </EuiLink> - ), - }} - /> - </EuiText> - <EuiSpacer /> - <EuiCodeBlock fontSize="m" paddingSize="m" color="dark" isCopyable> - {getConnectorTemplate({ - apiKeyData, - connectorData: { - id: connector.id, - service_type: connector.service_type, - }, - host: cloudContext.elasticsearchUrl, - })} - </EuiCodeBlock> - <EuiSpacer /> - <EuiText size="s"> - <FormattedMessage - id="xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.description.fourthParagraph" - defaultMessage="Because this connector is self-managed, you need to deploy the connector service on your own infrastructure. You can build from source or use Docker. Refer to the {link} for your deployment options." - values={{ - link: ( - <EuiLink - data-test-subj="entSearchContent-connector-configuration-deploymentModeLink" - data-telemetry-id="entSearchContent-connector-configuration-deploymentModeLink" - href={docLinks.connectorsClientDeploy} - target="_blank" - external - > - {i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.deploymentModeLink', - { defaultMessage: 'documentation' } - )} - </EuiLink> - ), - }} - /> - </EuiText> - </> - ), - status: - !connector.status || connector.status === ConnectorStatus.CREATED - ? 'incomplete' - : 'complete', - title: i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.deployConnector.title', - { - defaultMessage: 'Set up and deploy connector', - } - ), - titleSize: 'xs', - }, - { - children: ( - <ConnectorConfigurationComponent - connector={connector} - hasPlatinumLicense={hasPlatinumLicense} - isLoading={updateConnectorConfigurationStatus === Status.LOADING} - saveConfig={(configuration) => - updateConnectorConfiguration({ - configuration, - connectorId: connector.id, - }) - } - subscriptionLink={docLinks.licenseManagement} - stackManagementLink={http.basePath.prepend( - '/app/management/stack/license_management' - )} - > - {!connector.status || connector.status === ConnectorStatus.CREATED ? ( - <EuiCallOut - title={i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.waitingForConnectorTitle', - { - defaultMessage: 'Waiting for your connector', - } - )} - iconType="iInCircle" - > - {i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.waitingForConnectorText', - { - defaultMessage: - 'Your connector has not connected to Search. Troubleshoot your configuration and refresh the page.', - } - )} - <EuiSpacer size="s" /> - <EuiButton - disabled={!index} - data-test-subj="entSearchContent-connector-configuration-recheckNow" - data-telemetry-id="entSearchContent-connector-configuration-recheckNow" - iconType="refresh" - onClick={() => fetchConnector({ connectorId: connector.id })} - isLoading={isLoading} - > - {i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.waitingForConnector.button.label', - { - defaultMessage: 'Recheck now', - } - )} - </EuiButton> - </EuiCallOut> - ) : ( - !isLastSeenOld(connector) && ( - <EuiCallOut - iconType="check" - color="success" - title={i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.connectorConnected', - { - defaultMessage: - 'Your connector {name} has connected to Search successfully.', - values: { name: connector.name }, - } - )} - /> - ) - )} - <EuiSpacer size="s" /> - {connector.status && - hasAdvancedFilteringFeature && - !isAdvancedSnippetEmpty && ( - <EuiCallOut - title={i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.advancedRulesCallout', - { defaultMessage: 'Configuration warning' } - )} - iconType="iInCircle" - color="warning" - > - <FormattedMessage - id="xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.advancedRulesCallout.description" - defaultMessage="{advancedSyncRulesDocs} can override some configuration fields." - values={{ - advancedSyncRulesDocs: ( - <EuiLink - data-test-subj="entSearchContent-connector-configuration-advancedSyncRulesDocsLink" - data-telemetry-id="entSearchContent-connector-configuration-advancedSyncRulesDocsLink" - href={docLinks.syncRules} - target="_blank" - > - {i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.advancedSyncRulesDocs', - { defaultMessage: 'Advanced Sync Rules' } - )} - </EuiLink> - ), - }} - /> - </EuiCallOut> - )} - </ConnectorConfigurationComponent> - ), - status: - connector.status === ConnectorStatus.CONNECTED ? 'complete' : 'incomplete', - title: i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.enhance.title', - { - defaultMessage: 'Configure your connector', - } - ), - titleSize: 'xs', - }, - { - children: ( - <EuiFlexGroup direction="column"> - <EuiFlexItem> - <EuiText size="s"> - {i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.scheduleSync.description', - { - defaultMessage: - 'Finalize your connector by triggering a one-time sync, or setting a recurring sync to keep your data source in sync over time', - } - )} - </EuiText> - </EuiFlexItem> - <EuiFlexItem> - <EuiFlexGroup responsive={false}> - <EuiFlexItem grow={false}> - <EuiButtonTo - data-test-subj="entSearchContent-connector-configuration-setScheduleAndSync" - data-telemetry-id="entSearchContent-connector-configuration-setScheduleAndSync" - isDisabled={ - (connector?.is_native && !!errorConnectingMessage) || - [ - ConnectorStatus.NEEDS_CONFIGURATION, - ConnectorStatus.CREATED, - ].includes(connector?.status) || - !connector?.index_name - } - to={`${generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, { - connectorId: connector.id, - tabId: ConnectorDetailTabId.SCHEDULING, - })}`} - > - {i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.schedule.button.label', - { - defaultMessage: 'Set schedule and sync', - } - )} - </EuiButtonTo> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <SyncsContextMenu /> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexItem> - </EuiFlexGroup> - ), - status: connector.scheduling.full.enabled ? 'complete' : 'incomplete', - title: i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.schedule.title', - { - defaultMessage: 'Sync your data', - } - ), - titleSize: 'xs', - }, - ]} - /> - </> + <EuiFlexItem> + <EuiFlexGroup gutterSize="m" direction="row" alignItems="center"> + {iconPath && ( + <EuiFlexItem grow={false}> + <EuiIcon size="xl" type={iconPath} /> + </EuiFlexItem> )} - </EuiPanel> - </EuiFlexItem> - <EuiFlexItem grow={1}> - <EuiFlexGroup direction="column"> <EuiFlexItem grow={false}> - <EuiPanel hasBorder hasShadow={false}> - <EuiFlexGroup direction="column"> - <EuiFlexItem> - <EuiText> - <h4> - {i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.title', - { - defaultMessage: 'Support and documentation', - } - )} - </h4> - </EuiText> - </EuiFlexItem> - <EuiFlexItem> - <EuiText size="s"> - {i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.description', - { - defaultMessage: - 'You need to deploy this connector on your own infrastructure.', - } - )} - </EuiText> - </EuiFlexItem> - <EuiFlexItem> - <EuiLink - data-test-subj="entSearchContent-connector-configuration-connectorDocumentationLink" - data-telemetry-id="entSearchContent-connector-configuration-connectorDocumentationLink" - href={docLinks.connectors} - target="_blank" - > - {i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.viewDocumentation.label', - { - defaultMessage: 'View documentation', - } - )} - </EuiLink> - </EuiFlexItem> - <EuiFlexItem> - <EuiLinkTo to={'/app/management/security/api_keys'} shouldNotCreateHref> - {i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.manageKeys.label', - { - defaultMessage: 'Manage API keys', - } - )} - </EuiLinkTo> - </EuiFlexItem> - <EuiFlexItem> - <EuiLink - data-test-subj="entSearchContent-connector-configuration-readmeLink" - data-telemetry-id="entSearchContent-connector-configuration-readmeLink" - href="https://github.com/elastic/connectors-python/blob/main/README.md" - target="_blank" - > - {i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.readme.label', - { - defaultMessage: 'Connector readme', - } + <EuiTitle size="s"> + <h2>{nativeConnector?.name ?? connector.name}</h2> + </EuiTitle> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiBadge color="hollow"> + {connector.is_native + ? i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.badgeType.nativeConnector', + { defaultMessage: 'Native connector' } + ) + : i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.badgeType.connectorClient', + { defaultMessage: 'Connector client' } + )} + </EuiBadge> + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer size="l" /> + <AttachIndexBox connector={connector} /> + <EuiSpacer /> + {connector.index_name && ( + <> + <ConnectorDeployment /> + <EuiSpacer /> + <EuiPanel hasShadow={false} hasBorder> + <EuiTitle size="s"> + <h3> + {i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.configuration.title', + { defaultMessage: 'Configuration' } + )} + </h3> + </EuiTitle> + <EuiSpacer /> + <EuiSkeletonLoading + isLoading={isWaitingForConnector} + loadingContent={<ConfigurationSkeleton />} + loadedContent={ + <ConnectorConfigurationComponent + connector={connector} + hasPlatinumLicense={hasPlatinumLicense} + isLoading={updateConnectorConfigurationStatus === Status.LOADING} + saveConfig={(configuration) => + updateConnectorConfiguration({ + configuration, + connectorId: connector.id, + }) + } + subscriptionLink={docLinks.licenseManagement} + stackManagementLink={http.basePath.prepend( + '/app/management/stack/license_management' )} - </EuiLink> - </EuiFlexItem> - {docsUrl && ( - <EuiFlexItem> - <EuiLink - data-test-subj="entSearchContent-connector-configuration-deployWithDockerLink" - data-telemetry-id="entSearchContent-connector-configuration-deployWithDockerLink" - href={docsUrl} - target="_blank" - > - {i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.dockerDeploy.label', - { - defaultMessage: 'Deploy with Docker', - } - )} - </EuiLink> - </EuiFlexItem> - )} - <EuiFlexItem> - <EuiLink - data-test-subj="entSearchContent-connector-configuration-deployWithoutDockerLink" - data-telemetry-id="entSearchContent-connector-configuration-deployWithoutDockerLink" - href="https://github.com/elastic/connectors-python/blob/main/docs/CONFIG.md#run-the-connector-service-for-a-custom-connector" - target="_blank" > - {i18n.translate( - 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.deploy.label', - { - defaultMessage: 'Deploy without Docker', - } + <EuiSpacer size="s" /> + {hasNonEmptyAdvancedSnippet(connector, advancedSnippet) && ( + <AdvancedConfigOverrideCallout /> )} - </EuiLink> - </EuiFlexItem> - </EuiFlexGroup> + </ConnectorConfigurationComponent> + } + /> </EuiPanel> - </EuiFlexItem> - {isBeta ? ( - <EuiFlexItem> - <BetaConnectorCallout /> - </EuiFlexItem> - ) : null} - </EuiFlexGroup> + <EuiSpacer /> + <WhatsNextBox + connectorId={connector.id} + disabled={isWaitingForConnector || !connector.last_synced} + isWaitingForConnector={isWaitingForConnector} + connectorIndex={connector.index_name} + connectorStatus={connector.status} + isSyncing={Boolean(isSyncing || isWaitingForSync)} + /> + </> + )} </EuiFlexItem> </EuiFlexGroup> </> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_view_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_view_logic.ts index 8c85969915523..88594a9f0ea9a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_view_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_view_logic.ts @@ -7,12 +7,7 @@ import { kea, MakeLogicType } from 'kea'; -import { - Connector, - FeatureName, - IngestPipelineParams, - IngestionMethod, -} from '@kbn/search-connectors'; +import { Connector, IngestionMethod, IngestPipelineParams } from '@kbn/search-connectors'; import { Status } from '../../../../../common/types/api'; @@ -22,6 +17,10 @@ import { CachedFetchConnectorByIdApiLogicValues, } from '../../api/connector/cached_fetch_connector_by_id_api_logic'; +import { + GenerateConnectorApiKeyApiLogicActions, + GenerateConnectorApiKeyApiLogic, +} from '../../api/connector/generate_connector_api_key_api_logic'; import { ConnectorConfigurationApiLogic, PostConnectorConfigurationActions, @@ -30,15 +29,18 @@ import { FetchIndexActions, FetchIndexApiLogic } from '../../api/index/fetch_ind import { ElasticsearchViewIndex } from '../../types'; import { + hasAdvancedFilteringFeature, + hasBasicFilteringFeature, hasDocumentLevelSecurityFeature, hasIncrementalSyncFeature, } from '../../utils/connector_helpers'; import { getConnectorLastSeenError, isLastSeenOld } from '../../utils/connector_status_helpers'; import { - ConnectorNameAndDescriptionLogic, ConnectorNameAndDescriptionActions, + ConnectorNameAndDescriptionLogic, } from './connector_name_and_description_logic'; +import { DeploymentLogic, DeploymentLogicActions } from './deployment_logic'; export interface ConnectorViewActions { fetchConnector: CachedFetchConnectorByIdApiLogicActions['makeRequest']; @@ -49,6 +51,8 @@ export interface ConnectorViewActions { fetchIndexApiError: FetchIndexActions['apiError']; fetchIndexApiReset: FetchIndexActions['apiReset']; fetchIndexApiSuccess: FetchIndexActions['apiSuccess']; + generateApiKeySuccess: GenerateConnectorApiKeyApiLogicActions['apiSuccess']; + generateConfigurationSuccess: DeploymentLogicActions['generateConfigurationSuccess']; nameAndDescriptionApiError: ConnectorNameAndDescriptionActions['apiError']; nameAndDescriptionApiSuccess: ConnectorNameAndDescriptionActions['apiSuccess']; startConnectorPoll: CachedFetchConnectorByIdApiLogicActions['startPolling']; @@ -78,8 +82,6 @@ export interface ConnectorViewValues { isCanceling: boolean; isHiddenIndex: boolean; isLoading: boolean; - isSyncing: boolean; - isWaitingForSync: boolean; lastUpdated: string | null; pipelineData: IngestPipelineParams | undefined; recheckIndexLoading: boolean; @@ -114,6 +116,10 @@ export const ConnectorViewLogic = kea<MakeLogicType<ConnectorViewValues, Connect ], ConnectorNameAndDescriptionLogic, ['apiSuccess as nameAndDescriptionApiSuccess', 'apiError as nameAndDescriptionApiError'], + DeploymentLogic, + ['generateConfigurationSuccess'], + GenerateConnectorApiKeyApiLogic, + ['apiSuccess as generateApiKeySuccess'], ], values: [ CachedFetchConnectorByIdApiLogic, @@ -131,6 +137,21 @@ export const ConnectorViewLogic = kea<MakeLogicType<ConnectorViewValues, Connect }, }), listeners: ({ actions, values }) => ({ + fetchConnectorApiSuccess: ({ connector }) => { + if (!values.index && connector?.index_name) { + actions.fetchIndex({ indexName: connector.index_name }); + } + }, + generateApiKeySuccess: () => { + if (values.connectorId) { + actions.fetchConnector({ connectorId: values.connectorId }); + } + }, + generateConfigurationSuccess: () => { + if (values.connectorId) { + actions.fetchConnector({ connectorId: values.connectorId }); + } + }, nameAndDescriptionApiError: () => { if (values.connectorId) { actions.fetchConnector({ connectorId: values.connectorId }); @@ -146,11 +167,6 @@ export const ConnectorViewLogic = kea<MakeLogicType<ConnectorViewValues, Connect actions.fetchConnector({ connectorId: values.connectorId }); } }, - fetchConnectorApiSuccess: ({ connector }) => { - if (!values.index && connector?.index_name) { - actions.fetchIndex({ indexName: connector.index_name }); - } - }, }), path: ['enterprise_search', 'content', 'connector_view_logic'], selectors: ({ selectors }) => ({ @@ -176,19 +192,11 @@ export const ConnectorViewLogic = kea<MakeLogicType<ConnectorViewValues, Connect ], hasAdvancedFilteringFeature: [ () => [selectors.connector], - (connector?: Connector) => - connector?.features - ? connector.features[FeatureName.SYNC_RULES]?.advanced?.enabled ?? - connector.features[FeatureName.FILTERING_ADVANCED_CONFIG] - : false, + (connector?: Connector) => hasAdvancedFilteringFeature(connector), ], hasBasicFilteringFeature: [ () => [selectors.connector], - (connector?: Connector) => - connector?.features - ? connector.features[FeatureName.SYNC_RULES]?.basic?.enabled ?? - connector.features[FeatureName.FILTERING_RULES] - : false, + (connector?: Connector) => hasBasicFilteringFeature(connector), ], hasDocumentLevelSecurityFeature: [ () => [selectors.connector], diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment.tsx new file mode 100644 index 0000000000000..fb64279019849 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment.tsx @@ -0,0 +1,250 @@ +/* + * 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, { useEffect, useState } from 'react'; + +import { useActions, useValues } from 'kea'; + +import useLocalStorage from 'react-use/lib/useLocalStorage'; + +import { + EuiCode, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiSpacer, + EuiSteps, + EuiText, + EuiTitle, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { FormattedMessage } from '@kbn/i18n-react'; + +import { ConnectorStatus } from '@kbn/search-connectors'; + +import { Status } from '../../../../../common/types/api'; + +import { GetApiKeyByIdLogic } from '../../api/api_key/get_api_key_by_id_api_logic'; + +import { GenerateConnectorApiKeyApiLogic } from '../../api/connector/generate_connector_api_key_api_logic'; + +import { ConnectorLinked } from './components/connector_linked'; +import { DockerInstructionsStep } from './components/docker_instructions_step'; +import { GenerateConfigButton } from './components/generate_config_button'; +import { GeneratedConfigFields } from './components/generated_config_fields'; +import { RunFromSourceStep } from './components/run_from_source_step'; +import { RunOptionsButtons } from './components/run_options_buttons'; +import { WaitingForConnectorStep } from './components/waiting_for_connector_step'; +import { ConnectorViewLogic } from './connector_view_logic'; +import { DeploymentLogic } from './deployment_logic'; + +export const ConnectorDeployment: React.FC = () => { + const [selectedDeploymentMethod, setSelectedDeploymentMethod] = useState<'docker' | 'source'>( + 'docker' + ); + const { generatedData, isGenerateLoading } = useValues(DeploymentLogic); + const { index, isLoading, connector, connectorId } = useValues(ConnectorViewLogic); + const { fetchConnector } = useActions(ConnectorViewLogic); + const { generateConfiguration } = useActions(DeploymentLogic); + const { makeRequest: getApiKeyById } = useActions(GetApiKeyByIdLogic); + const { data: apiKeyMetaData } = useValues(GetApiKeyByIdLogic); + const { makeRequest: generateConnectorApiKey } = useActions(GenerateConnectorApiKeyApiLogic); + const { status, data: apiKeyData } = useValues(GenerateConnectorApiKeyApiLogic); + + const [connectorUiOptions, setConnectorUiOptions] = useLocalStorage< + Record<string, { deploymentMethod: 'docker' | 'source' }> + >('search:connector-ui-options', {}); + + useEffect(() => { + if (connectorUiOptions && connectorId && connectorUiOptions[connectorId]) { + setSelectedDeploymentMethod(connectorUiOptions[connectorId].deploymentMethod); + } else { + selectDeploymentMethod('docker'); + } + }, [connectorUiOptions, connectorId]); + + useEffect(() => { + if (connectorId && connector && connector.api_key_id) { + getApiKeyById(connector.api_key_id); + } + }, [connector, connectorId]); + + if (!connector || connector.is_native) { + return <></>; + } + + const selectDeploymentMethod = (deploymentMethod: 'docker' | 'source') => { + setSelectedDeploymentMethod(deploymentMethod); + setConnectorUiOptions({ + ...connectorUiOptions, + [connector.id]: { deploymentMethod }, + }); + }; + + const hasApiKey = !!(connector.api_key_id ?? generatedData?.apiKey); + + const isWaitingForConnector = !connector.status || connector.status === ConnectorStatus.CREATED; + const apiKey = generatedData?.apiKey || apiKeyData || apiKeyMetaData; + + return ( + <EuiFlexGroup> + <EuiFlexItem> + <EuiPanel hasShadow={false} hasBorder> + <> + <EuiTitle size="s"> + <h3> + {i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.DeploymentTitle', + { + defaultMessage: 'Deployment', + } + )} + </h3> + </EuiTitle> + <EuiSpacer /> + <EuiSteps + steps={[ + { + children: ( + <RunOptionsButtons + selectDeploymentMethod={selectDeploymentMethod} + selectedDeploymentMethod={selectedDeploymentMethod} + /> + ), + status: selectedDeploymentMethod === null ? 'incomplete' : 'complete', + title: i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.runConnectorService.title', + { + defaultMessage: 'Run connector service', + } + ), + titleSize: 'xs', + }, + { + children: ( + <> + <EuiSpacer size="s" /> + <EuiText size="s"> + {selectedDeploymentMethod === 'source' ? ( + <FormattedMessage + id="xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.configureIndexAndApiKey.description.source" + defaultMessage="When you generate a configuration, Elastic will create an index, an API key and a Connector ID. You'll need to add this information to the {configYaml} file for your connector. Alternatively use an existing index and API key. " + values={{ + configYaml: ( + <EuiCode> + {i18n.translate( + 'xpack.enterpriseSearch.connectorConfiguration.configymlCodeBlockLabel', + { defaultMessage: 'config.yml' } + )} + </EuiCode> + ), + }} + /> + ) : ( + <FormattedMessage + id="xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.configureIndexAndApiKey.description.docker" + defaultMessage="When you generate a configuration, Elastic will create an index, an API key and a Connector ID. Alternatively use an existing index and API key." + /> + )} + </EuiText> + + <EuiSpacer /> + {hasApiKey && connector.index_name ? ( + <GeneratedConfigFields + apiKey={apiKey} + connector={connector} + generateApiKey={() => { + if (connector.index_name) { + generateConnectorApiKey({ + indexName: connector.index_name, + isNative: connector.is_native, + }); + } + }} + isGenerateLoading={status === Status.LOADING} + /> + ) : ( + <GenerateConfigButton + connectorId={connector.id} + generateConfiguration={generateConfiguration} + isGenerateLoading={isGenerateLoading} + /> + )} + </> + ), + status: hasApiKey ? 'complete' : 'incomplete', + title: i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.generateApiKey.title', + { + defaultMessage: 'Configure index and API key', + } + ), + titleSize: 'xs', + }, + { + children: ( + <> + <EuiSpacer size="s" /> + {selectedDeploymentMethod === 'source' ? ( + <RunFromSourceStep + connectorId={connectorId ?? ''} + serviceType={connector.service_type ?? ''} + apiKeyData={apiKey} + isWaitingForConnector={isWaitingForConnector} + /> + ) : ( + <DockerInstructionsStep + connectorId={connectorId ?? ''} + hasApiKey={hasApiKey} + serviceType={connector.service_type ?? ''} + isWaitingForConnector={isWaitingForConnector} + apiKeyData={apiKey} + /> + )} + </> + ), + status: + !connector.status || connector.status === ConnectorStatus.CREATED + ? 'incomplete' + : 'complete', + title: i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.runConnector.title', + { + defaultMessage: 'Run connector service', + } + ), + titleSize: 'xs', + }, + { + children: isWaitingForConnector ? ( + <WaitingForConnectorStep + isLoading={isLoading} + isRecheckDisabled={!index} + recheck={() => fetchConnector({ connectorId: connector.id })} + /> + ) : ( + <ConnectorLinked /> + ), + status: isWaitingForConnector ? 'loading' : 'complete', + title: i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.waitingForConnector.title', + { + defaultMessage: 'Waiting for your connector', + } + ), + titleSize: 'xs', + }, + ]} + /> + </> + </EuiPanel> + </EuiFlexItem> + </EuiFlexGroup> + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment_logic.ts new file mode 100644 index 0000000000000..09c2c8db48e03 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/deployment_logic.ts @@ -0,0 +1,58 @@ +/* + * 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 { kea, MakeLogicType } from 'kea'; + +import { Connector } from '@kbn/search-connectors'; + +import { HttpError, Status } from '../../../../../common/types/api'; +import { Actions } from '../../../shared/api_logic/create_api_logic'; +import { + GenerateConfigApiArgs, + GenerateConfigApiLogic, +} from '../../api/connector/generate_connector_config_api_logic'; +import { APIKeyResponse } from '../../api/generate_api_key/generate_api_key_logic'; + +type GenerateConfigApiActions = Actions<GenerateConfigApiArgs, {}>; + +export interface DeploymentLogicValues { + generateConfigurationError: HttpError; + generateConfigurationStatus: Status; + generatedData: { + apiKey: APIKeyResponse['apiKey']; + connectorId: Connector['id']; + indexName: string; + }; + isGenerateLoading: boolean; +} +export interface DeploymentLogicActions { + generateConfiguration: GenerateConfigApiActions['makeRequest']; + generateConfigurationSuccess: GenerateConfigApiActions['apiSuccess']; +} + +export const DeploymentLogic = kea<MakeLogicType<DeploymentLogicValues, DeploymentLogicActions>>({ + connect: { + actions: [ + GenerateConfigApiLogic, + ['makeRequest as generateConfiguration', 'apiSuccess as generateConfigurationSuccess'], + ], + values: [ + GenerateConfigApiLogic, + [ + 'status as generateConfigurationStatus', + 'data as generatedData', + 'error as generateConfigurationError', + ], + ], + }, + selectors: { + isGenerateLoading: [ + (selectors) => [selectors.generateConfigurationStatus], + (status) => status === Status.LOADING, + ], + }, +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/native_connector_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/native_connector_configuration.tsx index ada3b65114ef1..fac70afd156d2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/native_connector_configuration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/native_connector_configuration.tsx @@ -10,42 +10,31 @@ import React, { useMemo } from 'react'; import { useValues } from 'kea'; import { + EuiBadge, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiIcon, - EuiLink, EuiPanel, EuiSpacer, - EuiSteps, - EuiText, EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { FeatureName } from '@kbn/search-connectors'; - import { BetaConnectorCallout } from '../../../shared/beta/beta_connector_callout'; -import { docLinks } from '../../../shared/doc_links'; -import { generateEncodedPath } from '../../../shared/encode_path_params'; import { HttpLogic } from '../../../shared/http'; import { KibanaLogic } from '../../../shared/kibana'; -import { EuiButtonTo } from '../../../shared/react_router_helpers'; import { GenerateConnectorApiKeyApiLogic } from '../../api/connector/generate_connector_api_key_api_logic'; -import { CONNECTOR_DETAIL_TAB_PATH } from '../../routes'; -import { hasConfiguredConfiguration } from '../../utils/has_configured_configuration'; import { ApiKeyConfig } from '../search_index/connector/api_key_configuration'; import { ConvertConnector } from '../search_index/connector/native_connector_configuration/convert_connector'; import { NativeConnectorConfigurationConfig } from '../search_index/connector/native_connector_configuration/native_connector_configuration_config'; import { ResearchConfiguration } from '../search_index/connector/native_connector_configuration/research_configuration'; -import { SyncsContextMenu } from '../shared/header_actions/syncs_context_menu'; import { AttachIndexBox } from './attach_index_box'; -import { ConnectorDetailTabId } from './connector_detail'; +import { WhatsNextBox } from './components/whats_next_box'; import { ConnectorViewLogic } from './connector_view_logic'; export const NativeConnectorConfiguration: React.FC = () => { @@ -78,17 +67,7 @@ export const NativeConnectorConfiguration: React.FC = () => { serviceType: connector.service_type ?? '', }; - const hasDescription = !!connector.description; - const hasConfigured = hasConfiguredConfiguration(connector.configuration); - const hasConfiguredAdvanced = - connector.last_synced || - connector.scheduling.full.enabled || - connector.scheduling.incremental.enabled; - const hasResearched = hasDescription || hasConfigured || hasConfiguredAdvanced; const iconPath = nativeConnector.iconPath; - const hasDocumentLevelSecurity = - connector.features?.[FeatureName.DOCUMENT_LEVEL_SECURITY]?.enabled || false; - const hasApiKey = !!(connector.api_key_id ?? apiKeyData); // TODO service_type === "" is considered unknown/custom connector multipleplaces replace all of them with a better solution @@ -98,249 +77,127 @@ export const NativeConnectorConfiguration: React.FC = () => { return ( <> - <EuiSpacer /> + {isBeta ? ( + <> + <EuiFlexItem grow={false}> + <EuiPanel hasBorder hasShadow={false}> + <BetaConnectorCallout /> + </EuiPanel> + </EuiFlexItem> + <EuiSpacer /> + </> + ) : null} <EuiFlexGroup> - <EuiFlexItem grow={2}> - <EuiPanel hasShadow={false} hasBorder> - <EuiFlexGroup gutterSize="m" direction="row" alignItems="center"> - {iconPath && ( - <EuiFlexItem grow={false}> - <EuiIcon size="xl" type={iconPath} /> - </EuiFlexItem> - )} + <EuiFlexItem> + <EuiFlexGroup gutterSize="m" direction="row" alignItems="center"> + {iconPath && ( <EuiFlexItem grow={false}> - <EuiTitle size="s"> - <h2>{nativeConnector?.name ?? connector.name}</h2> - </EuiTitle> + <EuiIcon size="xl" type={iconPath} /> </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer /> - {config.host && config.canDeployEntSearch && errorConnectingMessage && ( - <> - <EuiCallOut - color="warning" - size="m" - title={i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.entSearchWarning.title', - { - defaultMessage: 'No running Enterprise Search instance detected', - } - )} - iconType="warning" - > - <p> - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.entSearchWarning.text', - { - defaultMessage: - 'Native connectors require a running Enterprise Search instance to sync content from source.', - } - )} - </p> - </EuiCallOut> - - <EuiSpacer /> - </> )} - { - <> - <EuiSpacer /> - <AttachIndexBox connector={connector} /> - </> - } - {connector.index_name && ( - <> - <EuiSpacer /> - <EuiSteps - steps={[ - { - children: <ResearchConfiguration nativeConnector={nativeConnector} />, - status: hasResearched ? 'complete' : 'incomplete', - title: i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.researchConfigurationTitle', - { - defaultMessage: 'Research configuration requirements', - } - ), - titleSize: 'xs', - }, - { - children: ( - <NativeConnectorConfigurationConfig - connector={connector} - nativeConnector={nativeConnector} - status={connector.status} - /> - ), - status: hasConfigured ? 'complete' : 'incomplete', - title: i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.configurationTitle', - { - defaultMessage: 'Configuration', - } - ), - titleSize: 'xs', - }, - { - children: ( - <ApiKeyConfig - indexName={connector.index_name || ''} - hasApiKey={hasApiKey} - isNative - /> - ), - status: hasApiKey ? 'complete' : 'incomplete', - title: i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.manageApiKeyTitle', - { - defaultMessage: 'Manage API key', - } - ), - titleSize: 'xs', - }, - { - children: ( - <EuiFlexGroup direction="column"> - <EuiFlexItem> - <EuiText size="s"> - <FormattedMessage - id="xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnectorAdvancedConfiguration.description" - defaultMessage="Finalize your connector by triggering a one time sync, or setting a recurring sync schedule." - /> - </EuiText> - </EuiFlexItem> - <EuiFlexItem> - <EuiFlexGroup responsive={false}> - <EuiFlexItem grow={false}> - <EuiButtonTo - to={`${generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, { - connectorId: connector.id, - tabId: ConnectorDetailTabId.SCHEDULING, - })}`} - > - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnectorAdvancedConfiguration.schedulingButtonLabel', - { - defaultMessage: 'Set schedule and sync', - } - )} - </EuiButtonTo> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <SyncsContextMenu /> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexItem> - </EuiFlexGroup> - ), - status: hasConfiguredAdvanced ? 'complete' : 'incomplete', - title: i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.advancedConfigurationTitle', - { - defaultMessage: 'Sync your data', - } - ), - titleSize: 'xs', - }, - ]} - /> - </> - )} - </EuiPanel> - </EuiFlexItem> - <EuiFlexItem grow={1}> - <EuiFlexGroup direction="column"> <EuiFlexItem grow={false}> - <EuiPanel hasBorder hasShadow={false}> - <EuiFlexGroup direction="row" alignItems="center" gutterSize="s"> - <EuiFlexItem grow={false}> - <EuiIcon type="clock" /> - </EuiFlexItem> - <EuiFlexItem> - <EuiTitle size="xs"> - <h3> - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.schedulingReminder.title', - { - defaultMessage: 'Configurable sync schedule', - } - )} - </h3> - </EuiTitle> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer size="s" /> - <EuiText size="s"> + <EuiTitle size="s"> + <h2>{nativeConnector?.name ?? connector.name}</h2> + </EuiTitle> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiBadge color="hollow"> + {connector.is_native + ? i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.badgeType.nativeConnector', + { defaultMessage: 'Native connector' } + ) + : i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.configurationConnector.badgeType.connectorClient', + { defaultMessage: 'Connector client' } + )} + </EuiBadge> + </EuiFlexItem> + </EuiFlexGroup> + {config.host && config.canDeployEntSearch && errorConnectingMessage && ( + <> + <EuiCallOut + color="warning" + size="m" + title={i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.entSearchWarning.title', + { + defaultMessage: 'No running Enterprise Search instance detected', + } + )} + iconType="warning" + > + <p> {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.schedulingReminder.description', + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.entSearchWarning.text', { defaultMessage: - 'Remember to set a sync schedule in the Scheduling tab to continually refresh your searchable data.', + 'Native connectors require a running Enterprise Search instance to sync content from source.', } )} - </EuiText> + </p> + </EuiCallOut> + + <EuiSpacer /> + </> + )} + { + <> + <EuiSpacer /> + <AttachIndexBox connector={connector} /> + </> + } + {connector.index_name && ( + <> + <EuiSpacer /> + <EuiPanel hasBorder> + <EuiTitle size="s"> + <h3> + {i18n.translate( + 'xpack.enterpriseSearch.content.connector_detail.nativeConfigurationConnector.configuration.title', + { defaultMessage: 'Configuration' } + )} + </h3> + </EuiTitle> + <EuiSpacer /> + <ResearchConfiguration nativeConnector={nativeConnector} /> + <EuiSpacer size="m" /> + <NativeConnectorConfigurationConfig + connector={connector} + nativeConnector={nativeConnector} + status={connector.status} + /> + <EuiSpacer /> </EuiPanel> - </EuiFlexItem> - {hasDocumentLevelSecurity && ( - <EuiFlexItem grow={false}> - <EuiPanel hasBorder hasShadow={false}> - <EuiFlexGroup direction="row" alignItems="center" gutterSize="s"> - <EuiFlexItem grow={false}> - <EuiIcon type="globe" /> - </EuiFlexItem> - <EuiFlexItem> - <EuiTitle size="xs"> - <h3> - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.securityReminder.title', - { - defaultMessage: 'Document level security', - } - )} - </h3> - </EuiTitle> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer size="s" /> - <EuiText size="s"> + <EuiSpacer /> + <EuiPanel hasBorder> + <EuiTitle size="s"> + <h4> {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.securityReminder.description', - { - defaultMessage: - 'Restrict and personalize the read access users have to the index documents at query time.', - } + 'xpack.enterpriseSearch.content.connector_detail.nativeConfigurationConnector.apiKey.title', + { defaultMessage: 'API Key' } )} - <EuiSpacer size="s" /> - <EuiLink - data-test-subj="entSearchContent-connectorDetail-documentLevelSecurityLink" - data-telemetry-id="entSearchContent-connectorDetail-documentLevelSecurityLink" - href={docLinks.documentLevelSecurity} - target="_blank" - > - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.securityReminder.securityLinkLabel', - { - defaultMessage: 'Document level security', - } - )} - </EuiLink> - </EuiText> - </EuiPanel> - </EuiFlexItem> - )} - <EuiFlexItem grow={false}> - <EuiPanel hasBorder hasShadow={false}> + </h4> + </EuiTitle> + <EuiSpacer size="m" /> + <ApiKeyConfig + indexName={connector.index_name || ''} + hasApiKey={hasApiKey} + isNative + /> + </EuiPanel> + <EuiSpacer /> + <EuiPanel hasBorder> <ConvertConnector /> </EuiPanel> - </EuiFlexItem> - {isBeta ? ( - <EuiFlexItem grow={false}> - <EuiPanel hasBorder hasShadow={false}> - <BetaConnectorCallout /> - </EuiPanel> - </EuiFlexItem> - ) : null} - </EuiFlexGroup> + <EuiSpacer /> + <WhatsNextBox + connectorId={connector.id} + connectorStatus={connector.status} + connectorIndex={connector.index_name} + /> + </> + )} </EuiFlexItem> </EuiFlexGroup> </> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/delete_connector_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/delete_connector_modal.tsx index 512b0bd384697..a047b8ab8219b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/delete_connector_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/delete_connector_modal.tsx @@ -9,6 +9,9 @@ import React, { useState, useEffect } from 'react'; import { useActions, useValues } from 'kea'; +import { omit } from 'lodash'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; + import { EuiCheckbox, EuiConfirmModal, @@ -30,7 +33,11 @@ export interface DeleteConnectorModalProps { isCrawler: boolean; } export const DeleteConnectorModal: React.FC<DeleteConnectorModalProps> = ({ isCrawler }) => { + const [connectorUiOptions, setConnectorUiOptions] = useLocalStorage< + Record<string, { deploymentMethod: 'docker' | 'source' | null }> + >('search:connector-ui-options', {}); const { closeDeleteModal, deleteConnector, deleteIndex } = useActions(ConnectorsLogic); + const { deleteModalConnectorId: connectorId, deleteModalConnectorName, @@ -75,6 +82,7 @@ export const DeleteConnectorModal: React.FC<DeleteConnectorModalProps> = ({ isCr connectorId, shouldDeleteIndex, }); + setConnectorUiOptions(omit(connectorUiOptions, connectorId)); } }} cancelButtonText={ diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/api_key_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/api_key_configuration.tsx index 868bf4fe50721..29655218034dd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/api_key_configuration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/api_key_configuration.tsx @@ -20,6 +20,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import { Status } from '../../../../../../common/types/api'; import { GenerateConnectorApiKeyApiLogic } from '../../../api/connector/generate_connector_api_key_api_logic'; @@ -150,9 +151,10 @@ export const ApiKeyConfig: React.FC<{ <></> )} <EuiFlexItem> - <EuiFlexGroup justifyContent="spaceBetween" alignItems="center"> + <EuiFlexGroup alignItems="center"> <EuiFlexItem grow={false}> <EuiButton + data-test-subj="enterpriseSearchApiKeyConfigGenerateApiKeyButton" onClick={clickGenerateApiKey} isLoading={status === Status.LOADING} isDisabled={indexName.length === 0} @@ -166,6 +168,21 @@ export const ApiKeyConfig: React.FC<{ )} </EuiButton> </EuiFlexItem> + {status === Status.SUCCESS && ( + <EuiFlexItem grow={false}> + <EuiCallOut + color="success" + size="s" + iconType="check" + title={ + <FormattedMessage + id="xpack.enterpriseSearch.apiKeyConfig.newApiKeyCreatedCalloutLabel" + defaultMessage="New API key created succesfully" + /> + } + /> + </EuiFlexItem> + )} </EuiFlexGroup> </EuiFlexItem> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts index 08a47b9a1b097..3962bbb888d6e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts @@ -34,3 +34,18 @@ export const getConnectorTemplate = ({ host: "${host || 'http://localhost:9200'}" api_key: "${apiKeyData?.encoded || ''}" `; + +export const getRunFromDockerSnippet = ({ version }: { version: string }) => dedent` +docker run \\ + + -v "</absolute/path/to>/connectors-config:/config" \ # NOTE: change absolute path to match where config.yml is located on your machine + --tty \\ + + --rm \\ + + docker.elastic.co/enterprise-search/elastic-connectors:${version} \\ + + /app/bin/elastic-ingest \\ + + -c /config/config.yml # Path to your configuration file in the container +`; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx index 454b056a87f43..5b1478086aadb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/convert_connector.tsx @@ -12,7 +12,6 @@ import { useActions, useValues } from 'kea'; import { EuiFlexGroup, EuiFlexItem, - EuiIcon, EuiTitle, EuiSpacer, EuiText, @@ -37,11 +36,8 @@ export const ConvertConnector: React.FC = () => { <> {isModalVisible && <ConvertConnectorModal />} <EuiFlexGroup direction="row" alignItems="center" gutterSize="s"> - <EuiFlexItem grow={false}> - <EuiIcon type="wrench" /> - </EuiFlexItem> <EuiFlexItem> - <EuiTitle size="xs"> + <EuiTitle size="s"> <h3> {i18n.translate( 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.title', @@ -53,7 +49,7 @@ export const ConvertConnector: React.FC = () => { </EuiTitle> </EuiFlexItem> </EuiFlexGroup> - <EuiSpacer size="s" /> + <EuiSpacer size="l" /> <EuiText size="s"> <FormattedMessage id="xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.description" @@ -69,7 +65,7 @@ export const ConvertConnector: React.FC = () => { ), }} /> - <EuiSpacer size="s" /> + <EuiSpacer size="l" /> <EuiButton onClick={() => showModal()}> {i18n.translate( 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.buttonTitle', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration_config.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration_config.tsx index d4e3ae19ae43e..d2681a5d3df97 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration_config.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration_config.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { useActions, useValues } from 'kea'; -import { EuiSpacer, EuiLink, EuiText, EuiFlexGroup, EuiFlexItem, EuiCallOut } from '@elastic/eui'; +import { EuiSpacer, EuiLink, EuiFlexGroup, EuiFlexItem, EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -62,32 +62,7 @@ export const NativeConnectorConfigurationConfig: React.FC< subscriptionLink={docLinks.licenseManagement} stackManagementLink={http.basePath.prepend('/app/management/stack/license_management')} > - <EuiText size="s"> - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.config.encryptionWarningMessage', - { - defaultMessage: - 'Encryption for data source credentials is unavailable in this version. Your data source credentials will be stored, unencrypted, in Elasticsearch.', - } - )} - </EuiText> - <EuiSpacer /> <EuiFlexGroup direction="row"> - <EuiFlexItem grow={false}> - <EuiLink - data-test-subj="entSearchContent-connector-nativeConnector-learnMoreAboutSecurityLink" - data-telemetry-id="entSearchContent-connector-nativeConnector-learnMoreAboutSecurityLink" - href={docLinks.elasticsearchSecureCluster} - target="_blank" - > - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.config.securityDocumentationLinkLabel', - { - defaultMessage: 'Learn more about Elasticsearch security', - } - )} - </EuiLink> - </EuiFlexItem> {nativeConnector.externalAuthDocsUrl && ( <EuiFlexItem grow={false}> <EuiLink diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/research_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/research_configuration.tsx index 993c7b9e1ac0b..0625c60a354f7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/research_configuration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/research_configuration.tsx @@ -7,9 +7,10 @@ import React from 'react'; -import { EuiText, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; +import { EuiText, EuiFlexGroup, EuiFlexItem, EuiLink, EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import { ConnectorDefinition } from '@kbn/search-connectors-plugin/common/types'; @@ -22,42 +23,63 @@ export const ResearchConfiguration: React.FC<ResearchConfigurationProps> = ({ const { docsUrl, externalDocsUrl, name } = nativeConnector; return ( - <> - <EuiText size="s"> - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.researchConfiguration.description', - { - defaultMessage: - 'This connector supports several authentication methods. Ask your administrator for the correct connection credentials.', - } - )} - </EuiText> - <EuiSpacer /> - <EuiFlexGroup direction="row" alignItems="flexStart"> - <EuiFlexItem grow={false}> - <EuiLink target="_blank" href={docsUrl}> - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.researchConfiguration.connectorDocumentationLinkLabel', - { - defaultMessage: 'Documentation', - } - )} - </EuiLink> + <EuiCallOut + title={ + <FormattedMessage + id="xpack.enterpriseSearch.researchConfiguration.euiText.checkRequirementsLabel" + defaultMessage="Check Requirements" + /> + } + iconType="iInCircle" + > + <EuiFlexGroup direction="column" alignItems="flexStart" gutterSize="s"> + <EuiFlexItem> + <EuiText size="s"> + <p> + <FormattedMessage + id="xpack.enterpriseSearch.researchConfiguration.p.referToTheDocumentationLabel" + defaultMessage="Refer to the documentation for this connector to learn about prerequisites for connecting to {serviceType} and configuration requirements." + values={{ + serviceType: name, + }} + /> + </p> + </EuiText> </EuiFlexItem> - {externalDocsUrl && ( + <EuiFlexGroup direction="row" alignItems="center"> <EuiFlexItem grow={false}> - <EuiLink target="_blank" href={externalDocsUrl}> + <EuiLink + data-test-subj="enterpriseSearchResearchConfigurationDocumentationLink" + target="_blank" + href={docsUrl} + > {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.researchConfiguration.serviceDocumentationLinkLabel', + 'xpack.enterpriseSearch.content.indices.configurationConnector.researchConfiguration.connectorDocumentationLinkLabel', { - defaultMessage: '{name} documentation', - values: { name }, + defaultMessage: 'Documentation', } )} </EuiLink> </EuiFlexItem> - )} + {externalDocsUrl && ( + <EuiFlexItem grow={false}> + <EuiLink + data-test-subj="enterpriseSearchResearchConfigurationNameDocumentationLink" + target="_blank" + href={externalDocsUrl} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.researchConfiguration.serviceDocumentationLinkLabel', + { + defaultMessage: '{name} documentation', + values: { name }, + } + )} + </EuiLink> + </EuiFlexItem> + )} + </EuiFlexGroup> </EuiFlexGroup> - </> + </EuiCallOut> ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/header_actions/syncs_context_menu.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/header_actions/syncs_context_menu.tsx index c525c2f672075..b4c8f39c253df 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/header_actions/syncs_context_menu.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/header_actions/syncs_context_menu.tsx @@ -34,7 +34,11 @@ import { IndexViewLogic } from '../../search_index/index_view_logic'; import { SyncsLogic } from './syncs_logic'; -export const SyncsContextMenu: React.FC = () => { +export interface SyncsContextMenuProps { + disabled?: boolean; +} + +export const SyncsContextMenu: React.FC<SyncsContextMenuProps> = ({ disabled = false }) => { const { config, productFeatures } = useValues(KibanaLogic); const { ingestionStatus, isCanceling, isSyncing, isWaitingForSync } = useValues(IndexViewLogic); const { connector, hasDocumentLevelSecurityFeature, hasIncrementalSyncFeature } = @@ -171,6 +175,7 @@ export const SyncsContextMenu: React.FC = () => { <EuiPopover button={ <EuiButton + disabled={disabled} data-test-subj="enterpriseSearchSyncsContextMenuButton" data-telemetry-id="entSearchContent-connector-header-sync-openSyncMenu" iconType="arrowDown" diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/connector_helpers.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/connector_helpers.ts index 2d91be4c269b3..03b3fba0ed331 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/connector_helpers.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/connector_helpers.ts @@ -7,6 +7,10 @@ import { Connector, FeatureName } from '@kbn/search-connectors'; +import { EXAMPLE_CONNECTOR_SERVICE_TYPES } from '../../../../common/constants'; + +import { isAdvancedSyncRuleSnippetEmpty } from './sync_rules_helpers'; + export const hasIncrementalSyncFeature = (connector: Connector | undefined): boolean => { return connector?.features?.[FeatureName.INCREMENTAL_SYNC]?.enabled || false; }; @@ -14,3 +18,38 @@ export const hasIncrementalSyncFeature = (connector: Connector | undefined): boo export const hasDocumentLevelSecurityFeature = (connector: Connector | undefined): boolean => { return connector?.features?.[FeatureName.DOCUMENT_LEVEL_SECURITY]?.enabled || false; }; + +// TODO remove this when example status is removed +export const isExampleConnector = (connector: Connector | undefined): boolean => + Boolean( + connector && + connector.service_type && + EXAMPLE_CONNECTOR_SERVICE_TYPES.includes(connector.service_type) + ); + +export const hasAdvancedFilteringFeature = (connector: Connector | undefined): boolean => + Boolean( + connector?.features + ? connector.features[FeatureName.SYNC_RULES]?.advanced?.enabled ?? + connector.features[FeatureName.FILTERING_ADVANCED_CONFIG] + : false + ); + +export const hasBasicFilteringFeature = (connector: Connector | undefined): boolean => + Boolean( + connector?.features + ? connector.features[FeatureName.SYNC_RULES]?.basic?.enabled ?? + connector.features[FeatureName.FILTERING_RULES] + : false + ); + +export const hasNonEmptyAdvancedSnippet = ( + connector: Connector | undefined, + advancedSnippet: string +): boolean => + Boolean( + connector && + connector.status && + hasAdvancedFilteringFeature(connector) && + !isAdvancedSyncRuleSnippetEmpty(advancedSnippet) + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx index 32e029f9f5f21..bc1f6f13a0bb1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx @@ -83,7 +83,6 @@ export const EnterpriseSearchPageTemplateWrapper: React.FC<PageTemplateProps> = }, []); return ( <KibanaPageTemplate - restrictWidth={false} {...pageTemplateProps} className={classNames('enterpriseSearchPageTemplate', className)} mainProps={{ diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/generate_config.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/generate_config.ts new file mode 100644 index 0000000000000..d9f0eefd0fb5c --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/generate_config.ts @@ -0,0 +1,48 @@ +/* + * 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 { IScopedClusterClient } from '@kbn/core/server'; + +import { Connector, CONNECTORS_INDEX } from '@kbn/search-connectors'; + +import { createIndex } from '../indices/create_index'; +import { indexOrAliasExists } from '../indices/exists_index'; +import { generateApiKey } from '../indices/generate_api_key'; +import { generatedIndexName } from '../indices/generate_index_name'; + +export const generateConfig = async (client: IScopedClusterClient, connector: Connector) => { + let associatedIndex: string; + + if (connector.index_name) { + associatedIndex = connector.index_name; + } else { + associatedIndex = await generatedIndexName( + client, + connector.name || connector.service_type || 'my-connector' // pass a default name to generate a readable index name rather than gibberish + ); + } + + if (!indexOrAliasExists(client, associatedIndex)) { + await createIndex(client, associatedIndex, connector.language, true); + } + + await client.asCurrentUser.transport.request({ + body: { + index_name: associatedIndex, + }, + method: 'PUT', + path: `/_connector/${connector.id}/_index_name`, + }); + + await client.asCurrentUser.indices.refresh({ index: CONNECTORS_INDEX }); + const apiKeyResponse = await generateApiKey(client, associatedIndex, connector.is_native); + + return { + apiKeyResponse, + associatedIndex, + }; +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/generate_index_name.ts b/x-pack/plugins/enterprise_search/server/lib/indices/generate_index_name.ts new file mode 100644 index 0000000000000..5a4f1cd8208ff --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/indices/generate_index_name.ts @@ -0,0 +1,31 @@ +/* + * 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 { v4 as uuidv4 } from 'uuid'; + +import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; + +import { ErrorCode } from '../../../common/types/error_codes'; + +import { toAlphanumeric } from '../../../common/utils/to_alphanumeric'; + +import { indexOrAliasExists } from './exists_index'; + +export const generatedIndexName = async (client: IScopedClusterClient, indexNamePrefix: string) => { + const prefix = toAlphanumeric(indexNamePrefix); + if (!prefix || prefix.length === 0) { + throw new Error('Index name prefix is required'); + } + for (let i = 0; i < 20; i++) { + const indexName = `${prefix}-${uuidv4().split('-')[0]}`; + const result = await indexOrAliasExists(client, indexName); + if (!result) { + return indexName; + } + } + throw new Error(ErrorCode.GENERATE_INDEX_NAME_ERROR); +}; diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts index 7549f7ed002c9..8b94de5e6955c 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts @@ -76,6 +76,40 @@ export function registerApiKeysRoutes( } ); + router.get( + { + path: '/internal/enterprise_search/api_keys/{apiKeyId}', + validate: { + params: schema.object({ + apiKeyId: schema.string(), + }), + }, + }, + async (context, request, response) => { + const core = await context.core; + const { client } = core.elasticsearch; + const { apiKeyId } = request.params; + const user = core.security.authc.getCurrentUser(); + + if (user) { + try { + const apiKey = await client.asCurrentUser.security.getApiKey({ id: apiKeyId }); + return response.ok({ body: apiKey.api_keys[0] }); + } catch { + // Ideally we check the error response here for unauthorized user + // Unfortunately the error response is not structured enough for us to filter those + // Always returning an empty array should also be fine, and deals with transient errors + + return response.ok({ body: { api_keys: [] } }); + } + } + return response.customError({ + body: 'Could not retrieve current user, security plugin is not ready', + statusCode: 502, + }); + } + ); + router.post( { path: '/internal/enterprise_search/api_keys', diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts index 66cea5b830730..e3ba2bd9d53cb 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts @@ -37,6 +37,7 @@ import { import { ErrorCode } from '../../../common/types/error_codes'; import { addConnector } from '../../lib/connectors/add_connector'; +import { generateConfig } from '../../lib/connectors/generate_config'; import { startSync } from '../../lib/connectors/start_sync'; import { deleteAccessControlIndex } from '../../lib/indices/delete_access_control_index'; import { fetchIndexCounts } from '../../lib/indices/fetch_index_counts'; @@ -770,4 +771,68 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) { }); }) ); + + router.post( + { + path: '/internal/enterprise_search/connectors/{connectorId}/generate_config', + validate: { + params: schema.object({ + connectorId: schema.string(), + }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const { client } = (await context.core).elasticsearch; + const { connectorId } = request.params; + + let associatedIndex; + let apiKeyResponse; + try { + const connector = await fetchConnectorById(client.asCurrentUser, connectorId); + + if (!connector) { + return createError({ + errorCode: ErrorCode.RESOURCE_NOT_FOUND, + message: i18n.translate( + 'xpack.enterpriseSearch.server.routes.connectors.resource_not_found_error', + { + defaultMessage: 'Connector with id {connectorId} is not found.', + values: { connectorId }, + } + ), + response, + statusCode: 404, + }); + } + + const configResponse = await generateConfig(client, connector); + associatedIndex = configResponse.associatedIndex; + apiKeyResponse = configResponse.apiKeyResponse; + } catch (error) { + if (error.message === ErrorCode.GENERATE_INDEX_NAME_ERROR) { + createError({ + errorCode: ErrorCode.GENERATE_INDEX_NAME_ERROR, + message: i18n.translate( + 'xpack.enterpriseSearch.server.routes.connectors.generateConfiguration.indexAlreadyExistsError', + { + defaultMessage: 'Cannot find a unique index name to generate configuration', + } + ), + response, + statusCode: 409, + }); + throw error; + } + } + + return response.ok({ + body: { + apiKey: apiKeyResponse, + connectorId, + indexName: associatedIndex, + }, + headers: { 'content-type': 'application/json' }, + }); + }) + ); } diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 8516ca3a3d33b..4a7e02dad81ac 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -13617,7 +13617,6 @@ "xpack.enterpriseSearch.connectors.connectorStats.p.DocumentsLabel": "{documentAmount} documents", "xpack.enterpriseSearch.connectorStats.connectedBadgeLabel": "{number} connecté(s)", "xpack.enterpriseSearch.connectorStats.runningSyncsTextLabel": "{syncs} synchronisations en cours", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.connectorConnected": "Votre connecteur {name} s'est bien connecté à Search.", "xpack.enterpriseSearch.content.connectors.connectorsTable.columns.actions.viewIndex.caption": "Voir l'index {connectorName}", "xpack.enterpriseSearch.content.connectors.connectorTable.column.actions.deleteIndex": "Supprimer le connecteur {connectorName}", "xpack.enterpriseSearch.content.connectors.deleteModal.syncsWarning.indexNameDescription": "Cette action ne peut pas être annulée. Veuillez saisir {connectorName} pour confirmer.", @@ -14901,23 +14900,7 @@ "xpack.enterpriseSearch.content.analytics.api.generateAnalyticsApiKeyModal.title": "Créer une clé d'API d'analyse", "xpack.enterpriseSearch.content.cannotConnect.body": "En savoir plus.", "xpack.enterpriseSearch.content.cannotConnect.title": "Impossible de se connecter à Enterprise Search", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.configurationFileLink": "fichier de configuration", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.waitingForConnector.button.label": "Revérifier maintenant", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.waitingForConnectorText": "Votre connecteur ne s'est pas connecté à Search. Résolvez vos problèmes de configuration et actualisez la page.", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.waitingForConnectorTitle": "En attente de votre connecteur", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.scheduleSync.description": "Finalisez votre connecteur en déclenchant une synchronisation unique ou en définissant une synchronisation récurrente pour assurer la synchronisation de votre source de données au fil du temps", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.deployConnector.title": "Déployer un connecteur", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.enhance.title": "Améliorer votre client connecteur", "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.generateApiKey.title": "Générer une clé d’API", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.schedule.button.label": "Définir un calendrier et synchroniser", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.schedule.title": "Synchroniser vos données", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.deploy.label": "Déployer sans Docker", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.description": "Vous devez déployer ce connecteur dans votre propre infrastructure.", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.dockerDeploy.label": "Déployer avec Docker", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.manageKeys.label": "Gérer les clés d'API", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.readme.label": "Fichier readme du connecteur", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.title": "Support technique et documentation", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.viewDocumentation.label": "Afficher la documentation", "xpack.enterpriseSearch.content.connectors.breadcrumb": "Connecteurs", "xpack.enterpriseSearch.content.connectors.connectorDetail.configurationTabLabel": "Configuration", "xpack.enterpriseSearch.content.connectors.connectorDetail.documentsTabLabel": "Documents", @@ -15046,25 +15029,14 @@ "xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionFlyout.saveButtonLabel": "Enregistrer le nom et la description", "xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionFlyout.title": "Décrire ce robot d'indexation", "xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionForm.description": "En nommant et en décrivant ce connecteur, vos collègues et votre équipe tout entière sauront à quelle utilisation ce connecteur est dédié.", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.config.encryptionWarningMessage": "Le chiffrement pour les informations d'identification de la source de données n'est pas disponible dans cette version. Les informations d'identification de votre source de données seront stockées, non chiffrées, dans Elasticsearch.", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.config.securityDocumentationLinkLabel": "En savoir plus sur Elasticsearch Security", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.buttonTitle": "Convertir un connecteur", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.linkTitle": "client de connecteur", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.title": "Autogestion de ce connecteur", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.entSearchWarning.text": "Les connecteurs natifs nécessitent une instance Enterprise Search pour synchroniser le contenu à partir de la source.", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.entSearchWarning.title": "Aucune instance Enterprise Search en cours d'exécution détectée", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.schedulingReminder.description": "N'oubliez pas de définir un calendrier de synchronisation dans l'onglet Planification pour actualiser continuellement vos données interrogeables.", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.schedulingReminder.title": "Calendrier de synchronisation configurable", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.securityReminder.description": "Limitez et personnalisez l'accès en lecture dont les utilisateurs disposent sur les documents d'indexation à l'heure de la requête.", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.securityReminder.securityLinkLabel": "Sécurité au niveau du document", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.securityReminder.title": "Sécurité au niveau du document", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.advancedConfigurationTitle": "Synchroniser vos données", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.configurationTitle": "Configuration", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.researchConfigurationTitle": "Exigences de la configuration des recherches", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnectorAdvancedConfiguration.description": "Finalisez votre connecteur en déclenchant une synchronisation unique, ou en définissant un calendrier de synchronisation récurrent.", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnectorAdvancedConfiguration.schedulingButtonLabel": "Définir un calendrier et synchroniser", "xpack.enterpriseSearch.content.indices.configurationConnector.researchConfiguration.connectorDocumentationLinkLabel": "Documentation", - "xpack.enterpriseSearch.content.indices.configurationConnector.researchConfiguration.description": "Ce connecteur prend en charge plusieurs méthodes d'authentification. Demandez à votre administrateur les informations d'identification correctes pour la connexion.", "xpack.enterpriseSearch.content.indices.configurationConnector.scheduling.successToast.title": "Mise à jour réussie du calendrier", "xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.error": "Le format JSON n'est pas valide", "xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.title": "Règles avancées", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8600d6d980144..ffebfa6c1e4f4 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13596,7 +13596,6 @@ "xpack.enterpriseSearch.connectors.connectorStats.p.DocumentsLabel": "{documentAmount}ドキュメント", "xpack.enterpriseSearch.connectorStats.connectedBadgeLabel": "{number}個が接続済み", "xpack.enterpriseSearch.connectorStats.runningSyncsTextLabel": "{syncs}実行中の同期", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.connectorConnected": "コネクター{name}は、正常にSearchに接続されました。", "xpack.enterpriseSearch.content.connectors.connectorsTable.columns.actions.viewIndex.caption": "インデックス{connectorName}を表示", "xpack.enterpriseSearch.content.connectors.connectorTable.column.actions.deleteIndex": "コネクター\"{connectorName}\"を削除", "xpack.enterpriseSearch.content.connectors.deleteModal.syncsWarning.indexNameDescription": "この操作は元に戻すことができません。{connectorName}を入力して確認してください。", @@ -14879,23 +14878,7 @@ "xpack.enterpriseSearch.content.analytics.api.generateAnalyticsApiKeyModal.title": "分析APIキーを作成", "xpack.enterpriseSearch.content.cannotConnect.body": "詳細。", "xpack.enterpriseSearch.content.cannotConnect.title": "エンタープライズ サーチに接続できません", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.configurationFileLink": "構成ファイル", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.waitingForConnector.button.label": "今すぐ再確認", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.waitingForConnectorText": "コネクターはSearchに接続されていません。構成のトラブルシューティングを行い、ページを更新してください。", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.waitingForConnectorTitle": "コネクターを待機しています", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.scheduleSync.description": "ワンタイム同期をトリガーするか、経時的にデータソースを同期し続ける繰り返し同期を設定して、コネクターを確定", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.deployConnector.title": "コネクターをデプロイ", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.enhance.title": "コネクタークライアントを強化", "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.generateApiKey.title": "APIキーを生成", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.schedule.button.label": "スケジュールを設定して同期", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.schedule.title": "データを同期", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.deploy.label": "Dockerを使用せずにデプロイ", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.description": "このコネクターは、お客様自身のインフラにデプロイする必要があります。", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.dockerDeploy.label": "Dockerを使用してデプロイ", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.manageKeys.label": "APIキーの管理", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.readme.label": "コネクターReadme", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.title": "サポートとドキュメント", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.viewDocumentation.label": "ドキュメンテーションを表示", "xpack.enterpriseSearch.content.connectors.breadcrumb": "コネクター", "xpack.enterpriseSearch.content.connectors.connectorDetail.configurationTabLabel": "構成", "xpack.enterpriseSearch.content.connectors.connectorDetail.documentsTabLabel": "ドキュメント", @@ -15024,25 +15007,14 @@ "xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionFlyout.saveButtonLabel": "名前と説明を保存", "xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionFlyout.title": "このクローラーの説明", "xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionForm.description": "このコネクターの名前と説明を設定すると、他のユーザーやチームでもこのコネクターの目的がわかります。", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.config.encryptionWarningMessage": "このバージョンでは、データソース資格情報の暗号化を使用できません。データソース資格情報は、暗号化されずに、Elasticsearchに保存されます。", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.config.securityDocumentationLinkLabel": "Elasticsearchセキュリティの詳細", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.buttonTitle": "コネクターを変換", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.linkTitle": "コネクタークライアント", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.title": "このコネクターを自己管理", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.entSearchWarning.text": "ネイティブコネクターは、ソースからコンテンツを同期するために、実行中のエンタープライズ サーチインスタンスが必要です。", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.entSearchWarning.title": "実行中のエンタープライズ サーチインスタンスが検出されません", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.schedulingReminder.description": "必ず[スケジュール]タブで同期スケジュールを設定し、検索可能データを継続的に更新してください。", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.schedulingReminder.title": "設定可能な同期スケジュール", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.securityReminder.description": "クエリ時にユーザーに割り当てられているインデックスドキュメントの読み取りアクセス権を制限、パーソナライズします。", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.securityReminder.securityLinkLabel": "ドキュメントレベルのセキュリティ", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.securityReminder.title": "ドキュメントレベルのセキュリティ", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.advancedConfigurationTitle": "データを同期", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.configurationTitle": "構成", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.researchConfigurationTitle": "構成要件の調査", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnectorAdvancedConfiguration.description": "ワンタイム同期をトリガーするか、繰り返し同期スケジュールを設定して、コネクターを確定します。", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnectorAdvancedConfiguration.schedulingButtonLabel": "スケジュールを設定して同期", "xpack.enterpriseSearch.content.indices.configurationConnector.researchConfiguration.connectorDocumentationLinkLabel": "ドキュメント", - "xpack.enterpriseSearch.content.indices.configurationConnector.researchConfiguration.description": "このコネクターは複数の認証方法をサポートします。正しい接続資格情報については、管理者に確認してください。", "xpack.enterpriseSearch.content.indices.configurationConnector.scheduling.successToast.title": "スケジュールは正常に更新されました", "xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.error": "JSON形式が無効です", "xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.title": "詳細ルール", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6290890e7c6c4..14b42d0c36523 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13622,7 +13622,6 @@ "xpack.enterpriseSearch.connectors.connectorStats.p.DocumentsLabel": "{documentAmount} 个文档", "xpack.enterpriseSearch.connectorStats.connectedBadgeLabel": "{number} 个已连接", "xpack.enterpriseSearch.connectorStats.runningSyncsTextLabel": "{syncs} 个正在运行的同步", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.connectorConnected": "您的连接器 {name} 已成功连接到 Search。", "xpack.enterpriseSearch.content.connectors.connectorsTable.columns.actions.viewIndex.caption": "查看索引 {connectorName}", "xpack.enterpriseSearch.content.connectors.connectorTable.column.actions.deleteIndex": "删除连接器 {connectorName}", "xpack.enterpriseSearch.content.connectors.deleteModal.syncsWarning.indexNameDescription": "此操作无法撤消。请尝试 {connectorName} 以确认。", @@ -14906,23 +14905,7 @@ "xpack.enterpriseSearch.content.analytics.api.generateAnalyticsApiKeyModal.title": "创建分析 API 密钥", "xpack.enterpriseSearch.content.cannotConnect.body": "更多信息。", "xpack.enterpriseSearch.content.cannotConnect.title": "无法连接到 Enterprise Search", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.configurationFileLink": "配置文件", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.waitingForConnector.button.label": "立即重新检查", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.waitingForConnectorText": "您的连接器尚未连接到 Search。排除配置故障并刷新页面。", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.connectorPackage.waitingForConnectorTitle": "等候您的连接器", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.scheduleSync.description": "通过触发一次性同步或设置重复同步来最终确定您的连接器,以使数据源在一段时间内保持同步", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.deployConnector.title": "部署连接器", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.enhance.title": "增强连接器客户端", "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.generateApiKey.title": "生成 API 密钥", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.schedule.button.label": "设置计划并同步", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.schedule.title": "同步您的数据", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.deploy.label": "不通过 Docker 部署", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.description": "您需要在自己的基础设施上部署此连接器。", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.dockerDeploy.label": "通过 Docker 部署", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.manageKeys.label": "管理 API 密钥", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.readme.label": "连接器自述文件", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.title": "支持和文档", - "xpack.enterpriseSearch.content.connector_detail.configurationConnector.support.viewDocumentation.label": "查看文档", "xpack.enterpriseSearch.content.connectors.breadcrumb": "连接器", "xpack.enterpriseSearch.content.connectors.connectorDetail.configurationTabLabel": "配置", "xpack.enterpriseSearch.content.connectors.connectorDetail.documentsTabLabel": "文档", @@ -15051,25 +15034,14 @@ "xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionFlyout.saveButtonLabel": "保存名称和描述", "xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionFlyout.title": "描述此网络爬虫", "xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionForm.description": "通过命名和描述此连接器,您的同事和更广泛的团队将了解本连接器的用途。", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.config.encryptionWarningMessage": "在此版本中无法加密数据源凭据。将在 Elasticsearch 中以未加密方式存储您的数据源凭据。", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.config.securityDocumentationLinkLabel": "详细了解 Elasticsearch 安全", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.buttonTitle": "转换连接器", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.linkTitle": "连接器客户端", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.convertConnector.title": "自我管理此连接器", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.entSearchWarning.text": "本机连接器需要正在运行的 Enterprise Search 实例才能同步源中的内容。", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.entSearchWarning.title": "未检测到正在运行的 Enterprise Search 实例", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.schedulingReminder.description": "请记得在“计划”选项卡中设置同步计划,以继续刷新您的可搜索数据。", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.schedulingReminder.title": "可配置同步计划", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.securityReminder.description": "在查询时将用户拥有的读取访问权限限定为索引文档并进行个性化。", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.securityReminder.securityLinkLabel": "文档级别安全性", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.securityReminder.title": "文档级别安全性", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.advancedConfigurationTitle": "同步您的数据", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.configurationTitle": "配置", - "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.researchConfigurationTitle": "研究配置要求", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnectorAdvancedConfiguration.description": "通过触发一次时间同步或设置重复同步计划来最终确定您的连接器。", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnectorAdvancedConfiguration.schedulingButtonLabel": "设置计划并同步", "xpack.enterpriseSearch.content.indices.configurationConnector.researchConfiguration.connectorDocumentationLinkLabel": "文档", - "xpack.enterpriseSearch.content.indices.configurationConnector.researchConfiguration.description": "此连接器支持几种身份验证方法。请联系管理员获取正确的连接凭据。", "xpack.enterpriseSearch.content.indices.configurationConnector.scheduling.successToast.title": "计划已成功更新", "xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.error": "JSON 格式无效", "xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.title": "高级规则", From fb991fab4c313c86257f113ac98f459293ffe361 Mon Sep 17 00:00:00 2001 From: "Quynh Nguyen (Quinn)" <43350163+qn895@users.noreply.github.com> Date: Tue, 2 Jul 2024 08:58:04 -0500 Subject: [PATCH 077/128] [ML] Updates UI FTR serverless tests to not run with operator privilege (#187283) ## Summary Addresses https://github.com/elastic/kibana/issues/184904. This PR updates UI FTR serverless tests to not run with operator privilege. ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../observability/ml/anomaly_detection_jobs_list.ts | 2 +- .../test_suites/security/ml/anomaly_detection_jobs_list.ts | 4 ++-- .../security/ml/data_frame_analytics_jobs_list.ts | 4 ++-- .../test_suites/security/ml/search_bar_features.ts | 7 ++----- .../test_suites/security/ml/trained_models_list.ts | 7 ++----- 5 files changed, 9 insertions(+), 15 deletions(-) diff --git a/x-pack/test_serverless/functional/test_suites/observability/ml/anomaly_detection_jobs_list.ts b/x-pack/test_serverless/functional/test_suites/observability/ml/anomaly_detection_jobs_list.ts index a7bf63f95ba88..bdd5d443b3592 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/ml/anomaly_detection_jobs_list.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/ml/anomaly_detection_jobs_list.ts @@ -19,7 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Error: Failed to delete all indices with pattern [.ml-*] this.tags(['failsOnMKI']); before(async () => { - await PageObjects.svlCommonPage.loginWithRole('admin'); + await PageObjects.svlCommonPage.loginWithPrivilegedRole(); // Load logstash* data and create dataview for logstash*, logstash-2015.09.22 await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); diff --git a/x-pack/test_serverless/functional/test_suites/security/ml/anomaly_detection_jobs_list.ts b/x-pack/test_serverless/functional/test_suites/security/ml/anomaly_detection_jobs_list.ts index f73703010b877..9e1154ea09bbc 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ml/anomaly_detection_jobs_list.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ml/anomaly_detection_jobs_list.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { ServerlessRoleName } from '../../../../shared/lib/security/types'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { @@ -18,7 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Error: Failed to delete all indices with pattern [.ml-*] this.tags(['failsOnMKI']); before(async () => { - await PageObjects.svlCommonPage.login(); + await PageObjects.svlCommonPage.loginWithRole(ServerlessRoleName.PLATFORM_ENGINEER); // Load logstash* data and create dataview for logstash*, logstash-2015.09.22 await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await kibanaServer.importExport.load( @@ -28,7 +29,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async () => { - await PageObjects.svlCommonPage.forceLogout(); await ml.api.cleanMlIndices(); await ml.testResources.cleanMLSavedObjects(); await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); diff --git a/x-pack/test_serverless/functional/test_suites/security/ml/data_frame_analytics_jobs_list.ts b/x-pack/test_serverless/functional/test_suites/security/ml/data_frame_analytics_jobs_list.ts index 3a4153b264cc6..d3caa3425f753 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ml/data_frame_analytics_jobs_list.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ml/data_frame_analytics_jobs_list.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { ServerlessRoleName } from '../../../../shared/lib/security/types'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { @@ -17,7 +18,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Error: Failed to delete all indices with pattern [.ml-*] this.tags(['failsOnMKI']); before(async () => { - await PageObjects.svlCommonPage.login(); + await PageObjects.svlCommonPage.loginWithRole(ServerlessRoleName.PLATFORM_ENGINEER); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/ihp_outlier'); await ml.testResources.createDataViewIfNeeded('ft_ihp_outlier', '@timestamp'); @@ -28,7 +29,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async () => { - await PageObjects.svlCommonPage.forceLogout(); await ml.api.cleanMlIndices(); await ml.testResources.cleanMLSavedObjects(); }); diff --git a/x-pack/test_serverless/functional/test_suites/security/ml/search_bar_features.ts b/x-pack/test_serverless/functional/test_suites/security/ml/search_bar_features.ts index 35075b9f0da41..85c710a3f380f 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ml/search_bar_features.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ml/search_bar_features.ts @@ -5,6 +5,7 @@ * 2.0. */ import expect from '@kbn/expect'; +import { ServerlessRoleName } from '../../../../shared/lib'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getPageObjects }: FtrProviderContext) { @@ -41,11 +42,7 @@ export default function ({ getPageObjects }: FtrProviderContext) { describe('Search bar features', () => { before(async () => { - await PageObjects.svlCommonPage.login(); - }); - - after(async () => { - await PageObjects.svlCommonPage.forceLogout(); + await PageObjects.svlCommonPage.loginWithRole(ServerlessRoleName.PLATFORM_ENGINEER); }); describe('list features', () => { diff --git a/x-pack/test_serverless/functional/test_suites/security/ml/trained_models_list.ts b/x-pack/test_serverless/functional/test_suites/security/ml/trained_models_list.ts index 745f2b8d4a65f..51edbadf2e6b9 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ml/trained_models_list.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ml/trained_models_list.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { ServerlessRoleName } from '../../../../shared/lib'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { @@ -13,14 +14,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Trained models list', function () { before(async () => { - await PageObjects.svlCommonPage.login(); + await PageObjects.svlCommonPage.loginWithRole(ServerlessRoleName.PLATFORM_ENGINEER); await ml.api.syncSavedObjects(); }); - after(async () => { - await PageObjects.svlCommonPage.forceLogout(); - }); - describe('page navigation', () => { it('renders trained models list', async () => { await ml.navigation.navigateToMl(); From e3165b94d889b29bf46795587c971c8b46b78156 Mon Sep 17 00:00:00 2001 From: Tiago Costa <tiago.costa@elastic.co> Date: Tue, 2 Jul 2024 15:20:19 +0100 Subject: [PATCH 078/128] skip flaky suite (#183663) --- .../public/components/all_cases/multi_select_filter.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/cases/public/components/all_cases/multi_select_filter.test.tsx b/x-pack/plugins/cases/public/components/all_cases/multi_select_filter.test.tsx index 50ea3a82974bf..10bdd185ef9f1 100644 --- a/x-pack/plugins/cases/public/components/all_cases/multi_select_filter.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/multi_select_filter.test.tsx @@ -10,7 +10,8 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; -describe('multi select filter', () => { +// FLAKY: https://github.com/elastic/kibana/issues/183663 +describe.skip('multi select filter', () => { it('should render the amount of options available', async () => { const onChange = jest.fn(); const props = { From 744bcdccaab08dee02661d802db5feb3c00ef9da Mon Sep 17 00:00:00 2001 From: Kevin Delemme <kevin.delemme@elastic.co> Date: Tue, 2 Jul 2024 10:29:35 -0400 Subject: [PATCH 079/128] feat(slo): allow burn rate short window configuration (#187177) --- .../long_window_duration.tsx | 32 +++------ .../short_window_duration.tsx | 69 +++++++++++++++++++ .../burn_rate_rule_editor/validation.test.ts | 29 ++++++++ .../burn_rate_rule_editor/validation.ts | 35 +++++++++- .../burn_rate_rule_editor/windows.tsx | 28 ++++++-- 5 files changed, 164 insertions(+), 29 deletions(-) create mode 100644 x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/short_window_duration.tsx diff --git a/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/long_window_duration.tsx b/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/long_window_duration.tsx index 707712798addd..aaad1d6ae6d0a 100644 --- a/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/long_window_duration.tsx +++ b/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/long_window_duration.tsx @@ -8,23 +8,15 @@ import { EuiFieldNumber, EuiFormRow, EuiIconTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { ChangeEvent, useState } from 'react'; - import { Duration } from '../../typings'; -import { toMinutes } from '../../utils/slo/duration'; interface Props { - shortWindowDuration: Duration; initialDuration?: Duration; errors?: string[]; onChange: (duration: Duration) => void; } -export function LongWindowDuration({ - shortWindowDuration, - initialDuration, - onChange, - errors, -}: Props) { +export function LongWindowDuration({ initialDuration, onChange, errors }: Props) { const [durationValue, setDurationValue] = useState<number>(initialDuration?.value ?? 1); const hasError = errors !== undefined && errors.length > 0; @@ -35,7 +27,7 @@ export function LongWindowDuration({ }; return ( - <EuiFormRow label={getRowLabel(shortWindowDuration)} fullWidth isInvalid={hasError}> + <EuiFormRow label={getRowLabel()} fullWidth isInvalid={hasError}> <EuiFieldNumber isInvalid={hasError} min={1} @@ -44,7 +36,7 @@ export function LongWindowDuration({ value={String(durationValue)} onChange={onDurationValueChange} aria-label={i18n.translate('xpack.slo.rules.longWindow.valueLabel', { - defaultMessage: 'Lookback period in hours', + defaultMessage: 'Long lookback period in hours', })} data-test-subj="durationValueInput" /> @@ -52,18 +44,16 @@ export function LongWindowDuration({ ); } -const getRowLabel = (shortWindowDuration: Duration) => ( +const getRowLabel = () => ( <> {i18n.translate('xpack.slo.rules.longWindow.rowLabel', { - defaultMessage: 'Lookback (hours)', + defaultMessage: 'Long lookback (hours)', })}{' '} - <EuiIconTip position="top" content={getTooltipText(shortWindowDuration)} /> + <EuiIconTip + position="top" + content={i18n.translate('xpack.slo.rules.longWindowDuration.tooltip', { + defaultMessage: 'Long lookback period over which the burn rate is computed.', + })} + /> </> ); - -const getTooltipText = (shortWindowDuration: Duration) => - i18n.translate('xpack.slo.rules.longWindowDuration.tooltip', { - defaultMessage: - 'Lookback period over which the burn rate is computed. A shorter lookback period of {shortWindowDuration} minutes (1/12 the lookback period) will be used for faster recovery', - values: { shortWindowDuration: toMinutes(shortWindowDuration) }, - }); diff --git a/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/short_window_duration.tsx b/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/short_window_duration.tsx new file mode 100644 index 0000000000000..53ab30de8ca63 --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/short_window_duration.tsx @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFieldNumber, EuiFormRow, EuiIconTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { ChangeEvent, useState } from 'react'; + +import { Duration } from '../../typings'; +import { toMinutes } from '../../utils/slo/duration'; + +interface Props { + longWindowDuration: Duration; + initialDuration?: Duration; + errors?: string[]; + onChange: (duration: Duration) => void; +} + +export function ShortWindowDuration({ + longWindowDuration, + initialDuration, + onChange, + errors, +}: Props) { + const [durationValue, setDurationValue] = useState<number>(initialDuration?.value ?? 1); + const hasError = errors !== undefined && errors.length > 0; + const maxShortWindowDuration = toMinutes(longWindowDuration); + + const onDurationValueChange = (e: ChangeEvent<HTMLInputElement>) => { + const value = Number(e.target.value); + setDurationValue(value); + onChange({ value, unit: 'm' }); + }; + + return ( + <EuiFormRow label={getRowLabel()} fullWidth isInvalid={hasError}> + <EuiFieldNumber + isInvalid={hasError} + min={1} + max={maxShortWindowDuration} + step={1} + value={String(durationValue)} + onChange={onDurationValueChange} + aria-label={i18n.translate('xpack.slo.rules.shortWindow.valueLabel', { + defaultMessage: 'short lookback period in minutes', + })} + data-test-subj="durationValueInput" + /> + </EuiFormRow> + ); +} + +const getRowLabel = () => ( + <> + {i18n.translate('xpack.slo.rules.shortWindow.rowLabel', { + defaultMessage: 'Short lookback (min)', + })}{' '} + <EuiIconTip + position="top" + content={i18n.translate('xpack.slo.rules.shortWindowDuration.tooltip', { + defaultMessage: + 'Short lookback period over which the burn rate is computed. Used for faster recovery, a good default value is 1/12th of the long lookback period.', + })} + /> + </> +); diff --git a/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/validation.test.ts b/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/validation.test.ts index 4e4d7fbc66556..245c0c0e59c85 100644 --- a/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/validation.test.ts +++ b/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/validation.test.ts @@ -102,4 +102,33 @@ describe('ValidateBurnRateRule', () => { ).errors.windows[0].longWindow.length ).toBe(0); }); + + it('validates shortWindow is less than longWindow', () => { + expect( + validateBurnRateRule( + createTestParams({ + shortWindow: { value: 61, unit: 'm' }, + longWindow: { value: 1, unit: 'h' }, + }) + ).errors.windows[0].shortWindow.length + ).toBe(1); + + expect( + validateBurnRateRule( + createTestParams({ + shortWindow: { value: 60, unit: 'm' }, + longWindow: { value: 1, unit: 'h' }, + }) + ).errors.windows[0].shortWindow.length + ).toBe(0); + + expect( + validateBurnRateRule( + createTestParams({ + shortWindow: { value: 15, unit: 'm' }, + longWindow: { value: 1, unit: 'h' }, + }) + ).errors.windows[0].shortWindow.length + ).toBe(0); + }); }); diff --git a/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/validation.ts b/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/validation.ts index 6880cf9426439..72362e98aa108 100644 --- a/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/validation.ts +++ b/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/validation.ts @@ -8,9 +8,11 @@ import { i18n } from '@kbn/i18n'; import { ValidationResult } from '@kbn/triggers-actions-ui-plugin/public'; import { BurnRateRuleParams, Duration } from '../../typings'; +import { toMinutes } from '../../utils/slo/duration'; export interface WindowResult { longWindow: string[]; + shortWindow: string[]; burnRateThreshold: string[]; } @@ -42,8 +44,12 @@ export function validateBurnRateRule( } if (windows) { - windows.forEach(({ burnRateThreshold, longWindow, maxBurnRateThreshold }) => { - const result = { longWindow: new Array<string>(), burnRateThreshold: new Array<string>() }; + windows.forEach(({ burnRateThreshold, longWindow, shortWindow, maxBurnRateThreshold }) => { + const result = { + longWindow: new Array<string>(), + shortWindow: new Array<string>(), + burnRateThreshold: new Array<string>(), + }; if (burnRateThreshold === undefined || maxBurnRateThreshold === undefined) { result.burnRateThreshold.push(BURN_RATE_THRESHOLD_REQUIRED); } else if (sloId && (burnRateThreshold < 0.01 || burnRateThreshold > maxBurnRateThreshold)) { @@ -54,6 +60,13 @@ export function validateBurnRateRule( } else if (!isValidLongWindowDuration(longWindow)) { result.longWindow.push(LONG_WINDOW_DURATION_INVALID); } + + if (shortWindow === undefined) { + result.shortWindow.push(SHORT_WINDOW_DURATION_REQUIRED); + } else if (!isValidShortWindowDuration(shortWindow, longWindow)) { + result.shortWindow.push(SHORT_WINDOW_DURATION_INVALID); + } + validationResult.errors.windows.push(result); }); } @@ -72,13 +85,29 @@ const SLO_REQUIRED = i18n.translate('xpack.slo.rules.burnRate.errors.sloRequired const LONG_WINDOW_DURATION_REQUIRED = i18n.translate( 'xpack.slo.rules.burnRate.errors.windowDurationRequired', - { defaultMessage: 'The lookback period is required.' } + { defaultMessage: 'The long lookback period is required.' } ); const LONG_WINDOW_DURATION_INVALID = i18n.translate('xpack.slo.rules.longWindow.errorText', { defaultMessage: 'The lookback period must be between 1 and 72 hours.', }); +const isValidShortWindowDuration = (shortWindow: Duration, longWindow: Duration): boolean => { + const longWindowInMinutes = toMinutes(longWindow); + const shortWindowInMinutes = toMinutes(shortWindow); + const { unit } = shortWindow; + return shortWindowInMinutes >= 1 && shortWindowInMinutes <= longWindowInMinutes && unit === 'm'; +}; + +const SHORT_WINDOW_DURATION_REQUIRED = i18n.translate( + 'xpack.slo.rules.burnRate.errors.shortWindowDurationRequired', + { defaultMessage: 'The short lookback period is required.' } +); + +const SHORT_WINDOW_DURATION_INVALID = i18n.translate('xpack.slo.rules.shortWindow.errorText', { + defaultMessage: 'The short lookback period must be lower than the long lookback period.', +}); + const BURN_RATE_THRESHOLD_REQUIRED = i18n.translate( 'xpack.slo.rules.burnRate.errors.burnRateThresholdRequired', { defaultMessage: 'Burn rate threshold is required.' } diff --git a/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/windows.tsx b/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/windows.tsx index e0b960a7303da..240ad192ca0e9 100644 --- a/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/windows.tsx +++ b/x-pack/plugins/observability_solution/slo/public/components/burn_rate_rule_editor/windows.tsx @@ -34,6 +34,7 @@ import { } from '../../../common/constants'; import { WindowResult } from './validation'; import { BudgetConsumed } from './budget_consumed'; +import { ShortWindowDuration } from './short_window_duration'; interface WindowProps extends WindowSchema { slo?: SLODefinitionResponse; @@ -75,14 +76,23 @@ function Window({ budgetMode = true, }: WindowProps) { const onLongWindowDurationChange = (duration: Duration) => { - const longWindowDurationInMinutes = toMinutes(duration); - const shortWindowDurationValue = Math.floor(longWindowDurationInMinutes / 12); onChange({ id, burnRateThreshold, maxBurnRateThreshold, longWindow: duration, - shortWindow: { value: shortWindowDurationValue, unit: 'm' }, + shortWindow, + actionGroup, + }); + }; + + const onShortWindowDurationChange = (duration: Duration) => { + onChange({ + id, + burnRateThreshold, + maxBurnRateThreshold, + longWindow, + shortWindow: duration, actionGroup, }); }; @@ -135,15 +145,22 @@ function Window({ return ( <> - <EuiFlexGroup direction="row" alignItems="center"> + <EuiFlexGroup direction="row" alignItems="flexEnd"> <EuiFlexItem> <LongWindowDuration initialDuration={longWindow} - shortWindowDuration={shortWindow} onChange={onLongWindowDurationChange} errors={errors.longWindow} /> </EuiFlexItem> + <EuiFlexItem> + <ShortWindowDuration + longWindowDuration={longWindow} + initialDuration={shortWindow} + onChange={onShortWindowDurationChange} + errors={errors.shortWindow} + /> + </EuiFlexItem> {!budgetMode && ( <EuiFlexItem> <BurnRate @@ -300,6 +317,7 @@ export function Windows({ slo, windows, errors, onChange, totalNumberOfWindows } {windows.map((windowDef, index) => { const windowErrors = errors[index] || { longWindow: new Array<string>(), + shortWindow: new Array<string>(), burnRateThreshold: new Array<string>(), }; return ( From 011bfcfe646d9315789e39ed6d75f69a2c45db48 Mon Sep 17 00:00:00 2001 From: Sergi Massaneda <sergi.massaneda@elastic.co> Date: Tue, 2 Jul 2024 16:30:41 +0200 Subject: [PATCH 080/128] [GenAI] Integrations Assistant license and default image fixes (#187347) ## Summary This PR contains 2 different fixes: - License upgrade: The `isAvailable` route context function was returning _false_ after the license was upgraded to `Enterprise`, the server needed to be restarted for the license check to pass. That was happening because we were using the same flag for the license and the `setIsAvailable` contract API (serverless). The flags had been split so the license check can now be updated to _true_ if needed, without issues. - Default logo: We were using a different default logo than the integrations application, when the user was not setting a custom one we were forcing our default logo, which was inconsistent with the Integrations app's default logo: ![old](https://github.com/elastic/kibana/assets/17747913/9203b847-ab99-4dc8-b178-c461e035bae0) This logic has been removed and, in the scenario where the user doesn't want a custom logo, we leave the package manifest without logo definition so the integration app can apply their default logo (package) and everything is consistent. ![no_icon_package](https://github.com/elastic/kibana/assets/17747913/1c65dd6a-5626-4968-8571-6e1a9fa61f99) --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../integration_assistant/common/constants.ts | 5 +++++ .../public/common/hooks/use_availability.ts | 6 ++---- .../steps/default_logo.ts | 9 --------- .../deploy_step/use_deploy_integration.ts | 5 +---- .../integration_step/package_card_preview.tsx | 7 +++++-- .../integration_builder/build_integration.ts | 18 ++++++++---------- .../integration_assistant/server/plugin.ts | 9 +++++---- .../manifest/package_manifest.yml.njk | 4 ++-- 8 files changed, 28 insertions(+), 35 deletions(-) delete mode 100644 x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/default_logo.ts diff --git a/x-pack/plugins/integration_assistant/common/constants.ts b/x-pack/plugins/integration_assistant/common/constants.ts index 7e365342adb89..69b383d882869 100644 --- a/x-pack/plugins/integration_assistant/common/constants.ts +++ b/x-pack/plugins/integration_assistant/common/constants.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { LicenseType } from '@kbn/licensing-plugin/common/types'; + // Plugin information export const PLUGIN_ID = 'integrationAssistant'; @@ -20,3 +22,6 @@ export const RELATED_GRAPH_PATH = `${INTEGRATION_ASSISTANT_BASE_PATH}/related`; export const CHECK_PIPELINE_PATH = `${INTEGRATION_ASSISTANT_BASE_PATH}/pipeline`; export const INTEGRATION_BUILDER_PATH = `${INTEGRATION_ASSISTANT_BASE_PATH}/build`; export const FLEET_PACKAGES_PATH = `/api/fleet/epm/packages`; + +// License +export const MINIMUM_LICENSE_TYPE: LicenseType = 'enterprise'; diff --git a/x-pack/plugins/integration_assistant/public/common/hooks/use_availability.ts b/x-pack/plugins/integration_assistant/public/common/hooks/use_availability.ts index 547c6dd2842c5..9681ea7000623 100644 --- a/x-pack/plugins/integration_assistant/public/common/hooks/use_availability.ts +++ b/x-pack/plugins/integration_assistant/public/common/hooks/use_availability.ts @@ -7,12 +7,10 @@ import { useMemo } from 'react'; import { useObservable } from 'react-use'; -import type { LicenseType } from '@kbn/licensing-plugin/public'; +import { MINIMUM_LICENSE_TYPE } from '../../../common/constants'; import { useKibana } from './use_kibana'; import type { RenderUpselling } from '../../services'; -const MinimumLicenseRequired: LicenseType = 'enterprise'; - export const useAvailability = (): { hasLicense: boolean; renderUpselling: RenderUpselling | undefined; @@ -21,7 +19,7 @@ export const useAvailability = (): { const licenseService = useObservable(licensing.license$); const renderUpselling = useObservable(renderUpselling$); const hasLicense = useMemo( - () => licenseService?.hasAtLeast(MinimumLicenseRequired) ?? true, + () => licenseService?.hasAtLeast(MINIMUM_LICENSE_TYPE) ?? true, [licenseService] ); return { hasLicense, renderUpselling }; diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/default_logo.ts b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/default_logo.ts deleted file mode 100644 index 26f4b8ca6ddc1..0000000000000 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/default_logo.ts +++ /dev/null @@ -1,9 +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. - */ -/* geoToken icon svg base64 encoded */ -export const defaultLogoEncoded = - 'PHN2ZyB3aWR0aD0iMzEiIGhlaWdodD0iMzEiIHZpZXdCb3g9IjAgMCAzMSAzMSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzk4XzcwMDApIj4KPHJlY3Qgd2lkdGg9IjMxIiBoZWlnaHQ9IjMxIiByeD0iMyIgZmlsbD0id2hpdGUiLz4KPHJlY3Qgb3BhY2l0eT0iMC4xIiB3aWR0aD0iMzEiIGhlaWdodD0iMzEiIHJ4PSIzIiBmaWxsPSIjRDZCRjU3Ii8+CjxyZWN0IG9wYWNpdHk9IjAuMyIgeD0iMC41IiB5PSIwLjUiIHdpZHRoPSIzMCIgaGVpZ2h0PSIzMCIgcng9IjIuNSIgc3Ryb2tlPSIjRDZCRjU3Ii8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTUuNSA1LjgxMjVDMTguNjY5MiA1LjgxMjUgMjEuNDgzIDcuMzM0MzUgMjMuMjUwNCA5LjY4NzEyTDIzLjI1IDkuNjg3NUMyNC40NjczIDExLjMwNzkgMjUuMTg3NSAxMy4zMTk4IDI1LjE4NzUgMTUuNUMyNS4xODc1IDE3LjY4MDIgMjQuNDY3MyAxOS42OTIxIDIzLjI1MTkgMjEuMzEwOUwyMy4yNSAyMS4zMTI1QzIxLjQ4MTUgMjMuNjY2NSAxOC42Njg0IDI1LjE4NzUgMTUuNSAyNS4xODc1QzEyLjMzMTYgMjUuMTg3NSA5LjUxODU0IDIzLjY2NjUgNy43NTEwNCAyMS4zMTQ4TDcuNzUgMjEuMzEyNUM2LjUzMzI1IDE5LjY5MzcgNS44MTI1IDE3LjY4MSA1LjgxMjUgMTUuNUM1LjgxMjUgMTAuMTQ5NyAxMC4xNDk3IDUuODEyNSAxNS41IDUuODEyNVpNMTcuMzM2NSAyMS4zMTM1SDEzLjY2MzVDMTQuMjAwNSAyMi41MjQ2IDE0Ljg2OTUgMjMuMjUgMTUuNSAyMy4yNUMxNi4xMzA1IDIzLjI1IDE2Ljc5OTUgMjIuNTI0NiAxNy4zMzY1IDIxLjMxMzVaTTExLjYyNTIgMjEuMzEzOUwxMC4zNzU4IDIxLjMxNDRDMTAuOTA2NSAyMS43ODI0IDExLjUwMTcgMjIuMTc4OSAxMi4xNDY0IDIyLjQ4ODhDMTEuOTU3IDIyLjEyNjUgMTEuNzgyOCAyMS43MzM0IDExLjYyNTIgMjEuMzEzOVpNMjAuNjI0MiAyMS4zMTQ0TDE5LjM3NDggMjEuMzEzOUMxOS4yMTcyIDIxLjczMzQgMTkuMDQzIDIyLjEyNjUgMTguODU0IDIyLjQ4OTJDMTkuNDk4MyAyMi4xNzg5IDIwLjA5MzUgMjEuNzgyNCAyMC42MjQyIDIxLjMxNDRaTTEwLjY4MDIgMTYuNDY5M0w3LjgxMDE0IDE2LjQ3MDJDNy45NDEwOCAxNy41MTg3IDguMjgxNDUgMTguNTAyIDguNzg4MDYgMTkuMzc3MUwxMS4wNTk3IDE5LjM3NjdDMTAuODYxOCAxOC40NzEzIDEwLjczMTEgMTcuNDkzOCAxMC42ODAyIDE2LjQ2OTNaTTE4LjM4MDUgMTYuNDcxSDEyLjYxOTVDMTIuNjc1NSAxNy41MjQ2IDEyLjgyMDYgMTguNTA1NyAxMy4wMjczIDE5LjM3NkgxNy45NzI3QzE4LjE3OTQgMTguNTA1NyAxOC4zMjQ1IDE3LjUyNDYgMTguMzgwNSAxNi40NzFaTTIzLjE4OTkgMTYuNDcwMkwyMC4zMTk4IDE2LjQ2OTNDMjAuMjY4OSAxNy40OTM4IDIwLjEzODIgMTguNDcxMyAxOS45NDAzIDE5LjM3NjdMMjIuMjExOSAxOS4zNzcxQzIyLjcxODUgMTguNTAyIDIzLjA1ODkgMTcuNTE4NyAyMy4xODk5IDE2LjQ3MDJaTTExLjA1OTIgMTEuNjI1M0w4Ljc4Njk1IDExLjYyNDhDOC4yODA2NCAxMi40OTk5IDcuOTQwNTcgMTMuNDgzMyA3LjgwOTkgMTQuNTMxN0wxMC42ODAxIDE0LjUzMjZDMTAuNzMwOSAxMy41MDgyIDEwLjg2MTUgMTIuNTMwNiAxMS4wNTkyIDExLjYyNTNaTTE3Ljk3MzIgMTEuNjI2SDEzLjAyNjhDMTIuODIwMiAxMi40OTYzIDEyLjY3NTMgMTMuNDc3NCAxMi42MTk0IDE0LjUzMUgxOC4zODA2QzE4LjMyNDcgMTMuNDc3NCAxOC4xNzk4IDEyLjQ5NjMgMTcuOTczMiAxMS42MjZaTTIyLjIxMzEgMTEuNjI0OEwxOS45NDA4IDExLjYyNTNDMjAuMTM4NSAxMi41MzA2IDIwLjI2OTEgMTMuNTA4MiAyMC4zMTk5IDE0LjUzMjZMMjMuMTkwMSAxNC41MzE3QzIzLjA1OTQgMTMuNDgzMyAyMi43MTk0IDEyLjQ5OTkgMjIuMjEzMSAxMS42MjQ4Wk0xMi4xNDYgOC41MTA3NUwxMS45MDYyIDguNjMxODZDMTEuMzUyNiA4LjkyMjEyIDEwLjgzODQgOS4yNzczNCAxMC4zNzM4IDkuNjg3MzlMMTEuNjI0NCA5LjY4ODA0QzExLjc4MjIgOS4yNjc4IDExLjk1NjcgOC44NzQwNiAxMi4xNDYgOC41MTA3NVpNMTUuNSA3Ljc1QzE0Ljg2OTEgNy43NSAxNC4xOTk3IDguNDc2MjEgMTMuNjYyNiA5LjY4ODQ4SDE3LjMzNzRDMTYuODAwMyA4LjQ3NjIxIDE2LjEzMDkgNy43NSAxNS41IDcuNzVaTTE4Ljg1MzYgOC41MTExN0wxOC45MjUgOC42NDk5QzE5LjA4NzEgOC45NzM5NyAxOS4yMzc3IDkuMzIwODkgMTkuMzc1NiA5LjY4ODA0TDIwLjYyNjIgOS42ODczOUMyMC4wOTUgOS4yMTg2MSAxOS40OTkxIDguODIxNDkgMTguODUzNiA4LjUxMTE3WiIgZmlsbD0iIzgwNzIzNCIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzk4XzcwMDAiPgo8cmVjdCB3aWR0aD0iMzEiIGhlaWdodD0iMzEiIHJ4PSIzIiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo='; diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/deploy_step/use_deploy_integration.ts b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/deploy_step/use_deploy_integration.ts index f036562edc9d8..ca5385d701964 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/deploy_step/use_deploy_integration.ts +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/deploy_step/use_deploy_integration.ts @@ -10,7 +10,6 @@ import { useKibana } from '../../../../../common/hooks/use_kibana'; import type { BuildIntegrationRequestBody } from '../../../../../../common'; import type { State } from '../../state'; import { runBuildIntegration, runInstallPackage } from '../../../../../common/lib/api'; -import { defaultLogoEncoded } from '../default_logo'; import { getIntegrationNameFromResponse } from '../../../../../common/lib/api_parsers'; import { useTelemetry } from '../../../telemetry'; @@ -20,8 +19,6 @@ interface PipelineGenerationProps { connector: State['connector']; } -export type ProgressItem = 'build' | 'install'; - export const useDeployIntegration = ({ integrationSettings, result, @@ -54,7 +51,7 @@ export const useDeployIntegration = ({ title: integrationSettings.title ?? '', description: integrationSettings.description ?? '', name: integrationSettings.name ?? '', - logo: integrationSettings.logo ?? defaultLogoEncoded, + logo: integrationSettings.logo, dataStreams: [ { title: integrationSettings.dataStreamTitle ?? '', diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/integration_step/package_card_preview.tsx b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/integration_step/package_card_preview.tsx index b73a796f4cb1b..9ca163f44b7d5 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/integration_step/package_card_preview.tsx +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/integration_step/package_card_preview.tsx @@ -7,7 +7,6 @@ import React from 'react'; import { useEuiTheme, EuiCard, EuiIcon } from '@elastic/eui'; import { css } from '@emotion/react'; -import { defaultLogoEncoded } from '../default_logo'; import type { IntegrationSettings } from '../../types'; import * as i18n from './translations'; @@ -50,7 +49,11 @@ export const PackageCardPreview = React.memo<PackageCardPreviewProps>(({ integra icon={ <EuiIcon size={'xl'} - type={`data:image/svg+xml;base64,${integrationSettings?.logo ?? defaultLogoEncoded}`} + type={ + integrationSettings?.logo + ? `data:image/svg+xml;base64,${integrationSettings.logo}` + : 'package' + } /> } betaBadgeProps={{ diff --git a/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.ts b/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.ts index 064fc5cd3ca8f..9422b99b0ab29 100644 --- a/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.ts +++ b/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.ts @@ -10,7 +10,7 @@ import nunjucks from 'nunjucks'; import { tmpdir } from 'os'; import { join as joinPath } from 'path'; import type { DataStream, Integration } from '../../common'; -import { copySync, createSync, ensureDirSync, generateUniqueId } from '../util'; +import { createSync, ensureDirSync, generateUniqueId } from '../util'; import { createAgentInput } from './agent'; import { createDataStream } from './data_stream'; import { createFieldMapping } from './fields'; @@ -63,20 +63,17 @@ function createPackage(packageDir: string, integration: Integration): void { createPackageManifest(packageDir, integration); // Skipping creation of system tests temporarily for custom package generation // createPackageSystemTests(packageDir, integration); - createLogo(packageDir, integration); + if (integration?.logo !== undefined) { + createLogo(packageDir, integration.logo); + } } -function createLogo(packageDir: string, integration: Integration): void { +function createLogo(packageDir: string, logo: string): void { const logoDir = joinPath(packageDir, 'img'); ensureDirSync(logoDir); - if (integration?.logo !== undefined) { - const buffer = Buffer.from(integration.logo, 'base64'); - createSync(joinPath(logoDir, 'logo.svg'), buffer); - } else { - const imgTemplateDir = joinPath(__dirname, '../templates/img'); - copySync(joinPath(imgTemplateDir, 'logo.svg'), joinPath(logoDir, 'logo.svg')); - } + const buffer = Buffer.from(logo, 'base64'); + createSync(joinPath(logoDir, 'logo.svg'), buffer); } function createBuildFile(packageDir: string): void { @@ -137,6 +134,7 @@ function createPackageManifest(packageDir: string, integration: Integration): vo package_name: integration.name, package_version: '0.1.0', package_description: integration.description, + package_logo: integration.logo, package_owner: '@elastic/custom-integrations', min_version: '^8.13.0', inputs: uniqueInputsList, diff --git a/x-pack/plugins/integration_assistant/server/plugin.ts b/x-pack/plugins/integration_assistant/server/plugin.ts index 247ebee04739e..64989d23e7dd8 100644 --- a/x-pack/plugins/integration_assistant/server/plugin.ts +++ b/x-pack/plugins/integration_assistant/server/plugin.ts @@ -14,6 +14,7 @@ import type { CustomRequestHandlerContext, } from '@kbn/core/server'; import type { PluginStartContract as ActionsPluginsStart } from '@kbn/actions-plugin/server/plugin'; +import { MINIMUM_LICENSE_TYPE } from '../common/constants'; import { registerRoutes } from './routes'; import type { IntegrationAssistantPluginSetup, @@ -36,10 +37,12 @@ export class IntegrationAssistantPlugin { private readonly logger: Logger; private isAvailable: boolean; + private hasLicense: boolean; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); this.isAvailable = true; + this.hasLicense = false; } public setup( @@ -52,7 +55,7 @@ export class IntegrationAssistantPlugin 'integrationAssistant' >('integrationAssistant', () => ({ getStartServices: core.getStartServices, - isAvailable: () => this.isAvailable, + isAvailable: () => this.isAvailable && this.hasLicense, logger: this.logger, })); const router = core.http.createRouter<IntegrationAssistantRouteHandlerContext>(); @@ -77,9 +80,7 @@ export class IntegrationAssistantPlugin const { licensing } = dependencies; licensing.license$.subscribe((license) => { - if (!license.hasAtLeast('enterprise')) { - this.isAvailable = false; - } + this.hasLicense = license.hasAtLeast(MINIMUM_LICENSE_TYPE); }); return {}; diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/package_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/package_manifest.yml.njk index 5d18001fb16a5..d9cf502144a9f 100644 --- a/x-pack/plugins/integration_assistant/server/templates/manifest/package_manifest.yml.njk +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/package_manifest.yml.njk @@ -12,11 +12,11 @@ categories: conditions: kibana: version: {{ min_version }} -icons: +{% if package_logo %}icons: - src: /img/logo.svg title: "{{ package_name }} Logo" size: 32x32 - type: image/svg+xml + type: image/svg+xml{% endif %} policy_templates: - name: {{ package_name }} title: | From e105a34924fbf8bbf714215d2203e1289cb0a2aa Mon Sep 17 00:00:00 2001 From: Miriam <31922082+MiriamAparicio@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:32:53 +0100 Subject: [PATCH 081/128] [obsUX] [APM] Unskip dependencies e2e test (#187349) Closes https://github.com/elastic/kibana/issues/179083 [Flaky test runner](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6442) --- .../apm/ftr_e2e/cypress/e2e/dependencies/dependencies.cy.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/dependencies/dependencies.cy.ts b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/dependencies/dependencies.cy.ts index 9fd6dc9bb48be..d5e22e38c5d5e 100644 --- a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/dependencies/dependencies.cy.ts +++ b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/dependencies/dependencies.cy.ts @@ -112,8 +112,7 @@ describe('Dependencies', () => { }); }); -// FLAKY: https://github.com/elastic/kibana/issues/179083 -describe.skip('Dependencies with high volume of data', () => { +describe('Dependencies with high volume of data', () => { before(() => { synthtrace.index( generateManyDependencies({ From 7c6186ab91ffd4d8d42e7f9fcbe0fd7d104e37f1 Mon Sep 17 00:00:00 2001 From: christineweng <18648970+christineweng@users.noreply.github.com> Date: Tue, 2 Jul 2024 09:41:37 -0500 Subject: [PATCH 082/128] [Security Solution] Enable alert preview in document details flyout (#186857) ## Summary This PR enables alert preview in the document flyout. How to test: - Enable feature flag `entityAlertPreviewEnabled` - Generate some alerts and open alert flyout - Go to correlations details (expanded section) - Click on any hyperlinked rule to open an alert preview https://github.com/elastic/kibana/assets/18648970/118a3e22-94d2-4b68-bf23-0f77ad5e8cfd ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../src/components/preview_section.tsx | 11 +- ...correlations_details_alerts_table.test.tsx | 114 +++++---- .../correlations_details_alerts_table.tsx | 217 +++++++++++------- .../related_alerts_by_ancestry.test.tsx | 6 +- ...lated_alerts_by_same_source_event.test.tsx | 14 +- .../related_alerts_by_session.test.tsx | 6 +- .../left/components/test_ids.ts | 3 + .../document_details/preview/footer.test.tsx | 55 +++++ .../document_details/preview/footer.tsx | 54 +++++ .../flyout/document_details/preview/index.tsx | 74 ++++++ .../document_details/preview/test_ids.ts | 11 + .../components/correlations_overview.test.tsx | 20 ++ .../components/correlations_overview.tsx | 41 ++-- .../components/entities_overview.test.tsx | 12 + .../right/components/entities_overview.tsx | 32 ++- .../components/investigation_guide.test.tsx | 22 +- .../right/components/investigation_guide.tsx | 18 +- .../components/prevalence_overview.test.tsx | 17 ++ .../right/components/prevalence_overview.tsx | 37 ++- .../components/response_section.test.tsx | 16 ++ .../right/components/response_section.tsx | 7 +- .../threat_intelligence_overview.test.tsx | 15 ++ .../threat_intelligence_overview.tsx | 33 ++- .../flyout/document_details/right/header.tsx | 7 +- .../rule_overview/components/footer.test.tsx | 2 +- .../rule_overview/components/footer.tsx | 2 +- .../document_details/rule_overview/index.tsx | 6 +- .../document_details/shared/context.tsx | 13 +- .../shared/mocks/mock_context.ts | 1 + .../flyout/document_details/shared/types.tsx | 1 + .../security_solution/public/flyout/index.tsx | 10 + .../flyout/shared/components/flyout_body.tsx | 12 +- ...t_details_preview_panel_rule_preview.cy.ts | 2 +- 33 files changed, 691 insertions(+), 200 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.test.tsx create mode 100644 x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.tsx create mode 100644 x-pack/plugins/security_solution/public/flyout/document_details/preview/index.tsx create mode 100644 x-pack/plugins/security_solution/public/flyout/document_details/preview/test_ids.ts diff --git a/packages/kbn-expandable-flyout/src/components/preview_section.tsx b/packages/kbn-expandable-flyout/src/components/preview_section.tsx index bc0c1c2e9d93a..64493c75bd3e9 100644 --- a/packages/kbn-expandable-flyout/src/components/preview_section.tsx +++ b/packages/kbn-expandable-flyout/src/components/preview_section.tsx @@ -14,6 +14,7 @@ import { EuiText, useEuiTheme, EuiSplitPanel, + transparentize, } from '@elastic/eui'; import React from 'react'; import { css } from '@emotion/react'; @@ -120,8 +121,8 @@ export const PreviewSection: React.FC<PreviewSectionProps> = ({ <div css={css` position: absolute; - top: 4px; - bottom: 12px; + top: 8px; + bottom: 8px; right: 4px; left: ${left}px; z-index: 1000; @@ -130,7 +131,7 @@ export const PreviewSection: React.FC<PreviewSectionProps> = ({ <EuiSplitPanel.Outer css={css` margin: ${euiTheme.size.xs}; - box-shadow: 0 0 4px 4px ${euiTheme.colors.darkShade}; + box-shadow: 0 0 16px 0px ${transparentize(euiTheme.colors.mediumShade, 0.5)}; `} data-test-subj={PREVIEW_SECTION_TEST_ID} className="eui-fullHeight" @@ -139,13 +140,13 @@ export const PreviewSection: React.FC<PreviewSectionProps> = ({ <EuiSplitPanel.Inner grow={false} color={banner.backgroundColor} - paddingSize="none" + paddingSize="xs" data-test-subj={`${PREVIEW_SECTION_TEST_ID}BannerPanel`} > <EuiText textAlign="center" color={banner.textColor} - size="s" + size="xs" data-test-subj={`${PREVIEW_SECTION_TEST_ID}BannerText`} > {banner.title} diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/correlations_details_alerts_table.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/correlations_details_alerts_table.test.tsx index f183df7f95913..1fce080352a08 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/correlations_details_alerts_table.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/correlations_details_alerts_table.test.tsx @@ -8,24 +8,49 @@ import React from 'react'; import { render } from '@testing-library/react'; import { TestProviders } from '../../../../common/mock'; -import { EuiBasicTable } from '@elastic/eui'; -import { CorrelationsDetailsAlertsTable, columns } from './correlations_details_alerts_table'; +import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { CorrelationsDetailsAlertsTable } from './correlations_details_alerts_table'; import { usePaginatedAlerts } from '../hooks/use_paginated_alerts'; +import { CORRELATIONS_DETAILS_ALERT_PREVIEW_BUTTON_TEST_ID } from './test_ids'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { mockFlyoutApi } from '../../shared/mocks/mock_flyout_context'; +import { mockContextValue } from '../../shared/mocks/mock_context'; +import { DocumentDetailsPreviewPanelKey } from '../../shared/constants/panel_keys'; +import { ALERT_PREVIEW_BANNER } from '../../preview'; +import { DocumentDetailsContext } from '../../shared/context'; jest.mock('../hooks/use_paginated_alerts'); -jest.mock('@elastic/eui', () => ({ - ...jest.requireActual('@elastic/eui'), - EuiBasicTable: jest.fn(() => <div data-testid="mock-euibasictable" />), +jest.mock('../../../../common/hooks/use_experimental_features'); +const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock; + +jest.mock('@kbn/expandable-flyout', () => ({ + useExpandableFlyoutApi: jest.fn(), + ExpandableFlyoutProvider: ({ children }: React.PropsWithChildren<{}>) => <>{children}</>, })); const TEST_ID = 'TEST'; -const scopeId = 'scopeId'; -const eventId = 'eventId'; +const alertIds = ['id1', 'id2', 'id3']; -describe('CorrelationsDetailsAlertsTable', () => { - const alertIds = ['id1', 'id2', 'id3']; +const renderCorrelationsTable = (panelContext: DocumentDetailsContext) => + render( + <TestProviders> + <DocumentDetailsContext.Provider value={panelContext}> + <CorrelationsDetailsAlertsTable + title={<p>{'title'}</p>} + loading={false} + alertIds={alertIds} + scopeId={mockContextValue.scopeId} + eventId={mockContextValue.eventId} + data-test-subj={TEST_ID} + /> + </DocumentDetailsContext.Provider> + </TestProviders> + ); +describe('CorrelationsDetailsAlertsTable', () => { beforeEach(() => { + jest.mocked(useExpandableFlyoutApi).mockReturnValue(mockFlyoutApi); + mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); jest.mocked(usePaginatedAlerts).mockReturnValue({ setPagination: jest.fn(), setSorting: jest.fn(), @@ -64,44 +89,45 @@ describe('CorrelationsDetailsAlertsTable', () => { }); it('renders EuiBasicTable with correct props', () => { - const { getByTestId } = render( - <TestProviders> - <CorrelationsDetailsAlertsTable - title={<p>{'title'}</p>} - loading={false} - alertIds={alertIds} - scopeId={scopeId} - eventId={eventId} - data-test-subj={TEST_ID} - /> - </TestProviders> - ); + const { getByTestId, queryByTestId, queryAllByRole } = + renderCorrelationsTable(mockContextValue); + expect(getByTestId(`${TEST_ID}InvestigateInTimeline`)).toBeInTheDocument(); + expect(getByTestId(`${TEST_ID}Table`)).toBeInTheDocument(); + expect( + queryByTestId(CORRELATIONS_DETAILS_ALERT_PREVIEW_BUTTON_TEST_ID) + ).not.toBeInTheDocument(); expect(jest.mocked(usePaginatedAlerts)).toHaveBeenCalled(); - expect(jest.mocked(EuiBasicTable)).toHaveBeenCalledWith( - expect.objectContaining({ - loading: false, - items: [ - { - '@timestamp': '2022-01-01', - 'kibana.alert.rule.name': 'Rule1', - 'kibana.alert.reason': 'Reason1', - 'kibana.alert.severity': 'Severity1', - }, - { - '@timestamp': '2022-01-02', - 'kibana.alert.rule.name': 'Rule2', - 'kibana.alert.reason': 'Reason2', - 'kibana.alert.severity': 'Severity2', - }, - ], - columns, - pagination: { pageIndex: 0, pageSize: 5, totalItemCount: 10, pageSizeOptions: [5, 10, 20] }, - sorting: { sort: { field: '@timestamp', direction: 'asc' }, enableAllColumns: true }, - }), - expect.anything() - ); + expect(queryAllByRole('columnheader').length).toBe(4); + expect(queryAllByRole('row').length).toBe(3); // 1 header row and 2 data rows + expect(queryAllByRole('row')[1].textContent).toContain('Jan 1, 2022 @ 00:00:00.000'); + expect(queryAllByRole('row')[1].textContent).toContain('Reason1'); + expect(queryAllByRole('row')[1].textContent).toContain('Rule1'); + expect(queryAllByRole('row')[1].textContent).toContain('Severity1'); + }); + + it('renders open preview button when feature flag is on', () => { + mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); + const { getByTestId, getAllByTestId } = renderCorrelationsTable({ + ...mockContextValue, + isPreviewMode: true, + }); + + expect(getByTestId(`${TEST_ID}InvestigateInTimeline`)).toBeInTheDocument(); + expect(getAllByTestId(CORRELATIONS_DETAILS_ALERT_PREVIEW_BUTTON_TEST_ID).length).toBe(2); + + getAllByTestId(CORRELATIONS_DETAILS_ALERT_PREVIEW_BUTTON_TEST_ID)[0].click(); + expect(mockFlyoutApi.openPreviewPanel).toHaveBeenCalledWith({ + id: DocumentDetailsPreviewPanelKey, + params: { + id: '1', + indexName: 'index', + scopeId: mockContextValue.scopeId, + banner: ALERT_PREVIEW_BANNER, + isPreviewMode: true, + }, + }); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/correlations_details_alerts_table.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/correlations_details_alerts_table.tsx index 557c8bae274fe..bf1a28201fc87 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/correlations_details_alerts_table.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/correlations_details_alerts_table.tsx @@ -7,13 +7,17 @@ import type { ReactElement, ReactNode } from 'react'; import React, { type FC, useMemo, useCallback } from 'react'; -import { type Criteria, EuiBasicTable, formatDate } from '@elastic/eui'; +import { type Criteria, EuiBasicTable, formatDate, EuiButtonIcon } from '@elastic/eui'; import { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; import type { Filter } from '@kbn/es-query'; import { isRight } from 'fp-ts/lib/Either'; +import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { ALERT_REASON, ALERT_RULE_NAME } from '@kbn/rule-data-utils'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { useDocumentDetailsContext } from '../../shared/context'; +import { CORRELATIONS_DETAILS_ALERT_PREVIEW_BUTTON_TEST_ID } from './test_ids'; import { CellTooltipWrapper } from '../../shared/components/cell_tooltip_wrapper'; import type { DataProvider } from '../../../../../common/types'; import { SeverityBadge } from '../../../../common/components/severity_badge'; @@ -22,80 +26,50 @@ import { ExpandablePanel } from '../../../shared/components/expandable_panel'; import { InvestigateInTimelineButton } from '../../../../common/components/event_details/table/investigate_in_timeline_button'; import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../../detections/components/alerts_table/translations'; import { getDataProvider } from '../../../../common/components/event_details/table/use_action_cell_data_provider'; +import { DocumentDetailsPreviewPanelKey } from '../../shared/constants/panel_keys'; +import { ALERT_PREVIEW_BANNER } from '../../preview'; export const TIMESTAMP_DATE_FORMAT = 'MMM D, YYYY @ HH:mm:ss.SSS'; const dataProviderLimit = 5; -export const columns = [ - { - field: '@timestamp', - name: ( - <FormattedMessage - id="xpack.securitySolution.flyout.left.insights.correlations.timestampColumnLabel" - defaultMessage="Timestamp" - /> - ), - truncateText: true, - dataType: 'date' as const, - render: (value: string) => { - const date = formatDate(value, TIMESTAMP_DATE_FORMAT); - return ( - <CellTooltipWrapper tooltip={date}> - <span>{date}</span> - </CellTooltipWrapper> - ); - }, - }, - { - field: ALERT_RULE_NAME, - name: ( - <FormattedMessage - id="xpack.securitySolution.flyout.left.insights.correlations.ruleColumnLabel" - defaultMessage="Rule" - /> - ), - truncateText: true, - render: (value: string) => ( - <CellTooltipWrapper tooltip={value}> - <span>{value}</span> - </CellTooltipWrapper> - ), - }, - { - field: ALERT_REASON, - name: ( - <FormattedMessage - id="xpack.securitySolution.flyout.left.insights.correlations.reasonColumnLabel" - defaultMessage="Reason" - /> - ), - truncateText: true, - render: (value: string) => ( - <CellTooltipWrapper tooltip={value} anchorPosition="left"> - <span>{value}</span> - </CellTooltipWrapper> - ), - }, - { - field: 'kibana.alert.severity', - name: ( - <FormattedMessage - id="xpack.securitySolution.flyout.left.insights.correlations.severityColumnLabel" - defaultMessage="Severity" - /> - ), - truncateText: true, - render: (value: string) => { - const decodedSeverity = Severity.decode(value); - const renderValue = isRight(decodedSeverity) ? ( - <SeverityBadge value={decodedSeverity.right} /> - ) : ( - <p>{value}</p> - ); - return <CellTooltipWrapper tooltip={value}>{renderValue}</CellTooltipWrapper>; - }, - }, -]; +interface AlertPreviewButtonProps { + /** + * Id of the document + */ + id: string; + /** + * Name of the index used in the parent's page + */ + indexName: string; +} + +const AlertPreviewButton: FC<AlertPreviewButtonProps> = ({ id, indexName }) => { + const { openPreviewPanel } = useExpandableFlyoutApi(); + const { scopeId } = useDocumentDetailsContext(); + + const openAlertPreview = useCallback( + () => + openPreviewPanel({ + id: DocumentDetailsPreviewPanelKey, + params: { + id, + indexName, + scopeId, + isPreviewMode: true, + banner: ALERT_PREVIEW_BANNER, + }, + }), + [openPreviewPanel, id, indexName, scopeId] + ); + + return ( + <EuiButtonIcon + iconType="expand" + data-test-subj={CORRELATIONS_DETAILS_ALERT_PREVIEW_BUTTON_TEST_ID} + onClick={openAlertPreview} + /> + ); +}; export interface CorrelationsDetailsAlertsTableProps { /** @@ -149,6 +123,7 @@ export const CorrelationsDetailsAlertsTable: FC<CorrelationsDetailsAlertsTablePr sorting, error, } = usePaginatedAlerts(alertIds || []); + const isPreviewEnabled = useIsExperimentalFeatureEnabled('entityAlertPreviewEnabled'); const onTableChange = useCallback( ({ page, sort }: Criteria<Record<string, unknown>>) => { @@ -166,13 +141,17 @@ export const CorrelationsDetailsAlertsTable: FC<CorrelationsDetailsAlertsTablePr const mappedData = useMemo(() => { return data - .map((hit) => hit.fields) - .map((fields = {}) => - Object.keys(fields).reduce((result, fieldName) => { - result[fieldName] = fields?.[fieldName]?.[0] || fields?.[fieldName]; + .map((hit) => ({ fields: hit.fields ?? {}, id: hit._id, index: hit._index })) + .map((dataWithMeta) => { + const res = Object.keys(dataWithMeta.fields).reduce((result, fieldName) => { + result[fieldName] = + dataWithMeta.fields?.[fieldName]?.[0] || dataWithMeta.fields?.[fieldName]; return result; - }, {} as Record<string, unknown>) - ); + }, {} as Record<string, unknown>); + res.id = dataWithMeta.id; + res.index = dataWithMeta.index; + return res; + }); }, [data]); const shouldUseFilters = Boolean( @@ -187,6 +166,90 @@ export const CorrelationsDetailsAlertsTable: FC<CorrelationsDetailsAlertsTablePr [alertIds, shouldUseFilters] ); + const columns = useMemo( + () => [ + ...(isPreviewEnabled + ? [ + { + render: (row: Record<string, unknown>) => ( + <AlertPreviewButton id={row.id as string} indexName={row.index as string} /> + ), + width: '5%', + }, + ] + : []), + { + field: '@timestamp', + name: ( + <FormattedMessage + id="xpack.securitySolution.flyout.left.insights.correlations.timestampColumnLabel" + defaultMessage="Timestamp" + /> + ), + truncateText: true, + dataType: 'date' as const, + render: (value: string) => { + const date = formatDate(value, TIMESTAMP_DATE_FORMAT); + return ( + <CellTooltipWrapper tooltip={date}> + <span>{date}</span> + </CellTooltipWrapper> + ); + }, + }, + { + field: ALERT_RULE_NAME, + name: ( + <FormattedMessage + id="xpack.securitySolution.flyout.left.insights.correlations.ruleColumnLabel" + defaultMessage="Rule" + /> + ), + truncateText: true, + render: (value: string) => ( + <CellTooltipWrapper tooltip={value}> + <span>{value}</span> + </CellTooltipWrapper> + ), + }, + { + field: ALERT_REASON, + name: ( + <FormattedMessage + id="xpack.securitySolution.flyout.left.insights.correlations.reasonColumnLabel" + defaultMessage="Reason" + /> + ), + truncateText: true, + render: (value: string) => ( + <CellTooltipWrapper tooltip={value} anchorPosition="left"> + <span>{value}</span> + </CellTooltipWrapper> + ), + }, + { + field: 'kibana.alert.severity', + name: ( + <FormattedMessage + id="xpack.securitySolution.flyout.left.insights.correlations.severityColumnLabel" + defaultMessage="Severity" + /> + ), + truncateText: true, + render: (value: string) => { + const decodedSeverity = Severity.decode(value); + const renderValue = isRight(decodedSeverity) ? ( + <SeverityBadge value={decodedSeverity.right} /> + ) : ( + <p>{value}</p> + ); + return <CellTooltipWrapper tooltip={value}>{renderValue}</CellTooltipWrapper>; + }, + }, + ], + [isPreviewEnabled] + ); + return ( <ExpandablePanel header={{ diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/related_alerts_by_ancestry.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/related_alerts_by_ancestry.test.tsx index 063ebce7354aa..52468f0aedbb9 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/related_alerts_by_ancestry.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/related_alerts_by_ancestry.test.tsx @@ -8,6 +8,8 @@ import React from 'react'; import { render } from '@testing-library/react'; import { TestProviders } from '../../../../common/mock'; +import { DocumentDetailsContext } from '../../shared/context'; +import { mockContextValue } from '../../shared/mocks/mock_context'; import { CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TABLE_TEST_ID, CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID, @@ -41,7 +43,9 @@ const TITLE_TEXT = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID( const renderRelatedAlertsByAncestry = () => render( <TestProviders> - <RelatedAlertsByAncestry documentId={documentId} indices={indices} scopeId={scopeId} /> + <DocumentDetailsContext.Provider value={mockContextValue}> + <RelatedAlertsByAncestry documentId={documentId} indices={indices} scopeId={scopeId} /> + </DocumentDetailsContext.Provider> </TestProviders> ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/related_alerts_by_same_source_event.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/related_alerts_by_same_source_event.test.tsx index e8334613d1d24..3cf2d93896bc3 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/related_alerts_by_same_source_event.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/related_alerts_by_same_source_event.test.tsx @@ -8,6 +8,8 @@ import React from 'react'; import { render } from '@testing-library/react'; import { TestProviders } from '../../../../common/mock'; +import { DocumentDetailsContext } from '../../shared/context'; +import { mockContextValue } from '../../shared/mocks/mock_context'; import { CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID, CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TABLE_TEST_ID, @@ -41,11 +43,13 @@ const TITLE_TEXT = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID( const renderRelatedAlertsBySameSourceEvent = () => render( <TestProviders> - <RelatedAlertsBySameSourceEvent - originalEventId={originalEventId} - scopeId={scopeId} - eventId={eventId} - /> + <DocumentDetailsContext.Provider value={mockContextValue}> + <RelatedAlertsBySameSourceEvent + originalEventId={originalEventId} + scopeId={scopeId} + eventId={eventId} + /> + </DocumentDetailsContext.Provider> </TestProviders> ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/related_alerts_by_session.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/related_alerts_by_session.test.tsx index ca5489b13c8c3..0120f462b9ac5 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/related_alerts_by_session.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/related_alerts_by_session.test.tsx @@ -8,6 +8,8 @@ import React from 'react'; import { render } from '@testing-library/react'; import { TestProviders } from '../../../../common/mock'; +import { DocumentDetailsContext } from '../../shared/context'; +import { mockContextValue } from '../../shared/mocks/mock_context'; import { CORRELATIONS_DETAILS_BY_SESSION_SECTION_TABLE_TEST_ID, CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID, @@ -41,7 +43,9 @@ const TITLE_TEXT = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID( const renderRelatedAlertsBySession = () => render( <TestProviders> - <RelatedAlertsBySession entityId={entityId} scopeId={scopeId} eventId={eventId} /> + <DocumentDetailsContext.Provider value={mockContextValue}> + <RelatedAlertsBySession entityId={entityId} scopeId={scopeId} eventId={eventId} /> + </DocumentDetailsContext.Provider> </TestProviders> ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/test_ids.ts index 95ec61d66fff3..c5bf2abd6988d 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/test_ids.ts @@ -69,6 +69,9 @@ export const THREAT_INTELLIGENCE_DETAILS_LOADING_TEST_ID = export const CORRELATIONS_DETAILS_TEST_ID = `${PREFIX}CorrelationsDetails` as const; +export const CORRELATIONS_DETAILS_ALERT_PREVIEW_BUTTON_TEST_ID = + `${CORRELATIONS_DETAILS_TEST_ID}AlertPreviewButton` as const; + export const CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID = `${CORRELATIONS_DETAILS_TEST_ID}AlertsByAncestrySection` as const; export const CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TABLE_TEST_ID = diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.test.tsx new file mode 100644 index 0000000000000..8210bd2dd2f3d --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.test.tsx @@ -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 React from 'react'; +import { render } from '@testing-library/react'; +import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { DocumentDetailsRightPanelKey } from '../shared/constants/panel_keys'; +import { mockFlyoutApi } from '../shared/mocks/mock_flyout_context'; +import { mockContextValue } from '../shared/mocks/mock_context'; +import { DocumentDetailsContext } from '../shared/context'; +import { PreviewPanelFooter } from './footer'; +import { PREVIEW_FOOTER_TEST_ID, PREVIEW_FOOTER_LINK_TEST_ID } from './test_ids'; + +jest.mock('@kbn/expandable-flyout', () => ({ + useExpandableFlyoutApi: jest.fn(), + ExpandableFlyoutProvider: ({ children }: React.PropsWithChildren<{}>) => <>{children}</>, +})); + +describe('<PreviewPanelFooter />', () => { + beforeAll(() => { + jest.mocked(useExpandableFlyoutApi).mockReturnValue(mockFlyoutApi); + }); + + it('should render footer', () => { + const { getByTestId } = render( + <DocumentDetailsContext.Provider value={mockContextValue}> + <PreviewPanelFooter /> + </DocumentDetailsContext.Provider> + ); + expect(getByTestId(PREVIEW_FOOTER_TEST_ID)).toBeInTheDocument(); + }); + + it('should open document details flyout when clicked', () => { + const { getByTestId } = render( + <DocumentDetailsContext.Provider value={mockContextValue}> + <PreviewPanelFooter /> + </DocumentDetailsContext.Provider> + ); + + getByTestId(PREVIEW_FOOTER_LINK_TEST_ID).click(); + expect(mockFlyoutApi.openFlyout).toHaveBeenCalledWith({ + right: { + id: DocumentDetailsRightPanelKey, + params: { + id: mockContextValue.eventId, + indexName: mockContextValue.indexName, + scopeId: mockContextValue.scopeId, + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.tsx new file mode 100644 index 0000000000000..38c7e61148566 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/preview/footer.tsx @@ -0,0 +1,54 @@ +/* + * 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 { EuiLink, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React, { useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { FlyoutFooter } from '../../shared/components/flyout_footer'; +import { DocumentDetailsRightPanelKey } from '../shared/constants/panel_keys'; +import { useDocumentDetailsContext } from '../shared/context'; +import { PREVIEW_FOOTER_TEST_ID, PREVIEW_FOOTER_LINK_TEST_ID } from './test_ids'; + +/** + * Footer at the bottom of preview panel with a link to open document details flyout + */ +export const PreviewPanelFooter = () => { + const { eventId, indexName, scopeId } = useDocumentDetailsContext(); + const { openFlyout } = useExpandableFlyoutApi(); + + const openDocumentFlyout = useCallback(() => { + openFlyout({ + right: { + id: DocumentDetailsRightPanelKey, + params: { + id: eventId, + indexName, + scopeId, + }, + }, + }); + }, [openFlyout, eventId, indexName, scopeId]); + + return ( + <FlyoutFooter data-test-subj={PREVIEW_FOOTER_TEST_ID}> + <EuiFlexGroup justifyContent="center"> + <EuiFlexItem grow={false}> + <EuiLink + onClick={openDocumentFlyout} + target="_blank" + data-test-subj={PREVIEW_FOOTER_LINK_TEST_ID} + > + {i18n.translate('xpack.securitySolution.flyout.preview.openFlyoutLabel', { + defaultMessage: 'Show full alert details', + })} + </EuiLink> + </EuiFlexItem> + </EuiFlexGroup> + </FlyoutFooter> + ); +}; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/preview/index.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/preview/index.tsx new file mode 100644 index 0000000000000..ff65d4c264c42 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/preview/index.tsx @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FC } from 'react'; +import React, { memo } from 'react'; +import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { i18n } from '@kbn/i18n'; +import { DocumentDetailsPreviewPanelKey } from '../shared/constants/panel_keys'; +import { useTabs } from '../right/hooks/use_tabs'; +import { useFlyoutIsExpandable } from '../right/hooks/use_flyout_is_expandable'; +import { useDocumentDetailsContext } from '../shared/context'; +import type { DocumentDetailsProps } from '../shared/types'; +import { PanelHeader } from '../right/header'; +import { PanelContent } from '../right/content'; +import { PreviewPanelFooter } from './footer'; +import type { RightPanelTabType } from '../right/tabs'; + +export const ALERT_PREVIEW_BANNER = { + title: i18n.translate( + 'xpack.securitySolution.flyout.left.insights.correlations.alertPreviewTitle', + { + defaultMessage: 'Preview alert details', + } + ), + backgroundColor: 'warning', + textColor: 'warning', +}; + +/** + * Panel to be displayed in the document details expandable flyout on top of right section + */ +export const PreviewPanel: FC<Partial<DocumentDetailsProps>> = memo(({ path }) => { + const { openPreviewPanel } = useExpandableFlyoutApi(); + const { eventId, indexName, scopeId, getFieldsData, dataAsNestedObject } = + useDocumentDetailsContext(); + const flyoutIsExpandable = useFlyoutIsExpandable({ getFieldsData, dataAsNestedObject }); + + const { tabsDisplayed, selectedTabId } = useTabs({ flyoutIsExpandable, path }); + + const setSelectedTabId = (tabId: RightPanelTabType['id']) => { + openPreviewPanel({ + id: DocumentDetailsPreviewPanelKey, + path: { + tab: tabId, + }, + params: { + id: eventId, + indexName, + scopeId, + isPreviewMode: true, + banner: ALERT_PREVIEW_BANNER, + }, + }); + }; + + return ( + <> + <PanelHeader + tabs={tabsDisplayed} + selectedTabId={selectedTabId} + setSelectedTabId={setSelectedTabId} + style={{ marginTop: '-15px' }} + /> + <PanelContent tabs={tabsDisplayed} selectedTabId={selectedTabId} /> + <PreviewPanelFooter /> + </> + ); +}); + +PreviewPanel.displayName = 'PreviewPanel'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/preview/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/document_details/preview/test_ids.ts new file mode 100644 index 0000000000000..75318d8d18bee --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/preview/test_ids.ts @@ -0,0 +1,11 @@ +/* + * 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 { PREFIX } from '../../shared/test_ids'; + +export const PREVIEW_FOOTER_TEST_ID = `${PREFIX}PreviewFooter` as const; +export const PREVIEW_FOOTER_LINK_TEST_ID = `${PREVIEW_FOOTER_TEST_ID}Link` as const; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.test.tsx index 2145d3efff129..fb6b2f4edcfb2 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.test.tsx @@ -141,6 +141,26 @@ describe('<CorrelationsOverview />', () => { expect(queryByTestId(TITLE_TEXT_TEST_ID)).not.toBeInTheDocument(); }); + it('should not render link when isPreviewMode is true', () => { + jest + .mocked(useShowRelatedAlertsByAncestry) + .mockReturnValue({ show: false, documentId: 'event-id' }); + jest + .mocked(useShowRelatedAlertsBySameSourceEvent) + .mockReturnValue({ show: false, originalEventId }); + jest.mocked(useShowRelatedAlertsBySession).mockReturnValue({ show: false }); + jest.mocked(useShowRelatedCases).mockReturnValue(false); + jest.mocked(useShowSuppressedAlerts).mockReturnValue({ show: false, alertSuppressionCount: 0 }); + + const { getByTestId, queryByTestId } = render( + renderCorrelationsOverview({ ...panelContextValue, isPreviewMode: true }) + ); + expect(queryByTestId(TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(TITLE_LINK_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(TITLE_ICON_TEST_ID)).not.toBeInTheDocument(); + expect(getByTestId(TITLE_TEXT_TEST_ID)).toBeInTheDocument(); + }); + it('should show component with all rows in expandable panel', () => { jest .mocked(useShowRelatedAlertsByAncestry) diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.tsx index 3bf57aa916963..d4e9a6fe58113 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.tsx @@ -6,7 +6,7 @@ */ import { get } from 'lodash'; -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import { EuiFlexGroup } from '@elastic/eui'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -42,8 +42,15 @@ import { * and the SummaryPanel component for data rendering. */ export const CorrelationsOverview: React.FC = () => { - const { dataAsNestedObject, eventId, indexName, getFieldsData, scopeId, isPreview } = - useDocumentDetailsContext(); + const { + dataAsNestedObject, + eventId, + indexName, + getFieldsData, + scopeId, + isPreview, + isPreviewMode, + } = useDocumentDetailsContext(); const { openLeftPanel } = useExpandableFlyoutApi(); const { isTourShown, activeStep } = useTourContext(); @@ -95,6 +102,22 @@ export const CorrelationsOverview: React.FC = () => { const ruleType = get(dataAsNestedObject, ALERT_RULE_TYPE)?.[0]; + const link = useMemo( + () => + !isPreviewMode + ? { + callback: goToCorrelationsTab, + tooltip: ( + <FormattedMessage + id="xpack.securitySolution.flyout.right.insights.correlations.overviewTooltip" + defaultMessage="Show all correlations" + /> + ), + } + : undefined, + [isPreviewMode, goToCorrelationsTab] + ); + return ( <ExpandablePanel header={{ @@ -104,16 +127,8 @@ export const CorrelationsOverview: React.FC = () => { defaultMessage="Correlations" /> ), - link: { - callback: goToCorrelationsTab, - tooltip: ( - <FormattedMessage - id="xpack.securitySolution.flyout.right.insights.correlations.overviewTooltip" - defaultMessage="Show all correlations" - /> - ), - }, - iconType: 'arrowStart', + link, + iconType: !isPreviewMode ? 'arrowStart' : undefined, }} data-test-subj={CORRELATIONS_TEST_ID} > diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/entities_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/entities_overview.test.tsx index 7c8c119b31c6b..92248c6de2828 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/entities_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/entities_overview.test.tsx @@ -97,6 +97,18 @@ describe('<EntitiesOverview />', () => { expect(queryByTestId(TITLE_TEXT_TEST_ID)).not.toBeInTheDocument(); }); + it('should not render link if isPreviewMode is true', () => { + const { getByTestId, queryByTestId } = renderEntitiesOverview({ + ...mockContextValue, + isPreviewMode: true, + }); + + expect(queryByTestId(TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(TITLE_LINK_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(TITLE_ICON_TEST_ID)).not.toBeInTheDocument(); + expect(getByTestId(TITLE_TEXT_TEST_ID)).toBeInTheDocument(); + }); + it('should render user and host', () => { const { getByTestId, queryByText } = renderEntitiesOverview(mockContextValue); expect(getByTestId(ENTITIES_USER_OVERVIEW_TEST_ID)).toBeInTheDocument(); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/entities_overview.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/entities_overview.tsx index 51ec7f002ed0a..16fe6cbe1c1e0 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/entities_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/entities_overview.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -23,7 +23,7 @@ import { ENTITIES_TAB_ID } from '../../left/components/entities_details'; * Entities section under Insights section, overview tab. It contains a preview of host and user information. */ export const EntitiesOverview: React.FC = () => { - const { eventId, getFieldsData, indexName, scopeId } = useDocumentDetailsContext(); + const { eventId, getFieldsData, indexName, scopeId, isPreviewMode } = useDocumentDetailsContext(); const { openLeftPanel } = useExpandableFlyoutApi(); const hostName = getField(getFieldsData('host.name')); const userName = getField(getFieldsData('user.name')); @@ -43,6 +43,22 @@ export const EntitiesOverview: React.FC = () => { }); }, [eventId, openLeftPanel, indexName, scopeId]); + const link = useMemo( + () => + !isPreviewMode + ? { + callback: goToEntitiesTab, + tooltip: ( + <FormattedMessage + id="xpack.securitySolution.flyout.right.insights.entities.entitiesTooltip" + defaultMessage="Show all entities" + /> + ), + } + : undefined, + [goToEntitiesTab, isPreviewMode] + ); + return ( <> <ExpandablePanel @@ -53,16 +69,8 @@ export const EntitiesOverview: React.FC = () => { defaultMessage="Entities" /> ), - link: { - callback: goToEntitiesTab, - tooltip: ( - <FormattedMessage - id="xpack.securitySolution.flyout.right.insights.entities.entitiesTooltip" - defaultMessage="Show all entities" - /> - ), - }, - iconType: 'arrowStart', + link, + iconType: !isPreviewMode ? 'arrowStart' : undefined, }} data-test-subj={INSIGHTS_ENTITIES_TEST_ID} > diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.test.tsx index 128ca3b643af9..1ccf79664512e 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.test.tsx @@ -27,8 +27,9 @@ jest.mock('@kbn/expandable-flyout', () => ({ useExpandableFlyoutApi: jest.fn() } const mockFlyoutContextValue = { openLeftPanel: jest.fn() }; -const NO_DATA_MESSAGE = 'Investigation guideThere’s no investigation guide for this rule.'; +const NO_DATA_MESSAGE = "Investigation guideThere's no investigation guide for this rule."; const PREVIEW_MESSAGE = 'Investigation guide is not available in alert preview.'; +const OPEN_FLYOUT_MESSAGE = 'Open alert details to access investigation guides.'; const renderInvestigationGuide = () => render( @@ -107,6 +108,12 @@ describe('<InvestigationGuide />', () => { }); it('should render preview message when flyout is in preview', () => { + (useInvestigationGuide as jest.Mock).mockReturnValue({ + loading: false, + error: false, + basicAlertData: { ruleId: 'ruleId' }, + ruleNote: 'test note', + }); const { queryByTestId, getByTestId } = render( <IntlProvider locale="en"> <DocumentDetailsContext.Provider value={{ ...mockContextValue, isPreview: true }}> @@ -119,6 +126,19 @@ describe('<InvestigationGuide />', () => { expect(getByTestId(INVESTIGATION_GUIDE_TEST_ID)).toHaveTextContent(PREVIEW_MESSAGE); }); + it('should render open flyout message if isPreviewMode is true', () => { + const { queryByTestId, getByTestId } = render( + <IntlProvider locale="en"> + <DocumentDetailsContext.Provider value={{ ...mockContextValue, isPreviewMode: true }}> + <InvestigationGuide /> + </DocumentDetailsContext.Provider> + </IntlProvider> + ); + + expect(queryByTestId(INVESTIGATION_GUIDE_BUTTON_TEST_ID)).not.toBeInTheDocument(); + expect(getByTestId(INVESTIGATION_GUIDE_TEST_ID)).toHaveTextContent(OPEN_FLYOUT_MESSAGE); + }); + it('should navigate to investigation guide when clicking on button', () => { (useInvestigationGuide as jest.Mock).mockReturnValue({ loading: false, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.tsx index 33fa0db42c453..efbe095ae9e4d 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSkeletonText } from '@elastic/eui'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -25,7 +25,7 @@ import { */ export const InvestigationGuide: React.FC = () => { const { openLeftPanel } = useExpandableFlyoutApi(); - const { eventId, indexName, scopeId, dataFormattedForFieldBrowser, isPreview } = + const { eventId, indexName, scopeId, dataFormattedForFieldBrowser, isPreview, isPreviewMode } = useDocumentDetailsContext(); const { loading, error, basicAlertData, ruleNote } = useInvestigationGuide({ @@ -46,6 +46,11 @@ export const InvestigationGuide: React.FC = () => { }); }, [eventId, indexName, openLeftPanel, scopeId]); + const hasInvesigationGuide = useMemo( + () => !error && basicAlertData && basicAlertData.ruleId && ruleNote, + [error, basicAlertData, ruleNote] + ); + return ( <EuiFlexGroup direction="column" gutterSize="s" data-test-subj={INVESTIGATION_GUIDE_TEST_ID}> <EuiFlexItem> @@ -71,7 +76,12 @@ export const InvestigationGuide: React.FC = () => { { defaultMessage: 'investigation guide' } )} /> - ) : !error && basicAlertData.ruleId && ruleNote ? ( + ) : hasInvesigationGuide && isPreviewMode ? ( + <FormattedMessage + id="xpack.securitySolution.flyout.right.investigation.investigationGuide.openFlyoutMessage" + defaultMessage="Open alert details to access investigation guides." + /> + ) : hasInvesigationGuide ? ( <EuiFlexItem> <EuiButton onClick={goToInvestigationsTab} @@ -93,7 +103,7 @@ export const InvestigationGuide: React.FC = () => { ) : ( <FormattedMessage id="xpack.securitySolution.flyout.right.investigation.investigationGuide.noDataDescription" - defaultMessage="There’s no investigation guide for this rule." + defaultMessage="There's no investigation guide for this rule." /> )} </EuiFlexGroup> diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.test.tsx index d2fa414ac746a..6b3e287d80915 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.test.tsx @@ -72,6 +72,23 @@ describe('<PrevalenceOverview />', () => { expect(queryByTestId(TITLE_TEXT_TEST_ID)).not.toBeInTheDocument(); }); + it('should not render link and icon if isPreviewMode is true', () => { + (usePrevalence as jest.Mock).mockReturnValue({ + loading: false, + error: false, + data: [], + }); + + const { getByTestId, queryByTestId } = renderPrevalenceOverview({ + ...mockContextValue, + isPreviewMode: true, + }); + expect(queryByTestId(TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(TITLE_LINK_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(TITLE_ICON_TEST_ID)).not.toBeInTheDocument(); + expect(getByTestId(TITLE_TEXT_TEST_ID)).toBeInTheDocument(); + }); + it('should render loading', () => { (usePrevalence as jest.Mock).mockReturnValue({ loading: true, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.tsx index 7135df1ec79ec..3776e486b426b 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.tsx @@ -28,8 +28,14 @@ const DEFAULT_TO = 'now'; * The component fetches the necessary data at once. The loading and error states are handled by the ExpandablePanel component. */ export const PrevalenceOverview: FC = () => { - const { eventId, indexName, dataFormattedForFieldBrowser, scopeId, investigationFields } = - useDocumentDetailsContext(); + const { + eventId, + indexName, + dataFormattedForFieldBrowser, + scopeId, + investigationFields, + isPreviewMode, + } = useDocumentDetailsContext(); const { openLeftPanel } = useExpandableFlyoutApi(); const goPrevalenceTab = useCallback(() => { @@ -67,6 +73,21 @@ export const PrevalenceOverview: FC = () => { ), [data] ); + const link = useMemo( + () => + !isPreviewMode + ? { + callback: goPrevalenceTab, + tooltip: ( + <FormattedMessage + id="xpack.securitySolution.flyout.right.insights.prevalence.prevalenceTooltip" + defaultMessage="Show all prevalence" + /> + ), + } + : undefined, + [goPrevalenceTab, isPreviewMode] + ); return ( <ExpandablePanel @@ -77,16 +98,8 @@ export const PrevalenceOverview: FC = () => { defaultMessage="Prevalence" /> ), - link: { - callback: goPrevalenceTab, - tooltip: ( - <FormattedMessage - id="xpack.securitySolution.flyout.right.insights.prevalence.prevalenceTooltip" - defaultMessage="Show all prevalence" - /> - ), - }, - iconType: 'arrowStart', + link, + iconType: !isPreviewMode ? 'arrowStart' : undefined, }} content={{ loading, error }} data-test-subj={PREVALENCE_TEST_ID} diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_section.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_section.test.tsx index a401b8da14adb..5ae2e2741f5bf 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_section.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_section.test.tsx @@ -22,6 +22,7 @@ import { useExpandSection } from '../hooks/use_expand_section'; jest.mock('../hooks/use_expand_section'); const PREVIEW_MESSAGE = 'Response is not available in alert preview.'; +const OPEN_FLYOUT_MESSAGE = 'Open alert details to access response actions.'; const renderResponseSection = () => render( @@ -99,6 +100,21 @@ describe('<ResponseSection />', () => { expect(getByTestId(RESPONSE_SECTION_CONTENT_TEST_ID)).toHaveTextContent(PREVIEW_MESSAGE); }); + it('should render open details flyout message if flyout is in preview', () => { + (useExpandSection as jest.Mock).mockReturnValue(true); + + const { getByTestId } = render( + <IntlProvider locale="en"> + <TestProvider> + <DocumentDetailsContext.Provider value={{ ...mockContextValue, isPreviewMode: true }}> + <ResponseSection /> + </DocumentDetailsContext.Provider> + </TestProvider> + </IntlProvider> + ); + expect(getByTestId(RESPONSE_SECTION_CONTENT_TEST_ID)).toHaveTextContent(OPEN_FLYOUT_MESSAGE); + }); + it('should render empty component if document is not signal', () => { (useExpandSection as jest.Mock).mockReturnValue(true); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_section.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_section.tsx index 6a802dfd94cf4..19c77b6d9478b 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_section.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_section.tsx @@ -21,7 +21,7 @@ const KEY = 'response'; * Most bottom section of the overview tab. It contains a summary of the response tab. */ export const ResponseSection = memo(() => { - const { isPreview, getFieldsData } = useDocumentDetailsContext(); + const { isPreview, getFieldsData, isPreviewMode } = useDocumentDetailsContext(); const expanded = useExpandSection({ title: KEY, defaultValue: false }); @@ -47,6 +47,11 @@ export const ResponseSection = memo(() => { id="xpack.securitySolution.flyout.right.response.previewMessage" defaultMessage="Response is not available in alert preview." /> + ) : isPreviewMode ? ( + <FormattedMessage + id="xpack.securitySolution.flyout.right.response.openFlyoutMessage" + defaultMessage="Open alert details to access response actions." + /> ) : ( <ResponseButton /> )} diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.test.tsx index 542aae9ce18c0..2e2ffa99efe42 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.test.tsx @@ -85,6 +85,21 @@ describe('<ThreatIntelligenceOverview />', () => { expect(queryByTestId(TITLE_TEXT_TEST_ID)).not.toBeInTheDocument(); }); + it('should not render link if isPrenviewMode is true', () => { + (useFetchThreatIntelligence as jest.Mock).mockReturnValue({ + loading: false, + }); + + const { getByTestId, queryByTestId } = render( + renderThreatIntelligenceOverview({ ...panelContextValue, isPreviewMode: true }) + ); + + expect(queryByTestId(TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(TITLE_ICON_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(TITLE_LINK_TEST_ID)).not.toBeInTheDocument(); + expect(getByTestId(TITLE_TEXT_TEST_ID)).toBeInTheDocument(); + }); + it('should render 1 match detected and 1 field enriched', () => { (useFetchThreatIntelligence as jest.Mock).mockReturnValue({ loading: false, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.tsx index 1bc0191f8bce2..ca47113ad12c3 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.tsx @@ -6,7 +6,7 @@ */ import type { FC } from 'react'; -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { EuiFlexGroup } from '@elastic/eui'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -25,7 +25,8 @@ import { THREAT_INTELLIGENCE_TAB_ID } from '../../left/components/threat_intelli * and the SummaryPanel component for data rendering. */ export const ThreatIntelligenceOverview: FC = () => { - const { eventId, indexName, scopeId, dataFormattedForFieldBrowser } = useDocumentDetailsContext(); + const { eventId, indexName, scopeId, dataFormattedForFieldBrowser, isPreviewMode } = + useDocumentDetailsContext(); const { openLeftPanel } = useExpandableFlyoutApi(); const goToThreatIntelligenceTab = useCallback(() => { @@ -47,6 +48,22 @@ export const ThreatIntelligenceOverview: FC = () => { dataFormattedForFieldBrowser, }); + const link = useMemo( + () => + !isPreviewMode + ? { + callback: goToThreatIntelligenceTab, + tooltip: ( + <FormattedMessage + id="xpack.securitySolution.flyout.right.insights.threatIntelligence.threatIntelligenceTooltip" + defaultMessage="Show all threat intelligence" + /> + ), + } + : undefined, + [isPreviewMode, goToThreatIntelligenceTab] + ); + return ( <ExpandablePanel header={{ @@ -56,16 +73,8 @@ export const ThreatIntelligenceOverview: FC = () => { defaultMessage="Threat intelligence" /> ), - link: { - callback: goToThreatIntelligenceTab, - tooltip: ( - <FormattedMessage - id="xpack.securitySolution.flyout.right.insights.threatIntelligence.threatIntelligenceTooltip" - defaultMessage="Show all threat intelligence" - /> - ), - }, - iconType: 'arrowStart', + link, + iconType: !isPreviewMode ? 'arrowStart' : undefined, }} data-test-subj={INSIGHTS_THREAT_INTELLIGENCE_TEST_ID} content={{ loading }} diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/header.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/header.tsx index 22e6df6d01fd7..b327fccea3bed 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/header.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/header.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import type { EuiFlyoutHeader } from '@elastic/eui'; import { EuiSpacer, EuiTab } from '@elastic/eui'; import type { FC } from 'react'; import React, { memo, useMemo } from 'react'; @@ -23,7 +24,7 @@ import { } from '../../../common/components/guided_onboarding_tour/tour_config'; import { GuidedOnboardingTourStep } from '../../../common/components/guided_onboarding_tour/tour_step'; -export interface PanelHeaderProps { +export interface PanelHeaderProps extends React.ComponentProps<typeof EuiFlyoutHeader> { /** * Id of the tab selected in the parent component to display its content */ @@ -40,7 +41,7 @@ export interface PanelHeaderProps { } export const PanelHeader: FC<PanelHeaderProps> = memo( - ({ selectedTabId, setSelectedTabId, tabs }) => { + ({ selectedTabId, setSelectedTabId, tabs, ...flyoutHeaderProps }) => { const { dataFormattedForFieldBrowser } = useDocumentDetailsContext(); const { isAlert } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser); const onSelectedTabChanged = (id: RightPanelPaths) => setSelectedTabId(id); @@ -88,7 +89,7 @@ export const PanelHeader: FC<PanelHeaderProps> = memo( ); return ( - <FlyoutHeader> + <FlyoutHeader {...flyoutHeaderProps}> {isAlert ? <AlertHeaderTitle /> : <EventHeaderTitle />} <EuiSpacer size="m" /> <FlyoutHeaderTabs>{renderTabs}</FlyoutHeaderTabs> diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/rule_overview/components/footer.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/rule_overview/components/footer.test.tsx index 6db228e75cb81..b2c9b895bc0ce 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/rule_overview/components/footer.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/rule_overview/components/footer.test.tsx @@ -34,7 +34,7 @@ describe('<RulePreviewFooter />', () => { expect(getByTestId(RULE_OVERVIEW_FOOTER_TEST_ID)).toBeInTheDocument(); expect(getByTestId(RULE_OVERVIEW_NAVIGATE_TO_RULE_TEST_ID)).toBeInTheDocument(); expect(getByTestId(RULE_OVERVIEW_NAVIGATE_TO_RULE_TEST_ID)).toHaveTextContent( - 'Show rule details' + 'Show full rule details' ); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/rule_overview/components/footer.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/rule_overview/components/footer.tsx index d1af7096ef5fc..ebc204f8cb921 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/rule_overview/components/footer.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/rule_overview/components/footer.tsx @@ -30,7 +30,7 @@ export const RuleFooter = memo(() => { data-test-subj={RULE_OVERVIEW_NAVIGATE_TO_RULE_TEST_ID} > {i18n.translate('xpack.securitySolution.flyout.preview.rule.viewDetailsLabel', { - defaultMessage: 'Show rule details', + defaultMessage: 'Show full rule details', })} </EuiLink> </EuiFlexItem> diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/rule_overview/index.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/rule_overview/index.tsx index c9a1a62114f73..504be510a09f7 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/rule_overview/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/rule_overview/index.tsx @@ -7,7 +7,7 @@ import React, { memo } from 'react'; import type { FlyoutPanelProps } from '@kbn/expandable-flyout'; -import { EuiFlyoutBody } from '@elastic/eui'; +import { FlyoutBody } from '../../shared/components/flyout_body'; import type { DocumentDetailsRuleOverviewPanelKey } from '../shared/constants/panel_keys'; import { RuleOverview } from './components/rule_overview'; import { RuleFooter } from './components/footer'; @@ -25,11 +25,11 @@ export interface RuleOverviewPanelProps extends FlyoutPanelProps { export const RuleOverviewPanel: React.FC = memo(() => { return ( <> - <EuiFlyoutBody> + <FlyoutBody> <div style={{ marginTop: '-15px' }}> <RuleOverview /> </div> - </EuiFlyoutBody> + </FlyoutBody> <RuleFooter /> </> ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/context.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/context.tsx index 388706e4bd0b3..1197e39ad86cb 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/context.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/context.tsx @@ -60,11 +60,18 @@ export interface DocumentDetailsContext { */ getFieldsData: GetFieldsData; /** - * Boolean to indicate whether it is a preview flyout + * Boolean to indicate whether flyout is opened in rule preview */ isPreview: boolean; + /** + * Boolean to indicate whether it is a preview panel + */ + isPreviewMode: boolean; } +/** + * A context provider shared by the right, left and preview panels in expandable document details flyout + */ export const DocumentDetailsContext = createContext<DocumentDetailsContext | undefined>(undefined); export type DocumentDetailsProviderProps = { @@ -75,7 +82,7 @@ export type DocumentDetailsProviderProps = { } & Partial<DocumentDetailsProps['params']>; export const DocumentDetailsProvider = memo( - ({ id, indexName, scopeId, children }: DocumentDetailsProviderProps) => { + ({ id, indexName, scopeId, isPreviewMode, children }: DocumentDetailsProviderProps) => { const { browserFields, dataAsNestedObject, @@ -109,6 +116,7 @@ export const DocumentDetailsProvider = memo( refetchFlyoutData, getFieldsData, isPreview: scopeId === TableId.rulePreview, + isPreviewMode: Boolean(isPreviewMode), } : undefined, [ @@ -122,6 +130,7 @@ export const DocumentDetailsProvider = memo( searchHit, refetchFlyoutData, getFieldsData, + isPreviewMode, ] ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/mocks/mock_context.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/mocks/mock_context.ts index 11148dc2e0993..a7f7024952167 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/mocks/mock_context.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/mocks/mock_context.ts @@ -27,4 +27,5 @@ export const mockContextValue: DocumentDetailsContext = { investigationFields: [], refetchFlyoutData: jest.fn(), isPreview: false, + isPreviewMode: false, }; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/types.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/types.tsx index e72220ae02ac3..00fb1da32449c 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/types.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/types.tsx @@ -18,5 +18,6 @@ export interface DocumentDetailsProps extends FlyoutPanelProps { id: string; indexName: string; scopeId: string; + isPreviewMode?: boolean; }; } diff --git a/x-pack/plugins/security_solution/public/flyout/index.tsx b/x-pack/plugins/security_solution/public/flyout/index.tsx index b9e6b06196b2a..f768b71e32abc 100644 --- a/x-pack/plugins/security_solution/public/flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/index.tsx @@ -12,6 +12,7 @@ import { DocumentDetailsIsolateHostPanelKey, DocumentDetailsLeftPanelKey, DocumentDetailsRightPanelKey, + DocumentDetailsPreviewPanelKey, DocumentDetailsAlertReasonPanelKey, DocumentDetailsRuleOverviewPanelKey, } from './document_details/shared/constants/panel_keys'; @@ -22,6 +23,7 @@ import type { DocumentDetailsProps } from './document_details/shared/types'; import { DocumentDetailsProvider } from './document_details/shared/context'; import { RightPanel } from './document_details/right'; import { LeftPanel } from './document_details/left'; +import { PreviewPanel } from './document_details/preview'; import type { AlertReasonPanelProps } from './document_details/alert_reason'; import { AlertReasonPanel } from './document_details/alert_reason'; import { AlertReasonPanelProvider } from './document_details/alert_reason/context'; @@ -58,6 +60,14 @@ const expandableFlyoutDocumentsPanels: ExpandableFlyoutProps['registeredPanels'] </DocumentDetailsProvider> ), }, + { + key: DocumentDetailsPreviewPanelKey, + component: (props) => ( + <DocumentDetailsProvider {...(props as DocumentDetailsProps).params}> + <PreviewPanel path={props.path as DocumentDetailsProps['path']} /> + </DocumentDetailsProvider> + ), + }, { key: DocumentDetailsAlertReasonPanelKey, component: (props) => ( diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_body.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_body.tsx index 7974690663b55..116f7d5144969 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_body.tsx +++ b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_body.tsx @@ -8,6 +8,7 @@ import type { FC } from 'react'; import React, { memo } from 'react'; import { EuiFlyoutBody, EuiPanel } from '@elastic/eui'; +import { css } from '@emotion/react'; interface FlyoutBodyProps extends React.ComponentProps<typeof EuiFlyoutBody> { children: React.ReactNode; @@ -18,7 +19,16 @@ interface FlyoutBodyProps extends React.ComponentProps<typeof EuiFlyoutBody> { */ export const FlyoutBody: FC<FlyoutBodyProps> = memo(({ children, ...flyoutBodyProps }) => { return ( - <EuiFlyoutBody {...flyoutBodyProps}> + <EuiFlyoutBody + {...flyoutBodyProps} + css={css` + .euiFlyoutBody__overflow { + // fix a bug with red overlay when position was not set + // remove when changes in EUI are merged + transform: translateZ(0); + } + `} + > <EuiPanel hasShadow={false} color="transparent"> {children} </EuiPanel> diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_rule_preview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_rule_preview.cy.ts index 7e3bc819df853..e798f181593d4 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_rule_preview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_rule_preview.cy.ts @@ -120,7 +120,7 @@ describe( cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_FOOTER).should('be.visible'); cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_FOOTER_LINK).should( 'contain.text', - 'Show rule details' + 'Show full rule details' ); }); }); From 74772c4c896c09dd8a31e016a137de2552188750 Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Tue, 2 Jul 2024 16:43:30 +0200 Subject: [PATCH 083/128] [Fleet] Update enrolling_agents.md (#187357) Small update to Fleet readme --- x-pack/plugins/fleet/dev_docs/local_setup/enrolling_agents.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/plugins/fleet/dev_docs/local_setup/enrolling_agents.md b/x-pack/plugins/fleet/dev_docs/local_setup/enrolling_agents.md index 6c657cf7e5862..88aa370ca3aae 100644 --- a/x-pack/plugins/fleet/dev_docs/local_setup/enrolling_agents.md +++ b/x-pack/plugins/fleet/dev_docs/local_setup/enrolling_agents.md @@ -12,6 +12,9 @@ Add the following to your `kibana.dev.yml`. Note that the only differences betwe # Set the Kibana server address to Fleet Server default host. server.host: 0.0.0.0 +# Use default version resolution to let APIs work without version header +server.versioned.versionResolution: oldest + # Install Fleet Server package. xpack.fleet.packages: - name: fleet_server From ae30c30445a77c734edbca5e7f2b3d64e1ab4835 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet <nicolas.chaulet@elastic.co> Date: Tue, 2 Jul 2024 10:59:07 -0400 Subject: [PATCH 084/128] [Fleet] Allow global data tags to be readonly (#187161) --- .../global_data_tags_table.test.tsx | 18 +++++++++++++++++- .../custom_fields/global_data_tags_table.tsx | 12 +++++++++++- .../custom_fields/index.tsx | 3 +++ .../agent_policy_advanced_fields/index.tsx | 6 +++++- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/global_data_tags_table.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/global_data_tags_table.test.tsx index a79c298a6e9e9..dffe682bcc452 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/global_data_tags_table.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/global_data_tags_table.test.tsx @@ -35,7 +35,7 @@ describe('GlobalDataTagsTable', () => { ]; let renderer: TestRenderer; - const renderComponent = (tags: GlobalDataTag[]) => { + const renderComponent = (tags: GlobalDataTag[], options?: { isDisabled?: boolean }) => { mockUpdateAgentPolicy = jest.fn(); renderer = createFleetTestRendererMock(); @@ -53,6 +53,7 @@ describe('GlobalDataTagsTable', () => { <GlobalDataTagsTable updateAgentPolicy={updateAgentPolicy} globalDataTags={agentPolicy.global_data_tags} + isDisabled={options?.isDisabled} /> ); }; @@ -287,4 +288,19 @@ describe('GlobalDataTagsTable', () => { ], }); }); + + it('should not allow to add tag when disabled and no tags exists', () => { + renderComponent([], { isDisabled: true }); + + const test = renderResult.getByTestId('globalDataTagAddFieldBtn'); + expect(test).toBeDisabled(); + }); + + it('should not allow to add/edit/remove tag when disabled and tags already exists', () => { + renderComponent(globalDataTags, { isDisabled: true }); + + expect(renderResult.getByTestId('globalDataTagAddAnotherFieldBtn')).toBeDisabled(); + expect(renderResult.getByTestId('globalDataTagDeleteField1Btn')).toBeDisabled(); + expect(renderResult.getByTestId('globalDataTagEditField1Btn')).toBeDisabled(); + }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/global_data_tags_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/global_data_tags_table.tsx index 9f51b5c181459..228b666af38f3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/global_data_tags_table.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/global_data_tags_table.tsx @@ -34,6 +34,7 @@ import type { interface Props { updateAgentPolicy: (u: Partial<NewAgentPolicy | AgentPolicy>) => void; globalDataTags: GlobalDataTag[]; + isDisabled?: boolean; } function parseValue(value: string | number): string | number { @@ -50,6 +51,7 @@ function parseValue(value: string | number): string | number { export const GlobalDataTagsTable: React.FunctionComponent<Props> = ({ updateAgentPolicy, globalDataTags, + isDisabled, }) => { const { overlays } = useStartServices(); const [editTags, setEditTags] = useState<{ [k: number]: GlobalDataTag }>({}); @@ -358,6 +360,8 @@ export const GlobalDataTagsTable: React.FunctionComponent<Props> = ({ aria-label="Edit" iconType="pencil" color="text" + data-test-subj={`globalDataTagEditField${index}Btn`} + isDisabled={isDisabled} onClick={() => handleStartEdit(index)} /> ); @@ -387,6 +391,8 @@ export const GlobalDataTagsTable: React.FunctionComponent<Props> = ({ aria-label="Delete" iconType="trash" color="text" + data-test-subj={`globalDataTagDeleteField${index}Btn`} + isDisabled={isDisabled} onClick={() => deleteTag(index)} /> ); @@ -408,6 +414,7 @@ export const GlobalDataTagsTable: React.FunctionComponent<Props> = ({ newTagErrors, deleteTag, handleStartEdit, + isDisabled, ] ); @@ -431,6 +438,8 @@ export const GlobalDataTagsTable: React.FunctionComponent<Props> = ({ iconType="plusInCircle" onClick={handleAddField} style={{ marginTop: '16px' }} + disabled={isDisabled} + data-test-subj="globalDataTagAddFieldBtn" > <FormattedMessage id="xpack.fleet.globalDataTagsTable.addFieldBtn" @@ -449,7 +458,8 @@ export const GlobalDataTagsTable: React.FunctionComponent<Props> = ({ iconType="plusInCircle" onClick={handleAddField} style={{ marginTop: '16px' }} - isDisabled={isAdding} + isDisabled={isDisabled || isAdding} + data-test-subj="globalDataTagAddAnotherFieldBtn" > <FormattedMessage id="xpack.fleet.globalDataTagsTable.addAnotherFieldBtn" diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/index.tsx index a10c6c3d2fb8e..ccd761c53e96b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/index.tsx @@ -24,11 +24,13 @@ import { GlobalDataTagsTable } from './global_data_tags_table'; interface Props { agentPolicy: Partial<AgentPolicy | NewAgentPolicy>; updateAgentPolicy: (u: Partial<NewAgentPolicy | AgentPolicy>) => void; + isDisabled?: boolean; } export const CustomFields: React.FunctionComponent<Props> = ({ agentPolicy, updateAgentPolicy, + isDisabled, }) => { const isAgentPolicy = (policy: Partial<AgentPolicy | NewAgentPolicy>): policy is AgentPolicy => { return (policy as AgentPolicy).package_policies !== undefined; @@ -103,6 +105,7 @@ export const CustomFields: React.FunctionComponent<Props> = ({ } > <GlobalDataTagsTable + isDisabled={isDisabled} updateAgentPolicy={updateAgentPolicy} globalDataTags={agentPolicy.global_data_tags ? agentPolicy.global_data_tags : []} /> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx index 470288f2ebac5..ef5dc9b8e3c4d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx @@ -308,7 +308,11 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> = /> </EuiFormRow> </EuiDescribedFormGroup> - <CustomFields updateAgentPolicy={updateAgentPolicy} agentPolicy={agentPolicy} /> + <CustomFields + updateAgentPolicy={updateAgentPolicy} + agentPolicy={agentPolicy} + isDisabled={disabled || agentPolicy.is_managed === true} + /> <EuiDescribedFormGroup fullWidth title={ From 4554b758994671bdc2c1e34c5f5efaaaaac5bd45 Mon Sep 17 00:00:00 2001 From: Kurt <kc13greiner@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:03:28 -0400 Subject: [PATCH 085/128] Add license check for FIPS (#181187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Updates ### Latest updates - Expose whether KB is configured to run in FIPS mode from Core -> Security <img width="653" alt="Screenshot 2024-06-20 at 9 55 17 PM" src="https://github.com/elastic/kibana/assets/21210601/56a9f50f-0a05-41ca-9292-ed225b3d8062"> Consolidating all FIPS PRs into this PR *Previous PRs were Approved ### Changes - Config option is now experimental: `xpack.security.experimental.fipsMode.enabled` - Documentation has been revised - Listed as an experimental feature - Added keystore references for adding a password ## Summary Closes #169738 Closes #169739 Closes #169740 Closes #185948 FIPS is a platinum license feature. KIbana instances must have a platinum or better license to start up in FIPS mode, a lesser license will result in Kibana failing to start up If the license is degraded, Kibana will still run, but an error will be logged letting the user know that Kibana will not be able to restart. ## Config changes This PR required the changes that were approved from [a previous PR](https://github.com/elastic/kibana/pull/174558), since that PR couldn't be merged into main, I merged it here. ## Testing ### Locally In your `kibana.dev.yml` add: `xpack.security.experimental.fipsMode.enabled: true` To allow Kibana to start without actually providing a compliant OpenSSL provider, in `x-pack/plugins/security/server/config.ts` change L328 from `if (isFipsEnabled !== isNodeRunningWithFipsEnabled)` to `if (false)` You are now configured to run in FIPS-spoof mode! Run: `yarn es snapshot` and `yarn start` > You should see Kibana fail to start with an error about using a basic license. Run: `yarn es snapshot --license trial` and `yarn start` > Kibana should start. Login as `elastic` and navigate to Stack Management > License Management Switch your license to `basic` and accept. In your logs, you will see an error letting users know that you no longer have an appropriate license and Kibana will not restart. ### For FIPS enthusiasts Start an ES instance in a method of your choosing, but not using `yarn es snapshot`. I like to use an 8.15.0-snapshot from the `.es/cache` directory by running `tar -xzvf elasticsearch-8.15.0-SNAPSHOT-darwin-aarch64.tar.gz ` and cd into the new directory's `bin` folder to run `./elasticsearch` Ensure you have Docker running locally. From any command line, run: `docker run --rm -it -e XPACK_SECURITY_FIPSMODE_ENABLED='true' -p 5601:5601/tcp docker.elastic.co/kibana-ci/kibana-ubi-fips:8.15.0-SNAPSHOT-bc3150316ed317c08d57c6bd785ba39586072e1d` This will start Kibana into Interactive Setup mode, copy and paste the token from the ES startup logs. Kibana should fail to start and you should see Kibana fail to start with an error about using a basic license. Repeat the above process except before you paste the token from ES, do the following to enable a trial license on your ES instance: In a new terminal window, navigate to your the top level of your elasticsearch folder and run `curl -X POST --cacert config/certs/http_ca.crt -u elastic:YOUR_PASSWORD_HERE "https://localhost:9200/_license/start_trial?acknowledge=true&pretty"` You should receive a successful response. Now paste the token from the ES startup logs into the Kibana Interactive Setup window and Kibana should start. Login as `elastic` and navigate to Stack Management > License Management Switch your license to `basic` and accept. In your logs, you will see an error letting users know that you no longer have an appropriate license and Kibana will not restart. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: lcawl <lcawley@elastic.co> --- docs/user/security/fips-140-2.asciidoc | 63 ++++++ docs/user/security/index.asciidoc | 1 + .../src/plugin_context.ts | 1 + .../src/fips/fips.test.ts | 119 ++++++++++ .../src/fips/fips.ts | 35 +++ .../src/security_service.test.ts | 10 + .../src/security_service.ts | 38 +++- .../src/utils/index.ts | 8 + .../tsconfig.json | 2 + .../src/security_service.mock.ts | 2 + .../security/core-security-server/index.ts | 1 + .../core-security-server/src/contracts.ts | 6 + .../security/core-security-server/src/fips.ts | 19 ++ .../resources/base/bin/kibana-docker | 1 + .../src/licensing/license.ts | 1 + .../src/licensing/license_features.ts | 6 + .../security/common/licensing/index.mock.ts | 27 ++- .../common/licensing/license_service.test.ts | 9 + .../common/licensing/license_service.ts | 5 + .../elasticsearch_privileges.test.tsx.snap | 2 + .../index_privileges.test.tsx.snap | 1 + .../remote_cluster_privileges.test.tsx.snap | 1 + .../plugins/security/public/plugin.test.tsx | 2 + x-pack/plugins/security/server/config.test.ts | 20 ++ x-pack/plugins/security/server/config.ts | 5 + .../security/server/fips/fips_service.test.ts | 210 ++++++++++++++++++ .../security/server/fips/fips_service.ts | 67 ++++++ x-pack/plugins/security/server/fips/index.ts | 10 + x-pack/plugins/security/server/plugin.test.ts | 1 + x-pack/plugins/security/server/plugin.ts | 10 + .../server/routes/views/login.test.ts | 1 + 31 files changed, 679 insertions(+), 5 deletions(-) create mode 100644 docs/user/security/fips-140-2.asciidoc create mode 100644 packages/core/security/core-security-server-internal/src/fips/fips.test.ts create mode 100644 packages/core/security/core-security-server-internal/src/fips/fips.ts create mode 100644 packages/core/security/core-security-server/src/fips.ts create mode 100644 x-pack/plugins/security/server/fips/fips_service.test.ts create mode 100644 x-pack/plugins/security/server/fips/fips_service.ts create mode 100644 x-pack/plugins/security/server/fips/index.ts diff --git a/docs/user/security/fips-140-2.asciidoc b/docs/user/security/fips-140-2.asciidoc new file mode 100644 index 0000000000000..2b4b195f38b05 --- /dev/null +++ b/docs/user/security/fips-140-2.asciidoc @@ -0,0 +1,63 @@ +[[xpack-security-fips-140-2]] +=== FIPS 140-2 + +experimental::[] + +The Federal Information Processing Standard (FIPS) Publication 140-2, (FIPS PUB 140-2), +titled "Security Requirements for Cryptographic Modules" is a U.S. government computer security standard +used to approve cryptographic modules. + +{kib} offers a FIPS 140-2 compliant mode and as such can run in a Node.js environment configured with a FIPS +140-2 compliant OpenSSL3 provider. + +To run {kib} in FIPS mode, you must have the appropriate {subscriptions}[subscription]. + +[IMPORTANT] +============================================================================ +The Node bundled with {kib} is not configured for FIPS 140-2. You must configure a FIPS 140-2 compliant OpenSSL3 +provider. Consult the Node.js documentation to learn how to configure your environment. +============================================================================ + +For {kib}, adherence to FIPS 140-2 is ensured by: + +* Using FIPS approved / NIST recommended cryptographic algorithms. + +* Delegating the implementation of these cryptographic algorithms to a NIST validated cryptographic module +(available via Node.js configured with an OpenSSL3 provider). + +* Allowing the configuration of {kib} in a FIPS 140-2 compliant manner, as documented below. + +==== Configuring {kib} for FIPS 140-2 + +Apart from setting `xpack.security.experimental.fipsMode.enabled` to `true` in your {kib} config, a number of security related +settings need to be reviewed and configured in order to run {kib} successfully in a FIPS 140-2 compliant Node.js +environment. + +===== Kibana keystore + +FIPS 140-2 (via NIST Special Publication 800-132) dictates that encryption keys should at least have an effective +strength of 112 bits. As such, the Kibana keystore that stores the application’s secure settings needs to be +password protected with a password that satisfies this requirement. This means that the password needs to be 14 bytes +long which is equivalent to a 14 character ASCII encoded password, or a 7 character UTF-8 encoded password. + +For more information on how to set this password, refer to the <<change-password,keystore documentation>>. + +===== TLS keystore and keys + +Keystores can be used in a number of General TLS settings in order to conveniently store key and trust material. +PKCS#12 keystores cannot be used in a FIPS 140-2 compliant Node.js environment. Avoid using these types of keystores. +Your FIPS 140-2 provider may provide a compliant keystore implementation that can be used, or you can use PEM encoded +files. To use PEM encoded key material, you can use the relevant `\*.key` and `*.certificate` configuration options, +and for trust material you can use `*.certificate_authorities`. + +As an example, avoid PKCS#12 specific settings such as: + +* `server.ssl.keystore.path` +* `server.ssl.truststore.path` +* `elasticsearch.ssl.keystore.path` +* `elasticsearch.ssl.truststore.path` + +===== Limitations + +Configuring {kib} to run in FIPS mode is still considered to be experimental. Not all features are guaranteed to +function as expected. diff --git a/docs/user/security/index.asciidoc b/docs/user/security/index.asciidoc index f4678700d5e77..906aee3d76d5a 100644 --- a/docs/user/security/index.asciidoc +++ b/docs/user/security/index.asciidoc @@ -46,3 +46,4 @@ include::authorization/index.asciidoc[] include::authorization/kibana-privileges.asciidoc[] include::api-keys/index.asciidoc[] include::role-mappings/index.asciidoc[] +include::fips-140-2.asciidoc[] diff --git a/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts index f1469fa57ced6..70551c1e27504 100644 --- a/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts @@ -284,6 +284,7 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>({ }, security: { registerSecurityDelegate: (api) => deps.security.registerSecurityDelegate(api), + fips: deps.security.fips, }, userProfile: { registerUserProfileDelegate: (delegate) => diff --git a/packages/core/security/core-security-server-internal/src/fips/fips.test.ts b/packages/core/security/core-security-server-internal/src/fips/fips.test.ts new file mode 100644 index 0000000000000..65f95aa7da691 --- /dev/null +++ b/packages/core/security/core-security-server-internal/src/fips/fips.test.ts @@ -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 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. + */ + +const mockGetFipsFn = jest.fn(); +jest.mock('crypto', () => ({ + randomBytes: jest.fn(), + constants: jest.requireActual('crypto').constants, + get getFips() { + return mockGetFipsFn; + }, +})); + +import { SecurityServiceConfigType } from '../utils'; +import { isFipsEnabled, checkFipsConfig } from './fips'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; + +describe('fips', () => { + let config: SecurityServiceConfigType; + describe('#isFipsEnabled', () => { + it('should return `true` if config.experimental.fipsMode.enabled is `true`', () => { + config = { experimental: { fipsMode: { enabled: true } } }; + + expect(isFipsEnabled(config)).toBe(true); + }); + + it('should return `false` if config.experimental.fipsMode.enabled is `false`', () => { + config = { experimental: { fipsMode: { enabled: false } } }; + + expect(isFipsEnabled(config)).toBe(false); + }); + + it('should return `false` if config.experimental.fipsMode.enabled is `undefined`', () => { + expect(isFipsEnabled(config)).toBe(false); + }); + }); + + describe('checkFipsConfig', () => { + let mockExit: jest.SpyInstance; + + beforeAll(() => { + mockExit = jest.spyOn(process, 'exit').mockImplementation((exitCode) => { + throw new Error(`Fake Exit: ${exitCode}`); + }); + }); + + afterAll(() => { + mockExit.mockRestore(); + }); + + it('should log an error message if FIPS mode is misconfigured - xpack.security.experimental.fipsMode.enabled true, Nodejs FIPS mode false', async () => { + config = { experimental: { fipsMode: { enabled: true } } }; + const logger = loggingSystemMock.create().get(); + try { + checkFipsConfig(config, logger); + } catch (e) { + expect(mockExit).toHaveBeenNthCalledWith(1, 78); + } + + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` + Array [ + Array [ + "Configuration mismatch error. xpack.security.experimental.fipsMode.enabled is set to true and the configured Node.js environment has FIPS disabled", + ], + ] + `); + }); + + it('should log an error message if FIPS mode is misconfigured - xpack.security.experimental.fipsMode.enabled false, Nodejs FIPS mode true', async () => { + mockGetFipsFn.mockImplementationOnce(() => { + return 1; + }); + + config = { experimental: { fipsMode: { enabled: false } } }; + const logger = loggingSystemMock.create().get(); + + try { + checkFipsConfig(config, logger); + } catch (e) { + expect(mockExit).toHaveBeenNthCalledWith(1, 78); + } + + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` + Array [ + Array [ + "Configuration mismatch error. xpack.security.experimental.fipsMode.enabled is set to false and the configured Node.js environment has FIPS enabled", + ], + ] + `); + }); + + it('should log an info message if FIPS mode is properly configured - xpack.security.experimental.fipsMode.enabled true, Nodejs FIPS mode true', async () => { + mockGetFipsFn.mockImplementationOnce(() => { + return 1; + }); + + config = { experimental: { fipsMode: { enabled: true } } }; + const logger = loggingSystemMock.create().get(); + + try { + checkFipsConfig(config, logger); + } catch (e) { + logger.error('Should not throw error!'); + } + + expect(loggingSystemMock.collect(logger).info).toMatchInlineSnapshot(` + Array [ + Array [ + "Kibana is running in FIPS mode.", + ], + ] + `); + }); + }); +}); diff --git a/packages/core/security/core-security-server-internal/src/fips/fips.ts b/packages/core/security/core-security-server-internal/src/fips/fips.ts new file mode 100644 index 0000000000000..2b48fb68ff607 --- /dev/null +++ b/packages/core/security/core-security-server-internal/src/fips/fips.ts @@ -0,0 +1,35 @@ +/* + * 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 type { Logger } from '@kbn/logging'; +import { getFips } from 'crypto'; +import { SecurityServiceConfigType } from '../utils'; + +export function isFipsEnabled(config: SecurityServiceConfigType): boolean { + return config?.experimental?.fipsMode?.enabled ?? false; +} + +export function checkFipsConfig(config: SecurityServiceConfigType, logger: Logger) { + const isFipsConfigEnabled = isFipsEnabled(config); + const isNodeRunningWithFipsEnabled = getFips() === 1; + + // Check if FIPS is enabled in either setting + if (isFipsConfigEnabled || isNodeRunningWithFipsEnabled) { + // FIPS must be enabled on both or log and error an exit Kibana + if (isFipsConfigEnabled !== isNodeRunningWithFipsEnabled) { + logger.error( + `Configuration mismatch error. xpack.security.experimental.fipsMode.enabled is set to ${isFipsConfigEnabled} and the configured Node.js environment has FIPS ${ + isNodeRunningWithFipsEnabled ? 'enabled' : 'disabled' + }` + ); + process.exit(78); + } else { + logger.info('Kibana is running in FIPS mode.'); + } + } +} diff --git a/packages/core/security/core-security-server-internal/src/security_service.test.ts b/packages/core/security/core-security-server-internal/src/security_service.test.ts index 4f5ae5e86cbab..5fb6b46f6dc63 100644 --- a/packages/core/security/core-security-server-internal/src/security_service.test.ts +++ b/packages/core/security/core-security-server-internal/src/security_service.test.ts @@ -45,6 +45,16 @@ describe('SecurityService', () => { ); }); }); + + describe('#fips', () => { + describe('#isEnabled', () => { + it('should return boolean', () => { + const { fips } = service.setup(); + + expect(fips.isEnabled()).toBe(false); + }); + }); + }); }); describe('#start', () => { diff --git a/packages/core/security/core-security-server-internal/src/security_service.ts b/packages/core/security/core-security-server-internal/src/security_service.ts index 826019f773b93..215e7ef376285 100644 --- a/packages/core/security/core-security-server-internal/src/security_service.ts +++ b/packages/core/security/core-security-server-internal/src/security_service.ts @@ -9,23 +9,49 @@ import type { Logger } from '@kbn/logging'; import type { CoreContext, CoreService } from '@kbn/core-base-server-internal'; import type { CoreSecurityDelegateContract } from '@kbn/core-security-server'; +import { Observable, Subscription } from 'rxjs'; +import { Config } from '@kbn/config'; +import { isFipsEnabled, checkFipsConfig } from './fips/fips'; import type { InternalSecurityServiceSetup, InternalSecurityServiceStart, } from './internal_contracts'; -import { getDefaultSecurityImplementation, convertSecurityApi } from './utils'; +import { + getDefaultSecurityImplementation, + convertSecurityApi, + SecurityServiceConfigType, +} from './utils'; export class SecurityService implements CoreService<InternalSecurityServiceSetup, InternalSecurityServiceStart> { private readonly log: Logger; private securityApi?: CoreSecurityDelegateContract; + private config$: Observable<Config>; + private configSubscription?: Subscription; + private config: Config | undefined; + private readonly getConfig = () => { + if (!this.config) { + throw new Error('Config is not available.'); + } + return this.config; + }; constructor(coreContext: CoreContext) { this.log = coreContext.logger.get('security-service'); + + this.config$ = coreContext.configService.getConfig$(); + this.configSubscription = this.config$.subscribe((config) => { + this.config = config; + }); } public setup(): InternalSecurityServiceSetup { + const config = this.getConfig(); + const securityConfig: SecurityServiceConfigType = config.get(['xpack', 'security']); + + checkFipsConfig(securityConfig, this.log); + return { registerSecurityDelegate: (api) => { if (this.securityApi) { @@ -33,6 +59,9 @@ export class SecurityService } this.securityApi = api; }, + fips: { + isEnabled: () => isFipsEnabled(securityConfig), + }, }; } @@ -44,5 +73,10 @@ export class SecurityService return convertSecurityApi(apiContract); } - public stop() {} + public stop() { + if (this.configSubscription) { + this.configSubscription.unsubscribe(); + this.configSubscription = undefined; + } + } } diff --git a/packages/core/security/core-security-server-internal/src/utils/index.ts b/packages/core/security/core-security-server-internal/src/utils/index.ts index e43884f204ece..6ce85739b44f6 100644 --- a/packages/core/security/core-security-server-internal/src/utils/index.ts +++ b/packages/core/security/core-security-server-internal/src/utils/index.ts @@ -8,3 +8,11 @@ export { convertSecurityApi } from './convert_security_api'; export { getDefaultSecurityImplementation } from './default_implementation'; + +export interface SecurityServiceConfigType { + experimental?: { + fipsMode?: { + enabled: boolean; + }; + }; +} diff --git a/packages/core/security/core-security-server-internal/tsconfig.json b/packages/core/security/core-security-server-internal/tsconfig.json index ad66b66deeeeb..e1812dc77cf49 100644 --- a/packages/core/security/core-security-server-internal/tsconfig.json +++ b/packages/core/security/core-security-server-internal/tsconfig.json @@ -20,5 +20,7 @@ "@kbn/core-http-server", "@kbn/logging-mocks", "@kbn/core-base-server-mocks", + "@kbn/config", + "@kbn/core-logging-server-mocks", ] } diff --git a/packages/core/security/core-security-server-mocks/src/security_service.mock.ts b/packages/core/security/core-security-server-mocks/src/security_service.mock.ts index b19539fd862c0..59a560d562e06 100644 --- a/packages/core/security/core-security-server-mocks/src/security_service.mock.ts +++ b/packages/core/security/core-security-server-mocks/src/security_service.mock.ts @@ -20,6 +20,7 @@ import { auditServiceMock, type MockedAuditService } from './audit.mock'; const createSetupMock = () => { const mock: jest.Mocked<SecurityServiceSetup> = { registerSecurityDelegate: jest.fn(), + fips: { isEnabled: jest.fn() }, }; return mock; @@ -43,6 +44,7 @@ const createStartMock = (): SecurityStartMock => { const createInternalSetupMock = () => { const mock: jest.Mocked<InternalSecurityServiceSetup> = { registerSecurityDelegate: jest.fn(), + fips: { isEnabled: jest.fn() }, }; return mock; diff --git a/packages/core/security/core-security-server/index.ts b/packages/core/security/core-security-server/index.ts index a4d3027c97fdb..6a111ab6e27ab 100644 --- a/packages/core/security/core-security-server/index.ts +++ b/packages/core/security/core-security-server/index.ts @@ -26,3 +26,4 @@ export type { AuditRequest, } from './src/audit_logging/audit_events'; export type { AuditLogger } from './src/audit_logging/audit_logger'; +export type { CoreFipsService } from './src/fips'; diff --git a/packages/core/security/core-security-server/src/contracts.ts b/packages/core/security/core-security-server/src/contracts.ts index ed25737823f7b..d2bf7d97e9472 100644 --- a/packages/core/security/core-security-server/src/contracts.ts +++ b/packages/core/security/core-security-server/src/contracts.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import type { CoreFipsService } from './fips'; import type { CoreAuthenticationService } from './authc'; import type { CoreSecurityDelegateContract } from './api_provider'; import type { CoreAuditService } from './audit'; @@ -21,6 +22,11 @@ export interface SecurityServiceSetup { * @remark this should **exclusively** be used by the security plugin. */ registerSecurityDelegate(api: CoreSecurityDelegateContract): void; + + /** + * The {@link CoreFipsService | FIPS service} + */ + fips: CoreFipsService; } /** diff --git a/packages/core/security/core-security-server/src/fips.ts b/packages/core/security/core-security-server/src/fips.ts new file mode 100644 index 0000000000000..239903caba3bc --- /dev/null +++ b/packages/core/security/core-security-server/src/fips.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * Core's FIPS service + * + * @public + */ +export interface CoreFipsService { + /** + * Check if Kibana is configured to run in FIPS mode + */ + isEnabled(): boolean; +} diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index 27d7a465186a4..10fcbe6d06d6a 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -390,6 +390,7 @@ kibana_vars=( xpack.security.authc.selector.enabled xpack.security.cookieName xpack.security.encryptionKey + xpack.security.experimental.fipsMode.enabled xpack.security.loginAssistanceMessage xpack.security.loginHelp xpack.security.sameSiteCookies diff --git a/x-pack/packages/security/plugin_types_common/src/licensing/license.ts b/x-pack/packages/security/plugin_types_common/src/licensing/license.ts index 0a7e8e3b87c67..349395ee63fdc 100644 --- a/x-pack/packages/security/plugin_types_common/src/licensing/license.ts +++ b/x-pack/packages/security/plugin_types_common/src/licensing/license.ts @@ -13,6 +13,7 @@ import type { SecurityLicenseFeatures } from './license_features'; export interface SecurityLicense { isLicenseAvailable(): boolean; + getLicenseType(): string | undefined; getUnavailableReason: () => string | undefined; isEnabled(): boolean; getFeatures(): SecurityLicenseFeatures; diff --git a/x-pack/packages/security/plugin_types_common/src/licensing/license_features.ts b/x-pack/packages/security/plugin_types_common/src/licensing/license_features.ts index 58fb081a5760d..68dc87b0f5778 100644 --- a/x-pack/packages/security/plugin_types_common/src/licensing/license_features.ts +++ b/x-pack/packages/security/plugin_types_common/src/licensing/license_features.ts @@ -83,4 +83,10 @@ export interface SecurityLicenseFeatures { * Describes the layout of the login form if it's displayed. */ readonly layout?: LoginLayout; + + /** + * Indicates whether we allow FIPS mode + */ + + readonly allowFips: boolean; } diff --git a/x-pack/plugins/security/common/licensing/index.mock.ts b/x-pack/plugins/security/common/licensing/index.mock.ts index 9d2fef049de82..6ee9910b768bd 100644 --- a/x-pack/plugins/security/common/licensing/index.mock.ts +++ b/x-pack/plugins/security/common/licensing/index.mock.ts @@ -14,12 +14,33 @@ import type { SecurityLicense, SecurityLicenseFeatures } from '@kbn/security-plu export const licenseMock = { create: ( features: Partial<SecurityLicenseFeatures> | Observable<Partial<SecurityLicenseFeatures>> = {}, - licenseType: LicenseType = 'basic' // default to basic if this is not specified + licenseType: LicenseType = 'basic', // default to basic if this is not specified, + isAvailable: Observable<boolean> = of(true) ): jest.Mocked<SecurityLicense> => ({ - isLicenseAvailable: jest.fn().mockReturnValue(true), + isLicenseAvailable: jest.fn().mockImplementation(() => { + let result = true; + + isAvailable.subscribe((next) => { + result = next; + }); + + return result; + }), + getLicenseType: jest.fn().mockReturnValue(licenseType), getUnavailableReason: jest.fn(), isEnabled: jest.fn().mockReturnValue(true), - getFeatures: jest.fn().mockReturnValue(features), + getFeatures: + features instanceof Observable + ? jest.fn().mockImplementation(() => { + let subbedFeatures: Partial<SecurityLicenseFeatures> = {}; + + features.subscribe((next) => { + subbedFeatures = next; + }); + + return subbedFeatures; + }) + : jest.fn().mockReturnValue(features), hasAtLeast: jest .fn() .mockImplementation( diff --git a/x-pack/plugins/security/common/licensing/license_service.test.ts b/x-pack/plugins/security/common/licensing/license_service.test.ts index f1b80db5cba2d..ab8b5c803deab 100644 --- a/x-pack/plugins/security/common/licensing/license_service.test.ts +++ b/x-pack/plugins/security/common/licensing/license_service.test.ts @@ -32,6 +32,7 @@ describe('license features', function () { allowSubFeaturePrivileges: false, allowAuditLogging: false, allowUserProfileCollaboration: false, + allowFips: false, }); }); @@ -57,6 +58,7 @@ describe('license features', function () { allowSubFeaturePrivileges: false, allowAuditLogging: false, allowUserProfileCollaboration: false, + allowFips: false, }); }); @@ -78,6 +80,7 @@ describe('license features', function () { Object { "allowAccessAgreement": false, "allowAuditLogging": false, + "allowFips": false, "allowLogin": false, "allowRbac": false, "allowRemoteClusterPrivileges": false, @@ -102,6 +105,7 @@ describe('license features', function () { Object { "allowAccessAgreement": true, "allowAuditLogging": true, + "allowFips": true, "allowLogin": true, "allowRbac": true, "allowRemoteClusterPrivileges": true, @@ -146,6 +150,7 @@ describe('license features', function () { allowSubFeaturePrivileges: false, allowAuditLogging: false, allowUserProfileCollaboration: false, + allowFips: false, }); expect(getFeatureSpy).toHaveBeenCalledTimes(1); expect(getFeatureSpy).toHaveBeenCalledWith('security'); @@ -174,6 +179,7 @@ describe('license features', function () { allowSubFeaturePrivileges: false, allowAuditLogging: false, allowUserProfileCollaboration: false, + allowFips: false, }); }); @@ -201,6 +207,7 @@ describe('license features', function () { allowSubFeaturePrivileges: false, allowAuditLogging: false, allowUserProfileCollaboration: true, + allowFips: false, }); }); @@ -228,6 +235,7 @@ describe('license features', function () { allowSubFeaturePrivileges: true, allowAuditLogging: true, allowUserProfileCollaboration: true, + allowFips: false, }); }); @@ -255,6 +263,7 @@ describe('license features', function () { allowSubFeaturePrivileges: true, allowAuditLogging: true, allowUserProfileCollaboration: true, + allowFips: true, }); }); }); diff --git a/x-pack/plugins/security/common/licensing/license_service.ts b/x-pack/plugins/security/common/licensing/license_service.ts index 3066d32a72695..817b3f207aa14 100644 --- a/x-pack/plugins/security/common/licensing/license_service.ts +++ b/x-pack/plugins/security/common/licensing/license_service.ts @@ -28,6 +28,8 @@ export class SecurityLicenseService { license: Object.freeze({ isLicenseAvailable: () => rawLicense?.isAvailable ?? false, + getLicenseType: () => rawLicense?.type ?? undefined, + getUnavailableReason: () => rawLicense?.getUnavailableReason(), isEnabled: () => this.isSecurityEnabledFromRawLicense(rawLicense), @@ -81,6 +83,7 @@ export class SecurityLicenseService { allowRbac: false, allowSubFeaturePrivileges: false, allowUserProfileCollaboration: false, + allowFips: false, layout: rawLicense !== undefined && !rawLicense?.isAvailable ? 'error-xpack-unavailable' @@ -103,6 +106,7 @@ export class SecurityLicenseService { allowRbac: false, allowSubFeaturePrivileges: false, allowUserProfileCollaboration: false, + allowFips: false, }; } @@ -124,6 +128,7 @@ export class SecurityLicenseService { allowRemoteClusterPrivileges: isLicensePlatinumOrBetter, allowRbac: true, allowUserProfileCollaboration: isLicenseStandardOrBetter, + allowFips: isLicensePlatinumOrBetter, }; } } diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap index e64e867a71a57..ccb8decacd812 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap @@ -124,6 +124,7 @@ exports[`it renders correctly in serverless mode 1`] = ` "_subscribe": [Function], }, "getFeatures": [MockFunction], + "getLicenseType": [MockFunction], "getUnavailableReason": [MockFunction], "hasAtLeast": [MockFunction], "isEnabled": [MockFunction], @@ -322,6 +323,7 @@ exports[`it renders without crashing 1`] = ` "_subscribe": [Function], }, "getFeatures": [MockFunction], + "getLicenseType": [MockFunction], "getUnavailableReason": [MockFunction], "hasAtLeast": [MockFunction], "isEnabled": [MockFunction], diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/index_privileges.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/index_privileges.test.tsx.snap index c3df729a7e3ee..705af534bc71d 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/index_privileges.test.tsx.snap +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/index_privileges.test.tsx.snap @@ -24,6 +24,7 @@ exports[`it renders without crashing 1`] = ` "_subscribe": [Function], }, "getFeatures": [MockFunction], + "getLicenseType": [MockFunction], "getUnavailableReason": [MockFunction], "hasAtLeast": [MockFunction], "isEnabled": [MockFunction], diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/remote_cluster_privileges.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/remote_cluster_privileges.test.tsx.snap index e0939f7f55e02..7cc4e67ece4fc 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/remote_cluster_privileges.test.tsx.snap +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/remote_cluster_privileges.test.tsx.snap @@ -14,6 +14,7 @@ exports[`it renders without crashing 1`] = ` "_subscribe": [Function], }, "getFeatures": [MockFunction], + "getLicenseType": [MockFunction], "getUnavailableReason": [MockFunction], "hasAtLeast": [MockFunction], "isEnabled": [MockFunction], diff --git a/x-pack/plugins/security/public/plugin.test.tsx b/x-pack/plugins/security/public/plugin.test.tsx index 874196f3e4c0e..433e1981e9ce9 100644 --- a/x-pack/plugins/security/public/plugin.test.tsx +++ b/x-pack/plugins/security/public/plugin.test.tsx @@ -43,6 +43,7 @@ describe('Security Plugin', () => { authz: { isRoleManagementEnabled: expect.any(Function), roles: expect.any(Object) }, license: { isLicenseAvailable: expect.any(Function), + getLicenseType: expect.any(Function), isEnabled: expect.any(Function), getUnavailableReason: expect.any(Function), getFeatures: expect.any(Function), @@ -71,6 +72,7 @@ describe('Security Plugin', () => { authc: { getCurrentUser: expect.any(Function), areAPIKeysEnabled: expect.any(Function) }, license: { isLicenseAvailable: expect.any(Function), + getLicenseType: expect.any(Function), isEnabled: expect.any(Function), getUnavailableReason: expect.any(Function), getFeatures: expect.any(Function), diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index 3a6ccc619fdb9..5e6c59aee4668 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -61,6 +61,11 @@ describe('config schema', () => { }, "cookieName": "sid", "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "experimental": Object { + "fipsMode": Object { + "enabled": false, + }, + }, "loginAssistanceMessage": "", "public": Object {}, "secureCookies": false, @@ -115,6 +120,11 @@ describe('config schema', () => { }, "cookieName": "sid", "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "experimental": Object { + "fipsMode": Object { + "enabled": false, + }, + }, "loginAssistanceMessage": "", "public": Object {}, "secureCookies": false, @@ -168,6 +178,11 @@ describe('config schema', () => { "selector": Object {}, }, "cookieName": "sid", + "experimental": Object { + "fipsMode": Object { + "enabled": false, + }, + }, "loginAssistanceMessage": "", "public": Object {}, "secureCookies": false, @@ -224,6 +239,11 @@ describe('config schema', () => { "selector": Object {}, }, "cookieName": "sid", + "experimental": Object { + "fipsMode": Object { + "enabled": false, + }, + }, "loginAssistanceMessage": "", "public": Object {}, "roleManagementEnabled": false, diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 1ea1c87d31d5d..e12f1462b39b4 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -314,6 +314,11 @@ export const ConfigSchema = schema.object({ roleMappingManagementEnabled: schema.boolean({ defaultValue: true }), }), }), + experimental: schema.object({ + fipsMode: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + }), + }), }); export function createConfig( diff --git a/x-pack/plugins/security/server/fips/fips_service.test.ts b/x-pack/plugins/security/server/fips/fips_service.test.ts new file mode 100644 index 0000000000000..aba86633c281f --- /dev/null +++ b/x-pack/plugins/security/server/fips/fips_service.test.ts @@ -0,0 +1,210 @@ +/* + * 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. + */ + +const mockGetFipsFn = jest.fn(); +jest.mock('crypto', () => ({ + randomBytes: jest.fn(), + constants: jest.requireActual('crypto').constants, + get getFips() { + return mockGetFipsFn; + }, +})); + +import type { Observable } from 'rxjs'; +import { BehaviorSubject, of } from 'rxjs'; + +import { loggingSystemMock } from '@kbn/core/server/mocks'; +import type { LicenseType } from '@kbn/licensing-plugin/common/types'; +import type { SecurityLicenseFeatures } from '@kbn/security-plugin-types-common'; + +import type { FipsServiceSetupInternal, FipsServiceSetupParams } from './fips_service'; +import { FipsService } from './fips_service'; +import { licenseMock } from '../../common/licensing/index.mock'; +import { ConfigSchema, createConfig } from '../config'; + +const logger = loggingSystemMock.createLogger(); + +function buildMockFipsServiceSetupParams( + licenseType: LicenseType, + isFipsConfigured: boolean, + features$: Observable<Partial<SecurityLicenseFeatures>>, + isAvailable: Observable<boolean> = of(true) +): FipsServiceSetupParams { + mockGetFipsFn.mockImplementationOnce(() => { + return isFipsConfigured ? 1 : 0; + }); + + const license = licenseMock.create(features$, licenseType, isAvailable); + + let mockConfig = {}; + if (isFipsConfigured) { + mockConfig = { experimental: { fipsMode: { enabled: true } } }; + } + + return { + license, + config: createConfig(ConfigSchema.validate(mockConfig), loggingSystemMock.createLogger(), { + isTLSEnabled: false, + }), + }; +} + +describe('FipsService', () => { + let fipsService: FipsService; + let fipsServiceSetup: FipsServiceSetupInternal; + + beforeEach(() => { + fipsService = new FipsService(logger); + logger.error.mockClear(); + }); + + afterEach(() => { + logger.error.mockClear(); + }); + + describe('setup()', () => { + it('should expose correct setup contract', () => { + fipsService = new FipsService(logger); + fipsServiceSetup = fipsService.setup( + buildMockFipsServiceSetupParams('platinum', true, of({ allowFips: true })) + ); + + expect(fipsServiceSetup).toMatchInlineSnapshot(` + Object { + "validateLicenseForFips": [Function], + } + `); + }); + }); + + describe('#validateLicenseForFips', () => { + describe('start-up check', () => { + it('should not throw Error/log.error if license features allowFips and `experimental.fipsMode.enabled` is `false`', () => { + fipsServiceSetup = fipsService.setup( + buildMockFipsServiceSetupParams('platinum', false, of({ allowFips: true })) + ); + fipsServiceSetup.validateLicenseForFips(); + + expect(logger.error).not.toHaveBeenCalled(); + }); + + it('should not throw Error/log.error if license features allowFips and `experimental.fipsMode.enabled` is `true`', () => { + fipsServiceSetup = fipsService.setup( + buildMockFipsServiceSetupParams('platinum', true, of({ allowFips: true })) + ); + fipsServiceSetup.validateLicenseForFips(); + + expect(logger.error).not.toHaveBeenCalled(); + }); + + it('should not throw Error/log.error if license features do not allowFips and `experimental.fipsMode.enabled` is `false`', () => { + fipsServiceSetup = fipsService.setup( + buildMockFipsServiceSetupParams('basic', false, of({ allowFips: false })) + ); + fipsServiceSetup.validateLicenseForFips(); + + expect(logger.error).not.toHaveBeenCalled(); + }); + + it('should throw Error/log.error if license features do not allowFips and `experimental.fipsMode.enabled` is `true`', () => { + fipsServiceSetup = fipsService.setup( + buildMockFipsServiceSetupParams('basic', true, of({ allowFips: false })) + ); + + // Because the Error is thrown from within a SafeSubscriber and cannot be hooked into + fipsServiceSetup.validateLicenseForFips(); + + expect(logger.error).toHaveBeenCalled(); + }); + }); + + describe('monitoring check', () => { + describe('with experimental.fipsMode.enabled', () => { + let mockFeaturesSubject: BehaviorSubject<Partial<SecurityLicenseFeatures>>; + let mockIsAvailableSubject: BehaviorSubject<boolean>; + let mockFeatures$: Observable<Partial<SecurityLicenseFeatures>>; + let mockIsAvailable$: Observable<boolean>; + + beforeAll(() => { + mockFeaturesSubject = new BehaviorSubject<Partial<SecurityLicenseFeatures>>({ + allowFips: true, + }); + mockIsAvailableSubject = new BehaviorSubject<boolean>(true); + mockFeatures$ = mockFeaturesSubject.asObservable(); + mockIsAvailable$ = mockIsAvailableSubject.asObservable(); + fipsServiceSetup = fipsService.setup( + buildMockFipsServiceSetupParams('platinum', true, mockFeatures$, mockIsAvailable$) + ); + + fipsServiceSetup.validateLicenseForFips(); + }); + + beforeEach(() => { + mockFeaturesSubject.next({ allowFips: true }); + mockIsAvailableSubject.next(true); + }); + + it('should not log.error if license changes to unavailable and `experimental.fipsMode.enabled` is `true`', () => { + mockIsAvailableSubject.next(false); + expect(logger.error).not.toHaveBeenCalled(); + }); + + it('should not log.error if license features continue to allowFips and `experimental.fipsMode.enabled` is `true`', () => { + mockFeaturesSubject.next({ allowFips: true }); + expect(logger.error).not.toHaveBeenCalled(); + }); + + it('should log.error if license features change to not allowFips and `experimental.fipsMode.enabled` is `true`', () => { + mockFeaturesSubject.next({ allowFips: false }); + expect(logger.error).toHaveBeenCalledTimes(1); + }); + }); + + describe('with not experimental.fipsMode.enabled', () => { + let mockFeaturesSubject: BehaviorSubject<Partial<SecurityLicenseFeatures>>; + let mockIsAvailableSubject: BehaviorSubject<boolean>; + let mockFeatures$: Observable<Partial<SecurityLicenseFeatures>>; + let mockIsAvailable$: Observable<boolean>; + + beforeAll(() => { + mockFeaturesSubject = new BehaviorSubject<Partial<SecurityLicenseFeatures>>({ + allowFips: true, + }); + mockIsAvailableSubject = new BehaviorSubject<boolean>(true); + mockFeatures$ = mockFeaturesSubject.asObservable(); + mockIsAvailable$ = mockIsAvailableSubject.asObservable(); + + fipsServiceSetup = fipsService.setup( + buildMockFipsServiceSetupParams('platinum', false, mockFeatures$, mockIsAvailable$) + ); + + fipsServiceSetup.validateLicenseForFips(); + }); + + beforeEach(() => { + mockFeaturesSubject.next({ allowFips: true }); + mockIsAvailableSubject.next(true); + }); + + it('should not log.error if license changes to unavailable and `experimental.fipsMode.enabled` is `false`', () => { + mockIsAvailableSubject.next(false); + expect(logger.error).not.toHaveBeenCalled(); + }); + + it('should not log.error if license features continue to allowFips and `experimental.fipsMode.enabled` is `false`', () => { + mockFeaturesSubject.next({ allowFips: true }); + expect(logger.error).not.toHaveBeenCalled(); + }); + + it('should not log.error if license change to not allowFips and `experimental.fipsMode.enabled` is `false`', () => { + mockFeaturesSubject.next({ allowFips: false }); + expect(logger.error).not.toHaveBeenCalled(); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/security/server/fips/fips_service.ts b/x-pack/plugins/security/server/fips/fips_service.ts new file mode 100644 index 0000000000000..aa351ab48828d --- /dev/null +++ b/x-pack/plugins/security/server/fips/fips_service.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/logging'; +import type { SecurityLicense } from '@kbn/security-plugin-types-common'; + +import type { ConfigType } from '../config'; + +export interface FipsServiceSetupParams { + config: ConfigType; + license: SecurityLicense; +} + +export interface FipsServiceSetupInternal { + validateLicenseForFips: () => void; +} + +export class FipsService { + private readonly logger: Logger; + private isInitialLicenseLoaded: boolean; + + constructor(logger: Logger) { + this.logger = logger; + this.isInitialLicenseLoaded = false; + } + + setup({ config, license }: FipsServiceSetupParams): FipsServiceSetupInternal { + return { + validateLicenseForFips: () => this.validateLicenseForFips(config, license), + }; + } + + private validateLicenseForFips(config: ConfigType, license: SecurityLicense) { + license.features$.subscribe({ + next: (features) => { + const errorMessage = `Your current license level is ${license.getLicenseType()} and does not support running in FIPS mode.`; + + if (license.isLicenseAvailable() && !this.isInitialLicenseLoaded) { + if (config?.experimental.fipsMode.enabled && !license.getFeatures().allowFips) { + this.logger.error(errorMessage); + throw new Error(errorMessage); + } + + this.isInitialLicenseLoaded = true; + } + + if ( + this.isInitialLicenseLoaded && + license.isLicenseAvailable() && + config?.experimental.fipsMode.enabled && + !features.allowFips + ) { + this.logger.error( + `${errorMessage} Kibana will not be able to restart. Please upgrade your license to platinum or higher.` + ); + } + }, + error: (error) => { + this.logger.debug(`Unable to check license: ${error}`); + }, + }); + } +} diff --git a/x-pack/plugins/security/server/fips/index.ts b/x-pack/plugins/security/server/fips/index.ts new file mode 100644 index 0000000000000..3af4435169348 --- /dev/null +++ b/x-pack/plugins/security/server/fips/index.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export { FipsService } from './fips_service'; + +export type { FipsServiceSetupInternal, FipsServiceSetupParams } from './fips_service'; diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index be3d00b77cff9..a82b45753845b 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -121,6 +121,7 @@ describe('Security Plugin', () => { }, }, "getFeatures": [Function], + "getLicenseType": [Function], "getUnavailableReason": [Function], "hasAtLeast": [Function], "isEnabled": [Function], diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index cf362926bdd04..9d5ffde67b1d7 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -52,6 +52,8 @@ import { ElasticsearchService } from './elasticsearch'; import type { SecurityFeatureUsageServiceStart } from './feature_usage'; import { SecurityFeatureUsageService } from './feature_usage'; import { securityFeatures } from './features'; +import type { FipsServiceSetupInternal } from './fips'; +import { FipsService } from './fips'; import { defineRoutes } from './routes'; import { setupSavedObjects } from './saved_objects'; import type { Session } from './session_management'; @@ -177,6 +179,9 @@ export class SecurityPlugin return this.userProfileStart; }; + private readonly fipsService: FipsService; + private fipsServiceSetup?: FipsServiceSetupInternal; + constructor(private readonly initializerContext: PluginInitializerContext) { this.logger = this.initializerContext.logger.get(); @@ -199,6 +204,8 @@ export class SecurityPlugin ); this.analyticsService = new AnalyticsService(this.initializerContext.logger.get('analytics')); + + this.fipsService = new FipsService(this.initializerContext.logger.get('fips')); } public setup( @@ -280,6 +287,9 @@ export class SecurityPlugin this.userProfileService.setup({ authz: this.authorizationSetup, license }); + this.fipsServiceSetup = this.fipsService.setup({ config, license }); + this.fipsServiceSetup.validateLicenseForFips(); + setupSpacesClient({ spaces, audit: this.auditSetup, diff --git a/x-pack/plugins/security/server/routes/views/login.test.ts b/x-pack/plugins/security/server/routes/views/login.test.ts index 87e9bf9e4495b..b19ef41ca9098 100644 --- a/x-pack/plugins/security/server/routes/views/login.test.ts +++ b/x-pack/plugins/security/server/routes/views/login.test.ts @@ -175,6 +175,7 @@ describe('Login view routes', () => { allowAuditLogging: true, showLogin: true, allowUserProfileCollaboration: true, + allowFips: false, }); const request = httpServerMock.createKibanaRequest(); From 5b0dc5faeb5c0b5092cea98f76a16ff2b55acf71 Mon Sep 17 00:00:00 2001 From: Jon <jon@elastic.co> Date: Tue, 2 Jul 2024 10:03:58 -0500 Subject: [PATCH 086/128] [ci] Use org wide PR comment bot (#187354) --- .buildkite/pipeline-resource-definitions/kibana-pr.yml | 2 +- .buildkite/pipeline-utils/test-failures/annotate.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.buildkite/pipeline-resource-definitions/kibana-pr.yml b/.buildkite/pipeline-resource-definitions/kibana-pr.yml index 8d2a6c8bf9e99..af697452130fc 100644 --- a/.buildkite/pipeline-resource-definitions/kibana-pr.yml +++ b/.buildkite/pipeline-resource-definitions/kibana-pr.yml @@ -19,7 +19,7 @@ spec: description: Runs manually for pull requests spec: env: - PR_COMMENTS_ENABLED: 'true' + ELASTIC_PR_COMMENTS_ENABLED: 'true' GITHUB_BUILD_COMMIT_STATUS_ENABLED: 'true' GITHUB_BUILD_COMMIT_STATUS_CONTEXT: kibana-ci GITHUB_STEP_COMMIT_STATUS_ENABLED: 'true' diff --git a/.buildkite/pipeline-utils/test-failures/annotate.ts b/.buildkite/pipeline-utils/test-failures/annotate.ts index 39aa2d36b9ddb..89f651f6d9855 100644 --- a/.buildkite/pipeline-utils/test-failures/annotate.ts +++ b/.buildkite/pipeline-utils/test-failures/annotate.ts @@ -170,7 +170,7 @@ export const annotateTestFailures = async () => { buildkite.setAnnotation('test_failures', 'error', getAnnotation(failures, failureHtmlArtifacts)); - if (process.env.PR_COMMENTS_ENABLED === 'true') { + if (process.env.ELASTIC_PR_COMMENTS_ENABLED === 'true') { buildkite.setMetadata( 'pr_comment:test_failures:body', getPrComment(failures, failureHtmlArtifacts) From bd9a2ad358d281cda341f32561f8af14830fbe3b Mon Sep 17 00:00:00 2001 From: Jedr Blaszyk <jedrazb@gmail.com> Date: Tue, 2 Jul 2024 17:08:14 +0200 Subject: [PATCH 087/128] [Connectors] Include `manage_connector` privilege in generated api keys (#187361) ## Summary We introduced `manage_connector` privilege in ES: https://github.com/elastic/elasticsearch/pull/110128 Let's use it for new generated API keys for connectors. Note: this privilege was merged to ES yesterday, so CI might fail if the ES image was not updated yet. ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - We will add this privilege in ES docs - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../server/lib/indices/generate_api_key.test.ts | 12 ++++++------ .../server/lib/indices/generate_api_key.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/generate_api_key.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/generate_api_key.test.ts index 828ad73e03733..73dfce85f0114 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/generate_api_key.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/generate_api_key.test.ts @@ -69,7 +69,7 @@ describe('generateApiKey lib function for connector clients', () => { name: 'index_name-connector', role_descriptors: { ['index-name-connector-role']: { - cluster: ['monitor'], + cluster: ['monitor', 'manage_connector'], index: [ { names: ['index_name', '.search-acl-filter-index_name', `${CONNECTORS_INDEX}*`], @@ -108,7 +108,7 @@ describe('generateApiKey lib function for connector clients', () => { name: 'search-test-connector', role_descriptors: { ['search-test-connector-role']: { - cluster: ['monitor'], + cluster: ['monitor', 'manage_connector'], index: [ { names: ['search-test', '.search-acl-filter-search-test', `${CONNECTORS_INDEX}*`], @@ -159,7 +159,7 @@ describe('generateApiKey lib function for connector clients', () => { name: 'index_name-connector', role_descriptors: { ['index-name-connector-role']: { - cluster: ['monitor'], + cluster: ['monitor', 'manage_connector'], index: [ { names: ['index_name', '.search-acl-filter-index_name', `${CONNECTORS_INDEX}*`], @@ -229,7 +229,7 @@ describe('generateApiKey lib function for native connectors', () => { name: 'index_name-connector', role_descriptors: { ['index-name-connector-role']: { - cluster: ['monitor'], + cluster: ['monitor', 'manage_connector'], index: [ { names: ['index_name', '.search-acl-filter-index_name', `${CONNECTORS_INDEX}*`], @@ -270,7 +270,7 @@ describe('generateApiKey lib function for native connectors', () => { name: 'search-test-connector', role_descriptors: { ['search-test-connector-role']: { - cluster: ['monitor'], + cluster: ['monitor', 'manage_connector'], index: [ { names: ['search-test', '.search-acl-filter-search-test', `${CONNECTORS_INDEX}*`], @@ -323,7 +323,7 @@ describe('generateApiKey lib function for native connectors', () => { name: 'index_name-connector', role_descriptors: { ['index-name-connector-role']: { - cluster: ['monitor'], + cluster: ['monitor', 'manage_connector'], index: [ { names: ['index_name', '.search-acl-filter-index_name', `${CONNECTORS_INDEX}*`], diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/generate_api_key.ts b/x-pack/plugins/enterprise_search/server/lib/indices/generate_api_key.ts index 4c75cee5e4de7..9c1175dfa75d5 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/generate_api_key.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/generate_api_key.ts @@ -28,7 +28,7 @@ export const generateApiKey = async ( name: `${indexName}-connector`, role_descriptors: { [`${toAlphanumeric(indexName)}-connector-role`]: { - cluster: ['monitor'], + cluster: ['monitor', 'manage_connector'], index: [ { names: [indexName, aclIndexName, `${CONNECTORS_INDEX}*`], From 8fc3d3ca1b6ac5cb03b85cb5f7c6500d9d8f4a07 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger <walter.rafelsberger@elastic.co> Date: Tue, 2 Jul 2024 17:26:12 +0200 Subject: [PATCH 088/128] [APM] Correlations: Update field candidates request. (#186182) ## Summary Fixes #185875. Since we created the correlations feature for APM, some new options were added to the `_field_caps` API which allow us to improve the way we retrieve field candidates for the analysis. Previously we used 2 queries to get field candidates: We fetched all fields via `_field_caps`, then searched for a random sample of 1000 docs to identify fields with values. Additional code would then filter the supported fields. Now we can use additional `_field_caps` options to get rid of the random docs request and simplify some of the filtering code. - `filters: '-metadata,-parent'` will exclude metadata and object fields, - `include_empty_fields: false` will include populated fields only, - `index_filter: ...` allows to provide a range filter with `start/end` to limit the scope of indices, - `types: ...` allows us to get only fields of the type supported by the analysis (keyword, boolean, ip). ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../apm/common/correlations/constants.ts | 2 - .../fetch_duration_field_candidates.test.ts | 60 +++++++++++++ .../fetch_duration_field_candidates.ts | 87 ++++--------------- 3 files changed, 79 insertions(+), 70 deletions(-) create mode 100644 x-pack/plugins/observability_solution/apm/server/routes/correlations/queries/fetch_duration_field_candidates.test.ts diff --git a/x-pack/plugins/observability_solution/apm/common/correlations/constants.ts b/x-pack/plugins/observability_solution/apm/common/correlations/constants.ts index f42c5b1c4a81b..26421839b10ad 100644 --- a/x-pack/plugins/observability_solution/apm/common/correlations/constants.ts +++ b/x-pack/plugins/observability_solution/apm/common/correlations/constants.ts @@ -67,8 +67,6 @@ export const FIELD_PREFIX_TO_ADD_AS_CANDIDATE = ['cloud.', 'labels.', 'user_agen /** * Other constants */ -export const POPULATED_DOC_COUNT_SAMPLE_SIZE = 1000; - export const PERCENTILES_STEP = 2; export const TERMS_SIZE = 20; export const SIGNIFICANT_FRACTION = 3; diff --git a/x-pack/plugins/observability_solution/apm/server/routes/correlations/queries/fetch_duration_field_candidates.test.ts b/x-pack/plugins/observability_solution/apm/server/routes/correlations/queries/fetch_duration_field_candidates.test.ts new file mode 100644 index 0000000000000..cd9f1f2312f4a --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/server/routes/correlations/queries/fetch_duration_field_candidates.test.ts @@ -0,0 +1,60 @@ +/* + * 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 { ProcessorEvent } from '@kbn/observability-plugin/common'; +import type { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; +import { fetchDurationFieldCandidates } from './fetch_duration_field_candidates'; + +const mockResponse = { + indices: ['.ds-traces-apm-default-2024.06.17-000001'], + fields: { + 'keep.this.field': { + keyword: { type: 'keyword', metadata_field: false, searchable: true, aggregatable: true }, + }, + 'source.ip': { + ip: { type: 'ip', metadata_field: false, searchable: true, aggregatable: true }, + }, + // fields prefixed with 'observer.' should be ignored (via FIELD_PREFIX_TO_EXCLUDE_AS_CANDIDATE) + 'observer.version': { + keyword: { type: 'keyword', metadata_field: false, searchable: true, aggregatable: true }, + }, + 'observer.hostname': { + keyword: { type: 'keyword', metadata_field: false, searchable: true, aggregatable: true }, + }, + // example fields to exclude (via FIELDS_TO_EXCLUDE_AS_CANDIDATE) + 'agent.name': { + keyword: { type: 'keyword', metadata_field: false, searchable: true, aggregatable: true }, + }, + 'parent.id': { + keyword: { type: 'keyword', metadata_field: false, searchable: true, aggregatable: true }, + }, + }, +}; + +const mockApmEventClient = { + fieldCaps: async () => { + return mockResponse; + }, +} as unknown as APMEventClient; + +describe('fetchDurationFieldCandidates', () => { + it('returns duration field candidates', async () => { + const response = await fetchDurationFieldCandidates({ + apmEventClient: mockApmEventClient, + eventType: ProcessorEvent.transaction, + start: 0, + end: 1, + environment: 'ENVIRONMENT_ALL', + query: { match_all: {} }, + kuery: '', + }); + + expect(response).toStrictEqual({ + fieldCandidates: ['keep.this.field', 'source.ip'], + }); + }); +}); diff --git a/x-pack/plugins/observability_solution/apm/server/routes/correlations/queries/fetch_duration_field_candidates.ts b/x-pack/plugins/observability_solution/apm/server/routes/correlations/queries/fetch_duration_field_candidates.ts index 97d0ff54fc418..9f7e35ec56f16 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/correlations/queries/fetch_duration_field_candidates.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/correlations/queries/fetch_duration_field_candidates.ts @@ -8,15 +8,12 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ES_FIELD_TYPES } from '@kbn/field-types'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { rangeQuery } from '@kbn/observability-plugin/server'; import type { CommonCorrelationsQueryParams } from '../../../../common/correlations/types'; import { FIELD_PREFIX_TO_EXCLUDE_AS_CANDIDATE, - FIELDS_TO_ADD_AS_CANDIDATE, FIELDS_TO_EXCLUDE_AS_CANDIDATE, - POPULATED_DOC_COUNT_SAMPLE_SIZE, } from '../../../../common/correlations/constants'; -import { hasPrefixToInclude } from '../../../../common/correlations/utils'; -import { getCommonCorrelationsQuery } from './get_common_correlations_query'; import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; const SUPPORTED_ES_FIELD_TYPES = [ @@ -25,13 +22,6 @@ const SUPPORTED_ES_FIELD_TYPES = [ ES_FIELD_TYPES.BOOLEAN, ]; -export const shouldBeExcluded = (fieldName: string) => { - return ( - FIELDS_TO_EXCLUDE_AS_CANDIDATE.has(fieldName) || - FIELD_PREFIX_TO_EXCLUDE_AS_CANDIDATE.some((prefix) => fieldName.startsWith(prefix)) - ); -}; - export interface DurationFieldCandidatesResponse { fieldCandidates: string[]; } @@ -39,73 +29,34 @@ export interface DurationFieldCandidatesResponse { export async function fetchDurationFieldCandidates({ apmEventClient, eventType, - query, start, end, - environment, - kuery, }: CommonCorrelationsQueryParams & { query: estypes.QueryDslQueryContainer; apmEventClient: APMEventClient; eventType: ProcessorEvent.transaction | ProcessorEvent.span; }): Promise<DurationFieldCandidatesResponse> { // Get all supported fields - const [respMapping, respRandomDoc] = await Promise.all([ - apmEventClient.fieldCaps('get_field_caps', { - apm: { - events: [eventType], - }, - fields: '*', - }), - apmEventClient.search('get_random_doc_for_field_candidate', { - apm: { - events: [eventType], - }, - body: { - track_total_hits: false, - fields: ['*'], - _source: false, - query: getCommonCorrelationsQuery({ - start, - end, - environment, - kuery, - query, - }), - size: POPULATED_DOC_COUNT_SAMPLE_SIZE, - }, - }), - ]); - - const finalFieldCandidates = new Set(FIELDS_TO_ADD_AS_CANDIDATE); - const acceptableFields: Set<string> = new Set(); - - Object.entries(respMapping.fields).forEach(([key, value]) => { - const fieldTypes = Object.keys(value) as ES_FIELD_TYPES[]; - const isSupportedType = fieldTypes.some((type) => SUPPORTED_ES_FIELD_TYPES.includes(type)); - // Definitely include if field name matches any of the wild card - if (hasPrefixToInclude(key) && isSupportedType) { - finalFieldCandidates.add(key); - } - - // Check if fieldName is something we can aggregate on - if (isSupportedType) { - acceptableFields.add(key); - } - }); - - const sampledDocs = respRandomDoc.hits.hits.map((d) => d.fields ?? {}); - - // Get all field names for each returned doc and flatten it - // to a list of unique field names used across all docs - // and filter by list of acceptable fields and some APM specific unique fields. - [...new Set(sampledDocs.map(Object.keys).flat(1))].forEach((field) => { - if (acceptableFields.has(field) && !shouldBeExcluded(field)) { - finalFieldCandidates.add(field); - } + const respMapping = await apmEventClient.fieldCaps('get_field_caps', { + apm: { + events: [eventType], + }, + fields: '*', + // We exclude metadata and parent fields as they are not useful for correlations. + // There's an issue in ES (https://github.com/elastic/elasticsearch/issues/109797) + // that describes why we need to add -parent in addition to the types option. + filters: '-metadata,-parent', + include_empty_fields: false, + index_filter: rangeQuery(start, end)[0], + types: SUPPORTED_ES_FIELD_TYPES, }); return { - fieldCandidates: [...finalFieldCandidates], + fieldCandidates: Object.keys(respMapping.fields).filter((fieldName: string) => { + return ( + !FIELDS_TO_EXCLUDE_AS_CANDIDATE.has(fieldName) && + !FIELD_PREFIX_TO_EXCLUDE_AS_CANDIDATE.some((prefix) => fieldName.startsWith(prefix)) + ); + }), }; } From 3b9269faee18bb7c8819a27d73c7254f2fe271c8 Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski <tomasz.ciecierski@elastic.co> Date: Tue, 2 Jul 2024 17:27:45 +0200 Subject: [PATCH 089/128] [EDR Workflows] Fix saved query skipped test (#187352) --- .../cypress/e2e/all/saved_queries.cy.ts | 135 +++++++++++++++- .../osquery/cypress/tasks/saved_queries.ts | 146 ------------------ 2 files changed, 131 insertions(+), 150 deletions(-) delete mode 100644 x-pack/plugins/osquery/cypress/tasks/saved_queries.ts diff --git a/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts index 8f04a30c57048..e1aa13f739140 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts @@ -5,7 +5,12 @@ * 2.0. */ -import { LIVE_QUERY_EDITOR } from '../../screens/live_query'; +import { closeToastIfVisible, generateRandomStringName } from '../../tasks/integrations'; +import { + LIVE_QUERY_EDITOR, + RESULTS_TABLE_BUTTON, + RESULTS_TABLE_COLUMNS_BUTTON, +} from '../../screens/live_query'; import { ADD_QUERY_BUTTON, customActionEditSavedQuerySelector, @@ -16,15 +21,17 @@ import { import { preparePack } from '../../tasks/packs'; import { addToCase, + BIG_QUERY, checkResults, deleteAndConfirm, + fillInQueryTimeout, inputQuery, selectAllAgents, submitQuery, + verifyQueryTimeout, viewRecentCaseAndCheckResults, } from '../../tasks/live_query'; import { navigateTo } from '../../tasks/navigation'; -import { getSavedQueriesComplexTest } from '../../tasks/saved_queries'; import { loadCase, cleanupCase, @@ -34,6 +41,7 @@ import { cleanupSavedQuery, } from '../../tasks/api_fixtures'; import { ServerlessRoleName } from '../../support/roles'; +import { getAdvancedButton } from '../../screens/integrations'; describe('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { let caseId: string; @@ -53,9 +61,128 @@ describe('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { cleanupCase(caseId); }); - getSavedQueriesComplexTest(); + it( + 'should create a new query and verify: \n ' + + '- hidden columns, full screen and sorting \n' + + '- pagination \n' + + '- query can viewed (status), edited and deleted ', + () => { + const timeout = '601'; + const suffix = generateRandomStringName(1)[0]; + const savedQueryId = `Saved-Query-Id-${suffix}`; + const savedQueryDescription = `Test saved query description ${suffix}`; + cy.contains('New live query').click(); + selectAllAgents(); + inputQuery(BIG_QUERY); + getAdvancedButton().click(); + fillInQueryTimeout(timeout); + submitQuery(); + checkResults(); + // enter fullscreen + cy.getBySel(RESULTS_TABLE_BUTTON).trigger('mouseover'); + cy.contains(/Enter fullscreen$/).should('exist'); + cy.contains('Exit fullscreen').should('not.exist'); + cy.getBySel(RESULTS_TABLE_BUTTON).click(); + + cy.getBySel(RESULTS_TABLE_BUTTON).trigger('mouseover'); + cy.contains(/Enter Fullscreen$/).should('not.exist'); + cy.contains('Exit fullscreen').should('exist'); + + // hidden columns + cy.getBySel(RESULTS_TABLE_COLUMNS_BUTTON).should('have.text', 'Columns35'); + cy.getBySel('dataGridColumnSelectorButton').click(); + cy.get('[data-popover-open="true"]').should('be.visible'); + cy.getBySel('dataGridColumnSelectorToggleColumnVisibility-osquery.cmdline').click(); + cy.getBySel('dataGridColumnSelectorToggleColumnVisibility-osquery.cwd').click(); + cy.getBySel( + 'dataGridColumnSelectorToggleColumnVisibility-osquery.disk_bytes_written.number' + ).click(); + cy.getBySel('dataGridColumnSelectorButton').click(); + cy.get('[data-popover-open="true"]').should('not.exist'); + cy.getBySel(RESULTS_TABLE_COLUMNS_BUTTON).should('have.text', 'Columns32/35'); + + // change pagination + cy.getBySel('pagination-button-next').click(); + cy.getBySel('globalLoadingIndicator').should('not.exist'); + cy.getBySel('pagination-button-next').click(); + cy.getBySel(RESULTS_TABLE_COLUMNS_BUTTON).should('have.text', 'Columns32/35'); + + // enter fullscreen + cy.getBySel(RESULTS_TABLE_BUTTON).trigger('mouseover'); + cy.contains(/Enter fullscreen$/).should('not.exist'); + cy.contains('Exit fullscreen').should('exist'); + cy.getBySel(RESULTS_TABLE_BUTTON).click(); + + // sorting + cy.getBySel('dataGridHeaderCellActionButton-osquery.egid').click({ force: true }); + cy.contains(/Sort A-Z$/).click(); + cy.getBySel(RESULTS_TABLE_COLUMNS_BUTTON).should('have.text', 'Columns32/35'); + cy.getBySel(RESULTS_TABLE_BUTTON).trigger('mouseover'); + cy.contains(/Enter fullscreen$/).should('exist'); + + // visit Status results + cy.getBySel('osquery-status-tab').click(); + cy.get('tbody > tr.euiTableRow').should('have.lengthOf', 2); + + // save new query + cy.contains('Exit full screen').should('not.exist'); + cy.contains('Save for later').click(); + cy.contains('Save query'); + cy.get('input[name="id"]').type(`${savedQueryId}{downArrow}{enter}`); + cy.get('input[name="description"]').type(`${savedQueryDescription}{downArrow}{enter}`); + cy.getBySel('savedQueryFlyoutSaveButton').click(); + cy.contains('Successfully saved'); + closeToastIfVisible(); + + // play saved query + navigateTo('/app/osquery/saved_queries'); + cy.contains(savedQueryId); + cy.get(`[aria-label="Run ${savedQueryId}"]`).click(); + selectAllAgents(); + verifyQueryTimeout(timeout); + submitQuery(); + + // edit saved query + cy.contains('Saved queries').click(); + cy.contains(savedQueryId); + + cy.get(`[aria-label="Edit ${savedQueryId}"]`).click(); + cy.get('input[name="description"]').type(` Edited{downArrow}{enter}`); + + // Run in test configuration + cy.contains('Test configuration').click(); + selectAllAgents(); + verifyQueryTimeout(timeout); + submitQuery(); + checkResults(); + + // Disabled submit button in test configuration + cy.contains('Submit').should('not.be.disabled'); + cy.getBySel('osquery-save-query-flyout').within(() => { + cy.contains('Query is a required field').should('not.exist'); + // this clears the input + inputQuery('{selectall}{backspace}{selectall}{backspace}'); + cy.contains('Query is a required field'); + inputQuery(BIG_QUERY); + cy.contains('Query is a required field').should('not.exist'); + }); + + // Save edited + cy.getBySel('euiFlyoutCloseButton').click(); + cy.getBySel('update-query-button').click(); + cy.contains(`${savedQueryDescription} Edited`); + + // delete saved query + cy.contains(savedQueryId); + cy.get(`[aria-label="Edit ${savedQueryId}"]`).click(); + + deleteAndConfirm('query'); + cy.contains(savedQueryId).should('exist'); + cy.contains(savedQueryId).should('not.exist'); + } + ); - it.skip('checks that user cant add a saved query with an ID that already exists', () => { + it('checks that user cant add a saved query with an ID that already exists', () => { cy.contains('Saved queries').click(); cy.contains('Add saved query').click(); cy.get('input[name="id"]').type(`users_elastic{downArrow}{enter}`); diff --git a/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts b/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts deleted file mode 100644 index ee752ace3c1de..0000000000000 --- a/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts +++ /dev/null @@ -1,146 +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 { getAdvancedButton } from '../screens/integrations'; -import { RESULTS_TABLE_BUTTON, RESULTS_TABLE_COLUMNS_BUTTON } from '../screens/live_query'; -import { closeToastIfVisible, generateRandomStringName } from './integrations'; -import { - checkResults, - BIG_QUERY, - deleteAndConfirm, - inputQuery, - selectAllAgents, - submitQuery, - fillInQueryTimeout, - verifyQueryTimeout, -} from './live_query'; -import { navigateTo } from './navigation'; - -export const getSavedQueriesComplexTest = () => - describe('Saved queries Complex Test', () => { - const timeout = '601'; - const suffix = generateRandomStringName(1)[0]; - const savedQueryId = `Saved-Query-Id-${suffix}`; - const savedQueryDescription = `Test saved query description ${suffix}`; - - it( - 'should create a new query and verify: \n ' + - '- hidden columns, full screen and sorting \n' + - '- pagination \n' + - '- query can viewed (status), edited and deleted ', - () => { - cy.contains('New live query').click(); - selectAllAgents(); - inputQuery(BIG_QUERY); - getAdvancedButton().click(); - fillInQueryTimeout(timeout); - submitQuery(); - checkResults(); - // enter fullscreen - cy.getBySel(RESULTS_TABLE_BUTTON).trigger('mouseover'); - cy.contains(/Enter fullscreen$/).should('exist'); - cy.contains('Exit fullscreen').should('not.exist'); - cy.getBySel(RESULTS_TABLE_BUTTON).click(); - - cy.getBySel(RESULTS_TABLE_BUTTON).trigger('mouseover'); - cy.contains(/Enter Fullscreen$/).should('not.exist'); - cy.contains('Exit fullscreen').should('exist'); - - // hidden columns - cy.getBySel(RESULTS_TABLE_COLUMNS_BUTTON).should('have.text', 'Columns35'); - cy.getBySel('dataGridColumnSelectorButton').click(); - cy.get('[data-popover-open="true"]').should('be.visible'); - cy.getBySel('dataGridColumnSelectorToggleColumnVisibility-osquery.cmdline').click(); - cy.getBySel('dataGridColumnSelectorToggleColumnVisibility-osquery.cwd').click(); - cy.getBySel( - 'dataGridColumnSelectorToggleColumnVisibility-osquery.disk_bytes_written.number' - ).click(); - cy.getBySel('dataGridColumnSelectorButton').click(); - cy.get('[data-popover-open="true"]').should('not.exist'); - cy.getBySel(RESULTS_TABLE_COLUMNS_BUTTON).should('have.text', 'Columns32/35'); - - // change pagination - cy.getBySel('pagination-button-next').click(); - cy.getBySel('globalLoadingIndicator').should('not.exist'); - cy.getBySel('pagination-button-next').click(); - cy.getBySel(RESULTS_TABLE_COLUMNS_BUTTON).should('have.text', 'Columns32/35'); - - // enter fullscreen - cy.getBySel(RESULTS_TABLE_BUTTON).trigger('mouseover'); - cy.contains(/Enter fullscreen$/).should('not.exist'); - cy.contains('Exit fullscreen').should('exist'); - cy.getBySel(RESULTS_TABLE_BUTTON).click(); - - // sorting - cy.getBySel('dataGridHeaderCellActionButton-osquery.egid').click({ force: true }); - cy.contains(/Sort A-Z$/).click(); - cy.getBySel(RESULTS_TABLE_COLUMNS_BUTTON).should('have.text', 'Columns32/35'); - cy.getBySel(RESULTS_TABLE_BUTTON).trigger('mouseover'); - cy.contains(/Enter fullscreen$/).should('exist'); - - // visit Status results - cy.getBySel('osquery-status-tab').click(); - cy.get('tbody > tr.euiTableRow').should('have.lengthOf', 2); - - // save new query - cy.contains('Exit full screen').should('not.exist'); - cy.contains('Save for later').click(); - cy.contains('Save query'); - cy.get('input[name="id"]').type(`${savedQueryId}{downArrow}{enter}`); - cy.get('input[name="description"]').type(`${savedQueryDescription}{downArrow}{enter}`); - cy.getBySel('savedQueryFlyoutSaveButton').click(); - cy.contains('Successfully saved'); - closeToastIfVisible(); - - // play saved query - navigateTo('/app/osquery/saved_queries'); - cy.contains(savedQueryId); - cy.get(`[aria-label="Run ${savedQueryId}"]`).click(); - selectAllAgents(); - verifyQueryTimeout(timeout); - submitQuery(); - - // edit saved query - cy.contains('Saved queries').click(); - cy.contains(savedQueryId); - - cy.get(`[aria-label="Edit ${savedQueryId}"]`).click(); - cy.get('input[name="description"]').type(` Edited{downArrow}{enter}`); - - // Run in test configuration - cy.contains('Test configuration').click(); - selectAllAgents(); - verifyQueryTimeout(timeout); - submitQuery(); - checkResults(); - - // Disabled submit button in test configuration - cy.contains('Submit').should('not.be.disabled'); - cy.getBySel('osquery-save-query-flyout').within(() => { - cy.contains('Query is a required field').should('not.exist'); - // this clears the input - inputQuery('{selectall}{backspace}{selectall}{backspace}'); - cy.contains('Query is a required field'); - inputQuery(BIG_QUERY); - cy.contains('Query is a required field').should('not.exist'); - }); - - // Save edited - cy.getBySel('euiFlyoutCloseButton').click(); - cy.getBySel('update-query-button').click(); - cy.contains(`${savedQueryDescription} Edited`); - - // delete saved query - cy.contains(savedQueryId); - cy.get(`[aria-label="Edit ${savedQueryId}"]`).click(); - - deleteAndConfirm('query'); - cy.contains(savedQueryId).should('exist'); - cy.contains(savedQueryId).should('not.exist'); - } - ); - }); From 9cc4c41d05df5c37382b71b6d68fc1ef64ebfd3d Mon Sep 17 00:00:00 2001 From: Tim Sullivan <tsullivan@users.noreply.github.com> Date: Tue, 2 Jul 2024 08:33:12 -0700 Subject: [PATCH 090/128] =?UTF-8?q?[Lists=20Plugin]=20Migrate=20authc.getC?= =?UTF-8?q?urrentUser=20usage=20to=20coreContext.secu=E2=80=A6=20(#187179)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of https://github.com/elastic/kibana/issues/186574 ## Summary This PR migrates the Lists Plugin's `ListsRequestHandlerContext`, which consumes `authc.getCurrentUser`, to use `coreStart.security`. Background: This PR serves as an example of a plugin migrating away from depending on the Security plugin, which is a high priority effort for the last release before 9.0. ### Checklist Delete any items that are not applicable to this PR. - [X] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/lists/server/get_user.test.ts | 28 ++++++++------------ x-pack/plugins/lists/server/get_user.ts | 10 +++---- x-pack/plugins/lists/server/plugin.ts | 8 +++--- x-pack/plugins/lists/tsconfig.json | 3 ++- 4 files changed, 20 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/lists/server/get_user.test.ts b/x-pack/plugins/lists/server/get_user.test.ts index 429e0a71dc936..f44718b4ab1f3 100644 --- a/x-pack/plugins/lists/server/get_user.test.ts +++ b/x-pack/plugins/lists/server/get_user.test.ts @@ -5,17 +5,17 @@ * 2.0. */ -import { httpServerMock } from '@kbn/core/server/mocks'; -import { CoreKibanaRequest } from '@kbn/core/server'; -import { securityMock } from '@kbn/security-plugin/server/mocks'; +import { securityServiceMock } from '@kbn/core/server/mocks'; +import { SecurityRequestHandlerContext } from '@kbn/core-security-server'; import { getUser } from './get_user'; describe('get_user', () => { - let request = CoreKibanaRequest.from(httpServerMock.createRawRequest({})); + let security: SecurityRequestHandlerContext; + beforeEach(() => { jest.clearAllMocks(); - request = CoreKibanaRequest.from(httpServerMock.createRawRequest({})); + security = securityServiceMock.createRequestHandlerContext(); }); afterEach(() => { @@ -23,44 +23,38 @@ describe('get_user', () => { }); test('it returns "bob" as the user given a security request with "bob"', () => { - const security = securityMock.createStart(); security.authc.getCurrentUser = jest.fn().mockReturnValue({ username: 'bob' }); - const user = getUser({ request, security }); + const user = getUser({ security }); expect(user).toEqual('bob'); }); test('it returns "alice" as the user given a security request with "alice"', () => { - const security = securityMock.createStart(); security.authc.getCurrentUser = jest.fn().mockReturnValue({ username: 'alice' }); - const user = getUser({ request, security }); + const user = getUser({ security }); expect(user).toEqual('alice'); }); test('it returns "elastic" as the user given null as the current user', () => { - const security = securityMock.createStart(); security.authc.getCurrentUser = jest.fn().mockReturnValue(null); - const user = getUser({ request, security }); + const user = getUser({ security }); expect(user).toEqual('elastic'); }); test('it returns "elastic" as the user given undefined as the current user', () => { - const security = securityMock.createStart(); security.authc.getCurrentUser = jest.fn().mockReturnValue(undefined); - const user = getUser({ request, security }); + const user = getUser({ security }); expect(user).toEqual('elastic'); }); test('it returns "elastic" as the user given undefined as the plugin', () => { - const security = securityMock.createStart(); security.authc.getCurrentUser = jest.fn().mockReturnValue(undefined); - const user = getUser({ request, security: undefined }); + const user = getUser({ security }); expect(user).toEqual('elastic'); }); test('it returns "elastic" as the user given null as the plugin', () => { - const security = securityMock.createStart(); security.authc.getCurrentUser = jest.fn().mockReturnValue(undefined); - const user = getUser({ request, security: null }); + const user = getUser({ security }); expect(user).toEqual('elastic'); }); }); diff --git a/x-pack/plugins/lists/server/get_user.ts b/x-pack/plugins/lists/server/get_user.ts index a3adb05ae5ef6..6ea64ce6ff731 100644 --- a/x-pack/plugins/lists/server/get_user.ts +++ b/x-pack/plugins/lists/server/get_user.ts @@ -5,17 +5,15 @@ * 2.0. */ -import { KibanaRequest } from '@kbn/core/server'; -import { SecurityPluginStart } from '@kbn/security-plugin/server'; +import { SecurityRequestHandlerContext } from '@kbn/core-security-server'; export interface GetUserOptions { - security: SecurityPluginStart | null | undefined; - request: KibanaRequest; + security: SecurityRequestHandlerContext; } -export const getUser = ({ security, request }: GetUserOptions): string => { +export const getUser = ({ security }: GetUserOptions): string => { if (security != null) { - const authenticatedUser = security.authc.getCurrentUser(request); + const authenticatedUser = security.authc.getCurrentUser(); if (authenticatedUser != null) { return authenticatedUser.username; } else { diff --git a/x-pack/plugins/lists/server/plugin.ts b/x-pack/plugins/lists/server/plugin.ts index 688469ea063bd..5878eb45adfa5 100644 --- a/x-pack/plugins/lists/server/plugin.ts +++ b/x-pack/plugins/lists/server/plugin.ts @@ -12,7 +12,6 @@ import type { Plugin, PluginInitializerContext, } from '@kbn/core/server'; -import type { SecurityPluginStart } from '@kbn/security-plugin/server'; import type { SpacesServiceStart } from '@kbn/spaces-plugin/server'; import { ConfigType } from './config'; @@ -41,7 +40,6 @@ export class ListPlugin implements Plugin<ListPluginSetup, ListsPluginStart, {}, private readonly config: ConfigType; private readonly extensionPoints: ExtensionPointStorageInterface; private spaces: SpacesServiceStart | undefined | null; - private security: SecurityPluginStart | undefined | null; constructor(private readonly initializerContext: PluginInitializerContext) { this.logger = this.initializerContext.logger.get(); @@ -90,7 +88,6 @@ export class ListPlugin implements Plugin<ListPluginSetup, ListsPluginStart, {}, public start(core: CoreStart, plugins: PluginsStart): ListsPluginStart { this.logger.debug('Starting plugin'); - this.security = plugins.security; this.spaces = plugins.spaces?.spacesService; } @@ -101,8 +98,9 @@ export class ListPlugin implements Plugin<ListPluginSetup, ListsPluginStart, {}, private createRouteHandlerContext = (): ContextProvider => { return async (context, request): ContextProviderReturn => { - const { spaces, config, security, extensionPoints } = this; + const { spaces, config, extensionPoints } = this; const { + security, savedObjects: { client: savedObjectsClient }, elasticsearch: { client: { asCurrentUser: esClient }, @@ -112,7 +110,7 @@ export class ListPlugin implements Plugin<ListPluginSetup, ListsPluginStart, {}, throw new TypeError('Configuration is required for this plugin to operate'); } else { const spaceId = getSpaceId({ request, spaces }); - const user = getUser({ request, security }); + const user = getUser({ security }); return { getExceptionListClient: (): ExceptionListClient => new ExceptionListClient({ diff --git a/x-pack/plugins/lists/tsconfig.json b/x-pack/plugins/lists/tsconfig.json index 47dc15c00ec8b..8371f9de72c8c 100644 --- a/x-pack/plugins/lists/tsconfig.json +++ b/x-pack/plugins/lists/tsconfig.json @@ -39,7 +39,8 @@ "@kbn/utility-types", "@kbn/core-elasticsearch-client-server-mocks", "@kbn/core-saved-objects-server", - "@kbn/zod-helpers" + "@kbn/zod-helpers", + "@kbn/core-security-server" ], "exclude": ["target/**/*"] } From dc12ac8e9a87ed321b899da6c855077e77a0c9df Mon Sep 17 00:00:00 2001 From: Tim Sullivan <tsullivan@users.noreply.github.com> Date: Tue, 2 Jul 2024 08:33:32 -0700 Subject: [PATCH 091/128] [Actions Plugin] Use server-side authc.getCurrentUser from core.security (#186924) Part of https://github.com/elastic/kibana/issues/186574 Background: This PR serves as an example of a plugin migrating away from depending on the Security plugin, which is a high priority effort for the last release before 9.0. The Actions plugin uses the `authc.getCurrentUser` method to attribute the current user to persisted actions that are created in the system. ### Checklist Delete any items that are not applicable to this PR. - [X] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../authorization/actions_authorization.test.ts | 11 ++--------- .../server/authorization/actions_authorization.ts | 2 -- .../actions/server/lib/action_executor.test.ts | 10 +++++++--- x-pack/plugins/actions/server/lib/action_executor.ts | 5 +++-- .../actions/server/lib/task_runner_factory.test.ts | 2 ++ x-pack/plugins/actions/server/plugin.ts | 3 +-- 6 files changed, 15 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/actions/server/authorization/actions_authorization.test.ts b/x-pack/plugins/actions/server/authorization/actions_authorization.test.ts index 06a74c0f011fa..41eab4fbc2e43 100644 --- a/x-pack/plugins/actions/server/authorization/actions_authorization.test.ts +++ b/x-pack/plugins/actions/server/authorization/actions_authorization.test.ts @@ -12,7 +12,6 @@ import { ACTION_SAVED_OBJECT_TYPE, ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, } from '../constants/saved_objects'; -import { AuthenticatedUser } from '@kbn/security-plugin/server'; import { AuthorizationMode } from './get_authorization_mode_by_source'; import { CONNECTORS_ADVANCED_EXECUTE_PRIVILEGE_API_TAG, @@ -29,7 +28,6 @@ const ADVANCED_EXECUTE_AUTHZ = `api:${CONNECTORS_ADVANCED_EXECUTE_PRIVILEGE_API_ function mockSecurity() { const security = securityMock.createSetup(); const authorization = security.authz; - const authentication = security.authc; // typescript is having trouble inferring jest's automocking ( authorization.actions.savedObject.get as jest.MockedFunction< @@ -37,7 +35,7 @@ function mockSecurity() { > ).mockImplementation(mockAuthorizationAction); authorization.mode.useRbacForRequest.mockReturnValue(true); - return { authorization, authentication }; + return { authorization }; } beforeEach(() => { @@ -167,7 +165,7 @@ describe('ensureAuthorized', () => { }); test('exempts users from requiring privileges to execute actions when authorizationMode is Legacy', async () => { - const { authorization, authentication } = mockSecurity(); + const { authorization } = mockSecurity(); const checkPrivileges: jest.MockedFunction< ReturnType<typeof authorization.checkPrivilegesDynamicallyWithRequest> > = jest.fn(); @@ -175,14 +173,9 @@ describe('ensureAuthorized', () => { const actionsAuthorization = new ActionsAuthorization({ request, authorization, - authentication, authorizationMode: AuthorizationMode.Legacy, }); - authentication.getCurrentUser.mockReturnValueOnce({ - username: 'some-user', - } as unknown as AuthenticatedUser); - await actionsAuthorization.ensureAuthorized({ operation: 'execute', actionTypeId: 'myType' }); expect(authorization.actions.savedObject.get).not.toHaveBeenCalled(); diff --git a/x-pack/plugins/actions/server/authorization/actions_authorization.ts b/x-pack/plugins/actions/server/authorization/actions_authorization.ts index fd4477b051cf8..5739af64050ee 100644 --- a/x-pack/plugins/actions/server/authorization/actions_authorization.ts +++ b/x-pack/plugins/actions/server/authorization/actions_authorization.ts @@ -18,7 +18,6 @@ import { AuthorizationMode } from './get_authorization_mode_by_source'; export interface ConstructorOptions { request: KibanaRequest; authorization?: SecurityPluginSetup['authz']; - authentication?: SecurityPluginSetup['authc']; // In order to support legacy Alerts which predate the introduction of the // Actions feature in Kibana we need a way of "dialing down" the level of // authorization for certain opearations. @@ -49,7 +48,6 @@ export class ActionsAuthorization { constructor({ request, authorization, - authentication, authorizationMode = AuthorizationMode.RBAC, }: ConstructorOptions) { this.request = request; diff --git a/x-pack/plugins/actions/server/lib/action_executor.test.ts b/x-pack/plugins/actions/server/lib/action_executor.test.ts index dfb0ccda5c45a..bfcedc821368f 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.test.ts @@ -10,7 +10,12 @@ import { schema } from '@kbn/config-schema'; import { ActionExecutor } from './action_executor'; import { actionTypeRegistryMock } from '../action_type_registry.mock'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; -import { httpServerMock, loggingSystemMock, analyticsServiceMock } from '@kbn/core/server/mocks'; +import { + httpServerMock, + loggingSystemMock, + analyticsServiceMock, + securityServiceMock, +} from '@kbn/core/server/mocks'; import { eventLoggerMock } from '@kbn/event-log-plugin/server/mocks'; import { spacesServiceMock } from '@kbn/spaces-plugin/server/spaces_service/spaces_service.mock'; import { ActionType as ConnectorType } from '../types'; @@ -20,7 +25,6 @@ import { asHttpRequestExecutionSource, asSavedObjectExecutionSource, } from './action_execution_source'; -import { securityMock } from '@kbn/security-plugin/server/mocks'; import { finished } from 'stream/promises'; import { PassThrough } from 'stream'; import { SecurityConnectorFeatureId } from '../../common'; @@ -58,7 +62,7 @@ const executeParams = { const spacesMock = spacesServiceMock.createStartContract(); const loggerMock: ReturnType<typeof loggingSystemMock.createLogger> = loggingSystemMock.createLogger(); -const securityMockStart = securityMock.createStart(); +const securityMockStart = securityServiceMock.createStart(); const authorizationMock = actionsAuthorizationMock.create(); const getActionsAuthorizationWithRequest = jest.fn(); diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index 5ca37f6ee871c..f8f8f32547c10 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -7,6 +7,8 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { + type AuthenticatedUser, + type SecurityServiceStart, AnalyticsServiceStart, KibanaRequest, Logger, @@ -18,7 +20,6 @@ import { withSpan } from '@kbn/apm-utils'; import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; import { SpacesServiceStart } from '@kbn/spaces-plugin/server'; import { IEventLogger, SAVED_OBJECT_REL_PRIMARY } from '@kbn/event-log-plugin/server'; -import { AuthenticatedUser, SecurityPluginStart } from '@kbn/security-plugin/server'; import { createTaskRunError, TaskErrorSource } from '@kbn/task-manager-plugin/server'; import { getErrorSource } from '@kbn/task-manager-plugin/server/task_running'; import { GEN_AI_TOKEN_COUNT_EVENT } from './event_based_telemetry'; @@ -59,7 +60,7 @@ const Millis2Nanos = 1000 * 1000; export interface ActionExecutorContext { logger: Logger; spaces?: SpacesServiceStart; - security?: SecurityPluginStart; + security: SecurityServiceStart; getServices: GetServicesFunction; getUnsecuredServices: GetUnsecuredServicesFunction; encryptedSavedObjectsClient: EncryptedSavedObjectsClient; diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts index c5f10f728065e..3dd86bdcf148d 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts @@ -18,6 +18,7 @@ import { httpServiceMock, savedObjectsRepositoryMock, analyticsServiceMock, + securityServiceMock, } from '@kbn/core/server/mocks'; import { eventLoggerMock } from '@kbn/event-log-plugin/server/mocks'; import { ActionTypeDisabledError } from './errors'; @@ -98,6 +99,7 @@ const actionExecutorInitializerParams = { eventLogger, inMemoryConnectors: [], analyticsService: analyticsServiceMock.createAnalyticsServiceStart(), + security: securityServiceMock.createStart(), }; const taskRunnerFactoryInitializerParams = { diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 111e0509f81ac..3112124850bfb 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -553,7 +553,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon logger, eventLogger: this.eventLogger!, spaces: plugins.spaces?.spacesService, - security: plugins.security, + security: core.security, getServices: this.getServicesFactory( getScopedSavedObjectsClientWithoutAccessToActions, core.elasticsearch, @@ -647,7 +647,6 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon request, authorizationMode, authorization: this.security?.authz, - authentication: this.security?.authc, }); }; From 2dbd7fa67dbc60fba85926e2798f403602e65861 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez <melissa.alvarez@elastic.co> Date: Tue, 2 Jul 2024 09:35:23 -0600 Subject: [PATCH 092/128] [ML] Single Metric Viewer embeddable: ensure chart height is responsive on resize (#185907) ## Summary This PR ensures the SMV chart in dashboards is responsive. This PR takes the height on panel change and calculates the percentage height of each part of the SMV chart - these heights are then passed to the chart element. <img width="1332" alt="image" src="https://github.com/elastic/kibana/assets/6446462/fadcc0f5-420a-4417-872e-bec3fb9f14b6"> ## Known issues: While the svg of the chart as a whole grows as expected with increased height, the focus chart doesn't resize correctly until a full page refresh. There probably needs to be a comparison of previous/current height to force a full chart rerender - digging more into that. Same thing happens when resizing back down likely due to the same issue. ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../timeseries_chart/timeseries_chart.js | 71 +++++++++++++++---- .../timeseries_chart_with_tooltip.tsx | 6 +- .../timeseriesexplorer_embeddable_chart.js | 3 + .../single_metric_viewer.tsx | 17 +++-- 4 files changed, 78 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js index 27d158dfb130f..5be55fead11e6 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js @@ -57,6 +57,9 @@ import { MlAnnotationUpdatesContext } from '../../../contexts/ml/ml_annotation_u import { LinksMenuUI } from '../../../components/anomalies_table/links_menu'; import { RuleEditorFlyout } from '../../../components/rule_editor'; +const percentFocusChartHeight = 0.634; +const minSvgHeight = 350; + const focusZoomPanelHeight = 25; const focusChartHeight = 310; const focusHeight = focusZoomPanelHeight + focusChartHeight; @@ -90,10 +93,27 @@ const anomalyGrayScale = d3.scale .domain([3, 25, 50, 75, 100]) .range(['#dce7ed', '#b0c5d6', '#b1a34e', '#b17f4e', '#c88686']); -function getSvgHeight(showAnnotations) { +function getChartHeights(height) { + const actualHeight = height < minSvgHeight ? minSvgHeight : height; + const focusChartHeight = Math.round(actualHeight * percentFocusChartHeight); + + const heights = { + focusChartHeight, + focusHeight: focusZoomPanelHeight + focusChartHeight, + }; + return heights; +} + +function getSvgHeight(showAnnotations, incomingHeight) { const adjustedAnnotationHeight = showAnnotations ? annotationHeight : 0; + const incomingHeightActual = + incomingHeight && incomingHeight < minSvgHeight ? minSvgHeight : incomingHeight; + const { focusHeight: focusHeightIncoming } = incomingHeight + ? getChartHeights(incomingHeightActual) + : {}; + return ( - focusHeight + + (focusHeightIncoming ?? focusHeight) + contextChartHeight + swimlaneHeight + adjustedAnnotationHeight + @@ -168,13 +188,18 @@ class TimeseriesChartIntl extends Component { this.context.services.uiSettings ).getTimeBuckets; - const { svgWidth } = this.props; + const { svgWidth, svgHeight } = this.props; + const { focusHeight: focusHeightIncoming, focusChartHeight: focusChartIncoming } = svgHeight + ? getChartHeights(svgHeight) + : {}; this.vizWidth = svgWidth - margin.left - margin.right; const vizWidth = this.vizWidth; this.focusXScale = d3.time.scale().range([0, vizWidth]); - this.focusYScale = d3.scale.linear().range([focusHeight, focusZoomPanelHeight]); + this.focusYScale = d3.scale + .linear() + .range([focusHeightIncoming ?? focusHeight, focusZoomPanelHeight]); const focusXScale = this.focusXScale; const focusYScale = this.focusYScale; @@ -182,7 +207,7 @@ class TimeseriesChartIntl extends Component { .axis() .scale(focusXScale) .orient('bottom') - .innerTickSize(-focusChartHeight) + .innerTickSize(-(focusChartIncoming ?? focusChartHeight)) .outerTickSize(0) .tickPadding(10); this.focusYAxis = d3.svg @@ -292,6 +317,7 @@ class TimeseriesChartIntl extends Component { modelPlotEnabled, selectedJob, svgWidth, + svgHeight: incomingSvgHeight, showAnnotations, } = this.props; @@ -301,7 +327,10 @@ class TimeseriesChartIntl extends Component { const focusYAxis = this.focusYAxis; const focusYScale = this.focusYScale; - const svgHeight = getSvgHeight(showAnnotations); + const svgHeight = getSvgHeight(showAnnotations, incomingSvgHeight); + const { focusHeight: focusHeightIncoming } = incomingSvgHeight + ? getChartHeights(incomingSvgHeight) + : {}; // Clear any existing elements from the visualization, // then build the svg elements for the bubble chart. @@ -391,6 +420,10 @@ class TimeseriesChartIntl extends Component { focusXScale.range([0, this.vizWidth]); focusYAxis.innerTickSize(-this.vizWidth); + if (focusHeightIncoming !== undefined) { + focusYScale.range([focusHeightIncoming, focusZoomPanelHeight]); + } + const focus = svg .append('g') .attr('class', 'focus-chart') @@ -401,7 +434,11 @@ class TimeseriesChartIntl extends Component { .attr('class', 'context-chart') .attr( 'transform', - 'translate(' + margin.left + ',' + (focusHeight + margin.top + chartSpacing) + ')' + 'translate(' + + margin.left + + ',' + + ((focusHeightIncoming ?? focusHeight) + margin.top + chartSpacing) + + ')' ); // Mask to hide annotations overflow @@ -412,11 +449,11 @@ class TimeseriesChartIntl extends Component { .attr('x', 0) .attr('y', 0) .attr('width', this.vizWidth) - .attr('height', focusHeight) + .attr('height', focusHeightIncoming ?? focusHeight) .style('fill', 'white'); // Draw each of the component elements. - createFocusChart(focus, this.vizWidth, focusHeight); + createFocusChart(focus, this.vizWidth, focusHeightIncoming ?? focusHeight); drawContextElements( context, this.vizWidth, @@ -491,6 +528,9 @@ class TimeseriesChartIntl extends Component { // as we want to re-render the paths and points when the zoom area changes. const { contextForecastData } = this.props; + const { focusChartHeight: focusChartIncoming } = this.props.svgHeight + ? getChartHeights(this.props.svgHeight) + : {}; // Add a group at the top to display info on the chart aggregation interval // and links to set the brush span to 1h, 1d, 1w etc. @@ -524,7 +564,7 @@ class TimeseriesChartIntl extends Component { .attr('x', brushX) .attr('y', focusZoomPanelHeight) .attr('width', brushWidth) - .attr('height', focusChartHeight); + .attr('height', focusChartIncoming ?? focusChartHeight); fcsGroup.append('g').classed('mlAnnotations', true); @@ -534,7 +574,7 @@ class TimeseriesChartIntl extends Component { .attr('x', 0) .attr('y', focusZoomPanelHeight) .attr('width', fcsWidth) - .attr('height', focusChartHeight) + .attr('height', focusChartIncoming ?? focusChartHeight) .attr('class', 'chart-border'); // Add background for x axis. @@ -767,11 +807,16 @@ class TimeseriesChartIntl extends Component { .classed('hidden', !showModelBounds); } + const { focusChartHeight: focusChartIncoming, focusHeight: focusHeightIncoming } = this.props + .svgHeight + ? getChartHeights(this.props.svgHeight) + : {}; + renderAnnotations( focusChart, focusAnnotationData, focusZoomPanelHeight, - focusChartHeight, + focusChartIncoming ?? focusChartHeight, this.focusXScale, showAnnotations, showFocusChartTooltip, @@ -904,7 +949,7 @@ class TimeseriesChartIntl extends Component { .attr('x', (d) => this.focusXScale(d.date) - LINE_CHART_ANOMALY_RADIUS) .attr('y', (d) => { const focusYValue = this.focusYScale(d.value); - return isNaN(focusYValue) ? -focusHeight - 3 : focusYValue - 3; + return isNaN(focusYValue) ? -(focusHeightIncoming ?? focusHeight) - 3 : focusYValue - 3; }); // Plot any forecast data in scope. diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_with_tooltip.tsx b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_with_tooltip.tsx index 1c9bbc0f9afe3..3a5fe63f7a81a 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_with_tooltip.tsx +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_with_tooltip.tsx @@ -20,7 +20,6 @@ import { useTimeBucketsService } from '../../../util/time_buckets_service'; import { getControlsForDetector } from '../../get_controls_for_detector'; import { MlAnnotationUpdatesContext } from '../../../contexts/ml/ml_annotation_updates_context'; import type { SourceIndicesWithGeoFields } from '../../../explorer/explorer_utils'; - interface TimeSeriesChartWithTooltipsProps { bounds: any; detectorIndex: number; @@ -137,6 +136,11 @@ export const TimeSeriesChartWithTooltips: FC<TimeSeriesChartWithTooltipsProps> = contextAggregationInterval, ]); + if (chartProps.svgHeight) { + // 32 accounts for the height of the chart title + chartProps.svgHeight -= 32; + } + return ( <div className="ml-timeseries-chart" data-test-subj="mlSingleMetricViewerChart"> <MlTooltipComponent> diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_embeddable_chart/timeseriesexplorer_embeddable_chart.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_embeddable_chart/timeseriesexplorer_embeddable_chart.js index 4c09d4fe4adb8..06ae1979c2557 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_embeddable_chart/timeseriesexplorer_embeddable_chart.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_embeddable_chart/timeseriesexplorer_embeddable_chart.js @@ -78,6 +78,7 @@ export class TimeSeriesExplorerEmbeddableChart extends React.Component { autoZoomDuration: PropTypes.number.isRequired, bounds: PropTypes.object.isRequired, chartWidth: PropTypes.number.isRequired, + chartHeight: PropTypes.number, lastRefresh: PropTypes.number.isRequired, onRenderComplete: PropTypes.func, previousRefresh: PropTypes.number.isRequired, @@ -752,6 +753,7 @@ export class TimeSeriesExplorerEmbeddableChart extends React.Component { autoZoomDuration, bounds, chartWidth, + chartHeight, lastRefresh, selectedDetectorIndex, selectedJob, @@ -798,6 +800,7 @@ export class TimeSeriesExplorerEmbeddableChart extends React.Component { focusForecastData, focusAggregationInterval, svgWidth: chartWidth, + svgHeight: chartHeight, zoomFrom, zoomTo, zoomFromFocusLoaded, diff --git a/x-pack/plugins/ml/public/shared_components/single_metric_viewer/single_metric_viewer.tsx b/x-pack/plugins/ml/public/shared_components/single_metric_viewer/single_metric_viewer.tsx index a4c6681b45ef0..25cd21855912c 100644 --- a/x-pack/plugins/ml/public/shared_components/single_metric_viewer/single_metric_viewer.tsx +++ b/x-pack/plugins/ml/public/shared_components/single_metric_viewer/single_metric_viewer.tsx @@ -84,7 +84,10 @@ const SingleMetricViewerWrapper: FC<SingleMetricViewerPropsWithDeps> = ({ selectedJobId, uuid, }) => { - const [chartWidth, setChartWidth] = useState<number>(0); + const [chartDimensions, setChartDimensions] = useState<{ width: number; height: number }>({ + width: 0, + height: 0, + }); const [zoom, setZoom] = useState<Zoom>(); const [selectedForecastId, setSelectedForecastId] = useState<ForecastId>(); const [selectedJob, setSelectedJob] = useState<MlJob | undefined>(); @@ -150,11 +153,14 @@ const SingleMetricViewerWrapper: FC<SingleMetricViewerPropsWithDeps> = ({ // eslint-disable-next-line react-hooks/exhaustive-deps const resizeHandler = useCallback( throttle((e: { width: number; height: number }) => { - if (Math.abs(chartWidth - e.width) > minElemAndChartDiff) { - setChartWidth(e.width); + if ( + Math.abs(chartDimensions.width - e.width) > minElemAndChartDiff || + Math.abs(chartDimensions.height - e.height) > minElemAndChartDiff + ) { + setChartDimensions(e); } }, RESIZE_THROTTLE_TIME_MS), - [chartWidth] + [chartDimensions.width, chartDimensions.height] ); const autoZoomDuration = useMemo(() => { @@ -220,7 +226,8 @@ const SingleMetricViewerWrapper: FC<SingleMetricViewerPropsWithDeps> = ({ jobsLoaded && selectedJobId === selectedJob?.job_id && ( <TimeSeriesExplorerEmbeddableChart - chartWidth={chartWidth - containerPadding} + chartWidth={chartDimensions.width - containerPadding} + chartHeight={chartDimensions.height - containerPadding} dataViewsService={pluginStart.data.dataViews} toastNotificationService={toastNotificationService} appStateHandler={appStateHandler} From 47a92f45fec99938e860dfc6f58f17402dd65406 Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud <ievgen.sorokopud@elastic.co> Date: Tue, 2 Jul 2024 17:35:50 +0200 Subject: [PATCH 093/128] [Security Solution][Detections] Update copies for Custom Highlighted Fields bulk action (#187307) ## Summary With these changes we update the copies of the "Custom Highlighted Fields" bulk action. <img width="1775" alt="Screenshot 2024-07-01 at 22 28 43" src="https://github.com/elastic/kibana/assets/2700761/2effc5d6-4876-48e3-805f-69507d43dce2"> <img width="1775" alt="Screenshot 2024-07-01 at 22 28 36" src="https://github.com/elastic/kibana/assets/2700761/569c82fd-5a6f-43d2-9ce7-5b4ce56c879e"> <img width="1775" alt="Screenshot 2024-07-01 at 22 28 21" src="https://github.com/elastic/kibana/assets/2700761/bd5954ac-abba-42cb-9996-2688782d88c9"> --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../bulk_actions/forms/investigation_fields_form.tsx | 3 +-- .../detections/pages/detection_engine/rules/translations.ts | 6 +++--- .../rule_actions/bulk_actions/bulk_edit_rules.cy.ts | 2 +- .../cypress/tasks/rules_bulk_actions.ts | 4 ++-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/investigation_fields_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/investigation_fields_form.tsx index 449664e20222a..618def872a54c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/investigation_fields_form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/investigation_fields_form.tsx @@ -164,8 +164,7 @@ const InvestigationFieldsFormComponent = ({ > <FormattedMessage id="xpack.securitySolution.detectionEngine.components.allRules.bulkActions.bulkEditFlyoutForm.setInvestigationFieldsWarningCallout" - defaultMessage="You’re about to overwrite custom highlighted fields for {rulesCount, plural, one {# selected rule} other {# selected rules}}, press Save to - apply changes." + defaultMessage="You’re about to overwrite custom highlighted fields for the {rulesCount, plural, one {# rule} other {# rules}} you selected. To apply and save the changes, click Save." values={{ rulesCount }} /> </EuiCallOut> diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index 4fa96cf519bbf..ca42502d93c4e 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -489,7 +489,7 @@ export const BULK_EDIT_FLYOUT_FORM_ADD_INVESTIGATION_FIELDS_REQUIRED_ERROR = i18 export const BULK_EDIT_FLYOUT_FORM_ADD_INVESTIGATION_FIELDS_OVERWRITE_LABEL = i18n.translate( 'xpack.securitySolution.detectionEngine.components.allRules.bulkActions.bulkEditFlyoutForm.addInvestigationFieldsOverwriteCheckboxLabel', { - defaultMessage: "Overwrite all selected rules' custom highlighted fields", + defaultMessage: 'Overwrite the custom highlighted fields for the selected rules', } ); @@ -504,7 +504,7 @@ export const BULK_EDIT_FLYOUT_FORM_ADD_INVESTIGATION_FIELDS_HELP_TEXT = i18n.tra 'xpack.securitySolution.detectionEngine.components.allRules.bulkActions.bulkEditFlyoutForm.addInvestigationFieldsComboboxHelpText', { defaultMessage: - 'Enter fields that you would like to add. By default, the dropdown includes fields of the index patterns defined in Security Solution advanced settings.', + 'Enter fields that you want to add. You can choose from any of the fields included in the default Elastic Security indices.', } ); @@ -526,7 +526,7 @@ export const BULK_EDIT_FLYOUT_FORM_DELETE_INVESTIGATION_FIELDS_HELP_TEXT = i18n. 'xpack.securitySolution.detectionEngine.components.allRules.bulkActions.bulkEditFlyoutForm.deleteInvestigationFieldsComboboxHelpText', { defaultMessage: - 'Enter fields that you would like to delete. By default, the dropdown includes fields of the index patterns defined in Security Solution advanced settings.', + 'Enter the fields that you want to remove from the selected rules. After you remove these fields, they will no longer appear in the Highlighted fields section of the alerts generated by selected rules.', } ); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts index 1c7bb46c4cbc1..41bcc0ff3f938 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts @@ -611,7 +611,7 @@ describe('Detection rules, bulk edit', { tags: ['@ess', '@serverless'] }, () => cy.get(RULES_BULK_EDIT_INVESTIGATION_FIELDS_WARNING).should( 'have.text', - `You’re about to overwrite custom highlighted fields for ${rows.length} selected rules, press Save to apply changes.` + `You’re about to overwrite custom highlighted fields for the ${rows.length} rules you selected. To apply and save the changes, click Save.` ); typeInvestigationFields(fieldsToOverwrite); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/rules_bulk_actions.ts b/x-pack/test/security_solution_cypress/cypress/tasks/rules_bulk_actions.ts index fab3221fecb9d..c551054fbc6c7 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/rules_bulk_actions.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/rules_bulk_actions.ts @@ -273,10 +273,10 @@ export const typeInvestigationFields = (fields: string[]) => { export const checkOverwriteInvestigationFieldsCheckbox = () => { cy.get(RULES_BULK_EDIT_OVERWRITE_INVESTIGATION_FIELDS_CHECKBOX) - .should('have.text', "Overwrite all selected rules' custom highlighted fields") + .should('have.text', 'Overwrite the custom highlighted fields for the selected rules') .click(); cy.get(RULES_BULK_EDIT_OVERWRITE_INVESTIGATION_FIELDS_CHECKBOX) - .should('have.text', "Overwrite all selected rules' custom highlighted fields") + .should('have.text', 'Overwrite the custom highlighted fields for the selected rules') .get('input') .should('be.checked'); }; From 525d24e223f412a9ed0b74ee4f2f51df6075b6c7 Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" <christiane.heiligers@elastic.co> Date: Tue, 2 Jul 2024 08:57:59 -0700 Subject: [PATCH 094/128] Exposes authenticate user mock from core-security-server-mocks (#187318) Needed for https://github.com/elastic/kibana/pull/187306 and [probably more to come](https://github.com/elastic/kibana/issues/186574). Expose authenticated user mock and type from core-security server & browser mocks. Exposing the mock from Core avoids dependencies on the Security Plugin remaining just for mocks. It also helps avoid creating duplicates specifically for tests. --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../core-security-browser-mocks/src/security_service.mock.ts | 3 +++ .../core/security/core-security-browser-mocks/tsconfig.json | 1 + .../core-security-server-mocks/src/security_service.mock.ts | 3 +++ .../core/security/core-security-server-mocks/tsconfig.json | 1 + 4 files changed, 8 insertions(+) diff --git a/packages/core/security/core-security-browser-mocks/src/security_service.mock.ts b/packages/core/security/core-security-browser-mocks/src/security_service.mock.ts index 9fea0a6808170..feda2ade4f9d6 100644 --- a/packages/core/security/core-security-browser-mocks/src/security_service.mock.ts +++ b/packages/core/security/core-security-browser-mocks/src/security_service.mock.ts @@ -11,6 +11,7 @@ import type { InternalSecurityServiceSetup, InternalSecurityServiceStart, } from '@kbn/core-security-browser-internal'; +import { mockAuthenticatedUser, MockAuthenticatedUserProps } from '@kbn/core-security-common/mocks'; const createSetupMock = () => { const mock: jest.Mocked<SecurityServiceSetup> = { @@ -64,4 +65,6 @@ export const securityServiceMock = { createStart: createStartMock, createInternalSetup: createInternalSetupMock, createInternalStart: createInternalStartMock, + createMockAuthenticatedUser: (props: MockAuthenticatedUserProps = {}) => + mockAuthenticatedUser(props), }; diff --git a/packages/core/security/core-security-browser-mocks/tsconfig.json b/packages/core/security/core-security-browser-mocks/tsconfig.json index 7e98cd5bed84c..363e26375db29 100644 --- a/packages/core/security/core-security-browser-mocks/tsconfig.json +++ b/packages/core/security/core-security-browser-mocks/tsconfig.json @@ -18,5 +18,6 @@ "kbn_references": [ "@kbn/core-security-browser", "@kbn/core-security-browser-internal", + "@kbn/core-security-common", ] } diff --git a/packages/core/security/core-security-server-mocks/src/security_service.mock.ts b/packages/core/security/core-security-server-mocks/src/security_service.mock.ts index 59a560d562e06..d833048990ff5 100644 --- a/packages/core/security/core-security-server-mocks/src/security_service.mock.ts +++ b/packages/core/security/core-security-server-mocks/src/security_service.mock.ts @@ -16,6 +16,7 @@ import type { InternalSecurityServiceStart, } from '@kbn/core-security-server-internal'; import { auditServiceMock, type MockedAuditService } from './audit.mock'; +import { mockAuthenticatedUser, MockAuthenticatedUserProps } from '@kbn/core-security-common/mocks'; const createSetupMock = () => { const mock: jest.Mocked<SecurityServiceSetup> = { @@ -99,4 +100,6 @@ export const securityServiceMock = { createInternalSetup: createInternalSetupMock, createInternalStart: createInternalStartMock, createRequestHandlerContext: createRequestHandlerContextMock, + createMockAuthenticatedUser: (props: MockAuthenticatedUserProps = {}) => + mockAuthenticatedUser(props), }; diff --git a/packages/core/security/core-security-server-mocks/tsconfig.json b/packages/core/security/core-security-server-mocks/tsconfig.json index 28181e131badd..9c170d484ca9c 100644 --- a/packages/core/security/core-security-server-mocks/tsconfig.json +++ b/packages/core/security/core-security-server-mocks/tsconfig.json @@ -17,5 +17,6 @@ "@kbn/core-security-server", "@kbn/core-security-server-internal", "@kbn/core-http-server", + "@kbn/core-security-common", ] } From c978455544411c25c1532116f1dc10cc5e606f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?= <gergo.abraham@elastic.co> Date: Tue, 2 Jul 2024 18:21:16 +0200 Subject: [PATCH 095/128] [EDR Workflows][Process descendants filter] Display process descendant filtering on event filter cards (#187174) ## Summary The modifications indicate to the users if an Event Filter filters process descendants, displayed in 3 places. It's a bit of a prop drilling to be honest, but that was needed to keep `ArtifactXZ` components generic by passing a 'decorator' component from the outside. ### Testing Modifications are behind feature flag: `xpack.securitySolution.enableExperimental.filterProcessDescendantsForEventFiltersEnabled` To change an Event Filter to Process descendant filtering, you just need to change the toggle on the new/edit flyout: <img width="400" alt="image" src="https://github.com/elastic/kibana/assets/39014407/23c64d77-7d28-44c1-9a7f-07499652610b"> ### Manage / Event Filters - `ArtifactEntryCard` <img width="1393" alt="image" src="https://github.com/elastic/kibana/assets/39014407/79459f08-3b30-4f66-b058-e9b2bbaed705"> <img width="675" alt="image" src="https://github.com/elastic/kibana/assets/39014407/7be9d2d8-85d4-4a8d-b650-f1371bcaa903"> ### Manage / Policies / Event Filters tab / Assign flyout - `ArtifactEntryCardMinified` <img width="1315" alt="image" src="https://github.com/elastic/kibana/assets/39014407/57b6564b-8b43-4a37-9d4b-c7db5cbefbeb"> <img width="668" alt="image" src="https://github.com/elastic/kibana/assets/39014407/7e3e1b4a-e0f0-4b20-8b9b-f7d24589ce2a"> ### Manage / Policies / Event Filters tab - when there are assigned filters - `ArtifactEntryCollapsibleCard` <img width="1068" alt="image" src="https://github.com/elastic/kibana/assets/39014407/af31b89e-9845-4625-a95d-f610a57203f4"> <img width="1067" alt="image" src="https://github.com/elastic/kibana/assets/39014407/f9b98c4b-ed47-4f62-8bed-95c65420ab4f"> ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../artifact_card_grid.test.tsx | 1 + .../artifact_entry_card.test.tsx | 21 ++++- .../artifact_entry_card.tsx | 13 +++ .../artifact_entry_card_minified.test.tsx | 22 ++++- .../artifact_entry_card_minified.tsx | 10 +++ .../artifact_entry_collapsible_card.test.tsx | 31 ++++++- .../artifact_entry_collapsible_card.tsx | 3 + ...ters_process_descendant_indicator.test.tsx | 81 +++++++++++++++++++ ...t_filters_process_descendant_indicator.tsx | 52 ++++++++++++ .../artifact_list_page/artifact_list_page.tsx | 4 + .../artifact_list_page.test.tsx | 15 ++++ .../paginated_content.test.tsx | 1 + .../paginated_content/paginated_content.tsx | 17 ++-- .../view/components/form.test.tsx | 4 +- .../event_filters/view/components/form.tsx | 34 +------- .../components/process_descendant_tooltip.tsx | 66 +++++++++++++++ .../event_filters/view/event_filters_list.tsx | 2 + .../event_filters_list.test.tsx | 76 +++++++++++++++++ .../policy_artifacts_assignable_list.test.tsx | 4 + .../policy_artifacts_assignable_list.tsx | 11 ++- .../flyout/policy_artifacts_flyout.test.tsx | 1 + .../flyout/policy_artifacts_flyout.tsx | 5 +- .../layout/policy_artifacts_layout.tsx | 9 ++- .../list/policy_artifacts_list.test.tsx | 1 + .../artifacts/list/policy_artifacts_list.tsx | 4 + .../pages/policy/view/tabs/policy_tabs.tsx | 2 + 26 files changed, 443 insertions(+), 47 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/card_decorators/event_filters_process_descendant_indicator.test.tsx create mode 100644 x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/card_decorators/event_filters_process_descendant_indicator.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/process_descendant_tooltip.tsx diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_card_grid/artifact_card_grid.test.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_card_grid/artifact_card_grid.test.tsx index cb6fcf255126b..731c4076f75c4 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_card_grid/artifact_card_grid.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_card_grid/artifact_card_grid.test.tsx @@ -57,6 +57,7 @@ describe.each([ pageIndex: 0, }, 'data-test-subj': 'testGrid', + CardDecorator: undefined, ...props, }; diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.test.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.test.tsx index a1e6243a67a72..100a92f11b5e8 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.test.tsx @@ -5,10 +5,13 @@ * 2.0. */ -import React from 'react'; +import React, { memo } from 'react'; import type { AppContextTestRender } from '../../../common/mock/endpoint'; import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; -import type { ArtifactEntryCardProps } from './artifact_entry_card'; +import type { + ArtifactEntryCardDecoratorProps, + ArtifactEntryCardProps, +} from './artifact_entry_card'; import { ArtifactEntryCard } from './artifact_entry_card'; import { act, fireEvent, getByTestId } from '@testing-library/react'; import type { AnyArtifact } from './types'; @@ -268,5 +271,19 @@ describe.each([ expect(renderResult.getByText('policy-1').textContent).not.toBeNull(); }); + + it('should pass item to decorator function and display its result', () => { + let passedItem: ArtifactEntryCardDecoratorProps['item'] | null = null; + const MockDecorator = memo<ArtifactEntryCardDecoratorProps>(({ item: actualItem }) => { + passedItem = actualItem; + return <p>{'mock decorator'}</p>; + }); + MockDecorator.displayName = 'MockDecorator'; + + render({ Decorator: MockDecorator }); + + expect(renderResult.getByText('mock decorator')).toBeInTheDocument(); + expect(passedItem).toBe(item); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.tsx index f0037b691ceab..76b5142bf068d 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.tsx @@ -37,6 +37,12 @@ export interface CommonArtifactEntryCardProps extends CommonProps { */ policies?: MenuItemPropsByPolicyId; loadingPoliciesList?: boolean; + /** + * Artifact specific decorator component that receives the current artifact as a prop, and + * is displayed inside the card on the top of the card section, + * above the selected OS and the condition entries. + */ + Decorator?: React.ComponentType<ArtifactEntryCardDecoratorProps>; } export interface ArtifactEntryCardProps extends CommonArtifactEntryCardProps { @@ -46,6 +52,10 @@ export interface ArtifactEntryCardProps extends CommonArtifactEntryCardProps { hideComments?: boolean; } +export interface ArtifactEntryCardDecoratorProps extends CommonProps { + item: MaybeImmutable<AnyArtifact>; +} + /** * Display Artifact Items (ex. Trusted App, Event Filter, etc) as a card. * This component is a TS Generic that allows you to set what the Item type is @@ -58,6 +68,7 @@ export const ArtifactEntryCard = memo<ArtifactEntryCardProps>( actions, hideDescription = false, hideComments = false, + Decorator, 'data-test-subj': dataTestSubj, ...commonProps }) => { @@ -103,6 +114,8 @@ export const ArtifactEntryCard = memo<ArtifactEntryCardProps>( <EuiHorizontalRule margin="none" /> <CardSectionPanel className="bottom-section"> + {Decorator && <Decorator item={item} data-test-subj={getTestId('decorator')} />} + <CriteriaConditions os={artifact.os as CriteriaConditionsProps['os']} entries={artifact.entries} diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card_minified.test.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card_minified.test.tsx index 05b157b6f8711..6163d3795545a 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card_minified.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card_minified.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { memo } from 'react'; import type { AppContextTestRender } from '../../../common/mock/endpoint'; import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; import type { ArtifactEntryCardMinifiedProps } from './artifact_entry_card_minified'; @@ -13,6 +13,7 @@ import { ArtifactEntryCardMinified } from './artifact_entry_card_minified'; import { act, fireEvent } from '@testing-library/react'; import type { AnyArtifact } from './types'; import { getTrustedAppProviderMock, getExceptionProviderMock } from './test_utils'; +import type { ArtifactEntryCardDecoratorProps } from './artifact_entry_card'; describe.each([ ['trusted apps', getTrustedAppProviderMock], @@ -94,4 +95,23 @@ describe.each([ expect(onToggleSelectedArtifactMock).toHaveBeenCalledTimes(1); expect(onToggleSelectedArtifactMock).toHaveBeenCalledWith(false); }); + + it('should pass item to Decorator component and display the component', () => { + let passedItem: ArtifactEntryCardDecoratorProps['item'] | null = null; + const MockDecorator = memo<ArtifactEntryCardDecoratorProps>(({ item: actualItem }) => { + passedItem = actualItem; + return <p>{'mock decorator'}</p>; + }); + MockDecorator.displayName = 'MockDecorator'; + + render({ + item, + isSelected: false, + onToggleSelectedArtifact: onToggleSelectedArtifactMock, + Decorator: MockDecorator, + }); + + expect(renderResult.getByText('mock decorator')).toBeInTheDocument(); + expect(passedItem).toBe(item); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card_minified.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card_minified.tsx index 0d17cfaf7e45a..c1acc122eb2d3 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card_minified.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card_minified.tsx @@ -25,6 +25,7 @@ import { useNormalizedArtifact } from './hooks/use_normalized_artifact'; import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; import { DESCRIPTION_LABEL } from './components/translations'; import { DescriptionField } from './components/description_field'; +import type { ArtifactEntryCardDecoratorProps } from './artifact_entry_card'; const CardContainerPanel = styled(EuiSplitPanel.Outer)` &.artifactEntryCardMinified + &.artifactEntryCardMinified { @@ -40,6 +41,12 @@ export interface ArtifactEntryCardMinifiedProps extends CommonProps { item: AnyArtifact; isSelected: boolean; onToggleSelectedArtifact: (selected: boolean) => void; + /** + * Artifact specific decorator component that receives the current artifact as a prop, and + * is displayed inside the card on the top of the card section, + * above the selected OS and the condition entries. + */ + Decorator?: React.ComponentType<ArtifactEntryCardDecoratorProps>; } /** @@ -52,6 +59,7 @@ export const ArtifactEntryCardMinified = memo( isSelected = false, onToggleSelectedArtifact, 'data-test-subj': dataTestSubj, + Decorator, ...commonProps }: ArtifactEntryCardMinifiedProps) => { const artifact = useNormalizedArtifact(item); @@ -126,6 +134,8 @@ export const ArtifactEntryCardMinified = memo( {getAccordionTitle()} </EuiButtonEmpty> <EuiAccordion id="showDetails" arrowDisplay="none" forceState={accordionTrigger}> + {Decorator && <Decorator item={item} data-test-subj={getTestId('decorator')} />} + <CriteriaConditions os={artifact.os as CriteriaConditionsProps['os']} entries={artifact.entries} diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_collapsible_card.test.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_collapsible_card.test.tsx index 8fd87ad9cc261..d4c6c56d9e9df 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_collapsible_card.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_collapsible_card.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { memo } from 'react'; import type { AppContextTestRender } from '../../../common/mock/endpoint'; import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; import { act, fireEvent } from '@testing-library/react'; @@ -13,6 +13,7 @@ import type { AnyArtifact } from './types'; import { getTrustedAppProviderMock, getExceptionProviderMock } from './test_utils'; import type { ArtifactEntryCollapsibleCardProps } from './artifact_entry_collapsible_card'; import { ArtifactEntryCollapsibleCard } from './artifact_entry_collapsible_card'; +import type { ArtifactEntryCardDecoratorProps } from './artifact_entry_card'; describe.each([ ['trusted apps', getTrustedAppProviderMock], @@ -119,4 +120,32 @@ describe.each([ expect(renderResult.getByTestId(testSubjId).classList.contains('eui-textTruncate')).toBe(false); }); + + it('should pass item to decorator function and display its result when expanded', () => { + let passedItem: ArtifactEntryCardDecoratorProps['item'] | null = null; + const MockDecorator = memo<ArtifactEntryCardDecoratorProps>(({ item: actualItem }) => { + passedItem = actualItem; + return <p>{'mock decorator'}</p>; + }); + MockDecorator.displayName = 'MockDecorator'; + + render({ Decorator: MockDecorator, expanded: true }); + + expect(renderResult.getByText('mock decorator')).toBeInTheDocument(); + expect(passedItem).toBe(item); + }); + + it('should not display decorator when collapsed', () => { + let passedItem: ArtifactEntryCardDecoratorProps['item'] | null = null; + const MockDecorator = memo<ArtifactEntryCardDecoratorProps>(({ item: actualItem }) => { + passedItem = actualItem; + return <p>{'mock decorator'}</p>; + }); + MockDecorator.displayName = 'MockDecorator'; + + render({ Decorator: MockDecorator, expanded: false }); + + expect(renderResult.queryByText('mock decorator')).not.toBeInTheDocument(); + expect(passedItem).toBe(null); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_collapsible_card.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_collapsible_card.tsx index 29d336d45aefe..ecf99fac8343b 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_collapsible_card.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_collapsible_card.tsx @@ -29,6 +29,7 @@ export const ArtifactEntryCollapsibleCard = memo<ArtifactEntryCollapsibleCardPro actions, expanded = false, 'data-test-subj': dataTestSubj, + Decorator, ...commonProps }) => { const artifact = useNormalizedArtifact(item); @@ -51,6 +52,8 @@ export const ArtifactEntryCollapsibleCard = memo<ArtifactEntryCollapsibleCardPro <EuiHorizontalRule margin="xs" /> <CardSectionPanel> + {Decorator && <Decorator item={item} data-test-subj={getTestId('decorator')} />} + <CriteriaConditions os={artifact.os as CriteriaConditionsProps['os']} entries={artifact.entries} diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/card_decorators/event_filters_process_descendant_indicator.test.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/card_decorators/event_filters_process_descendant_indicator.test.tsx new file mode 100644 index 0000000000000..ce4c48a6863b7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/card_decorators/event_filters_process_descendant_indicator.test.tsx @@ -0,0 +1,81 @@ +/* + * 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 userEvent from '@testing-library/user-event'; +import { EventFiltersProcessDescendantIndicator } from './event_filters_process_descendant_indicator'; +import type { AnyArtifact } from '../../types'; +import type { AppContextTestRender } from '../../../../../common/mock/endpoint'; +import { createAppRootMockRenderer } from '../../../../../common/mock/endpoint'; +import { + FILTER_PROCESS_DESCENDANTS_TAG, + GLOBAL_ARTIFACT_TAG, +} from '../../../../../../common/endpoint/service/artifacts/constants'; +import type { ArtifactEntryCardDecoratorProps } from '../../artifact_entry_card'; + +describe('EventFiltersProcessDescendantIndicator', () => { + let appTestContext: AppContextTestRender; + let renderResult: ReturnType<AppContextTestRender['render']>; + let render: ( + props: ArtifactEntryCardDecoratorProps + ) => ReturnType<AppContextTestRender['render']>; + + const getStandardEventFilter: () => AnyArtifact = () => + ({ + tags: [GLOBAL_ARTIFACT_TAG], + } as Partial<AnyArtifact> as AnyArtifact); + + const getProcessDescendantEventFilter: () => AnyArtifact = () => + ({ + tags: [GLOBAL_ARTIFACT_TAG, FILTER_PROCESS_DESCENDANTS_TAG], + } as Partial<AnyArtifact> as AnyArtifact); + + beforeEach(() => { + appTestContext = createAppRootMockRenderer(); + render = (props) => { + renderResult = appTestContext.render( + <EventFiltersProcessDescendantIndicator data-test-subj="test" {...props} /> + ); + return renderResult; + }; + }); + + it('should not display anything if feature flag is disabled', () => { + appTestContext.setExperimentalFlag({ filterProcessDescendantsForEventFiltersEnabled: false }); + + render({ item: getProcessDescendantEventFilter() }); + + expect(renderResult.queryByTestId('test-processDescendantIndication')).not.toBeInTheDocument(); + }); + + it('should not display anything if Event Filter is not for process descendants', () => { + appTestContext.setExperimentalFlag({ filterProcessDescendantsForEventFiltersEnabled: true }); + + render({ item: getStandardEventFilter() }); + + expect(renderResult.queryByTestId('test-processDescendantIndication')).not.toBeInTheDocument(); + }); + + it('should display indication if Event Filter is for process descendants', () => { + appTestContext.setExperimentalFlag({ filterProcessDescendantsForEventFiltersEnabled: true }); + + render({ item: getProcessDescendantEventFilter() }); + + expect(renderResult.getByTestId('test-processDescendantIndication')).toBeInTheDocument(); + }); + + it('should mention additional `event.category is process` entry in tooltip', async () => { + const prefix = 'test-processDescendantIndicationTooltip'; + appTestContext.setExperimentalFlag({ filterProcessDescendantsForEventFiltersEnabled: true }); + render({ item: getProcessDescendantEventFilter() }); + + expect(renderResult.queryByTestId(`${prefix}-tooltipText`)).not.toBeInTheDocument(); + + userEvent.hover(renderResult.getByTestId(`${prefix}-tooltipIcon`)); + expect(await renderResult.findByTestId(`${prefix}-tooltipText`)).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/card_decorators/event_filters_process_descendant_indicator.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/card_decorators/event_filters_process_descendant_indicator.tsx new file mode 100644 index 0000000000000..93eaeb5fbc7e3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/card_decorators/event_filters_process_descendant_indicator.tsx @@ -0,0 +1,52 @@ +/* + * 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 { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import React, { memo } from 'react'; +import { EuiSpacer, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useTestIdGenerator } from '../../../../hooks/use_test_id_generator'; +import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; +import { isFilterProcessDescendantsEnabled } from '../../../../../../common/endpoint/service/artifacts/utils'; +import { ProcessDescendantsTooltip } from '../../../../pages/event_filters/view/components/process_descendant_tooltip'; +import type { ArtifactEntryCardDecoratorProps } from '../../artifact_entry_card'; + +export const EventFiltersProcessDescendantIndicator = memo<ArtifactEntryCardDecoratorProps>( + ({ item, 'data-test-subj': dataTestSubj, ...commonProps }) => { + const getTestId = useTestIdGenerator(dataTestSubj); + const isProcessDescendantFeatureEnabled = useIsExperimentalFeatureEnabled( + 'filterProcessDescendantsForEventFiltersEnabled' + ); + + if ( + isProcessDescendantFeatureEnabled && + isFilterProcessDescendantsEnabled(item as ExceptionListItemSchema) + ) { + return ( + <> + <EuiText {...commonProps} data-test-subj={getTestId('processDescendantIndication')}> + <code> + <strong> + <FormattedMessage + defaultMessage="Filtering descendants of process" + id="xpack.securitySolution.eventFilters.filteringProcessDescendants" + />{' '} + <ProcessDescendantsTooltip + indicateExtraEntry + data-test-subj={getTestId('processDescendantIndicationTooltip')} + /> + </strong> + </code> + </EuiText> + <EuiSpacer size="m" /> + </> + ); + } + + return <></>; + } +); +EventFiltersProcessDescendantIndicator.displayName = 'EventFiltersProcessDescendantIndicator'; diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.tsx index abe52767c5d5e..49755f88562f7 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.tsx @@ -18,6 +18,7 @@ import { AdministrationListPage } from '../administration_list_page'; import type { PaginatedContentProps } from '../paginated_content'; import { PaginatedContent } from '../paginated_content'; +import type { ArtifactEntryCardDecoratorProps } from '../artifact_entry_card'; import { ArtifactEntryCard } from '../artifact_entry_card'; import type { ArtifactListPageLabels } from './translations'; @@ -75,6 +76,7 @@ export interface ArtifactListPageProps { allowCardDeleteAction?: boolean; allowCardCreateAction?: boolean; secondaryPageInfo?: React.ReactNode; + CardDecorator?: React.ComponentType<ArtifactEntryCardDecoratorProps>; } export const ArtifactListPage = memo<ArtifactListPageProps>( @@ -90,6 +92,7 @@ export const ArtifactListPage = memo<ArtifactListPageProps>( allowCardEditAction = true, allowCardCreateAction = true, allowCardDeleteAction = true, + CardDecorator, }) => { const { state: routeState } = useLocation<ListPageRouteState | undefined>(); const getTestId = useTestIdGenerator(dataTestSubj); @@ -354,6 +357,7 @@ export const ArtifactListPage = memo<ArtifactListPageProps>( pagination={uiPagination} contentClassName="card-container" data-test-subj={getTestId('list')} + CardDecorator={CardDecorator} /> </> )} diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/integration_tests/artifact_list_page.test.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/integration_tests/artifact_list_page.test.tsx index 4ce4a80a00e9f..f67e694224713 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/integration_tests/artifact_list_page.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/integration_tests/artifact_list_page.test.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import React, { memo } from 'react'; import type { AppContextTestRender } from '../../../../common/mock/endpoint'; import type { trustedAppsAllHttpMocks } from '../../../mocks'; import type { ArtifactListPageProps } from '../artifact_list_page'; @@ -14,6 +15,7 @@ import type { ArtifactListPageRenderingSetup } from '../mocks'; import { getArtifactListPageRenderingSetup } from '../mocks'; import { getDeferred } from '../../../mocks/utils'; import { useGetEndpointSpecificPolicies } from '../../../services/policies/hooks'; +import type { ArtifactEntryCardDecoratorProps } from '../../artifact_entry_card'; jest.mock('../../../services/policies/hooks', () => ({ useGetEndpointSpecificPolicies: jest.fn(), @@ -144,6 +146,19 @@ describe('When using the ArtifactListPage component', () => { }); }); + it('should show per card decoration', async () => { + const MockCardDecorator = memo<ArtifactEntryCardDecoratorProps>(({ item: actualItem }) => { + return <p>{'mock decorator'}</p>; + }); + MockCardDecorator.displayName = 'MockCardDecorator'; + + const { getAllByText } = await renderWithListData({ + CardDecorator: MockCardDecorator, + }); + + expect(getAllByText('mock decorator')).toHaveLength(10); + }); + it('should call useGetEndpointSpecificPolicies hook with specific perPage value', () => { expect(mockUseGetEndpointSpecificPolicies).toHaveBeenCalledWith( expect.objectContaining({ diff --git a/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.test.tsx b/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.test.tsx index 78f7580a7b168..c560f155e99f9 100644 --- a/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.test.tsx @@ -60,6 +60,7 @@ describe('when using PaginatedContent', () => { totalItemCount: 10, }, 'data-test-subj': 'test', + CardDecorator: undefined, ...(additionalProps ?? {}), }; renderResult = mockedContext.render(<PaginatedContent<Foo, ItemComponentType> {...props} />); diff --git a/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.tsx b/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.tsx index 11797c0544ae6..edc5151ecfd11 100644 --- a/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.tsx +++ b/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.tsx @@ -29,6 +29,7 @@ import { v4 as generateUUI } from 'uuid'; import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; import type { MaybeImmutable } from '../../../../common/endpoint/types'; import { MANAGEMENT_DEFAULT_PAGE, MANAGEMENT_DEFAULT_PAGE_SIZE } from '../../common/constants'; +import type { ArtifactEntryCardDecoratorProps } from '../artifact_entry_card'; // eslint-disable-next-line @typescript-eslint/no-explicit-any type ComponentWithAnyProps = ComponentType<any>; @@ -52,6 +53,8 @@ export interface PaginatedContentProps<T, C extends ComponentWithAnyProps> exten error?: ReactNode; /** Classname applied to the area that holds the content items */ contentClassName?: string; + // Artifact specific decorations to display in the cards + CardDecorator: React.ComponentType<ArtifactEntryCardDecoratorProps> | undefined; /** * Children can be used to define custom content if the default creation of items is not sufficient * to accommodate a use case. @@ -139,6 +142,7 @@ export const PaginatedContent = memo( 'data-test-subj': dataTestSubj, 'aria-label': ariaLabel, className, + CardDecorator, children, }: PaginatedContentProps<T, C>) => { const [itemKeys] = useState<WeakMap<T, string>>(new WeakMap()); @@ -223,21 +227,22 @@ export const PaginatedContent = memo( } } - return <Item {...itemComponentProps(item)} key={key} />; + return <Item {...itemComponentProps(item)} key={key} Decorator={CardDecorator} />; }); } if (!loading) return noItemsMessage || <DefaultNoItemsFound data-test-subj={getTestId('noResults')} />; }, [ - ItemComponent, error, + ItemComponent, + items, + loading, + noItemsMessage, getTestId, - itemComponentProps, itemId, + itemComponentProps, + CardDecorator, itemKeys, - items, - noItemsMessage, - loading, ]); return ( diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.test.tsx index 9ea16a071a72b..c355cc8bdea0b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.test.tsx @@ -532,8 +532,8 @@ describe('Event filter form', () => { }); it('should display a tooltip to the user', async () => { - const tooltipIconSelector = `${formPrefix}-filterProcessDescendants-tooltipIcon`; - const tooltipTextSelector = `${formPrefix}-filterProcessDescendants-tooltipText`; + const tooltipIconSelector = `${formPrefix}-filterProcessDescendantsTooltip-tooltipIcon`; + const tooltipTextSelector = `${formPrefix}-filterProcessDescendantsTooltip-tooltipText`; render(); expect(renderResult.getByTestId(tooltipIconSelector)).toBeInTheDocument(); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx index ecb54e57baf4b..4275502961848 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx @@ -14,8 +14,6 @@ import { EuiSpacer, EuiFlexGroup, EuiButtonGroup, - EuiToolTip, - EuiIcon, useEuiTheme, EuiForm, EuiFormRow, @@ -84,6 +82,7 @@ import { EffectedPolicySelect } from '../../../../components/effected_policy_sel import { ExceptionItemComments } from '../../../../../detection_engine/rule_exceptions/components/item_comments'; import { EventFiltersApiClient } from '../../service/api_client'; import { ShowValueListModal } from '../../../../../value_list/components/show_value_list_modal'; +import { ProcessDescendantsTooltip } from './process_descendant_tooltip'; const OPERATING_SYSTEMS: readonly OperatingSystem[] = [ OperatingSystem.MAC, @@ -458,33 +457,6 @@ export const EventFiltersForm: React.FC<ArtifactFormComponentProps & { allowSele ); const filterTypeOptions: EuiButtonGroupOptionProps[] = useMemo(() => { - const descendantsTooltip = ( - <EuiToolTip - content={ - <EuiText size="s"> - <p> - <FormattedMessage - id="xpack.securitySolution.eventFilters.filterProcessDescendants.tooltip" - defaultMessage="Filtering the descendants of a process means that events from the matched process are ingested, but events from its descendant processes are omitted." - /> - </p> - <p> - <FormattedMessage - id="xpack.securitySolution.eventFilters.filterProcessDescendants.tooltipVersionInfo" - defaultMessage="Process descendant filtering works only with Agents v8.15 and newer." - /> - </p> - </EuiText> - } - data-test-subj={getTestId('filterProcessDescendants-tooltipText')} - > - <EuiIcon - type="iInCircle" - data-test-subj={getTestId('filterProcessDescendants-tooltipIcon')} - /> - </EuiToolTip> - ); - return [ { id: 'events', @@ -509,7 +481,9 @@ export const EventFiltersForm: React.FC<ArtifactFormComponentProps & { allowSele defaultMessage="Process Descendants" /> </EuiText> - {descendantsTooltip} + <ProcessDescendantsTooltip + data-test-subj={getTestId('filterProcessDescendantsTooltip')} + /> </EuiFlexGroup> ), iconType: isFilterProcessDescendantsSelected ? 'checkInCircleFilled' : 'empty', diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/process_descendant_tooltip.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/process_descendant_tooltip.tsx new file mode 100644 index 0000000000000..f8709306d2099 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/process_descendant_tooltip.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo } from 'react'; +import type { CommonProps } from '@elastic/eui'; +import { EuiToolTip, EuiText, EuiIcon } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useTestIdGenerator } from '../../../../hooks/use_test_id_generator'; +import { PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY_TEXT } from '../../../../../../common/endpoint/service/artifacts/constants'; + +interface ProcessDescendantsTooltipProps extends CommonProps { + indicateExtraEntry?: boolean; +} + +export const ProcessDescendantsTooltip = memo<ProcessDescendantsTooltipProps>( + ({ + indicateExtraEntry = false, + 'data-test-subj': dataTestSubj, + ...commonProps + }: ProcessDescendantsTooltipProps) => { + const getTestId = useTestIdGenerator(dataTestSubj); + + return ( + <EuiToolTip + {...commonProps} + content={ + <EuiText size="s"> + <p> + <FormattedMessage + id="xpack.securitySolution.eventFilters.filterProcessDescendants.tooltip" + defaultMessage="Filtering the descendants of a process means that events from the matched process are ingested, but events from its descendant processes are omitted." + /> + </p> + {indicateExtraEntry && ( + <> + <p> + <FormattedMessage + id="xpack.securitySolution.eventFilters.filterProcessDescendants.tooltipExtraEntry" + defaultMessage="Note: the following additional condition is applied:" + /> + </p> + <p> + <code>{PROCESS_DESCENDANT_EVENT_FILTER_EXTRA_ENTRY_TEXT}</code> + </p> + </> + )} + <p> + <FormattedMessage + id="xpack.securitySolution.eventFilters.filterProcessDescendants.tooltipVersionInfo" + defaultMessage="Process descendant filtering works only with Agents v8.15 and newer." + /> + </p> + </EuiText> + } + data-test-subj={getTestId('tooltipText')} + > + <EuiIcon type="iInCircle" data-test-subj={getTestId('tooltipIcon')} /> + </EuiToolTip> + ); + } +); +ProcessDescendantsTooltip.displayName = 'ProcessDescendantsTooltip'; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list.tsx index b2e2054ca0eda..87aae6a376733 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list.tsx @@ -18,6 +18,7 @@ import { ArtifactListPage } from '../../../components/artifact_list_page'; import { EventFiltersApiClient } from '../service/api_client'; import { EventFiltersForm } from './components/form'; import { SEARCHABLE_FIELDS } from '../constants'; +import { EventFiltersProcessDescendantIndicator } from '../../../components/artifact_entry_card/components/card_decorators/event_filters_process_descendant_indicator'; export const ABOUT_EVENT_FILTERS = i18n.translate('xpack.securitySolution.eventFilters.aboutInfo', { defaultMessage: @@ -155,6 +156,7 @@ export const EventFiltersList = memo(() => { allowCardCreateAction={canWriteEventFilters} allowCardEditAction={canWriteEventFilters} allowCardDeleteAction={canWriteEventFilters} + CardDecorator={EventFiltersProcessDescendantIndicator} /> ); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/integration_tests/event_filters_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/integration_tests/event_filters_list.test.tsx index c454cda4d49b2..8311c111ac8b5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/integration_tests/event_filters_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/integration_tests/event_filters_list.test.tsx @@ -17,6 +17,8 @@ import { SEARCHABLE_FIELDS } from '../../constants'; import { parseQueryFilterToKQL } from '../../../../common/utils'; import type { EndpointPrivileges } from '../../../../../../common/endpoint/types'; import { useUserPrivileges } from '../../../../../common/components/user_privileges'; +import { ExceptionsListItemGenerator } from '../../../../../../common/endpoint/data_generators/exceptions_list_item_generator'; +import { FILTER_PROCESS_DESCENDANTS_TAG } from '../../../../../../common/endpoint/service/artifacts/constants'; jest.mock('../../../../../common/components/user_privileges'); const mockUserPrivileges = useUserPrivileges as jest.Mock; @@ -69,6 +71,80 @@ describe('When on the Event Filters list page', () => { ); }); + describe('filtering process descendants', () => { + let renderWithData: () => Promise<ReturnType<AppContextTestRender['render']>>; + + beforeEach(() => { + renderWithData = async () => { + const generator = new ExceptionsListItemGenerator(); + + apiMocks.responseProvider.exceptionsFind.mockReturnValue({ + data: [ + generator.generateEventFilter(), + generator.generateEventFilter({ tags: [FILTER_PROCESS_DESCENDANTS_TAG] }), + generator.generateEventFilter({ tags: [FILTER_PROCESS_DESCENDANTS_TAG] }), + ], + total: 3, + per_page: 3, + page: 1, + }); + + render(); + + await act(async () => { + await waitFor(() => { + expect(renderResult.getByTestId('EventFiltersListPage-list')).toBeTruthy(); + }); + }); + + return renderResult; + }; + }); + + it('should not show indication if feature flag is disabled', async () => { + mockedContext.setExperimentalFlag({ filterProcessDescendantsForEventFiltersEnabled: false }); + + await renderWithData(); + + expect(renderResult.getAllByTestId('EventFiltersListPage-card')).toHaveLength(3); + expect( + renderResult.queryAllByTestId( + 'EventFiltersListPage-card-decorator-processDescendantIndication' + ) + ).toHaveLength(0); + }); + + it('should indicate to user if event filter filters process descendants', async () => { + mockedContext.setExperimentalFlag({ filterProcessDescendantsForEventFiltersEnabled: true }); + + await renderWithData(); + + expect(renderResult.getAllByTestId('EventFiltersListPage-card')).toHaveLength(3); + expect( + renderResult.getAllByTestId( + 'EventFiltersListPage-card-decorator-processDescendantIndication' + ) + ).toHaveLength(2); + }); + + it('should display additional `event.category is process` entry in tooltip', async () => { + mockedContext.setExperimentalFlag({ filterProcessDescendantsForEventFiltersEnabled: true }); + const prefix = 'EventFiltersListPage-card-decorator-processDescendantIndicationTooltip'; + + await renderWithData(); + + expect(renderResult.getAllByTestId(`${prefix}-tooltipIcon`)).toHaveLength(2); + expect(renderResult.queryByTestId(`${prefix}-tooltipText`)).not.toBeInTheDocument(); + + userEvent.hover(renderResult.getAllByTestId(`${prefix}-tooltipIcon`)[0]); + + expect(await renderResult.findByTestId(`${prefix}-tooltipText`)).toBeInTheDocument(); + expect(renderResult.getByTestId(`${prefix}-tooltipText`).textContent).toContain( + 'event.category is process' + ); + }); + }); + describe('RBAC Event Filters', () => { describe('ALL privilege', () => { beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/assignable/policy_artifacts_assignable_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/assignable/policy_artifacts_assignable_list.test.tsx index c1f95b1f4d067..f86e4bfd10f4d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/assignable/policy_artifacts_assignable_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/assignable/policy_artifacts_assignable_list.test.tsx @@ -35,6 +35,7 @@ describe('Policy artifacts list', () => { selectedArtifactIds: [], isListLoading: true, selectedArtifactsUpdated: selectedArtifactsUpdatedMock, + CardDecorator: undefined, }); expect(component.getByTestId('artifactsAssignableListLoader')).not.toBeNull(); @@ -47,6 +48,7 @@ describe('Policy artifacts list', () => { selectedArtifactIds: [], isListLoading: false, selectedArtifactsUpdated: selectedArtifactsUpdatedMock, + CardDecorator: undefined, }); expect(component.queryByTestId('artifactsList')).toBeNull(); }); @@ -58,6 +60,7 @@ describe('Policy artifacts list', () => { selectedArtifactIds: [], isListLoading: false, selectedArtifactsUpdated: selectedArtifactsUpdatedMock, + CardDecorator: undefined, }); expect(component.getByTestId('artifactsList')).not.toBeNull(); }); @@ -69,6 +72,7 @@ describe('Policy artifacts list', () => { selectedArtifactIds: [artifactsResponse.data[0].id], isListLoading: false, selectedArtifactsUpdated: selectedArtifactsUpdatedMock, + CardDecorator: undefined, }); const tACardCheckbox = component.getByTestId(`${getMockListResponse().data[1].name}_checkbox`); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/assignable/policy_artifacts_assignable_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/assignable/policy_artifacts_assignable_list.tsx index 4be70c364e0d8..d2f0ae02a2ddb 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/assignable/policy_artifacts_assignable_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/assignable/policy_artifacts_assignable_list.tsx @@ -12,7 +12,10 @@ import type { GetTrustedAppsListResponse, Immutable, } from '../../../../../../../common/endpoint/types'; -import type { AnyArtifact } from '../../../../../components/artifact_entry_card'; +import type { + AnyArtifact, + ArtifactEntryCardDecoratorProps, +} from '../../../../../components/artifact_entry_card'; import { ArtifactEntryCardMinified } from '../../../../../components/artifact_entry_card'; export interface PolicyArtifactsAssignableListProps { @@ -25,10 +28,11 @@ export interface PolicyArtifactsAssignableListProps { selectedArtifactIds: string[]; selectedArtifactsUpdated: (id: string, selected: boolean) => void; isListLoading: boolean; + CardDecorator: React.ComponentType<ArtifactEntryCardDecoratorProps> | undefined; } export const PolicyArtifactsAssignableList = React.memo<PolicyArtifactsAssignableListProps>( - ({ artifacts, isListLoading, selectedArtifactIds, selectedArtifactsUpdated }) => { + ({ artifacts, isListLoading, selectedArtifactIds, selectedArtifactsUpdated, CardDecorator }) => { const selectedArtifactIdsByKey = useMemo( () => selectedArtifactIds.reduce( @@ -51,11 +55,12 @@ export const PolicyArtifactsAssignableList = React.memo<PolicyArtifactsAssignabl onToggleSelectedArtifact={(selected) => selectedArtifactsUpdated(artifact.id, selected) } + Decorator={CardDecorator} /> ))} </div> ); - }, [artifacts, selectedArtifactIdsByKey, selectedArtifactsUpdated]); + }, [CardDecorator, artifacts, selectedArtifactIdsByKey, selectedArtifactsUpdated]); return ( <> diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.test.tsx index 48d133cd41f29..90438d7eb80da 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.test.tsx @@ -86,6 +86,7 @@ describe('Policy details artifacts flyout', () => { apiClient={EventFiltersApiClient.getInstance(mockedContext.coreStart.http)} onClose={onCloseMock} searchableFields={[...SEARCHABLE_FIELDS]} + CardDecorator={undefined} /> ); await waitFor(mockedApi.responseProvider.eventFiltersList); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.tsx index 7793061be4fea..9805c72a75ca6 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.tsx @@ -24,6 +24,7 @@ import { EuiEmptyPrompt, useGeneratedHtmlId, } from '@elastic/eui'; +import type { ArtifactEntryCardDecoratorProps } from '../../../../../components/artifact_entry_card'; import { SearchExceptions } from '../../../../../components/search_exceptions'; import type { ImmutableObject, PolicyData } from '../../../../../../../common/endpoint/types'; import { useToasts } from '../../../../../../common/lib/kibana'; @@ -38,12 +39,13 @@ interface PolicyArtifactsFlyoutProps { searchableFields: string[]; onClose: () => void; labels: typeof POLICY_ARTIFACT_FLYOUT_LABELS; + CardDecorator: React.ComponentType<ArtifactEntryCardDecoratorProps> | undefined; } export const MAX_ALLOWED_RESULTS = 100; export const PolicyArtifactsFlyout = React.memo<PolicyArtifactsFlyoutProps>( - ({ policyItem, apiClient, searchableFields, onClose, labels }) => { + ({ policyItem, apiClient, searchableFields, onClose, labels, CardDecorator }) => { const toasts = useToasts(); const queryClient = useQueryClient(); const [selectedArtifactIds, setSelectedArtifactIds] = useState<string[]>([]); @@ -210,6 +212,7 @@ export const PolicyArtifactsFlyout = React.memo<PolicyArtifactsFlyoutProps>( selectedArtifactIds={selectedArtifactIds} isListLoading={isLoadingArtifacts || isRefetchingArtifacts} selectedArtifactsUpdated={handleSelectArtifacts} + CardDecorator={CardDecorator} /> {noItemsMessage} diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.tsx index 70ac3ce16aab7..75927ece7dfde 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.tsx @@ -17,6 +17,7 @@ import { EuiButton, EuiPageSection, } from '@elastic/eui'; +import type { ArtifactEntryCardDecoratorProps } from '../../../../../components/artifact_entry_card'; import { useAppUrl } from '../../../../../../common/lib/kibana'; import { APP_UI_ID } from '../../../../../../../common/constants'; import type { ImmutableObject, PolicyData } from '../../../../../../../common/endpoint/types'; @@ -34,7 +35,7 @@ import { policyArtifactsPageLabels } from '../translations'; import { PolicyArtifactsDeleteModal } from '../delete_modal'; import type { ArtifactListPageUrlParams } from '../../../../../components/artifact_list_page'; -interface PolicyArtifactsLayoutProps { +export interface PolicyArtifactsLayoutProps { policyItem?: ImmutableObject<PolicyData> | undefined; /** A list of labels for the given policy artifact page. Not all have to be defined, only those that should override the defaults */ labels: PolicyArtifactsPageLabels; @@ -44,6 +45,8 @@ interface PolicyArtifactsLayoutProps { getPolicyArtifactsPath: (policyId: string) => string; /** A boolean to check if has write artifact privilege or not */ canWriteArtifact?: boolean; + // Artifact specific decorations to display in the cards + CardDecorator?: React.ComponentType<ArtifactEntryCardDecoratorProps>; } export const PolicyArtifactsLayout = React.memo<PolicyArtifactsLayoutProps>( ({ @@ -54,6 +57,7 @@ export const PolicyArtifactsLayout = React.memo<PolicyArtifactsLayoutProps>( getArtifactPath, getPolicyArtifactsPath, canWriteArtifact = false, + CardDecorator, }) => { const exceptionsListApiClient = useMemo( () => getExceptionsListApiClient(), @@ -154,6 +158,7 @@ export const PolicyArtifactsLayout = React.memo<PolicyArtifactsLayoutProps>( searchableFields={[...searchableFields]} onClose={handleOnCloseFlyout} labels={labels} + CardDecorator={CardDecorator} /> )} {allArtifacts && allArtifacts.total !== 0 ? ( @@ -205,6 +210,7 @@ export const PolicyArtifactsLayout = React.memo<PolicyArtifactsLayoutProps>( searchableFields={[...searchableFields]} onClose={handleOnCloseFlyout} labels={labels} + CardDecorator={CardDecorator} /> )} {exceptionItemToDelete && ( @@ -228,6 +234,7 @@ export const PolicyArtifactsLayout = React.memo<PolicyArtifactsLayoutProps>( canWriteArtifact={canWriteArtifact} getPolicyArtifactsPath={getPolicyArtifactsPath} getArtifactPath={getArtifactPath} + CardDecorator={CardDecorator} /> </EuiPageSection> </div> diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.test.tsx index 1ad26fd171c28..3d4469a4ec33f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.test.tsx @@ -68,6 +68,7 @@ describe('Policy details artifacts list', () => { canWriteArtifact={canWriteArtifact} getPolicyArtifactsPath={getPolicyEventFiltersPath} getArtifactPath={getEventFiltersListPath} + CardDecorator={undefined} /> ); await waitFor(() => expect(mockedApi.responseProvider.eventFiltersList).toHaveBeenCalled()); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.tsx index 497caf30d4020..082012295b023 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.tsx @@ -9,6 +9,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import type { Pagination } from '@elastic/eui'; import { EuiSpacer, EuiText } from '@elastic/eui'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import type { ArtifactEntryCardDecoratorProps } from '../../../../../components/artifact_entry_card'; import { useAppUrl } from '../../../../../../common/lib/kibana'; import { APP_UI_ID } from '../../../../../../../common/constants'; import { SearchExceptions } from '../../../../../components/search_exceptions'; @@ -38,6 +39,7 @@ interface PolicyArtifactsListProps { labels: typeof POLICY_ARTIFACT_LIST_LABELS; onDeleteActionCallback: (item: ExceptionListItemSchema) => void; canWriteArtifact?: boolean; + CardDecorator: React.ComponentType<ArtifactEntryCardDecoratorProps> | undefined; } export const PolicyArtifactsList = React.memo<PolicyArtifactsListProps>( @@ -50,6 +52,7 @@ export const PolicyArtifactsList = React.memo<PolicyArtifactsListProps>( labels, onDeleteActionCallback, canWriteArtifact = false, + CardDecorator, }) => { useOldUrlSearchPaginationReplace(); const { getAppUrl } = useAppUrl(); @@ -192,6 +195,7 @@ export const PolicyArtifactsList = React.memo<PolicyArtifactsListProps>( pagination={artifacts ? pagination : undefined} loading={isLoadingArtifacts || isRefetchingArtifacts} data-test-subj={'artifacts-collapsed-list'} + CardDecorator={CardDecorator} /> </> ); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/tabs/policy_tabs.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/tabs/policy_tabs.tsx index fd7e8793535e2..cb480615d27a5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/tabs/policy_tabs.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/tabs/policy_tabs.tsx @@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; +import { EventFiltersProcessDescendantIndicator } from '../../../../components/artifact_entry_card/components/card_decorators/event_filters_process_descendant_indicator'; import { UnsavedChangesConfirmModal } from './unsaved_changes_confirm_modal'; import { useLicense } from '../../../../../common/hooks/use_license'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; @@ -290,6 +291,7 @@ export const PolicyTabs = React.memo(() => { getArtifactPath={getEventFiltersListPath} getPolicyArtifactsPath={getPolicyEventFiltersPath} canWriteArtifact={canWriteEventFilters} + CardDecorator={EventFiltersProcessDescendantIndicator} /> </> ), From 965062dbea52e975389b9e2e1d6023c501c16dfa Mon Sep 17 00:00:00 2001 From: Jatin Kathuria <jatin.kathuria@elastic.co> Date: Tue, 2 Jul 2024 18:34:34 +0200 Subject: [PATCH 096/128] [Security Solution] [Fix] Row Renderer + Notes in Flyout (#186948) ## Summary This PR introduces below mentioned 3 changes: ### Row Renderer Switch A quick switch to switch on/off all the row-renderers without going into settings. > [!Caution] > This is only available with feature flag `unifiedComponentsInTimelineEnabled` https://github.com/elastic/kibana/assets/7485038/88fcf4e0-a5a2-4158-bc86-b313002790ec ### Notes in a separate Flyout - Notes do not appear inline anymore. They are now part of separate Flyout. - This Change also introduces a notification dot to highlight that existing notes are available. https://github.com/elastic/kibana/assets/7485038/491e256f-36dc-42f3-89f3-4c8c90c969a5 ### Color Distinction between enabled/disabled Row Renderers. Previously it was difficult to see what row renderers are available and what are not. This change introduces a small color distinction. https://github.com/elastic/kibana/assets/7485038/ba250c4c-cb93-4bc7-b593-235ccaf207cb ## Desk Testing Please desk test following functionalities with AND without below feature flag: 1. Add Note 2. Cancel when adding note. 3. Create a new timeline 4. Load saved timeline 5. Change from one timeline to other 6. Open Timeline page directly with saved timeline in the address bar. In all above scenarios row renders and notes should be shown as expected. ``` xpack.securitySolution.enableExperimental: - unifiedComponentsInTimelineEnabled ``` --- .../common/types/header_actions/index.ts | 5 + .../control_columns/row_action/index.tsx | 1 + .../header_actions/actions.test.tsx | 50 ++ .../components/header_actions/actions.tsx | 108 ++-- .../add_note_icon_item.test.tsx | 149 +++++- .../header_actions/add_note_icon_item.tsx | 25 +- .../components/header_actions/translations.ts | 20 +- .../public/common/mock/global_state.ts | 19 +- .../public/common/mock/timeline_results.ts | 23 +- .../components/alerts_table/actions.test.tsx | 2 +- .../use_investigate_in_timeline.tsx | 3 + .../left/components/notes_list.test.tsx | 20 +- .../public/notes/store/notes.slice.test.ts | 2 +- .../actions/new_timeline_button.test.tsx | 25 + .../components/new_timeline/index.test.tsx | 2 + .../notes/note_cards/index.test.tsx | 2 + .../components/notes/note_cards/index.tsx | 27 +- .../components/open_timeline/helpers.ts | 3 + .../components/open_timeline/index.tsx | 4 + .../row_renderer_switch/index.test.tsx | 75 +++ .../components/row_renderer_switch/index.tsx | 89 ++++ .../row_renderer_switch/translations.ts | 22 + .../row_renderers_browser.tsx | 5 + .../body/events/event_column_view.test.tsx | 41 +- .../body/events/event_column_view.tsx | 5 +- .../components/timeline/body/events/index.tsx | 3 + .../timeline/body/events/stateful_event.tsx | 88 +--- .../components/timeline/body/helpers.test.ts | 8 +- .../components/timeline/body/helpers.tsx | 2 +- .../components/timeline/body/index.test.tsx | 81 --- .../components/timeline/body/index.tsx | 3 + .../body/unified_timeline_body.test.tsx | 2 - .../timeline/body/unified_timeline_body.tsx | 4 - .../timelines/components/timeline/index.tsx | 3 + .../timeline/properties/helpers.test.tsx | 90 ++++ .../timeline/properties/helpers.tsx | 71 +-- .../timeline/properties/notes_flyout.test.tsx | 89 ++++ .../timeline/properties/notes_flyout.tsx | 87 ++++ .../properties/use_notes_in_flyout.test.tsx | 201 ++++++++ .../properties/use_notes_in_flyout.ts | 99 ++++ .../timelines/components/timeline/styles.tsx | 1 - .../components/timeline/tabs/eql/index.tsx | 45 +- .../components/timeline/tabs/pinned/index.tsx | 91 +++- .../timeline/tabs/query/index.test.tsx | 2 + .../components/timeline/tabs/query/index.tsx | 46 +- .../query_tab_unified_components.test.tsx | 468 +++++++++++++++--- .../use_timeline_control_columns.test.tsx | 9 + .../shared/use_timeline_control_columns.tsx | 49 +- .../timelines/components/timeline/types.ts | 12 +- ...stom_timeline_data_grid_body.test.tsx.snap | 152 ------ .../data_table/control_column_cell_render.tsx | 40 +- .../custom_timeline_data_grid_body.test.tsx | 70 --- .../custom_timeline_data_grid_body.tsx | 98 +--- .../unified_components/data_table/index.tsx | 39 +- .../toolbar_additional_controls.tsx | 2 + .../unified_components/index.test.tsx | 2 - .../timeline/unified_components/index.tsx | 35 -- .../timeline/unified_components/styles.tsx | 8 + .../timelines/hooks/use_create_timeline.tsx | 5 + .../public/timelines/store/defaults.ts | 23 +- .../store/middlewares/timeline_note.test.ts | 5 +- .../public/timelines/store/selectors.ts | 5 + 62 files changed, 1888 insertions(+), 877 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/index.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/translations.ts create mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/properties/notes_flyout.test.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/properties/notes_flyout.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_notes_in_flyout.test.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_notes_in_flyout.ts diff --git a/x-pack/plugins/security_solution/common/types/header_actions/index.ts b/x-pack/plugins/security_solution/common/types/header_actions/index.ts index 2bb0c4ff5f33a..abbb5d115fc40 100644 --- a/x-pack/plugins/security_solution/common/types/header_actions/index.ts +++ b/x-pack/plugins/security_solution/common/types/header_actions/index.ts @@ -103,11 +103,16 @@ export interface ActionProps { setEventsDeleted: SetEventsDeleted; setEventsLoading: SetEventsLoading; showCheckboxes: boolean; + /** + * This prop is used to determine if the notes button should be displayed + * as the part of Row Actions + * */ showNotes?: boolean; tabType?: string; timelineId: string; toggleShowNotes?: () => void; width?: number; + disablePinAction?: boolean; } interface AdditionalControlColumnProps { diff --git a/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx b/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx index 92ae4d094ed48..a86c1f181485d 100644 --- a/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx @@ -231,6 +231,7 @@ const RowActionComponent = ({ setEventsLoading={setEventsLoading} setEventsDeleted={setEventsDeleted} refetch={refetch} + showNotes={!expandableFlyoutDisabled && securitySolutionNotesEnabled ? true : false} /> )} </> diff --git a/x-pack/plugins/security_solution/public/common/components/header_actions/actions.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_actions/actions.test.tsx index a305c41cdc808..bc1ae98fe1be0 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_actions/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_actions/actions.test.tsx @@ -45,6 +45,12 @@ jest.mock( }) ); +jest.mock('./add_note_icon_item', () => { + return { + AddEventNoteAction: jest.fn(() => <div data-test-subj="add-note-mock-action" />), + }; +}); + jest.mock('../../lib/kibana', () => { const originalKibanaLib = jest.requireActual('../../lib/kibana'); @@ -430,6 +436,28 @@ describe('Actions', () => { }); }); + describe('Show notes action', () => { + test('should show notes action if showNotes is true', () => { + const wrapper = mount( + <TestProviders> + <Actions {...defaultProps} showNotes={true} /> + </TestProviders> + ); + + expect(wrapper.find('[data-test-subj="add-note-mock-action"]').exists()).toBeTruthy(); + }); + + test('should NOT show notes action if showNotes is false', () => { + const wrapper = mount( + <TestProviders> + <Actions {...defaultProps} showNotes={false} /> + </TestProviders> + ); + + expect(wrapper.find('[data-test-subj="add-note-mock-action"]').exists()).toBeFalsy(); + }); + }); + describe('Expand action', () => { test('should not be visible if disableExpandAction is true', () => { const wrapper = mount( @@ -441,4 +469,26 @@ describe('Actions', () => { expect(wrapper.find('[data-test-subj="expand-event"]').exists()).toBeFalsy(); }); }); + + describe('Pin action', () => { + test('should hide pin Action by default', () => { + const wrapper = mount( + <TestProviders> + <Actions {...defaultProps} disableExpandAction /> + </TestProviders> + ); + + expect(wrapper.find('[data-test-subj="pin-event"]').exists()).toBeFalsy(); + }); + + test('should show pin Action by when disablePinAction = false', () => { + const wrapper = mount( + <TestProviders> + <Actions {...defaultProps} disableExpandAction disablePinAction={false} /> + </TestProviders> + ); + + expect(wrapper.find('[data-test-subj="pin-event"]').exists()).toBeTruthy(); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/header_actions/actions.tsx b/x-pack/plugins/security_solution/public/common/components/header_actions/actions.tsx index 7d346d4fd2c7b..9ae3f3adfed80 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_actions/actions.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_actions/actions.tsx @@ -34,7 +34,6 @@ import { useGlobalFullScreen, useTimelineFullScreen } from '../../containers/use import { ALERTS_ACTIONS } from '../../lib/apm/user_actions'; import { setActiveTabTimeline } from '../../../timelines/store/actions'; import { EventsTdContent } from '../../../timelines/components/timeline/styles'; -import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; import { AlertContextMenu } from '../../../detections/components/alerts_table/timeline_actions/alert_context_menu'; import { InvestigateInTimelineAction } from '../../../detections/components/alerts_table/timeline_actions/investigate_in_timeline_action'; import * as i18n from './translations'; @@ -43,12 +42,15 @@ import { AlertsCasesTourSteps, SecurityStepId } from '../guided_onboarding_tour/ import { isDetectionsAlertsTable } from '../top_n/helpers'; import { GuidedOnboardingTourStep } from '../guided_onboarding_tour/tour_step'; import { DEFAULT_ACTION_BUTTON_WIDTH, isAlert } from './helpers'; +import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; const ActionsContainer = styled.div` align-items: center; display: flex; `; +const emptyNotes: string[] = []; + const ActionsComponent: React.FC<ActionProps> = ({ ariaRowindex, columnValues, @@ -62,16 +64,12 @@ const ActionsComponent: React.FC<ActionProps> = ({ onRuleChange, showNotes, timelineId, - toggleShowNotes, refetch, + toggleShowNotes, + disablePinAction = true, }) => { const dispatch = useDispatch(); - const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled( - 'securitySolutionNotesEnabled' - ); - const expandableFlyoutDisabled = useIsExperimentalFeatureEnabled('expandableFlyoutDisabled'); - const emptyNotes: string[] = []; const { timelineType } = useShallowEqualSelector((state) => isTimelineScope(timelineId) ? selectTimelineById(state, timelineId) : timelineDefaults ); @@ -110,8 +108,6 @@ const ActionsComponent: React.FC<ActionProps> = ({ ); }, [ecsData, eventType]); - const notes = useSelector((state: State) => selectNotesByDocumentId(state, eventId)); - const isDisabled = !useIsInvestigateInResolverActionEnabled(ecsData); const { setGlobalFullScreen } = useGlobalFullScreen(); const { setTimelineFullScreen } = useTimelineFullScreen(); @@ -220,6 +216,35 @@ const ActionsComponent: React.FC<ActionProps> = ({ onEventDetailsPanelOpened(); }, [activeStep, incrementStep, isTourAnchor, isTourShown, onEventDetailsPanelOpened]); + const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled( + 'securitySolutionNotesEnabled' + ); + + const expandableFlyoutDisabled = useIsExperimentalFeatureEnabled('expandableFlyoutDisabled'); + + /* only applicable for new event based notes */ + const documentBasedNotes = useSelector((state: State) => selectNotesByDocumentId(state, eventId)); + + /* only applicable notes before event based notes */ + const timelineNoteIds = useMemo( + () => eventIdToNoteIds?.[eventId] ?? emptyNotes, + [eventIdToNoteIds, eventId] + ); + + const notesCount = useMemo( + () => + securitySolutionNotesEnabled && !expandableFlyoutDisabled + ? documentBasedNotes.length + : timelineNoteIds.length, + [documentBasedNotes, timelineNoteIds, securitySolutionNotesEnabled, expandableFlyoutDisabled] + ); + + const noteIds = useMemo(() => { + return securitySolutionNotesEnabled && !expandableFlyoutDisabled + ? documentBasedNotes.map((note) => note.noteId) + : timelineNoteIds; + }, [documentBasedNotes, timelineNoteIds, securitySolutionNotesEnabled, expandableFlyoutDisabled]); + return ( <ActionsContainer> <> @@ -254,51 +279,28 @@ const ActionsComponent: React.FC<ActionProps> = ({ /> )} </> - {securitySolutionNotesEnabled && !expandableFlyoutDisabled && toggleShowNotes && ( - <> - <AddEventNoteAction - ariaLabel={i18n.ADD_NOTES_FOR_ROW({ ariaRowindex, columnValues })} - key="add-event-note" - showNotes={false} - toggleShowNotes={toggleShowNotes} - timelineType={timelineType} - eventId={eventId} - notesCount={notes.length} - /> - <PinEventAction - ariaLabel={i18n.PIN_EVENT_FOR_ROW({ ariaRowindex, columnValues, isEventPinned })} - isAlert={isAlert(eventType)} - key="pin-event" - onPinClicked={handlePinClicked} - noteIds={eventIdToNoteIds ? eventIdToNoteIds[eventId] || emptyNotes : emptyNotes} - eventIsPinned={isEventPinned} - timelineType={timelineType} - /> - </> + {!isEventViewer && showNotes && ( + <AddEventNoteAction + ariaLabel={i18n.ADD_NOTES_FOR_ROW({ ariaRowindex, columnValues })} + key="add-event-note" + timelineType={timelineType} + notesCount={notesCount} + eventId={eventId} + toggleShowNotes={toggleShowNotes} + /> + )} + + {!isEventViewer && !disablePinAction && ( + <PinEventAction + ariaLabel={i18n.PIN_EVENT_FOR_ROW({ ariaRowindex, columnValues, isEventPinned })} + isAlert={isAlert(eventType)} + key="pin-event" + onPinClicked={handlePinClicked} + noteIds={noteIds} + eventIsPinned={isEventPinned} + timelineType={timelineType} + /> )} - {(!securitySolutionNotesEnabled || expandableFlyoutDisabled) && - !isEventViewer && - toggleShowNotes && ( - <> - <AddEventNoteAction - ariaLabel={i18n.ADD_NOTES_FOR_ROW({ ariaRowindex, columnValues })} - key="add-event-note" - showNotes={showNotes ?? false} - toggleShowNotes={toggleShowNotes} - timelineType={timelineType} - eventId={eventId} - /> - <PinEventAction - ariaLabel={i18n.PIN_EVENT_FOR_ROW({ ariaRowindex, columnValues, isEventPinned })} - isAlert={isAlert(eventType)} - key="pin-event" - onPinClicked={handlePinClicked} - noteIds={eventIdToNoteIds ? eventIdToNoteIds[eventId] || emptyNotes : emptyNotes} - eventIsPinned={isEventPinned} - timelineType={timelineType} - /> - </> - )} <AlertContextMenu ariaLabel={i18n.MORE_ACTIONS_FOR_ROW({ ariaRowindex, columnValues })} ariaRowindex={ariaRowindex} diff --git a/x-pack/plugins/security_solution/public/common/components/header_actions/add_note_icon_item.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_actions/add_note_icon_item.test.tsx index 9c3b793197c9c..d3299d0816594 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_actions/add_note_icon_item.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_actions/add_note_icon_item.test.tsx @@ -6,59 +6,158 @@ */ import { TimelineType } from '../../../../common/api/timeline'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import type { ComponentProps } from 'react'; import React from 'react'; import { TestProviders } from '../../mock'; -import { useUserPrivileges } from '../user_privileges'; import { getEndpointPrivilegesInitialStateMock } from '../user_privileges/endpoint/mocks'; import { AddEventNoteAction } from './add_note_icon_item'; +import { NotesButton } from '../../../timelines/components/timeline/properties/helpers'; +import { useUserPrivileges } from '../user_privileges'; + +jest.mock('../../../timelines/components/timeline/properties/helpers', () => { + return { + NotesButton: jest.fn(), + }; +}); jest.mock('../user_privileges'); const useUserPrivilegesMock = useUserPrivileges as jest.Mock; +const NotesButtonMock = NotesButton as unknown as jest.Mock; + +const TestWrapper = (props: ComponentProps<typeof TestProviders>) => { + return <TestProviders {...props} />; +}; + +const toggleShowNotesMock = jest.fn(); + +const renderTestComponent = (props: Partial<ComponentProps<typeof AddEventNoteAction>> = {}) => { + const localProps: ComponentProps<typeof AddEventNoteAction> = { + timelineType: TimelineType.default, + eventId: 'event-1', + ariaLabel: 'Add Note', + toggleShowNotes: toggleShowNotesMock, + notesCount: 2, + ...props, + }; + + return render(<AddEventNoteAction {...localProps} />, { + wrapper: TestWrapper, + }); +}; + describe('AddEventNoteAction', () => { beforeEach(() => { jest.clearAllMocks(); + + useUserPrivilegesMock.mockReturnValue({ + kibanaSecuritySolutionsPrivileges: { crud: true, read: true }, + endpointPrivileges: getEndpointPrivilegesInitialStateMock(), + }); + + NotesButtonMock.mockImplementation(({ isDisabled }: { isDisabled: boolean }) => ( + <button + type="button" + disabled={isDisabled} + data-test-subj="timeline-notes-button-small-mock" + /> + )); }); - describe('isDisabled', () => { - test('it disables the add note button when the user does NOT have crud privileges', () => { + describe('display notes button', () => { + test('should render button correctly when multiple notes exist', async () => { + renderTestComponent({ eventId: 'event-1' }); + + await waitFor(() => { + expect(screen.getByTestId('timeline-notes-button-small-mock')).not.toBeDisabled(); + }); + + expect(NotesButtonMock).toHaveBeenCalledWith( + expect.objectContaining({ + ariaLabel: 'Add Note', + 'data-test-subj': 'add-note', + isDisabled: false, + timelineType: TimelineType.default, + toggleShowNotes: expect.any(Function), + toolTip: '2 Notes available. Click to view them & add more.', + eventId: 'event-1', + notesCount: 2, + }), + expect.anything() + ); + }); + + test('should render button correctly when single note exists', async () => { + renderTestComponent({ eventId: 'event-2', notesCount: 1 }); + + await waitFor(() => { + expect(screen.getByTestId('timeline-notes-button-small-mock')).not.toBeDisabled(); + }); + + expect(NotesButtonMock).toHaveBeenCalledWith( + expect.objectContaining({ + ariaLabel: 'Add Note', + 'data-test-subj': 'add-note', + isDisabled: false, + timelineType: TimelineType.default, + toggleShowNotes: expect.any(Function), + toolTip: '1 Note available. Click to view it & add more.', + eventId: 'event-2', + notesCount: 1, + }), + expect.anything() + ); + }); + + test('should render button correctly when no note exist', async () => { + renderTestComponent({ eventId: 'event-3', notesCount: 0 }); + + await waitFor(() => { + expect(screen.getByTestId('timeline-notes-button-small-mock')).not.toBeDisabled(); + }); + + expect(NotesButtonMock).toHaveBeenCalledWith( + expect.objectContaining({ + ariaLabel: 'Add Note', + 'data-test-subj': 'add-note', + isDisabled: false, + timelineType: TimelineType.default, + toggleShowNotes: expect.any(Function), + toolTip: 'Add Note', + eventId: 'event-3', + notesCount: 0, + }), + expect.anything() + ); + }); + }); + + describe('button state', () => { + test('should disable the add note button when the user does NOT have crud privileges', () => { useUserPrivilegesMock.mockReturnValue({ kibanaSecuritySolutionsPrivileges: { crud: false, read: true }, endpointPrivileges: getEndpointPrivilegesInitialStateMock(), }); - render( - <TestProviders> - <AddEventNoteAction - showNotes={false} - timelineType={TimelineType.default} - toggleShowNotes={jest.fn} - /> - </TestProviders> - ); + renderTestComponent(); - expect(screen.getByTestId('timeline-notes-button-small')).toHaveProperty('disabled', true); + expect(screen.getByTestId('timeline-notes-button-small-mock')).toHaveProperty( + 'disabled', + true + ); }); - test('it enables the add note button when the user has crud privileges', () => { + test('should enable the add note button when the user has crud privileges', () => { useUserPrivilegesMock.mockReturnValue({ kibanaSecuritySolutionsPrivileges: { crud: true, read: true }, endpointPrivileges: getEndpointPrivilegesInitialStateMock(), }); - render( - <TestProviders> - <AddEventNoteAction - showNotes={false} - timelineType={TimelineType.default} - toggleShowNotes={jest.fn} - /> - </TestProviders> - ); + renderTestComponent(); - expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled(); + expect(screen.getByTestId('timeline-notes-button-small-mock')).not.toBeDisabled(); }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/header_actions/add_note_icon_item.tsx b/x-pack/plugins/security_solution/public/common/components/header_actions/add_note_icon_item.tsx index 82671b399ee62..ff9ad479e89c9 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_actions/add_note_icon_item.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_actions/add_note_icon_item.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { NotesButton } from '../../../timelines/components/timeline/properties/helpers'; import { TimelineType } from '../../../../common/api/timeline'; import { useUserPrivileges } from '../user_privileges'; @@ -14,19 +14,17 @@ import { ActionIconItem } from './action_icon_item'; interface AddEventNoteActionProps { ariaLabel?: string; - showNotes: boolean; timelineType: TimelineType; - toggleShowNotes: () => void; + toggleShowNotes?: () => void | ((eventId: string) => void); eventId?: string; - /** + /* * Number of notes associated with the event */ - notesCount?: number; + notesCount: number; } const AddEventNoteActionComponent: React.FC<AddEventNoteActionProps> = ({ ariaLabel, - showNotes, timelineType, toggleShowNotes, eventId, @@ -34,12 +32,10 @@ const AddEventNoteActionComponent: React.FC<AddEventNoteActionProps> = ({ }) => { const { kibanaSecuritySolutionsPrivileges } = useUserPrivileges(); - const tooltip = - notesCount && notesCount > 0 - ? i18n.NOTE_COUNT_TOOLTIP(notesCount) - : timelineType === TimelineType.template - ? i18n.NOTES_DISABLE_TOOLTIP - : i18n.NOTES_TOOLTIP; + const NOTES_TOOLTIP = useMemo( + () => (notesCount > 0 ? i18n.NOTES_COUNT_TOOLTIP({ notesCount }) : i18n.NOTES_ADD_TOOLTIP), + [notesCount] + ); return ( <ActionIconItem> @@ -47,10 +43,11 @@ const AddEventNoteActionComponent: React.FC<AddEventNoteActionProps> = ({ ariaLabel={ariaLabel} data-test-subj="add-note" isDisabled={kibanaSecuritySolutionsPrivileges.crud === false} - showNotes={showNotes} timelineType={timelineType} toggleShowNotes={toggleShowNotes} - toolTip={tooltip} + toolTip={ + timelineType === TimelineType.template ? i18n.NOTES_DISABLE_TOOLTIP : NOTES_TOOLTIP + } eventId={eventId} notesCount={notesCount} /> diff --git a/x-pack/plugins/security_solution/public/common/components/header_actions/translations.ts b/x-pack/plugins/security_solution/public/common/components/header_actions/translations.ts index 4db668cc78d16..10832ccfac1e5 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_actions/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/header_actions/translations.ts @@ -21,19 +21,23 @@ export const NOTES_DISABLE_TOOLTIP = i18n.translate( } ); -export const NOTE_COUNT_TOOLTIP = (notesCount: number) => - i18n.translate('xpack.securitySolution.notes.noteCountTooltip', { - defaultMessage: '{notesCount} {notesCount, plural, one { note } other { notes }}', - values: { notesCount }, - }); - -export const NOTES_TOOLTIP = i18n.translate( +export const NOTES_ADD_TOOLTIP = i18n.translate( 'xpack.securitySolution.timeline.body.notes.addNoteTooltip', { - defaultMessage: 'Add note', + defaultMessage: 'Add Note', } ); +export const NOTES_COUNT_TOOLTIP = ({ notesCount }: { notesCount: number }) => + i18n.translate( + 'xpack.securitySolution.timeline.body.notes.addNote.multipleNotesAvailableTooltip', + { + values: { notesCount }, + defaultMessage: + '{notesCount} {notesCount, plural, one {Note} other {Notes} } available. Click to view {notesCount, plural, one {it} other {them}} & add more.', + } + ); + export const SORT_FIELDS = i18n.translate('xpack.securitySolution.timeline.sortFieldsButton', { defaultMessage: 'Sort fields', }); diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index 0a8aebee35f55..cacbbd243be7d 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -71,7 +71,19 @@ export const mockSourcererState: SourcererState = { export const mockGlobalState: State = { app: { - notesById: {}, + notesById: { + '1': { + created: new Date('2024-07-02T08:32:29.233Z'), + id: '1', + lastEdit: new Date('2024-07-02T08:32:29.233Z'), + note: 'New Note', + user: 'elastic', + saveObjectId: 'c1a44f63-eb20-4c65-a050-eb9e842d8492', + version: 'WzIyNDUsMV0=', + eventId: '1', + timelineId: 'some-timeline-id', + }, + }, errors: [ { id: 'error-id-1', title: 'title-1', message: ['error-message-1'] }, { id: 'error-id-2', title: 'title-2', message: ['error-message-2'] }, @@ -323,6 +335,7 @@ export const mockGlobalState: State = { timelineById: { [TimelineId.test]: { activeTab: TimelineTabs.query, + createdBy: 'elastic', prevActiveTab: TimelineTabs.notes, dataViewId: DEFAULT_DATA_VIEW_ID, deletedEventIds: [], @@ -341,7 +354,7 @@ export const mockGlobalState: State = { tiebreakerField: '', timestampField: '@timestamp', }, - eventIdToNoteIds: {}, + eventIdToNoteIds: { '1': ['1'] }, excludedRowRendererIds: [], expandedDetail: {}, highlightedDropAndProviderId: '', @@ -506,7 +519,7 @@ export const mockGlobalState: State = { notes: { entities: { '1': { - eventId: 'document-id-1', + eventId: '1', // should be a valid id based on mockTimelineData noteId: '1', note: 'note-1', timelineId: 'timeline-1', diff --git a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts index 7c2392445099c..0800d5cc14c81 100644 --- a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts +++ b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts @@ -11,7 +11,7 @@ import type { DataTableModel } from '@kbn/securitysolution-data-table'; import { VIEW_SELECTION } from '../../../common/constants'; import type { TimelineResult } from '../../../common/api/timeline'; import { TimelineId, TimelineTabs } from '../../../common/types/timeline'; -import { TimelineType, TimelineStatus } from '../../../common/api/timeline'; +import { RowRendererId, TimelineType, TimelineStatus } from '../../../common/api/timeline'; import type { OpenTimelineResult } from '../../timelines/components/open_timeline/types'; import type { TimelineEventsDetailsItem } from '../../../common/search_strategy'; @@ -2068,7 +2068,26 @@ export const defaultTimelineProps: CreateTimelineProps = { }, eventIdToNoteIds: {}, eventType: 'all', - excludedRowRendererIds: [], + excludedRowRendererIds: [ + RowRendererId.alert, + RowRendererId.alerts, + RowRendererId.auditd, + RowRendererId.auditd_file, + RowRendererId.library, + RowRendererId.netflow, + RowRendererId.plain, + RowRendererId.registry, + RowRendererId.suricata, + RowRendererId.system, + RowRendererId.system_dns, + RowRendererId.system_endgame_process, + RowRendererId.system_file, + RowRendererId.system_fim, + RowRendererId.system_security_event, + RowRendererId.system_socket, + RowRendererId.threat_match, + RowRendererId.zeek, + ], expandedDetail: {}, filters: [], highlightedDropAndProviderId: '', diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx index 5049ccd92e116..91ef01befe175 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx @@ -384,7 +384,7 @@ describe('alert actions', () => { }, eventIdToNoteIds: {}, eventType: 'all', - excludedRowRendererIds: [], + excludedRowRendererIds: defaultTimelineProps.timeline.excludedRowRendererIds, expandedDetail: {}, filters: [ { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx index 0cbd2daa58221..1035a508f7012 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx @@ -163,6 +163,9 @@ export const useInvestigateInTimeline = ({ columns: unifiedComponentsInTimelineEnabled ? defaultUdtHeaders : defaultHeaders, indexNames: timeline.indexNames ?? [], show: true, + excludedRowRendererIds: unifiedComponentsInTimelineEnabled + ? timeline.excludedRowRendererIds + : [], }, to: toTimeline, ruleNote, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_list.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_list.test.tsx index e35d71ec28d55..6678732ca7827 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_list.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_list.test.tsx @@ -41,7 +41,7 @@ jest.mock('react-redux', () => { const renderNotesList = () => render( <TestProviders> - <NotesList eventId={'document-id-1'} /> + <NotesList eventId={'1'} /> </TestProviders> ); @@ -69,7 +69,7 @@ describe('NotesList', () => { const { getByTestId } = render( <TestProviders store={store}> - <NotesList eventId={'document-id-1'} /> + <NotesList eventId={'1'} /> </TestProviders> ); @@ -115,7 +115,7 @@ describe('NotesList', () => { render( <TestProviders store={store}> - <NotesList eventId={'document-id-1'} /> + <NotesList eventId={'1'} /> </TestProviders> ); @@ -131,7 +131,7 @@ describe('NotesList', () => { ...mockGlobalState.notes, entities: { '1': { - eventId: 'document-id-1', + eventId: '1', noteId: '1', note: 'note-1', timelineId: '', @@ -147,7 +147,7 @@ describe('NotesList', () => { const { getByTestId } = render( <TestProviders store={store}> - <NotesList eventId={'document-id-1'} /> + <NotesList eventId={'1'} /> </TestProviders> ); const { getByText } = within(getByTestId(`${NOTE_AVATAR_TEST_ID}-0`)); @@ -169,7 +169,7 @@ describe('NotesList', () => { const { getByTestId } = render( <TestProviders store={store}> - <NotesList eventId={'document-id-1'} /> + <NotesList eventId={'1'} /> </TestProviders> ); @@ -203,7 +203,7 @@ describe('NotesList', () => { const { getByTestId } = render( <TestProviders store={store}> - <NotesList eventId={'document-id-1'} /> + <NotesList eventId={'1'} /> </TestProviders> ); @@ -228,7 +228,7 @@ describe('NotesList', () => { render( <TestProviders store={store}> - <NotesList eventId={'document-id-1'} /> + <NotesList eventId={'1'} /> </TestProviders> ); @@ -261,7 +261,7 @@ describe('NotesList', () => { ...mockGlobalState.notes, entities: { '1': { - eventId: 'document-id-1', + eventId: '1', noteId: '1', note: 'note-1', timelineId: '', @@ -277,7 +277,7 @@ describe('NotesList', () => { const { queryByTestId } = render( <TestProviders store={store}> - <NotesList eventId={'document-id-1'} /> + <NotesList eventId={'1'} /> </TestProviders> ); diff --git a/x-pack/plugins/security_solution/public/notes/store/notes.slice.test.ts b/x-pack/plugins/security_solution/public/notes/store/notes.slice.test.ts index 8290edb049e1e..ad0e3b198d0d9 100644 --- a/x-pack/plugins/security_solution/public/notes/store/notes.slice.test.ts +++ b/x-pack/plugins/security_solution/public/notes/store/notes.slice.test.ts @@ -506,7 +506,7 @@ describe('notesSlice', () => { }); it('should return all notes for an existing document id', () => { - expect(selectNotesByDocumentId(mockGlobalState, 'document-id-1')).toEqual([ + expect(selectNotesByDocumentId(mockGlobalState, '1')).toEqual([ mockGlobalState.notes.entities['1'], ]); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/new_timeline_button.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/new_timeline_button.test.tsx index 097d2d256a2c7..4b1824130c70b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/new_timeline_button.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/new_timeline_button.test.tsx @@ -12,6 +12,9 @@ import { TimelineId } from '../../../../../common/types'; import { timelineActions } from '../../../store'; import { defaultHeaders } from '../../timeline/body/column_headers/default_headers'; import { TestProviders } from '../../../../common/mock'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { RowRendererId } from '../../../../../common/api/timeline'; +import { defaultUdtHeaders } from '../../timeline/unified_components/default_headers'; jest.mock('../../../../common/components/discover_in_timeline/use_discover_in_timeline_context'); jest.mock('../../../../common/hooks/use_selector'); @@ -70,6 +73,28 @@ describe('NewTimelineButton', () => { show: true, timelineType: 'default', updated: undefined, + excludedRowRendererIds: [], + }); + }); + + // enable unified components in timeline + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); + + getByTestId('timeline-modal-new-timeline-dropdown-button').click(); + getByTestId('timeline-modal-new-timeline').click(); + + spy.mockClear(); + + await waitFor(() => { + expect(spy).toHaveBeenCalledWith({ + columns: defaultUdtHeaders, + dataViewId, + id: TimelineId.test, + indexNames: selectedPatterns, + show: true, + timelineType: 'default', + updated: undefined, + excludedRowRendererIds: [...Object.keys(RowRendererId)], }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/new_timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/new_timeline/index.test.tsx index e1bc119235e2e..7a7e3ce6061c2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/new_timeline/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/new_timeline/index.test.tsx @@ -64,6 +64,7 @@ describe('NewTimelineButton', () => { show: true, timelineType: TimelineType.default, updated: undefined, + excludedRowRendererIds: [], }); }); }); @@ -93,6 +94,7 @@ describe('NewTimelineButton', () => { show: true, timelineType: TimelineType.template, updated: undefined, + excludedRowRendererIds: [], }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.test.tsx index 0a7ed36a8495f..f5006589310c1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.test.tsx @@ -13,6 +13,7 @@ import { NoteCards } from '.'; import { TimelineStatus } from '../../../../../common/api/timeline'; import { TestProviders } from '../../../../common/mock'; import type { TimelineResultNote } from '../../open_timeline/types'; +import { TimelineId } from '../../../../../common/types'; const getNotesByIds = () => ({ abc: { @@ -60,6 +61,7 @@ describe('NoteCards', () => { status: TimelineStatus.active, toggleShowAddNote: jest.fn(), updateNote: jest.fn(), + timelineId: TimelineId.test, }; test('it renders the notes column when notes are specified', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx index 3616e4352cd89..656c80384fe86 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx @@ -41,24 +41,37 @@ const NotesContainer = styled(EuiFlexGroup)` `; NotesContainer.displayName = 'NotesContainer'; -interface Props { +export interface NoteCardsProps { ariaRowindex: number; associateNote: AssociateNote; className?: string; notes: TimelineResultNote[]; showAddNote: boolean; - toggleShowAddNote: (eventId?: string) => void; + toggleShowAddNote?: (eventId?: string) => void; eventId?: string; + timelineId: string; + onCancel?: () => void; } /** A view for entering and reviewing notes */ -export const NoteCards = React.memo<Props>( - ({ ariaRowindex, associateNote, className, notes, showAddNote, toggleShowAddNote, eventId }) => { +export const NoteCards = React.memo<NoteCardsProps>( + ({ + ariaRowindex, + associateNote, + className, + notes, + showAddNote, + toggleShowAddNote, + eventId, + timelineId, + onCancel, + }) => { const [newNote, setNewNote] = useState(''); const associateNoteAndToggleShow = useCallback( (noteId: string) => { associateNote(noteId); + if (!toggleShowAddNote) return; if (eventId != null) { toggleShowAddNote(eventId); } else { @@ -69,12 +82,14 @@ export const NoteCards = React.memo<Props>( ); const onCancelAddNote = useCallback(() => { + onCancel?.(); + if (!toggleShowAddNote) return; if (eventId != null) { toggleShowAddNote(eventId); } else { toggleShowAddNote(); } - }, [eventId, toggleShowAddNote]); + }, [eventId, toggleShowAddNote, onCancel]); return ( <NoteCardsCompContainer @@ -94,7 +109,7 @@ export const NoteCards = React.memo<Props>( <EuiScreenReaderOnly data-test-subj="screenReaderOnly"> <p>{i18n.YOU_ARE_VIEWING_NOTES(ariaRowindex)}</p> </EuiScreenReaderOnly> - <NotePreviews notes={notes} /> + <NotePreviews timelineId={timelineId} notes={notes} /> </NotesContainer> </NotePreviewsContainer> ) : null} diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts index 835862c04ced8..56d3937c301de 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts @@ -358,6 +358,9 @@ export const useQueryTimelineById = () => { show: openTimeline, initialized: true, savedSearchId: savedSearchId ?? null, + excludedRowRendererIds: unifiedComponentsInTimelineEnabled + ? timelineDefaults.excludedRowRendererIds + : [], }, }); resetDiscoverAppState(); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx index 11e35ce4a800a..8bab2e7dbe7a2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx @@ -57,6 +57,7 @@ import { useSourcererDataView } from '../../../sourcerer/containers'; import { useStartTransaction } from '../../../common/lib/apm/use_start_transaction'; import { TIMELINE_ACTIONS } from '../../../common/lib/apm/user_actions'; import { defaultUdtHeaders } from '../timeline/unified_components/default_headers'; +import { timelineDefaults } from '../../store/defaults'; interface OwnProps<TCache = object> { /** Displays open timeline in modal */ @@ -255,6 +256,9 @@ export const StatefulOpenTimelineComponent = React.memo<OpenTimelineOwnProps>( dataViewId, indexNames: selectedPatterns, show: false, + excludedRowRendererIds: unifiedComponentsInTimelineEnabled + ? timelineDefaults.excludedRowRendererIds + : [], }) ); } diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/index.test.tsx new file mode 100644 index 0000000000000..3978c06f2784d --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/index.test.tsx @@ -0,0 +1,75 @@ +/* + * 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 { ComponentProps } from 'react'; +import React from 'react'; +import { createMockStore, mockGlobalState, TestProviders } from '../../../common/mock'; +import { fireEvent, render, waitFor } from '@testing-library/react'; +import { RowRendererSwitch } from '.'; +import { TimelineId } from '../../../../common/types'; +import { RowRendererId } from '../../../../common/api/timeline'; + +const localState = structuredClone(mockGlobalState); + +// exclude all row renderers by default +localState.timeline.timelineById[TimelineId.test].excludedRowRendererIds = + Object.values(RowRendererId); + +const renderTestComponent = (props?: ComponentProps<typeof TestProviders>) => { + const store = props?.store ?? createMockStore(localState); + return render( + <TestProviders {...props} store={store}> + <RowRendererSwitch timelineId={TimelineId.test} /> + </TestProviders> + ); +}; + +describe('Row Renderer Switch', () => { + it('should render correctly', () => { + const { getByTestId } = renderTestComponent(); + + expect(getByTestId('row-renderer-switch')).toBeVisible(); + expect(getByTestId('row-renderer-switch')).toHaveAttribute('aria-checked', 'false'); + }); + + it('should successfully enable all row renderers', async () => { + const localStore = createMockStore(localState); + const { getByTestId } = renderTestComponent({ store: localStore }); + + fireEvent.click(getByTestId('row-renderer-switch')); + + await waitFor(() => { + expect(getByTestId('row-renderer-switch')).toHaveAttribute('aria-checked', 'true'); + + expect( + localStore.getState().timeline.timelineById[TimelineId.test].excludedRowRendererIds + ).toMatchObject([]); + }); + }); + + it('should successfully disable all row renderers', async () => { + const localStore = createMockStore(localState); + const { getByTestId } = renderTestComponent({ store: localStore }); + + // enable all row renderers + fireEvent.click(getByTestId('row-renderer-switch')); + + await waitFor(() => { + expect(getByTestId('row-renderer-switch')).toHaveAttribute('aria-checked', 'true'); + }); + + // disable all row renderers + fireEvent.click(getByTestId('row-renderer-switch')); + + await waitFor(() => { + expect(getByTestId('row-renderer-switch')).toHaveAttribute('aria-checked', 'false'); + expect( + localStore.getState().timeline.timelineById[TimelineId.test].excludedRowRendererIds + ).toMatchObject(Object.values(RowRendererId)); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/index.tsx new file mode 100644 index 0000000000000..12a6127a39053 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/index.tsx @@ -0,0 +1,89 @@ +/* + * 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 { EuiSwitchEvent } from '@elastic/eui'; +import { EuiToolTip, EuiSwitch, EuiFormRow, useGeneratedHtmlId } from '@elastic/eui'; +import React, { useCallback, useMemo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import styled from 'styled-components'; +import { RowRendererId } from '../../../../common/api/timeline'; +import type { State } from '../../../common/store'; +import { setExcludedRowRendererIds } from '../../store/actions'; +import { selectExcludedRowRendererIds } from '../../store/selectors'; +import * as i18n from './translations'; + +interface RowRendererSwitchProps { + timelineId: string; +} + +const CustomFormRow = styled(EuiFormRow)` + .euiFormRow__label { + font-weight: 400; + } +`; + +export const RowRendererSwitch = React.memo(function RowRendererSwitch( + props: RowRendererSwitchProps +) { + const toggleTextSwitchId = useGeneratedHtmlId({ prefix: 'rowRendererSwitch' }); + + const { timelineId } = props; + + const dispatch = useDispatch(); + + const excludedRowRendererIds = useSelector((state: State) => + selectExcludedRowRendererIds(state, timelineId) + ); + + const isAnyRowRendererEnabled = useMemo( + () => Object.values(RowRendererId).some((id) => !excludedRowRendererIds.includes(id)), + [excludedRowRendererIds] + ); + + const handleDisableAll = useCallback(() => { + dispatch( + setExcludedRowRendererIds({ + id: timelineId, + excludedRowRendererIds: Object.values(RowRendererId), + }) + ); + }, [dispatch, timelineId]); + + const handleEnableAll = useCallback(() => { + dispatch(setExcludedRowRendererIds({ id: timelineId, excludedRowRendererIds: [] })); + }, [dispatch, timelineId]); + + const onChange = useCallback( + (e: EuiSwitchEvent) => { + if (e.target.checked) { + handleEnableAll(); + } else { + handleDisableAll(); + } + }, + [handleDisableAll, handleEnableAll] + ); + + const rowRendererLabel = useMemo( + () => <span id={toggleTextSwitchId}>{i18n.EVENT_RENDERERS_SWITCH}</span>, + [toggleTextSwitchId] + ); + + return ( + <EuiToolTip position="top" content={i18n.EVENT_RENDERERS_SWITCH_WARNING}> + <CustomFormRow display="columnCompressedSwitch" label={rowRendererLabel}> + <EuiSwitch + data-test-subj="row-renderer-switch" + label="" + checked={isAnyRowRendererEnabled} + onChange={onChange} + compressed + /> + </CustomFormRow> + </EuiToolTip> + ); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/translations.ts new file mode 100644 index 0000000000000..064235fcfbfc5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/translations.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const EVENT_RENDERERS_SWITCH = i18n.translate( + 'xpack.securitySolution.timeline.eventRenderersSwitch.title', + { + defaultMessage: 'Row Renderers', + } +); + +export const EVENT_RENDERERS_SWITCH_WARNING = i18n.translate( + 'xpack.securitySolution.timeline.eventRenderersSwitch.warning', + { + defaultMessage: 'Enabling Row Renderers may impact table performance.', + } +); diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/row_renderers_browser.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/row_renderers_browser.tsx index eb195feee8858..45f7090139528 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/row_renderers_browser.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/row_renderers_browser.tsx @@ -22,6 +22,10 @@ interface RowRenderersBrowserProps { // eslint-disable-next-line @typescript-eslint/no-explicit-any const StyledEuiInMemoryTable = styled(EuiInMemoryTable as any)` .euiTable { + tr:has(.isNotSelected) { + background-color: ${(props) => props.theme.eui.euiColorLightestShade}; + } + tr > *:last-child { display: none; } @@ -105,6 +109,7 @@ const RowRenderersBrowserComponent = ({ <EuiCheckbox id={item.id} onChange={handleNameClick(item)} + className={`${!excludedRowRendererIds.includes(item.id) ? 'isSelected' : 'isNotSelected'}`} checked={!excludedRowRendererIds.includes(item.id)} /> ), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx index 34578db7e5a15..11cc6032242e3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx @@ -20,12 +20,14 @@ import { getDefaultControlColumn } from '../control_columns'; import { testLeadingControlColumn } from '../../../../../common/mock/mock_timeline_control_columns'; import { mockTimelines } from '../../../../../common/mock/mock_timelines_plugin'; import { mockCasesContract } from '@kbn/cases-plugin/public/mocks'; -import { - NOTES_DISABLE_TOOLTIP, - NOTES_TOOLTIP, -} from '../../../../../common/components/header_actions/translations'; import { getActionsColumnWidth } from '../../../../../common/components/header_actions'; +jest.mock('../../../../../common/components/header_actions/add_note_icon_item', () => { + return { + AddEventNoteAction: jest.fn(() => <div data-test-subj="add-note-button-mock" />), + }; +}); + jest.mock('../../../../../common/hooks/use_experimental_features'); const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock; jest.mock('../../../../../common/hooks/use_selector', () => ({ @@ -125,36 +127,15 @@ describe('EventColumnView', () => { wrappingComponent: TestProviders, }); - expect(wrapper.find('[data-test-subj="timeline-notes-button-small"]').exists()).toBe(false); + expect(wrapper.find('[data-test-subj="add-note-button-mock"]').exists()).toBe(false); }); - test('it invokes toggleShowNotes when the button for adding notes is clicked', () => { - const wrapper = mount(<EventColumnView {...props} />, { wrappingComponent: TestProviders }); - - expect(props.toggleShowNotes).not.toHaveBeenCalled(); - - wrapper.find('[data-test-subj="timeline-notes-button-small"]').first().simulate('click'); - - expect(props.toggleShowNotes).toHaveBeenCalled(); - }); - - test('it renders correct tooltip for NotesButton - timeline', () => { - const wrapper = mount(<EventColumnView {...props} />, { wrappingComponent: TestProviders }); - - expect(wrapper.find('[data-test-subj="add-note"]').prop('toolTip')).toEqual(NOTES_TOOLTIP); - }); - - test('it renders correct tooltip for NotesButton - timeline template', () => { - (useShallowEqualSelector as jest.Mock).mockReturnValue({ - timelineType: TimelineType.template, + test('it does NOT render a notes button when showNotes is false', () => { + const wrapper = mount(<EventColumnView {...props} showNotes={false} />, { + wrappingComponent: TestProviders, }); - const wrapper = mount(<EventColumnView {...props} />, { wrappingComponent: TestProviders }); - - expect(wrapper.find('[data-test-subj="add-note"]').prop('toolTip')).toEqual( - NOTES_DISABLE_TOOLTIP - ); - (useShallowEqualSelector as jest.Mock).mockReturnValue({ timelineType: TimelineType.default }); + expect(wrapper.find('[data-test-subj="add-note-button-mock"]').exists()).toBe(false); }); test('it does NOT render a pin button when isEventViewer is true', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx index d3d27ba083b7e..e184e27d428ef 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx @@ -48,7 +48,7 @@ interface Props { showNotes: boolean; tabType?: TimelineTabs; timelineId: string; - toggleShowNotes: () => void; + toggleShowNotes: (eventId?: string) => void; leadingControlColumns: ControlColumnProps[]; trailingControlColumns: ControlColumnProps[]; setEventsLoading: SetEventsLoading; @@ -149,6 +149,7 @@ export const EventColumnView = React.memo<Props>( toggleShowNotes={toggleShowNotes} setEventsLoading={setEventsLoading} setEventsDeleted={setEventsDeleted} + disablePinAction={false} /> )} </EventsTdGroupActions> @@ -173,12 +174,12 @@ export const EventColumnView = React.memo<Props>( refetch, selectedEventIds, showCheckboxes, - showNotes, tabType, timelineId, toggleShowNotes, setEventsLoading, setEventsDeleted, + showNotes, ] ); return ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx index 18bfd74ab9518..76c28f24b14d6 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx @@ -49,6 +49,7 @@ interface Props { tabType?: TimelineTabs; leadingControlColumns: ControlColumnProps[]; trailingControlColumns: ControlColumnProps[]; + onToggleShowNotes?: (eventId?: string) => void; } const EventsComponent: React.FC<Props> = ({ @@ -72,6 +73,7 @@ const EventsComponent: React.FC<Props> = ({ tabType, leadingControlColumns, trailingControlColumns, + onToggleShowNotes, }) => ( <EventsTbody data-test-subj="events"> {data.map((event, i) => ( @@ -100,6 +102,7 @@ const EventsComponent: React.FC<Props> = ({ timelineId={id} leadingControlColumns={leadingControlColumns} trailingControlColumns={trailingControlColumns} + onToggleShowNotes={onToggleShowNotes} /> ))} </EventsTbody> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx index 0e100d9a25bc3..ce8de17c22216 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx @@ -11,12 +11,8 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { isEventBuildingBlockType } from '@kbn/securitysolution-data-table'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; -import { LeftPanelNotesTab } from '../../../../../flyout/document_details/left'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; -import { - DocumentDetailsLeftPanelKey, - DocumentDetailsRightPanelKey, -} from '../../../../../flyout/document_details/shared/constants/panel_keys'; +import { DocumentDetailsRightPanelKey } from '../../../../../flyout/document_details/shared/constants/panel_keys'; import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector'; import type { ColumnHeaderOptions, @@ -32,7 +28,6 @@ import type { OnRowSelected } from '../../events'; import { STATEFUL_EVENT_CSS_CLASS_NAME } from '../../helpers'; import { EventsTrGroup, EventsTrSupplement, EventsTrSupplementContainer } from '../../styles'; import { getEventType, isEvenEqlSequence } from '../helpers'; -import { NoteCards } from '../../../notes/note_cards'; import { useEventDetailsWidthContext } from '../../../../../common/components/events_viewer/event_details_width_context'; import { EventColumnView } from './event_column_view'; import type { inputsModel } from '../../../../../common/store'; @@ -74,6 +69,7 @@ interface Props { timelineId: string; leadingControlColumns: ControlColumnProps[]; trailingControlColumns: ControlColumnProps[]; + onToggleShowNotes?: (eventId?: string) => void; } const emptyNotes: string[] = []; @@ -109,15 +105,13 @@ const StatefulEventComponent: React.FC<Props> = ({ timelineId, leadingControlColumns, trailingControlColumns, + onToggleShowNotes, }) => { const trGroupRef = useRef<HTMLDivElement | null>(null); const dispatch = useDispatch(); const expandableFlyoutDisabled = useIsExperimentalFeatureEnabled('expandableFlyoutDisabled'); const { openFlyout } = useExpandableFlyoutApi(); - const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled( - 'securitySolutionNotesEnabled' - ); // Store context in state rather than creating object in provider value={} to prevent re-renders caused by a new object being created const [activeStatefulEventContext] = useState({ @@ -127,7 +121,8 @@ const StatefulEventComponent: React.FC<Props> = ({ tabType, }); - const [showNotes, setShowNotes] = useState<{ [eventId: string]: boolean }>({}); + const [, setFocusedNotes] = useState<{ [eventId: string]: boolean }>({}); + const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const expandedDetail = useDeepEqualSelector( (state) => (getTimeline(state, timelineId) ?? timelineDefaults).expandedDetail ?? {} @@ -188,31 +183,10 @@ const StatefulEventComponent: React.FC<Props> = ({ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const indexName = event._index!; - const onToggleShowNotes = useCallback(() => { - if (!expandableFlyoutDisabled && securitySolutionNotesEnabled) { - openFlyout({ - right: { - id: DocumentDetailsRightPanelKey, - params: { - id: eventId, - indexName, - scopeId: timelineId, - }, - }, - left: { - id: DocumentDetailsLeftPanelKey, - path: { - tab: LeftPanelNotesTab, - }, - params: { - id: eventId, - indexName, - scopeId: timelineId, - }, - }, - }); - } else { - setShowNotes((prevShowNotes) => { + const onToggleShowNotesHandler = useCallback( + (currentEventId?: string) => { + onToggleShowNotes?.(currentEventId); + setFocusedNotes((prevShowNotes) => { if (prevShowNotes[eventId]) { // notes are closing, so focus the notes button on the next tick, after escaping the EuiFocusTrap setTimeout(() => { @@ -225,15 +199,9 @@ const StatefulEventComponent: React.FC<Props> = ({ return { ...prevShowNotes, [eventId]: !prevShowNotes[eventId] }; }); - } - }, [ - eventId, - expandableFlyoutDisabled, - indexName, - securitySolutionNotesEnabled, - openFlyout, - timelineId, - ]); + }, + [onToggleShowNotes, eventId] + ); const handleOnEventDetailPanelOpened = useCallback(() => { const updatedExpandedDetail: ExpandedDetailType = { @@ -277,19 +245,6 @@ const StatefulEventComponent: React.FC<Props> = ({ tabType, ]); - const associateNote = useCallback( - (noteId: string) => { - dispatch( - timelineActions.addNoteToEvent({ - eventId, - id: timelineId, - noteId, - }) - ); - }, - [dispatch, eventId, timelineId] - ); - const setEventsLoading = useCallback<SetEventsLoading>( ({ eventIds, isLoading }) => { dispatch(timelineActions.setEventsLoading({ id: timelineId, eventIds, isLoading })); @@ -337,10 +292,10 @@ const StatefulEventComponent: React.FC<Props> = ({ onRuleChange={onRuleChange} selectedEventIds={selectedEventIds} showCheckboxes={showCheckboxes} - showNotes={!!showNotes[eventId]} + showNotes={true} tabType={tabType} timelineId={timelineId} - toggleShowNotes={onToggleShowNotes} + toggleShowNotes={onToggleShowNotesHandler} leadingControlColumns={leadingControlColumns} trailingControlColumns={trailingControlColumns} setEventsLoading={setEventsLoading} @@ -348,21 +303,6 @@ const StatefulEventComponent: React.FC<Props> = ({ /> <EventsTrSupplementContainerWrapper> - <EventsTrSupplement - className="siemEventsTable__trSupplement--notes" - data-test-subj="event-notes-flex-item" - $display="block" - > - <NoteCards - ariaRowindex={ariaRowindex} - associateNote={associateNote} - data-test-subj="note-cards" - notes={notes} - showAddNote={!!showNotes[eventId]} - toggleShowAddNote={onToggleShowNotes} - /> - </EventsTrSupplement> - <EuiFlexGroup gutterSize="none" justifyContent="center"> <EuiFlexItem grow={false}> <EventsTrSupplement> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.test.ts index 8d496e607cd93..0fbe03302e908 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.test.ts @@ -238,7 +238,7 @@ describe('helpers', () => { ).toEqual('Unpin alert'); }); - test('it indicates the event is NOT pinned when `isPinned` is `false` and the event has notes', () => { + test('it indicates the event is pinned when `isPinned` is `false` and the event has notes', () => { expect( getPinTooltip({ isAlert: false, @@ -246,10 +246,10 @@ describe('helpers', () => { eventHasNotes: true, timelineType: TimelineType.default, }) - ).toEqual('Pin event'); + ).toEqual('This event cannot be unpinned because it has notes'); }); - test('it indicates the alert is NOT pinned when `isPinned` is `false` and the alert has notes', () => { + test('it indicates the alert is pinned when `isPinned` is `false` and the alert has notes', () => { expect( getPinTooltip({ isAlert: true, @@ -257,7 +257,7 @@ describe('helpers', () => { eventHasNotes: true, timelineType: TimelineType.default, }) - ).toEqual('Pin alert'); + ).toEqual('This alert cannot be unpinned because it has notes'); }); test('it indicates the event is NOT pinned when `isPinned` is `false` and the event does NOT have notes', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx index a578a7c2fff71..709ee375ad040 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx @@ -39,7 +39,7 @@ export const getPinTooltip = ({ }) => { if (timelineType === TimelineType.template) { return i18n.DISABLE_PIN(isAlert); - } else if (isPinned && eventHasNotes) { + } else if (eventHasNotes) { return i18n.PINNED_WITH_NOTES(isAlert); } else if (isPinned) { return i18n.PINNED(isAlert); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index 77bc18fe05802..fe46d0b878801 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -27,7 +27,6 @@ import type { Props } from '.'; import { StatefulBody } from '.'; import type { Sort } from './sort'; import { getDefaultControlColumn } from './control_columns'; -import { timelineActions } from '../../../store'; import { TimelineId, TimelineTabs } from '../../../../../common/types/timeline'; import { defaultRowRenderers } from './renderers'; import type { State } from '../../../../common/store'; @@ -338,86 +337,6 @@ describe('Body', () => { }); }); }); - describe('action on event', () => { - const addaNoteToEvent = (wrapper: ReturnType<typeof mount>, note: string) => { - wrapper.find('[data-test-subj="add-note"]').first().find('button').simulate('click'); - wrapper.update(); - wrapper - .find('[data-test-subj="new-note-tabs"] textarea') - .simulate('change', { target: { value: note } }); - wrapper.update(); - wrapper.find('button[data-test-subj="add-note"]').first().simulate('click'); - wrapper.update(); - }; - - beforeEach(() => { - mockDispatch.mockClear(); - }); - - test('Add a note to an event', async () => { - const wrapper = await getWrapper(<StatefulBody {...props} />); - - addaNoteToEvent(wrapper, 'hello world'); - wrapper.update(); - expect(mockDispatch).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - payload: { - eventId: '1', - id: 'timeline-test', - noteId: expect.anything(), - }, - type: timelineActions.addNoteToEvent({ - eventId: '1', - id: 'timeline-test', - noteId: '11', - }).type, - }) - ); - }); - - test('Add two notes to an event', async () => { - const state: State = { - ...mockGlobalState, - timeline: { - ...mockGlobalState.timeline, - timelineById: { - ...mockGlobalState.timeline.timelineById, - [TimelineId.test]: { - ...mockGlobalState.timeline.timelineById[TimelineId.test], - id: 'timeline-test', - pinnedEventIds: { 1: true }, - }, - }, - }, - }; - - const store = createMockStore(state); - - const Proxy = (proxyProps: Props) => <StatefulBody {...proxyProps} />; - - const wrapper = await getWrapper(<Proxy {...props} />, { store }); - - addaNoteToEvent(wrapper, 'hello world'); - mockDispatch.mockClear(); - addaNoteToEvent(wrapper, 'new hello world'); - expect(mockDispatch).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - payload: { - eventId: '1', - id: 'timeline-test', - noteId: expect.anything(), - }, - type: timelineActions.addNoteToEvent({ - eventId: '1', - id: 'timeline-test', - noteId: '11', - }).type, - }) - ); - }); - }); describe('event details', () => { beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx index 2a45d3b5f2338..ab60e061fcdf9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx @@ -51,6 +51,7 @@ export interface Props { tabType: TimelineTabs; totalPages: number; onRuleChange?: () => void; + onToggleShowNotes?: (eventId?: string) => void; } /** @@ -73,6 +74,7 @@ export const StatefulBody = React.memo<Props>( totalPages, leadingControlColumns = [], trailingControlColumns = [], + onToggleShowNotes, }) => { const dispatch = useDispatch(); const containerRef = useRef<HTMLDivElement | null>(null); @@ -256,6 +258,7 @@ export const StatefulBody = React.memo<Props>( leadingControlColumns={leadingControlColumns} trailingControlColumns={trailingControlColumns} tabType={tabType} + onToggleShowNotes={onToggleShowNotes} /> </EventsTable> </TimelineBody> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/unified_timeline_body.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/unified_timeline_body.test.tsx index 21a923653237a..401fe8763ada5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/unified_timeline_body.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/unified_timeline_body.test.tsx @@ -46,8 +46,6 @@ const defaultProps: UnifiedTimelineBodyProps = { activePage: 0, querySize: 0, }, - eventIdToNoteIds: {} as Record<string, string[]>, - pinnedEventIds: {} as Record<string, boolean>, }; const renderTestComponents = (props?: UnifiedTimelineBodyProps) => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/unified_timeline_body.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/unified_timeline_body.tsx index 6f98682678423..576812016dee8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/unified_timeline_body.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/unified_timeline_body.tsx @@ -42,8 +42,6 @@ export const UnifiedTimelineBody = (props: UnifiedTimelineBodyProps) => { updatedAt, trailingControlColumns, leadingControlColumns, - pinnedEventIds, - eventIdToNoteIds, } = props; const [pageRows, setPageRows] = useState<TimelineItem[][]>([]); @@ -91,8 +89,6 @@ export const UnifiedTimelineBody = (props: UnifiedTimelineBodyProps) => { isTextBasedQuery={false} trailingControlColumns={trailingControlColumns} leadingControlColumns={leadingControlColumns} - pinnedEventIds={pinnedEventIds} - eventIdToNoteIds={eventIdToNoteIds} /> </RootDragDropProvider> </StyledTableFlexItem> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx index f749fea36c4f6..977d3e51c9824 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx @@ -133,6 +133,9 @@ const StatefulTimelineComponent: React.FC<Props> = ({ dataViewId: selectedDataViewIdSourcerer, indexNames: selectedPatternsSourcerer, show: false, + excludedRowRendererIds: unifiedComponentsInTimelineEnabled + ? timelineDefaults.excludedRowRendererIds + : [], }) ); } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx new file mode 100644 index 0000000000000..e8508aaf0b4cd --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ComponentProps } from 'react'; +import React from 'react'; + +import { fireEvent, render, screen } from '@testing-library/react'; +import { NotesButton } from './helpers'; +import { TimelineType } from '../../../../../common/api/timeline'; +import { ThemeProvider } from 'styled-components'; + +const toggleShowNotesMock = jest.fn(); + +const defaultProps: ComponentProps<typeof NotesButton> = { + ariaLabel: 'Sample Notes', + isDisabled: false, + toggleShowNotes: toggleShowNotesMock, + eventId: 'event-id', + notesCount: 1, + timelineType: TimelineType.default, + toolTip: 'Sample Tooltip', +}; + +const TestWrapper: React.FC = ({ children }) => { + return <ThemeProvider theme={{ eui: { euiColorDanger: 'red' } }}>{children}</ThemeProvider>; +}; + +const renderTestComponent = (props?: Partial<ComponentProps<typeof NotesButton>>) => { + const localProps = { + ...defaultProps, + ...props, + }; + + render(<NotesButton {...localProps} />, { wrapper: TestWrapper }); +}; + +describe('helpers', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + test('should show the notes button correctly', () => { + renderTestComponent(); + + expect(screen.getByTestId('timeline-notes-button-small')).toBeVisible(); + }); + + test('should show the notification dot correctly when notes are available', () => { + renderTestComponent(); + + expect(screen.getByTestId('timeline-notes-button-small')).toBeVisible(); + expect(screen.getByTestId('timeline-notes-notification-dot')).toBeVisible(); + }); + + test('should not show the notification dot where there are no notes available', () => { + renderTestComponent({ + notesCount: 0, + }); + + expect(screen.getByTestId('timeline-notes-button-small')).toBeVisible(); + expect(screen.queryByTestId('timeline-notes-notification-dot')).not.toBeInTheDocument(); + }); + + test('should call the toggleShowNotes function when the button is clicked', () => { + renderTestComponent(); + + const button = screen.getByTestId('timeline-notes-button-small'); + + fireEvent.click(button); + + expect(toggleShowNotesMock).toHaveBeenCalledTimes(1); + expect(toggleShowNotesMock).toHaveBeenCalledWith('event-id'); + }); + + test('should call the toggleShowNotes correctly when the button is clicked and eventId is not available', () => { + renderTestComponent({ + eventId: undefined, + }); + + const button = screen.getByTestId('timeline-notes-button-small'); + + fireEvent.click(button); + + expect(toggleShowNotesMock).toHaveBeenCalledTimes(1); + expect(toggleShowNotesMock).toHaveBeenCalledWith(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx index 1a217abd674e0..739e07dca1995 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiBadge, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; +import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import React, { useCallback } from 'react'; import styled from 'styled-components'; @@ -14,12 +14,6 @@ import { TimelineType } from '../../../../../common/api/timeline'; import * as i18n from './translations'; -const NotesCountBadge = styled(EuiBadge)` - margin-left: 5px; -` as unknown as typeof EuiBadge; - -NotesCountBadge.displayName = 'NotesCountBadge'; - export const NotificationDot = styled.span` position: absolute; display: block; @@ -31,16 +25,10 @@ export const NotificationDot = styled.span` left: 52%; `; -const NotesButtonContainer = styled(EuiFlexGroup)` - position: relative; -`; - -export const NOTES_BUTTON_CLASS_NAME = 'notes-button'; - interface SmallNotesButtonProps { ariaLabel?: string; isDisabled?: boolean; - toggleShowNotes: (eventId?: string) => void; + toggleShowNotes?: (eventId?: string) => void; timelineType: TimelineTypeLiteral; eventId?: string; /** @@ -49,21 +37,32 @@ interface SmallNotesButtonProps { notesCount: number; } +export const NOTES_BUTTON_CLASS_NAME = 'notes-button'; + +const NotesButtonContainer = styled(EuiFlexGroup)` + position: relative; +`; + const SmallNotesButton = React.memo<SmallNotesButtonProps>( ({ ariaLabel = i18n.NOTES, isDisabled, toggleShowNotes, timelineType, eventId, notesCount }) => { const isTemplate = timelineType === TimelineType.template; const onClick = useCallback(() => { if (eventId != null) { - toggleShowNotes(eventId); + toggleShowNotes?.(eventId); } else { - toggleShowNotes(); + toggleShowNotes?.(); } }, [toggleShowNotes, eventId]); return ( <NotesButtonContainer> <EuiFlexItem grow={false}> - {notesCount > 0 ? <NotificationDot /> : null} + {notesCount > 0 ? ( + <NotificationDot + className="timeline-notes-notification-dot" + data-test-subj="timeline-notes-notification-dot" + /> + ) : null} <EuiButtonIcon aria-label={ariaLabel} className={NOTES_BUTTON_CLASS_NAME} @@ -84,49 +83,29 @@ SmallNotesButton.displayName = 'SmallNotesButton'; interface NotesButtonProps { ariaLabel?: string; isDisabled?: boolean; - showNotes: boolean; - toggleShowNotes: () => void | ((eventId: string) => void); - toolTip?: string; + toggleShowNotes?: () => void | ((eventId: string) => void); + toolTip: string; timelineType: TimelineTypeLiteral; eventId?: string; /** - * Number of notes associated with the event. - * Defaults to 0 + * Number of notes. If > 0, then a red dot is shown in the top right corner of the icon. */ notesCount?: number; } export const NotesButton = React.memo<NotesButtonProps>( - ({ - ariaLabel, - isDisabled, - showNotes, - timelineType, - toggleShowNotes, - toolTip, - eventId, - notesCount = 0, - }) => - showNotes ? ( + ({ ariaLabel, isDisabled, timelineType, toggleShowNotes, toolTip, eventId, notesCount }) => ( + <EuiToolTip content={toolTip} data-test-subj="timeline-notes-tool-tip"> <SmallNotesButton ariaLabel={ariaLabel} isDisabled={isDisabled} toggleShowNotes={toggleShowNotes} timelineType={timelineType} eventId={eventId} - notesCount={notesCount} + notesCount={notesCount ?? 0} /> - ) : ( - <EuiToolTip content={toolTip || ''} data-test-subj="timeline-notes-tool-tip"> - <SmallNotesButton - ariaLabel={ariaLabel} - isDisabled={isDisabled} - toggleShowNotes={toggleShowNotes} - timelineType={timelineType} - eventId={eventId} - notesCount={notesCount} - /> - </EuiToolTip> - ) + </EuiToolTip> + ) ); + NotesButton.displayName = 'NotesButton'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/notes_flyout.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/notes_flyout.test.tsx new file mode 100644 index 0000000000000..33836289d5e2b --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/notes_flyout.test.tsx @@ -0,0 +1,89 @@ +/* + * 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 { TimelineId } from '../../../../../common/types'; +import { fireEvent, render, screen } from '@testing-library/react'; +import type { ComponentProps } from 'react'; +import React from 'react'; +import { ThemeProvider } from 'styled-components'; +import { NoteCards } from '../../notes/note_cards'; +import type { TimelineResultNote } from '../../open_timeline/types'; +import { NotesFlyout } from './notes_flyout'; + +const onClose = jest.fn(); +const toggleShowAddNote = jest.fn(); +const associateNote = jest.fn(); +const eventId = 'sample_event_id'; +const notes = [] as TimelineResultNote[]; + +jest.mock('../../notes/note_cards', () => ({ + NoteCards: jest.fn(), +})); + +const renderTestComponent = (props?: Partial<ComponentProps<typeof NotesFlyout>>) => { + return render( + <NotesFlyout + show={true} + eventId={eventId} + onClose={onClose} + toggleShowAddNote={toggleShowAddNote} + associateNote={associateNote} + notes={notes} + timelineId={TimelineId.test} + {...props} + />, + { + wrapper: ({ children }) => ( + <ThemeProvider + theme={{ + eui: { + euiZFlyout: 1000, + }, + }} + > + {children} + </ThemeProvider> + ), + } + ); +}; + +describe('Notes Flyout', () => { + beforeEach(() => { + jest.clearAllMocks(); + + (NoteCards as unknown as jest.Mock).mockImplementation( + jest.fn().mockReturnValue(<div>{`NoteCards`}</div>) + ); + }); + + it('should respond to visibility prop correctly', () => { + renderTestComponent({ + show: false, + }); + + expect(screen.queryByTestId('timeline-notes-flyout')).not.toBeInTheDocument(); + }); + + it('should display notes correctly', () => { + renderTestComponent({ + show: true, + }); + + expect(screen.getByText('NoteCards')).toBeVisible(); + }); + + it('should trigger onClose correctly', () => { + renderTestComponent({ + show: true, + }); + + fireEvent.click(screen.getByTestId('euiFlyoutCloseButton')); + + expect(onClose).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/notes_flyout.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/notes_flyout.tsx new file mode 100644 index 0000000000000..438e04283e74a --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/notes_flyout.tsx @@ -0,0 +1,87 @@ +/* + * 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 { + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiTitle, + useGeneratedHtmlId, +} from '@elastic/eui'; +import styled from 'styled-components'; +import type { EuiTheme } from '@kbn/react-kibana-context-styled'; +import type { NoteCardsProps } from '../../notes/note_cards'; +import { NoteCards } from '../../notes/note_cards'; +import * as i18n from './translations'; + +export type NotesFlyoutProps = { + show: boolean; + onClose: () => void; + eventId?: string; +} & Pick< + NoteCardsProps, + 'notes' | 'associateNote' | 'toggleShowAddNote' | 'timelineId' | 'onCancel' +>; + +/* + * z-index override is needed because otherwise NotesFlyout appears below + * Timeline Modal as they both have same z-index of 1000 + */ +const NotesFlyoutContainer = styled(EuiFlyout)` + /* + * We want the width of flyout to be less than 50% of screen because + * otherwise it interferes with the delete notes modal + * */ + width: 30%; + z-index: ${(props) => + ((props.theme as EuiTheme).eui.euiZFlyout.toFixed() ?? 1000) + 2} !important; +`; + +export const NotesFlyout = React.memo(function NotesFlyout(props: NotesFlyoutProps) { + const { eventId, toggleShowAddNote, show, onClose, associateNote, notes, timelineId, onCancel } = + props; + + const notesFlyoutTitleId = useGeneratedHtmlId({ + prefix: 'notesFlyoutTitle', + }); + + if (!show || !eventId) { + return null; + } + + return ( + <NotesFlyoutContainer + ownFocus={false} + className="timeline-notes-flyout" + data-test-subj="timeline-notes-flyout" + onClose={onClose} + aria-labelledby={notesFlyoutTitleId} + maxWidth={750} + > + <EuiFlyoutHeader hasBorder> + <EuiTitle size="m"> + <h2>{i18n.NOTES}</h2> + </EuiTitle> + </EuiFlyoutHeader> + <EuiFlyoutBody> + <NoteCards + ariaRowindex={0} + associateNote={associateNote} + className="notes-in-flyout" + data-test-subj="note-cards" + notes={notes} + showAddNote={true} + toggleShowAddNote={toggleShowAddNote} + eventId={eventId} + timelineId={timelineId} + onCancel={onCancel} + /> + </EuiFlyoutBody> + </NotesFlyoutContainer> + ); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_notes_in_flyout.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_notes_in_flyout.test.tsx new file mode 100644 index 0000000000000..d40bf849dc32b --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_notes_in_flyout.test.tsx @@ -0,0 +1,201 @@ +/* + * 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 { TimelineId } from '../../../../../common/types'; +import { renderHook, act } from '@testing-library/react-hooks/dom'; +import { createMockStore, mockGlobalState, TestProviders } from '../../../../common/mock'; +import { useNotesInFlyout } from './use_notes_in_flyout'; +import { waitFor } from '@testing-library/react'; +import { useDispatch } from 'react-redux'; + +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useDispatch: jest.fn(), +})); + +const mockEventIdToNoteIds = { + 'event-1': ['note-1', 'note-3'], + 'event-2': ['note-2'], +}; + +const note1 = { + created: new Date('2024-06-25T13:34:35.669Z'), + id: 'note-1', + lastEdit: new Date('2024-06-25T13:34:35.669Z'), + note: 'First Comment', + user: 'elastic', + saveObjectId: '7402b6fc-34a8-42bd-b590-389df3011c6b', + version: 'WzU0OTcsMV0=', + eventId: 'event-1', + timelineId: '35937e12-b600-4bdd-a79e-5431aa39ab4b', +}; + +const note2 = { + created: new Date('2024-06-25T11:57:22.031Z'), + id: 'note-2', + lastEdit: new Date('2024-06-25T11:57:22.031Z'), + note: 'Some Note', + user: 'elastic', + saveObjectId: 'fafdfe3e-82b6-4c09-b116-fcba4a5390de', + version: 'WzU0OTUsMV0=', + eventId: 'event-2', + timelineId: '35937e12-b600-4bdd-a79e-5431aa39ab4b', +}; + +const note3 = { + ...note1, + id: 'note-3', + eventId: 'event-1', + note: 'Third Comment', + saveObjectId: 'note-3', +}; + +const mockState = structuredClone(mockGlobalState); + +const mockLocalState = { + ...mockState, + timeline: { + ...mockState.timeline, + timelineById: { + [TimelineId.test]: { + ...mockState.timeline.timelineById[TimelineId.test], + eventIdToNoteIds: { + ...mockEventIdToNoteIds, + }, + }, + }, + }, + app: { + ...mockState.app, + notesById: { + 'note-1': note1, + 'note-2': note2, + 'note-3': note3, + }, + }, +}; + +const dispatchMock = jest.fn(); +const refetchMock = jest.fn(); + +const renderTestHook = () => { + return renderHook( + () => + useNotesInFlyout({ + eventIdToNoteIds: mockEventIdToNoteIds, + timelineId: TimelineId.test, + refetch: refetchMock, + }), + { + wrapper: ({ children }) => ( + <TestProviders store={createMockStore(mockLocalState)}>{children}</TestProviders> + ), + } + ); +}; + +describe('useNotesInFlyout', () => { + beforeEach(() => { + jest.clearAllMocks(); + (useDispatch as jest.Mock).mockReturnValue(dispatchMock); + }); + it('should return correct array of notes based on Events', async () => { + const { result } = renderTestHook(); + + expect(result.current.notes).toEqual([]); + + act(() => { + result.current.setNotesEventId('event-1'); + }); + + await waitFor(() => { + expect(result.current.notes).toMatchObject( + [note1, note3].map((note) => ({ + savedObjectId: note.saveObjectId, + note: note.note, + noteId: note.id, + updated: (note.lastEdit ?? note.created).getTime(), + updatedBy: note.user, + })) + ); + }); + + act(() => { + result.current.setNotesEventId('event-2'); + }); + + await waitFor(() => { + expect(result.current.notes).toMatchObject( + [note2].map((note) => ({ + savedObjectId: note.saveObjectId, + note: note.note, + noteId: note.id, + updated: (note.lastEdit ?? note.created).getTime(), + updatedBy: note.user, + })) + ); + }); + }); + + it('should show flyout when eventId is not undefined', async () => { + const { result } = renderTestHook(); + + expect(result.current.eventId).toBeUndefined(); + expect(result.current.notes).toEqual([]); + + act(() => { + result.current.setNotesEventId('event-1'); + }); + + await waitFor(() => { + expect(result.current.eventId).toBe('event-1'); + }); + + act(() => { + result.current.showNotesFlyout(); + }); + + expect(result.current.isNotesFlyoutVisible).toBe(true); + }); + + it('should return correct instance of associate Note', () => { + const { result } = renderTestHook(); + + act(() => { + result.current.setNotesEventId('event-1'); + }); + + const { associateNote } = result.current; + + dispatchMock.mockClear(); + associateNote('some-noteId'); + + expect(dispatchMock).toHaveBeenCalledTimes(1); + expect(refetchMock).toHaveBeenCalledTimes(1); + }); + + it('should close flyout correctly', () => { + const { result } = renderTestHook(); + + act(() => { + result.current.setNotesEventId('event-1'); + }); + + act(() => { + result.current.showNotesFlyout(); + }); + + expect(result.current.isNotesFlyoutVisible).toBe(true); + + act(() => { + result.current.closeNotesFlyout(); + }); + + expect(result.current.isNotesFlyoutVisible).toBe(false); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_notes_in_flyout.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_notes_in_flyout.ts new file mode 100644 index 0000000000000..99a2dfe3953ce --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_notes_in_flyout.ts @@ -0,0 +1,99 @@ +/* + * 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 { useCallback, useMemo, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { appSelectors } from '../../../../common/store'; +import { timelineActions } from '../../../store'; + +interface UseNotesInFlyoutArgs { + eventIdToNoteIds: Record<string, string[]>; + refetch?: () => void; + timelineId: string; +} + +const EMPTY_STRING_ARRAY: string[] = []; + +function isNoteNotNull<T>(note: T | null): note is T { + return note !== null; +} + +export const useNotesInFlyout = (args: UseNotesInFlyoutArgs) => { + const [isNotesFlyoutVisible, setIsNotesFlyoutVisible] = useState(false); + + const [eventId, setNotesEventId] = useState<string>(); + + const closeNotesFlyout = useCallback(() => { + setIsNotesFlyoutVisible(false); + }, []); + + const showNotesFlyout = useCallback(() => { + setIsNotesFlyoutVisible(true); + }, []); + + const { eventIdToNoteIds, refetch, timelineId } = args; + + const getNotesByIds = useMemo(() => appSelectors.notesByIdsSelector(), []); + + const notesById = useDeepEqualSelector(getNotesByIds); + + const dispatch = useDispatch(); + + const noteIds: string[] = useMemo( + () => (eventId && eventIdToNoteIds?.[eventId]) || EMPTY_STRING_ARRAY, + [eventIdToNoteIds, eventId] + ); + + const associateNote = useCallback( + (currentNoteId: string) => { + if (!eventId) return; + dispatch( + timelineActions.addNoteToEvent({ + eventId, + id: timelineId, + noteId: currentNoteId, + }) + ); + if (refetch) { + refetch(); + } + }, + [dispatch, eventId, refetch, timelineId] + ); + + const notes = useMemo( + () => + noteIds + .map((currentNoteId) => { + const note = notesById[currentNoteId]; + if (note) { + return { + savedObjectId: note.saveObjectId, + note: note.note, + noteId: note.id, + updated: (note.lastEdit ?? note.created).getTime(), + updatedBy: note.user, + }; + } else { + return null; + } + }) + .filter(isNoteNotNull), + [noteIds, notesById] + ); + + return { + associateNote, + notes, + isNotesFlyoutVisible, + closeNotesFlyout, + showNotesFlyout, + eventId, + setNotesEventId, + }; +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx index 5068a80fb1018..97762de6bcb91 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx @@ -352,7 +352,6 @@ export const EventsTdContent = styled.div.attrs(({ className }) => ({ font-size: ${({ theme }) => theme.eui.euiFontSizeXS}; line-height: ${({ theme }) => theme.eui.euiLineHeight}; min-width: 0; - padding: ${({ theme }) => theme.eui.euiSizeXS}; text-align: ${({ textAlign }) => textAlign}; width: ${({ width }) => width != null diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.tsx index 38d2ade2d985c..6a5ccda2d1677 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.tsx @@ -63,6 +63,8 @@ import { EqlTabHeader } from './header'; import { useTimelineColumns } from '../shared/use_timeline_columns'; import { useTimelineControlColumn } from '../shared/use_timeline_control_columns'; import { LeftPanelNotesTab } from '../../../../../flyout/document_details/left'; +import { useNotesInFlyout } from '../../properties/use_notes_in_flyout'; +import { NotesFlyout } from '../../properties/notes_flyout'; export type Props = TimelineTabCommonProps & PropsFromRedux; @@ -146,6 +148,21 @@ export const EqlTabContentComponent: React.FC<Props> = ({ const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled( 'securitySolutionNotesEnabled' ); + + const { + associateNote, + notes, + isNotesFlyoutVisible, + closeNotesFlyout, + showNotesFlyout, + eventId: noteEventId, + setNotesEventId, + } = useNotesInFlyout({ + eventIdToNoteIds, + refetch, + timelineId, + }); + const onToggleShowNotes = useCallback( (eventId?: string) => { const indexName = selectedPatterns.join(','); @@ -171,6 +188,11 @@ export const EqlTabContentComponent: React.FC<Props> = ({ }, }, }); + } else { + if (eventId) { + setNotesEventId(eventId); + showNotesFlyout(); + } } }, [ @@ -179,6 +201,8 @@ export const EqlTabContentComponent: React.FC<Props> = ({ securitySolutionNotesEnabled, selectedPatterns, timelineId, + setNotesEventId, + showNotesFlyout, ] ); @@ -188,6 +212,9 @@ export const EqlTabContentComponent: React.FC<Props> = ({ timelineId, activeTab: TimelineTabs.eql, refetch, + events, + pinnedEventIds, + eventIdToNoteIds, onToggleShowNotes, }); @@ -225,6 +252,20 @@ export const EqlTabContentComponent: React.FC<Props> = ({ [activeTab, setTimelineFullScreen, timelineFullScreen, timelineId] ); + const NotesFlyoutMemo = useMemo(() => { + return ( + <NotesFlyout + associateNote={associateNote} + eventId={noteEventId} + show={isNotesFlyoutVisible} + notes={notes} + onClose={closeNotesFlyout} + onCancel={closeNotesFlyout} + timelineId={timelineId} + /> + ); + }, [associateNote, closeNotesFlyout, isNotesFlyoutVisible, noteEventId, notes, timelineId]); + return ( <> {unifiedComponentsInTimelineEnabled ? ( @@ -232,6 +273,7 @@ export const EqlTabContentComponent: React.FC<Props> = ({ <InPortal node={eqlEventsCountPortalNode}> {totalCount >= 0 ? <EventsCountBadge>{totalCount}</EventsCountBadge> : null} </InPortal> + {NotesFlyoutMemo} <FullWidthFlexGroup> <ScrollableFlexItem grow={2}> <UnifiedTimelineBody @@ -256,8 +298,6 @@ export const EqlTabContentComponent: React.FC<Props> = ({ isTextBasedQuery={false} pageInfo={pageInfo} leadingControlColumns={leadingControlColumns as EuiDataGridControlColumn[]} - pinnedEventIds={pinnedEventIds} - eventIdToNoteIds={eventIdToNoteIds} /> </ScrollableFlexItem> </FullWidthFlexGroup> @@ -267,6 +307,7 @@ export const EqlTabContentComponent: React.FC<Props> = ({ <InPortal node={eqlEventsCountPortalNode}> {totalCount >= 0 ? <EventsCountBadge>{totalCount}</EventsCountBadge> : null} </InPortal> + {NotesFlyoutMemo} <TimelineRefetch id={`${timelineId}-${TimelineTabs.eql}`} inputId={InputsModelId.timeline} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/pinned/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/pinned/index.tsx index a49b63a5e57fb..ba842d058d42e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/pinned/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/pinned/index.tsx @@ -52,6 +52,8 @@ import type { TimelineTabCommonProps } from '../shared/types'; import { useTimelineColumns } from '../shared/use_timeline_columns'; import { useTimelineControlColumn } from '../shared/use_timeline_control_columns'; import { LeftPanelNotesTab } from '../../../../../flyout/document_details/left'; +import { useNotesInFlyout } from '../../properties/use_notes_in_flyout'; +import { NotesFlyout } from '../../properties/notes_flyout'; const ExitFullScreenContainer = styled.div` width: 180px; @@ -182,6 +184,21 @@ export const PinnedTabContentComponent: React.FC<Props> = ({ const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled( 'securitySolutionNotesEnabled' ); + + const { + associateNote, + notes, + isNotesFlyoutVisible, + closeNotesFlyout, + showNotesFlyout, + eventId: noteEventId, + setNotesEventId, + } = useNotesInFlyout({ + eventIdToNoteIds, + refetch, + timelineId, + }); + const onToggleShowNotes = useCallback( (eventId?: string) => { const indexName = selectedPatterns.join(','); @@ -207,6 +224,11 @@ export const PinnedTabContentComponent: React.FC<Props> = ({ }, }, }); + } else { + if (eventId) { + setNotesEventId(eventId); + showNotesFlyout(); + } } }, [ @@ -215,6 +237,8 @@ export const PinnedTabContentComponent: React.FC<Props> = ({ securitySolutionNotesEnabled, selectedPatterns, timelineId, + setNotesEventId, + showNotesFlyout, ] ); @@ -224,6 +248,9 @@ export const PinnedTabContentComponent: React.FC<Props> = ({ timelineId, activeTab: TimelineTabs.pinned, refetch, + events, + pinnedEventIds, + eventIdToNoteIds, onToggleShowNotes, }); @@ -236,38 +263,54 @@ export const PinnedTabContentComponent: React.FC<Props> = ({ onEventClosed({ tabType: TimelineTabs.pinned, id: timelineId }); }, [timelineId, onEventClosed]); - if (unifiedComponentsInTimelineEnabled) { + const NotesFlyoutMemo = useMemo(() => { return ( - <UnifiedTimelineBody - header={<></>} - columns={augmentedColumnHeaders} - rowRenderers={rowRenderers} + <NotesFlyout + associateNote={associateNote} + eventId={noteEventId} + show={isNotesFlyoutVisible} + notes={notes} + onClose={closeNotesFlyout} + onCancel={closeNotesFlyout} timelineId={timelineId} - itemsPerPage={itemsPerPage} - itemsPerPageOptions={itemsPerPageOptions} - sort={sort} - events={events} - refetch={refetch} - dataLoadingState={queryLoadingState} - pinnedEventIds={pinnedEventIds} - totalCount={events.length} - onEventClosed={onEventClosed} - expandedDetail={expandedDetail} - eventIdToNoteIds={eventIdToNoteIds} - showExpandedDetails={showExpandedDetails} - onChangePage={loadPage} - activeTab={TimelineTabs.pinned} - updatedAt={refreshedAt} - isTextBasedQuery={false} - pageInfo={pageInfo} - leadingControlColumns={leadingControlColumns as EuiDataGridControlColumn[]} - trailingControlColumns={rowDetailColumn} /> ); + }, [associateNote, closeNotesFlyout, isNotesFlyoutVisible, noteEventId, notes, timelineId]); + + if (unifiedComponentsInTimelineEnabled) { + return ( + <> + {NotesFlyoutMemo} + <UnifiedTimelineBody + header={<></>} + columns={augmentedColumnHeaders} + rowRenderers={rowRenderers} + timelineId={timelineId} + itemsPerPage={itemsPerPage} + itemsPerPageOptions={itemsPerPageOptions} + sort={sort} + events={events} + refetch={refetch} + dataLoadingState={queryLoadingState} + totalCount={events.length} + onEventClosed={onEventClosed} + expandedDetail={expandedDetail} + showExpandedDetails={showExpandedDetails} + onChangePage={loadPage} + activeTab={TimelineTabs.pinned} + updatedAt={refreshedAt} + isTextBasedQuery={false} + pageInfo={pageInfo} + leadingControlColumns={leadingControlColumns as EuiDataGridControlColumn[]} + trailingControlColumns={rowDetailColumn} + /> + </> + ); } return ( <> + {NotesFlyoutMemo} <FullWidthFlexGroup data-test-subj={`${TimelineTabs.pinned}-tab`}> <ScrollableFlexItem grow={2}> {timelineFullScreen && setTimelineFullScreen != null && ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.test.tsx index 32c7a525f5258..c326105f3ce8f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.test.tsx @@ -59,6 +59,8 @@ jest.mock('../../../../containers/use_timeline_data_filters', () => ({ useTimelineDataFilters: jest.fn().mockReturnValue({ from: 'now-15m', to: 'now' }), })); +jest.mock('../../../../../common/hooks/use_experimental_features'); + describe('Timeline', () => { let props = {} as QueryTabContentComponentProps; const sort: Sort[] = [ diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.tsx index 443b290a53021..001eb31442790 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.tsx @@ -67,6 +67,8 @@ import { import type { TimelineTabCommonProps } from '../shared/types'; import { useTimelineColumns } from '../shared/use_timeline_columns'; import { useTimelineControlColumn } from '../shared/use_timeline_control_columns'; +import { NotesFlyout } from '../../properties/notes_flyout'; +import { useNotesInFlyout } from '../../properties/use_notes_in_flyout'; const compareQueryProps = (prevProps: Props, nextProps: Props) => prevProps.kqlMode === nextProps.kqlMode && @@ -214,6 +216,21 @@ export const QueryTabContentComponent: React.FC<Props> = ({ const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled( 'securitySolutionNotesEnabled' ); + + const { + associateNote, + notes, + isNotesFlyoutVisible, + closeNotesFlyout, + showNotesFlyout, + eventId: noteEventId, + setNotesEventId, + } = useNotesInFlyout({ + eventIdToNoteIds, + refetch, + timelineId, + }); + const onToggleShowNotes = useCallback( (eventId?: string) => { const indexName = selectedPatterns.join(','); @@ -239,6 +256,11 @@ export const QueryTabContentComponent: React.FC<Props> = ({ }, }, }); + } else { + if (eventId) { + setNotesEventId(eventId); + showNotesFlyout(); + } } }, [ @@ -247,6 +269,8 @@ export const QueryTabContentComponent: React.FC<Props> = ({ securitySolutionNotesEnabled, selectedPatterns, timelineId, + showNotesFlyout, + setNotesEventId, ] ); @@ -256,6 +280,9 @@ export const QueryTabContentComponent: React.FC<Props> = ({ timelineId, activeTab: TimelineTabs.query, refetch, + events, + pinnedEventIds, + eventIdToNoteIds, onToggleShowNotes, }); @@ -311,6 +338,20 @@ export const QueryTabContentComponent: React.FC<Props> = ({ }, [timelineDataService, combinedQueries, kqlQueryLanguage]); // </Synchronisation of the timeline data service> + const NotesFlyoutMemo = useMemo(() => { + return ( + <NotesFlyout + associateNote={associateNote} + eventId={noteEventId} + show={isNotesFlyoutVisible} + notes={notes} + onClose={closeNotesFlyout} + onCancel={closeNotesFlyout} + timelineId={timelineId} + /> + ); + }, [associateNote, closeNotesFlyout, isNotesFlyoutVisible, noteEventId, notes, timelineId]); + if (unifiedComponentsInTimelineEnabled) { return ( <> @@ -322,6 +363,7 @@ export const QueryTabContentComponent: React.FC<Props> = ({ refetch={refetch} skip={!canQueryTimeline} /> + {NotesFlyoutMemo} <UnifiedTimelineBody header={ @@ -350,8 +392,6 @@ export const QueryTabContentComponent: React.FC<Props> = ({ expandedDetail={expandedDetail} showExpandedDetails={showExpandedDetails} leadingControlColumns={leadingControlColumns as EuiDataGridControlColumn[]} - eventIdToNoteIds={eventIdToNoteIds} - pinnedEventIds={pinnedEventIds} onChangePage={loadPage} activeTab={activeTab} updatedAt={refreshedAt} @@ -372,6 +412,7 @@ export const QueryTabContentComponent: React.FC<Props> = ({ refetch={refetch} skip={!canQueryTimeline} /> + {NotesFlyoutMemo} <FullWidthFlexGroup gutterSize="none"> <ScrollableFlexItem grow={2}> <QueryTabHeader @@ -405,6 +446,7 @@ export const QueryTabContentComponent: React.FC<Props> = ({ })} leadingControlColumns={leadingControlColumns as ControlColumnProps[]} trailingControlColumns={timelineEmptyTrailingControlColumns} + onToggleShowNotes={onToggleShowNotes} /> </StyledEuiFlyoutBody> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/query_tab_unified_components.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/query_tab_unified_components.test.tsx index 13a7e9045f20f..9a712a8fbeaf1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/query_tab_unified_components.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/query_tab_unified_components.test.tsx @@ -774,95 +774,427 @@ describe('query tab with unified timeline', () => { ); }); - describe('row leading actions', () => { - // fix this with the new EUI flyout implementation for notes - it.skip( - 'should be able to add notes using EuiFlyout', - async () => { - (useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation( - jest.fn((feature: keyof ExperimentalFeatures) => { - if (feature === 'unifiedComponentsInTimelineEnabled') { - return true; - } - return allowedExperimentalValues[feature]; - }) + describe('Leading actions - notes', () => { + describe('securitySolutionNotesEnabled = true', () => { + describe('expandableFlyoutDisabled = false', () => { + beforeEach(() => { + (useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation( + jest.fn((feature: keyof ExperimentalFeatures) => { + if (feature === 'unifiedComponentsInTimelineEnabled') { + return true; + } + if (feature === 'securitySolutionNotesEnabled') { + return true; + } + return allowedExperimentalValues[feature]; + }) + ); + }); + + it( + 'should have the notification dot & correct tooltip', + async () => { + renderTestComponents(); + + expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); + + expect(screen.getAllByTestId('timeline-notes-button-small')).toHaveLength(1); + expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled(); + + expect(screen.getByTestId('timeline-notes-notification-dot')).toBeVisible(); + + fireEvent.mouseOver(screen.getByTestId('timeline-notes-button-small')); + + await waitFor(() => { + expect(screen.getByTestId('timeline-notes-tool-tip')).toBeVisible(); + expect(screen.getByTestId('timeline-notes-tool-tip')).toHaveTextContent( + '1 Note available. Click to view it & add more.' + ); + }); + }, + SPECIAL_TEST_TIMEOUT ); + it( + 'should be able to add notes through expandable flyout', + async () => { + renderTestComponents(); + expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); - renderTestComponents(); - expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); + await waitFor(() => { + expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled(); + }); - await waitFor(() => { - expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled(); + fireEvent.click(screen.getByTestId('timeline-notes-button-small')); + + await waitFor(() => { + expect(mockOpenFlyout).toHaveBeenCalled(); + }); + }, + SPECIAL_TEST_TIMEOUT + ); + }); + + describe('expandableFlyoutDisabled = true', () => { + beforeEach(() => { + (useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation( + jest.fn((feature: keyof ExperimentalFeatures) => { + if (feature === 'unifiedComponentsInTimelineEnabled') { + return true; + } + if (feature === 'expandableFlyoutDisabled') { + return true; + } + if (feature === 'securitySolutionNotesEnabled') { + return true; + } + return allowedExperimentalValues[feature]; + }) + ); }); - fireEvent.click(screen.getByTestId('timeline-notes-button-small')); + it( + 'should have the notification dot & correct tooltip', + async () => { + renderTestComponents(); - await waitFor(() => { - expect(screen.getByTestId('add-note-container')).toBeVisible(); - }); - }, - SPECIAL_TEST_TIMEOUT - ); + expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); - it( - 'should be able to add notes through expandable flyout', - async () => { - (useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation( - jest.fn((feature: keyof ExperimentalFeatures) => { - if (feature === 'unifiedComponentsInTimelineEnabled') { - return true; - } - if (feature === 'securitySolutionNotesEnabled') { - return true; - } - return allowedExperimentalValues[feature]; - }) + expect(screen.getAllByTestId('timeline-notes-button-small')).toHaveLength(1); + expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled(); + + expect(screen.getByTestId('timeline-notes-notification-dot')).toBeVisible(); + + fireEvent.mouseOver(screen.getByTestId('timeline-notes-button-small')); + + await waitFor(() => { + expect(screen.getByTestId('timeline-notes-tool-tip')).toBeVisible(); + expect(screen.getByTestId('timeline-notes-tool-tip')).toHaveTextContent( + '1 Note available. Click to view it & add more.' + ); + }); + }, + SPECIAL_TEST_TIMEOUT ); + it( + 'should be able to add notes using EuiFlyout', + async () => { + renderTestComponents(); + expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); - renderTestComponents(); - expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); + await waitFor(() => { + expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled(); + }); - await waitFor(() => { - expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled(); - }); + fireEvent.click(screen.getByTestId('timeline-notes-button-small')); - fireEvent.click(screen.getByTestId('timeline-notes-button-small')); + await waitFor(() => { + expect(screen.getByTestId('add-note-container')).toBeVisible(); + }); + }, + SPECIAL_TEST_TIMEOUT + ); - await waitFor(() => { - expect(mockOpenFlyout).toHaveBeenCalled(); - }); - }, - SPECIAL_TEST_TIMEOUT - ); + it( + 'should be cancel adding notes', + async () => { + renderTestComponents(); + expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); - // once the new EUI flyout for notes is implemented this test should be removed - it.skip( - 'should be cancel adding notes', - async () => { - renderTestComponents(); - expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); + await waitFor(() => { + expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled(); + }); - await waitFor(() => { - expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled(); - }); + fireEvent.click(screen.getByTestId('timeline-notes-button-small')); - fireEvent.click(screen.getByTestId('timeline-notes-button-small')); + await waitFor(() => { + expect(screen.getByTestId('add-note-container')).toBeVisible(); + }); - await waitFor(() => { - expect(screen.getByTestId('add-note-container')).toBeVisible(); + userEvent.type(screen.getByTestId('euiMarkdownEditorTextArea'), 'Test Note 1'); + + expect(screen.getByTestId('cancel')).not.toBeDisabled(); + + fireEvent.click(screen.getByTestId('cancel')); + + await waitFor(() => { + expect(screen.queryByTestId('add-note-container')).not.toBeInTheDocument(); + }); + }, + SPECIAL_TEST_TIMEOUT + ); + }); + }); + + describe('securitySolutionNotesEnabled = false', () => { + describe('expandableFlyoutDisabled = false', () => { + beforeEach(() => { + (useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation( + jest.fn((feature: keyof ExperimentalFeatures) => { + if (feature === 'unifiedComponentsInTimelineEnabled') { + return true; + } + if (feature === 'securitySolutionNotesEnabled') { + return false; + } + return allowedExperimentalValues[feature]; + }) + ); }); - userEvent.type(screen.getByTestId('euiMarkdownEditorTextArea'), 'Test Note 1'); + it( + 'should have the notification dot & correct tooltip', + async () => { + renderTestComponents(); - expect(screen.getByTestId('cancel')).not.toBeDisabled(); + expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); - fireEvent.click(screen.getByTestId('cancel')); + expect(screen.getAllByTestId('timeline-notes-button-small')).toHaveLength(1); + expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled(); - await waitFor(() => { - expect(screen.queryByTestId('add-note-container')).not.toBeInTheDocument(); + expect(screen.getByTestId('timeline-notes-notification-dot')).toBeVisible(); + + fireEvent.mouseOver(screen.getByTestId('timeline-notes-button-small')); + + await waitFor(() => { + expect(screen.getByTestId('timeline-notes-tool-tip')).toBeVisible(); + expect(screen.getByTestId('timeline-notes-tool-tip')).toHaveTextContent( + '1 Note available. Click to view it & add more.' + ); + }); + }, + SPECIAL_TEST_TIMEOUT + ); + it( + 'should be able to add notes using EuiFlyout', + async () => { + renderTestComponents(); + expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); + + await waitFor(() => { + expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled(); + }); + + fireEvent.click(screen.getByTestId('timeline-notes-button-small')); + + await waitFor(() => { + expect(screen.getByTestId('add-note-container')).toBeVisible(); + }); + }, + SPECIAL_TEST_TIMEOUT + ); + + it( + 'should be cancel adding notes', + async () => { + renderTestComponents(); + expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); + + await waitFor(() => { + expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled(); + }); + + fireEvent.click(screen.getByTestId('timeline-notes-button-small')); + + await waitFor(() => { + expect(screen.getByTestId('add-note-container')).toBeVisible(); + }); + + userEvent.type(screen.getByTestId('euiMarkdownEditorTextArea'), 'Test Note 1'); + + expect(screen.getByTestId('cancel')).not.toBeDisabled(); + + fireEvent.click(screen.getByTestId('cancel')); + + await waitFor(() => { + expect(screen.queryByTestId('add-note-container')).not.toBeInTheDocument(); + }); + }, + SPECIAL_TEST_TIMEOUT + ); + }); + + describe('expandableFlyoutDisabled = true', () => { + beforeEach(() => { + (useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation( + jest.fn((feature: keyof ExperimentalFeatures) => { + if (feature === 'unifiedComponentsInTimelineEnabled') { + return true; + } + if (feature === 'expandableFlyoutDisabled') { + return true; + } + if (feature === 'securitySolutionNotesEnabled') { + return true; + } + return allowedExperimentalValues[feature]; + }) + ); }); - }, - SPECIAL_TEST_TIMEOUT - ); + + it( + 'should have the notification dot & correct tooltip', + async () => { + renderTestComponents(); + + expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); + + expect(screen.getAllByTestId('timeline-notes-button-small')).toHaveLength(1); + expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled(); + + expect(screen.getByTestId('timeline-notes-notification-dot')).toBeVisible(); + + fireEvent.mouseOver(screen.getByTestId('timeline-notes-button-small')); + + await waitFor(() => { + expect(screen.getByTestId('timeline-notes-tool-tip')).toBeVisible(); + expect(screen.getByTestId('timeline-notes-tool-tip')).toHaveTextContent( + '1 Note available. Click to view it & add more.' + ); + }); + }, + SPECIAL_TEST_TIMEOUT + ); + it( + 'should be able to add notes using EuiFlyout', + async () => { + renderTestComponents(); + expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); + + await waitFor(() => { + expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled(); + }); + + fireEvent.click(screen.getByTestId('timeline-notes-button-small')); + + await waitFor(() => { + expect(screen.getByTestId('add-note-container')).toBeVisible(); + }); + }, + SPECIAL_TEST_TIMEOUT + ); + + it( + 'should be cancel adding notes', + async () => { + renderTestComponents(); + expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); + + await waitFor(() => { + expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled(); + }); + + fireEvent.click(screen.getByTestId('timeline-notes-button-small')); + + await waitFor(() => { + expect(screen.getByTestId('add-note-container')).toBeVisible(); + }); + + userEvent.type(screen.getByTestId('euiMarkdownEditorTextArea'), 'Test Note 1'); + + expect(screen.getByTestId('cancel')).not.toBeDisabled(); + + fireEvent.click(screen.getByTestId('cancel')); + + await waitFor(() => { + expect(screen.queryByTestId('add-note-container')).not.toBeInTheDocument(); + }); + }, + SPECIAL_TEST_TIMEOUT + ); + }); + }); + }); + + describe('Leading actions - pin', () => { + describe('securitySolutionNotesEnabled = true', () => { + beforeEach(() => { + (useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation( + jest.fn((feature: keyof ExperimentalFeatures) => { + if (feature === 'unifiedComponentsInTimelineEnabled') { + return true; + } + if (feature === 'securitySolutionNotesEnabled') { + return true; + } + return allowedExperimentalValues[feature]; + }) + ); + }); + it( + 'should have the pin button with correct tooltip', + async () => { + renderTestComponents(); + + expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); + + expect(screen.getAllByTestId('pin')).toHaveLength(1); + // disabled because it is already pinned + expect(screen.getByTestId('pin')).toBeDisabled(); + + fireEvent.mouseOver(screen.getByTestId('pin')); + + await waitFor(() => { + expect(screen.getByTestId('timeline-action-pin-tool-tip')).toBeVisible(); + expect(screen.getByTestId('timeline-action-pin-tool-tip')).toHaveTextContent( + 'This event cannot be unpinned because it has notes' + ); + /* + * Above event is alert and not an event but `getEventType` in + *x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx + * returns it has event and not an alert even though, it has event.kind as signal. + * Need to see if it is okay + * + * */ + }); + }, + SPECIAL_TEST_TIMEOUT + ); + }); + + describe('securitySolutionNotesEnabled = false', () => { + beforeEach(() => { + (useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation( + jest.fn((feature: keyof ExperimentalFeatures) => { + if (feature === 'unifiedComponentsInTimelineEnabled') { + return true; + } + if (feature === 'securitySolutionNotesEnabled') { + return false; + } + return allowedExperimentalValues[feature]; + }) + ); + }); + + it( + 'should have the pin button with correct tooltip', + async () => { + renderTestComponents(); + + expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); + + expect(screen.getAllByTestId('pin')).toHaveLength(1); + // disabled because it is already pinned + expect(screen.getByTestId('pin')).toBeDisabled(); + + fireEvent.mouseOver(screen.getByTestId('pin')); + + await waitFor(() => { + expect(screen.getByTestId('timeline-action-pin-tool-tip')).toBeVisible(); + expect(screen.getByTestId('timeline-action-pin-tool-tip')).toHaveTextContent( + 'This event cannot be unpinned because it has notes' + ); + /* + * Above event is alert and not an event but `getEventType` in + * x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx + * returns it has event and not an alert even though, it has event.kind as signal. + * Need to see if it is okay + * + * */ + }); + }, + SPECIAL_TEST_TIMEOUT + ); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.test.tsx index ea6490bf18dd9..f7d58b1c04a2a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.test.tsx @@ -51,6 +51,9 @@ describe('useTimelineColumns', () => { timelineId: TimelineId.test, activeTab: TimelineTabs.query, refetch: refetchMock, + events: [], + pinnedEventIds: {}, + eventIdToNoteIds: {}, onToggleShowNotes: jest.fn(), }), { @@ -71,6 +74,9 @@ describe('useTimelineColumns', () => { timelineId: TimelineId.test, activeTab: TimelineTabs.query, refetch: refetchMock, + events: [], + pinnedEventIds: {}, + eventIdToNoteIds: {}, onToggleShowNotes: jest.fn(), }), { @@ -92,6 +98,9 @@ describe('useTimelineColumns', () => { timelineId: TimelineId.test, activeTab: TimelineTabs.query, refetch: refetchMock, + events: [], + pinnedEventIds: {}, + eventIdToNoteIds: {}, onToggleShowNotes: jest.fn(), }), { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.tsx index f6d583572c937..a32338a9dc03d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.tsx @@ -8,6 +8,7 @@ import React, { useMemo } from 'react'; import type { EuiDataGridCellValueElementProps } from '@elastic/eui'; import type { SortColumnTable } from '@kbn/securitysolution-data-table'; +import type { TimelineItem } from '@kbn/timelines-plugin/common'; import { useLicense } from '../../../../../common/hooks/use_license'; import { SourcererScopeName } from '../../../../../sourcerer/store/model'; import { useSourcererDataView } from '../../../../../sourcerer/containers'; @@ -16,10 +17,10 @@ import { getDefaultControlColumn } from '../../body/control_columns'; import type { UnifiedActionProps } from '../../unified_components/data_table/control_column_cell_render'; import type { TimelineTabs } from '../../../../../../common/types/timeline'; import { HeaderActions } from '../../../../../common/components/header_actions/header_actions'; -import { ControlColumnCellRender } from '../../unified_components/data_table/control_column_cell_render'; +import { TimelineControlColumnCellRender } from '../../unified_components/data_table/control_column_cell_render'; import type { ColumnHeaderOptions } from '../../../../../../common/types'; import { useTimelineColumns } from './use_timeline_columns'; -import type { TimelineDataGridCellContext } from '../../types'; +import type { UnifiedTimelineDataGridCellContext } from '../../types'; interface UseTimelineControlColumnArgs { columns: ColumnHeaderOptions[]; @@ -27,6 +28,9 @@ interface UseTimelineControlColumnArgs { timelineId: string; activeTab: TimelineTabs; refetch: () => void; + events: TimelineItem[]; + pinnedEventIds: Record<string, boolean>; + eventIdToNoteIds: Record<string, string[]>; onToggleShowNotes: (eventId?: string) => void; } @@ -40,6 +44,9 @@ export const useTimelineControlColumn = ({ timelineId, activeTab, refetch, + events, + pinnedEventIds, + eventIdToNoteIds, onToggleShowNotes, }: UseTimelineControlColumnArgs) => { const { browserFields } = useSourcererDataView(SourcererScopeName.timeline); @@ -54,7 +61,6 @@ export const useTimelineControlColumn = ({ // We need one less when the unified components are enabled because the document expand is provided by the unified data table const UNIFIED_COMPONENTS_ACTION_BUTTON_COUNT = ACTION_BUTTON_COUNT - 1; - return useMemo(() => { if (unifiedComponentsInTimelineEnabled) { return getDefaultControlColumn(UNIFIED_COMPONENTS_ACTION_BUTTON_COUNT).map((x) => ({ @@ -78,18 +84,36 @@ export const useTimelineControlColumn = ({ /> ); }, - rowCellRender: (props: EuiDataGridCellValueElementProps & TimelineDataGridCellContext) => { + rowCellRender: ( + props: EuiDataGridCellValueElementProps & UnifiedTimelineDataGridCellContext + ) => { + /* + * In some cases, when number of events is updated + * but new table is not yet rendered it can result + * in the mismatch between the number of events v/s + * the number of rows in the table currently rendered. + * + * */ + if (props.rowIndex >= events.length) return <></>; + props.setCellProps({ + className: + props.expandedEventId === events[props.rowIndex]?._id + ? 'unifiedDataTable__cell--expanded' + : '', + }); + return ( - <ControlColumnCellRender - {...props} + <TimelineControlColumnCellRender + rowIndex={props.rowIndex} + columnId={props.columnId} timelineId={timelineId} ariaRowindex={props.rowIndex} checked={false} columnValues="" - data={props.events[props.rowIndex].data} - ecsData={props.events[props.rowIndex].ecs} + data={events[props.rowIndex].data} + ecsData={events[props.rowIndex].ecs} loadingEventIds={EMPTY_STRING_ARRAY} - eventId={props.events[props.rowIndex]?._id} + eventId={events[props.rowIndex]?._id} index={props.rowIndex} onEventDetailsPanelOpened={noOp} onRowSelected={noOp} @@ -97,7 +121,9 @@ export const useTimelineControlColumn = ({ showCheckboxes={false} setEventsLoading={noOp} setEventsDeleted={noOp} - onToggleShowNotes={onToggleShowNotes} + pinnedEventIds={pinnedEventIds} + eventIdToNoteIds={eventIdToNoteIds} + toggleShowNotes={onToggleShowNotes} /> ); }, @@ -117,6 +143,9 @@ export const useTimelineControlColumn = ({ activeTab, timelineId, refetch, + events, + pinnedEventIds, + eventIdToNoteIds, onToggleShowNotes, ACTION_BUTTON_COUNT, ]); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/types.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/types.ts index e7dedcfa9aad3..3ce0d3e876e77 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/types.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/types.ts @@ -5,14 +5,6 @@ * 2.0. */ -import type { TimelineItem } from '@kbn/timelines-plugin/common'; -import type { TimelineModel } from '../../store/model'; - -export interface TimelineDataGridCellContext { - events: TimelineItem[]; - pinnedEventIds: TimelineModel['pinnedEventIds']; - eventIdsAddingNotes: Set<string>; - onToggleShowNotes: (eventId?: string) => void; - eventIdToNoteIds: Record<string, string[]>; - refetch: () => void; +export interface UnifiedTimelineDataGridCellContext { + expandedEventId?: string; } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/__snapshots__/custom_timeline_data_grid_body.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/__snapshots__/custom_timeline_data_grid_body.test.tsx.snap index d11a5f23cbda6..4e220a4d2db51 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/__snapshots__/custom_timeline_data_grid_body.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/__snapshots__/custom_timeline_data_grid_body.test.tsx.snap @@ -1,24 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`CustomTimelineDataGridBody should render exactly as snapshots 1`] = ` -.c3 { - padding-top: 8px; -} - -.c2 { - border: none; - background-color: transparent; - box-shadow: none; -} - -.c2.euiPanel--plain { - background-color: transparent; -} - -.c4 { - margin-bottom: 5px; -} - .c0 { width: -webkit-fit-content; width: -moz-fit-content; @@ -110,140 +92,6 @@ exports[`CustomTimelineDataGridBody should render exactly as snapshots 1`] = ` Cell-0-2 </div> </div> - <div - class="euiPanel euiPanel--plain c2 udt--customRow emotion-euiPanel-grow-m-plain" - data-test-subj="note-cards" - > - <section - class="c3" - data-test-subj="note-previews-container" - > - <div - class="euiFlexGroup c4 notes-container-0 emotion-euiFlexGroup-none-flexStart-stretch-column" - data-test-subj="notes" - > - <p - class="emotion-euiScreenReaderOnly" - > - You are viewing notes for the event in row 0. Press the up arrow key when finished to return to the event. - </p> - <ol - class="euiTimeline euiCommentList emotion-euiTimeline-l" - data-test-subj="note-comment-list" - role="list" - > - <li - class="euiComment emotion-euiTimelineItem-top" - data-test-subj="note-preview-id" - > - <div - class="emotion-euiTimelineItemIcon-top" - > - <div - class="emotion-euiTimelineItemIcon__content" - > - <div - aria-label="test" - class="euiAvatar euiAvatar--l euiAvatar--user emotion-euiAvatar-user-l-uppercase" - data-test-subj="avatar" - role="img" - style="background-color: rgb(228, 166, 199); color: rgb(0, 0, 0);" - title="test" - > - <span - aria-hidden="true" - > - t - </span> - </div> - </div> - </div> - <div - class="emotion-euiTimelineItemEvent-top" - > - <figure - class="euiCommentEvent emotion-euiCommentEvent-border-subdued" - data-type="regular" - > - <figcaption - class="euiCommentEvent__header emotion-euiCommentEvent__header-border-subdued" - > - <div - class="euiPanel euiPanel--subdued euiPanel--paddingSmall emotion-euiPanel-grow-none-s-subdued" - > - <div - class="euiCommentEvent__headerMain emotion-euiCommentEvent__headerMain" - > - <div - class="euiCommentEvent__headerData emotion-euiCommentEvent__headerData" - > - <div - class="euiCommentEvent__headerUsername emotion-euiCommentEvent__headerUsername" - > - test - </div> - <div - class="euiCommentEvent__headerEvent emotion-euiCommentEvent__headerEvent" - > - added a note - </div> - <div - class="euiCommentEvent__headerTimestamp" - > - <time> - now - </time> - </div> - </div> - <div - class="euiCommentEvent__headerActions emotion-euiCommentEvent__headerActions" - > - <button - aria-label="Delete Note" - class="euiButtonIcon emotion-euiButtonIcon-xs-empty-text" - data-test-subj="delete-note" - title="Delete Note" - type="button" - > - <span - aria-hidden="true" - class="euiButtonIcon__icon" - color="inherit" - data-euiicon-type="trash" - /> - </button> - </div> - </div> - </div> - </figcaption> - <div - class="euiCommentEvent__body emotion-euiCommentEvent__body-regular" - > - <div - class="note-content" - tabindex="0" - > - <p - class="emotion-euiScreenReaderOnly" - > - test added a note - </p> - <div - class="euiText euiMarkdownFormat emotion-euiText-m-euiTextColor-default-euiMarkdownFormat-m-default" - > - <p> - note - </p> - </div> - </div> - </div> - </figure> - </div> - </li> - </ol> - </div> - </section> - </div> <div> Cell-0-3 </div> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/control_column_cell_render.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/control_column_cell_render.tsx index 1dcd3ccd9db25..ca9a1b0c06d5e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/control_column_cell_render.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/control_column_cell_render.tsx @@ -6,7 +6,6 @@ */ import React, { memo, useMemo } from 'react'; -import type { TimelineItem } from '@kbn/timelines-plugin/common'; import { eventIsPinned } from '../../body/helpers'; import { Actions } from '../../../../../common/components/header_actions'; import type { TimelineModel } from '../../../../store/model'; @@ -14,37 +13,46 @@ import type { ActionProps } from '../../../../../../common/types'; const noOp = () => {}; export interface UnifiedActionProps extends ActionProps { - onToggleShowNotes: (eventId?: string) => void; - events: TimelineItem[]; pinnedEventIds: TimelineModel['pinnedEventIds']; } -export const ControlColumnCellRender = memo(function ControlColumnCellRender( +export const TimelineControlColumnCellRender = memo(function TimelineControlColumnCellRender( props: UnifiedActionProps ) { - const { rowIndex, events, pinnedEventIds, onToggleShowNotes, eventIdToNoteIds, timelineId } = - props; + const { rowIndex, pinnedEventIds } = props; - const event = useMemo(() => events && events[rowIndex], [events, rowIndex]); const isPinned = useMemo( - () => eventIsPinned({ eventId: event?._id, pinnedEventIds }), - [event?._id, pinnedEventIds] + () => eventIsPinned({ eventId: props.eventId, pinnedEventIds }), + [props.eventId, pinnedEventIds] ); return ( <Actions - {...props} - ariaRowindex={rowIndex} + action={props.action} + columnId={props.columnId} columnValues="columnValues" - disableExpandAction - eventIdToNoteIds={eventIdToNoteIds} + data={props.data} + ecsData={props.ecsData} + eventId={props.eventId} + eventIdToNoteIds={props.eventIdToNoteIds} + index={rowIndex} isEventPinned={isPinned} isEventViewer={false} + refetch={props.refetch} + rowIndex={rowIndex} + setEventsDeleted={noOp} + setEventsLoading={noOp} onEventDetailsPanelOpened={noOp} + onRowSelected={noOp} onRuleChange={noOp} + showCheckboxes={false} showNotes={true} - timelineId={timelineId} - toggleShowNotes={onToggleShowNotes} - rowIndex={rowIndex} + timelineId={props.timelineId} + ariaRowindex={rowIndex} + checked={false} + loadingEventIds={props.loadingEventIds} + toggleShowNotes={props.toggleShowNotes} + disableExpandAction + disablePinAction={false} /> ); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/custom_timeline_data_grid_body.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/custom_timeline_data_grid_body.test.tsx index 779a1c5bed059..e93b9014785f4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/custom_timeline_data_grid_body.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/custom_timeline_data_grid_body.test.tsx @@ -16,7 +16,6 @@ import { defaultUdtHeaders } from '../default_headers'; import type { EuiDataGridColumn } from '@elastic/eui'; import { useStatefulRowRenderer } from '../../body/events/stateful_row_renderer/use_stateful_row_renderer'; import { TIMELINE_EVENT_DETAIL_ROW_ID } from '../../body/constants'; -import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector'; jest.mock('../../../../../common/hooks/use_selector'); @@ -40,8 +39,6 @@ const mockVisibleColumns = ['@timestamp', 'message', 'user.name'] .map((id) => defaultUdtHeaders.find((h) => h.id === id) as EuiDataGridColumn) .concat(additionalTrailingColumn); -const mockEventIdsAddingNotes = new Set<string>(); - const defaultProps: CustomTimelineDataGridBodyProps = { Cell: MockCellComponent, visibleRowData: { startRow: 0, endRow: 2, visibleRowCount: 2 }, @@ -49,34 +46,6 @@ const defaultProps: CustomTimelineDataGridBodyProps = { enabledRowRenderers: [], setCustomGridBodyProps: jest.fn(), visibleColumns: mockVisibleColumns, - eventIdsAddingNotes: mockEventIdsAddingNotes, - eventIdToNoteIds: { - event1: ['noteId1', 'noteId2'], - event2: ['noteId3'], - }, - events: [ - { - _id: 'event1', - _index: 'logs-*', - data: [], - ecs: { _id: 'event1', _index: 'logs-*' }, - }, - { - _id: 'event2', - _index: 'logs-*', - data: [], - ecs: { _id: 'event2', _index: 'logs-*' }, - }, - ], - onToggleShowNotes: (eventId?: string) => { - if (eventId) { - if (mockEventIdsAddingNotes.has(eventId)) { - mockEventIdsAddingNotes.delete(eventId); - } else { - mockEventIdsAddingNotes.add(eventId); - } - } - }, }; const renderTestComponents = (props?: CustomTimelineDataGridBodyProps) => { @@ -91,34 +60,9 @@ const renderTestComponents = (props?: CustomTimelineDataGridBodyProps) => { describe('CustomTimelineDataGridBody', () => { beforeEach(() => { - const now = new Date(); (useStatefulRowRenderer as jest.Mock).mockReturnValue({ canShowRowRenderer: true, }); - (useDeepEqualSelector as jest.Mock).mockReturnValue({ - noteId1: { - created: now, - eventId: 'event1', - id: 'test', - lastEdit: now, - note: 'note', - user: 'test', - saveObjectId: 'id', - timelineId: 'timeline-1', - version: '', - }, - noteId2: { - created: now, - eventId: 'event1', - id: 'test', - lastEdit: now, - note: 'note', - user: 'test', - saveObjectId: 'id', - timelineId: 'timeline-1', - version: '', - }, - }); }); afterEach(() => { @@ -145,18 +89,4 @@ describe('CustomTimelineDataGridBody', () => { expect(queryByText('Cell-0-3')).toBeFalsy(); expect(getByText('Cell-1-3')).toBeInTheDocument(); }); - - it('should render a note when notes are present', () => { - const { getByText } = renderTestComponents(); - expect(getByText('note')).toBeInTheDocument(); - }); - - it('should render the note creation form when the set of eventIds adding a note includes the eventId', () => { - const { getByTestId } = renderTestComponents({ - ...defaultProps, - eventIdsAddingNotes: new Set(['event1']), - }); - - expect(getByTestId('new-note-tabs')).toBeInTheDocument(); - }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/custom_timeline_data_grid_body.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/custom_timeline_data_grid_body.tsx index fecfb56f87b14..fd8d3a9011f3d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/custom_timeline_data_grid_body.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/custom_timeline_data_grid_body.tsx @@ -10,16 +10,9 @@ import type { DataTableRecord } from '@kbn/discover-utils/types'; import type { EuiTheme } from '@kbn/react-kibana-context-styled'; import type { TimelineItem } from '@kbn/timelines-plugin/common'; import type { FC } from 'react'; -import React, { memo, useMemo, useCallback } from 'react'; -import { useDispatch } from 'react-redux'; +import React, { memo, useMemo } from 'react'; import styled from 'styled-components'; import type { RowRenderer } from '../../../../../../common/types'; -import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector'; -import { appSelectors } from '../../../../../common/store'; -import { TimelineId } from '../../../../../../common/types/timeline'; -import { timelineActions } from '../../../../store'; -import { NoteCards } from '../../../notes/note_cards'; -import type { TimelineResultNote } from '../../../open_timeline/types'; import { TIMELINE_EVENT_DETAIL_ROW_ID } from '../../body/constants'; import { useStatefulRowRenderer } from '../../body/events/stateful_row_renderer/use_stateful_row_renderer'; import { useGetEventTypeRowClassName } from './use_get_event_type_row_classname'; @@ -27,16 +20,10 @@ import { useGetEventTypeRowClassName } from './use_get_event_type_row_classname' export type CustomTimelineDataGridBodyProps = EuiDataGridCustomBodyProps & { rows: Array<DataTableRecord & TimelineItem> | undefined; enabledRowRenderers: RowRenderer[]; - events: TimelineItem[]; - eventIdToNoteIds?: Record<string, string[]> | null; - eventIdsAddingNotes?: Set<string>; - onToggleShowNotes: (eventId?: string) => void; rowHeight?: number; refetch?: () => void; }; -const emptyNotes: string[] = []; - // THE DataGrid Row default is 34px, but we make ours 40 to account for our row actions const DEFAULT_UDT_ROW_HEIGHT = 40; @@ -55,48 +42,17 @@ const DEFAULT_UDT_ROW_HEIGHT = 40; * */ export const CustomTimelineDataGridBody: FC<CustomTimelineDataGridBodyProps> = memo( function CustomTimelineDataGridBody(props) { - const { - Cell, - visibleColumns, - visibleRowData, - rows, - rowHeight, - enabledRowRenderers, - events = [], - eventIdToNoteIds = {}, - eventIdsAddingNotes = new Set<string>(), - onToggleShowNotes, - refetch, - } = props; - const getNotesByIds = useMemo(() => appSelectors.notesByIdsSelector(), []); - const notesById = useDeepEqualSelector(getNotesByIds); + const { Cell, visibleColumns, visibleRowData, rows, rowHeight, enabledRowRenderers, refetch } = + props; + const visibleRows = useMemo( () => (rows ?? []).slice(visibleRowData.startRow, visibleRowData.endRow), [rows, visibleRowData] ); - const eventIds = useMemo(() => events.map((event) => event._id), [events]); return ( <> {visibleRows.map((row, rowIndex) => { - const eventId = eventIds[rowIndex]; - const noteIds: string[] = (eventIdToNoteIds && eventIdToNoteIds[eventId]) || emptyNotes; - const notes = noteIds - .map((noteId) => { - const note = notesById[noteId]; - if (note) { - return { - savedObjectId: note.saveObjectId, - note: note.note, - noteId: note.id, - updated: (note.lastEdit ?? note.created).getTime(), - updatedBy: note.user, - }; - } else { - return null; - } - }) - .filter((note) => note !== null) as TimelineResultNote[]; return ( <CustomDataGridSingleRow rowData={row} @@ -106,10 +62,6 @@ export const CustomTimelineDataGridBody: FC<CustomTimelineDataGridBodyProps> = m rowHeight={rowHeight} Cell={Cell} enabledRowRenderers={enabledRowRenderers} - notes={notes} - eventIdsAddingNotes={eventIdsAddingNotes} - eventId={eventId} - onToggleShowNotes={onToggleShowNotes} refetch={refetch} /> ); @@ -188,10 +140,6 @@ const CustomGridRowCellWrapper = styled.div.attrs<{ type CustomTimelineDataGridSingleRowProps = { rowData: DataTableRecord & TimelineItem; rowIndex: number; - notes?: TimelineResultNote[] | null; - eventId?: string; - eventIdsAddingNotes?: Set<string>; - onToggleShowNotes: (eventId?: string) => void; } & Pick< CustomTimelineDataGridBodyProps, 'visibleColumns' | 'Cell' | 'enabledRowRenderers' | 'refetch' | 'rowHeight' @@ -221,14 +169,8 @@ const CustomDataGridSingleRow = memo(function CustomDataGridSingleRow( enabledRowRenderers, visibleColumns, Cell, - notes, - eventIdsAddingNotes, - eventId = '', - onToggleShowNotes, - refetch, rowHeight: rowHeightMultiple = 0, } = props; - const dispatch = useDispatch(); const { canShowRowRenderer } = useStatefulRowRenderer({ data: rowData.ecs, rowRenderers: enabledRowRenderers, @@ -251,26 +193,6 @@ const CustomDataGridSingleRow = memo(function CustomDataGridSingleRow( ); const eventTypeRowClassName = useGetEventTypeRowClassName(rowData.ecs); - const associateNote = useCallback( - (noteId: string) => { - dispatch( - timelineActions.addNoteToEvent({ - eventId, - id: TimelineId.active, - noteId, - }) - ); - if (refetch) { - refetch(); - } - }, - [dispatch, eventId, refetch] - ); - - const renderNotesContainer = useMemo(() => { - return ((notes && notes.length > 0) || eventIdsAddingNotes?.has(eventId)) ?? false; - }, [notes, eventIdsAddingNotes, eventId]); - return ( <CustomGridRow className={`${rowIndex % 2 === 0 ? 'euiDataGridRow--striped' : ''}`} @@ -293,18 +215,6 @@ const CustomDataGridSingleRow = memo(function CustomDataGridSingleRow( return null; })} </CustomGridRowCellWrapper> - {renderNotesContainer && ( - <NoteCards - ariaRowindex={rowIndex} - associateNote={associateNote} - className="udt--customRow" - data-test-subj="note-cards" - notes={notes ?? []} - showAddNote={eventIdsAddingNotes?.has(eventId) ?? false} - toggleShowAddNote={onToggleShowNotes} - eventId={eventId} - /> - )} {/* Timeline Expanded Row */} {canShowRowRenderer ? ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx index f512fcbe04a0c..d41ba9dfcc5d7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx @@ -49,6 +49,7 @@ import { TimelineEventDetailRow } from './timeline_event_detail_row'; import { CustomTimelineDataGridBody } from './custom_timeline_data_grid_body'; import { TIMELINE_EVENT_DETAIL_ROW_ID } from '../../body/constants'; import { useUnifiedTableExpandableFlyout } from '../hooks/use_unified_timeline_expandable_flyout'; +import type { UnifiedTimelineDataGridCellContext } from '../../types'; export const SAMPLE_SIZE_SETTING = 500; const DataGridMemoized = React.memo(UnifiedDataTable); @@ -73,8 +74,6 @@ type CommonDataTableProps = { updatedAt: number; isTextBasedQuery?: boolean; leadingControlColumns: EuiDataGridProps['leadingControlColumns']; - cellContext?: EuiDataGridProps['cellContext']; - eventIdToNoteIds?: Record<string, string[]>; } & Pick< UnifiedDataTableProps, | 'onSort' @@ -117,8 +116,6 @@ export const TimelineDataTableComponent: React.FC<DataTableProps> = memo( onSort, onFilter, leadingControlColumns, - cellContext, - eventIdToNoteIds, }) { const dispatch = useDispatch(); @@ -389,28 +386,28 @@ export const TimelineDataTableComponent: React.FC<DataTableProps> = memo( Cell={Cell} visibleColumns={visibleColumns} visibleRowData={visibleRowData} - eventIdToNoteIds={eventIdToNoteIds} - rowHeight={rowHeight} setCustomGridBodyProps={setCustomGridBodyProps} - events={events} enabledRowRenderers={enabledRowRenderers} - eventIdsAddingNotes={cellContext?.eventIdsAddingNotes} - onToggleShowNotes={cellContext?.onToggleShowNotes} refetch={refetch} /> ), - [ - tableRows, - enabledRowRenderers, - events, - eventIdToNoteIds, - cellContext?.eventIdsAddingNotes, - cellContext?.onToggleShowNotes, - rowHeight, - refetch, - ] + [tableRows, enabledRowRenderers, refetch] ); + const cellContext: UnifiedTimelineDataGridCellContext = useMemo(() => { + return { + expandedEventId: expandedDoc?.id, + }; + }, [expandedDoc]); + + const finalRenderCustomBodyCallback = useMemo(() => { + return enabledRowRenderers.length > 0 ? renderCustomBodyCallback : undefined; + }, [enabledRowRenderers.length, renderCustomBodyCallback]); + + const finalTrailControlColumns = useMemo(() => { + return enabledRowRenderers.length > 0 ? trailingControlColumns : undefined; + }, [enabledRowRenderers.length, trailingControlColumns]); + return ( <StatefulEventContext.Provider value={activeStatefulEventContext}> <StyledTimelineUnifiedDataTable> @@ -460,8 +457,8 @@ export const TimelineDataTableComponent: React.FC<DataTableProps> = memo( showMultiFields={true} cellActionsMetadata={cellActionsMetadata} externalAdditionalControls={additionalControls} - renderCustomGridBody={renderCustomBodyCallback} - trailingControlColumns={trailingControlColumns} + renderCustomGridBody={finalRenderCustomBodyCallback} + trailingControlColumns={finalTrailControlColumns} externalControlColumns={leadingControlColumns} cellContext={cellContext} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/toolbar_additional_controls.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/toolbar_additional_controls.tsx index 8e24d7fcbcc5c..5897c1aec4014 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/toolbar_additional_controls.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/toolbar_additional_controls.tsx @@ -17,6 +17,7 @@ import { StatefulRowRenderersBrowser } from '../../../row_renderers_browser'; import * as i18n from './translations'; import { EXIT_FULL_SCREEN_CLASS_NAME } from '../../../../../common/components/exit_full_screen'; import { LastUpdatedContainer } from '../../footer/last_updated'; +import { RowRendererSwitch } from '../../../row_renderer_switch'; export const isFullScreen = ({ globalFullScreen, @@ -68,6 +69,7 @@ export const ToolbarAdditionalControlsComponent: React.FC<Props> = ({ timelineId return ( <> + <RowRendererSwitch timelineId={timelineId} /> <StatefulRowRenderersBrowser timelineId={timelineId} /> <LastUpdatedContainer updatedAt={updatedAt} /> <span className="rightPosition"> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.test.tsx index 4cb56cdeba012..881a129c90a83 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.test.tsx @@ -118,8 +118,6 @@ const TestComponent = (props: Partial<ComponentProps<typeof UnifiedTimeline>>) = dataLoadingState: DataLoadingState.loaded, updatedAt: Date.now(), isTextBasedQuery: false, - eventIdToNoteIds: {} as Record<string, string[]>, - pinnedEventIds: {} as Record<string, boolean>, }; const dispatch = useDispatch(); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.tsx index eaa85e635e4cc..b4e67b373c845 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.tsx @@ -47,10 +47,8 @@ import { DRAG_DROP_FIELD } from './data_table/translations'; import { TimelineResizableLayout } from './resizable_layout'; import TimelineDataTable from './data_table'; import { timelineActions } from '../../../store'; -import type { TimelineModel } from '../../../store/model'; import { getFieldsListCreationOptions } from './get_fields_list_creation_options'; import { defaultUdtHeaders } from './default_headers'; -import type { TimelineDataGridCellContext } from '../types'; const TimelineBodyContainer = styled.div.attrs(({ className = '' }) => ({ className: `${className}`, @@ -120,8 +118,6 @@ interface Props { dataView: DataView; trailingControlColumns?: EuiDataGridProps['trailingControlColumns']; leadingControlColumns?: EuiDataGridProps['leadingControlColumns']; - pinnedEventIds: TimelineModel['pinnedEventIds']; - eventIdToNoteIds: TimelineModel['eventIdToNoteIds']; } const UnifiedTimelineComponent: React.FC<Props> = ({ @@ -146,8 +142,6 @@ const UnifiedTimelineComponent: React.FC<Props> = ({ dataView, trailingControlColumns, leadingControlColumns, - pinnedEventIds, - eventIdToNoteIds, }) => { const dispatch = useDispatch(); const unifiedFieldListContainerRef = useRef<UnifiedFieldListSidebarContainerApi>(null); @@ -170,22 +164,6 @@ const UnifiedTimelineComponent: React.FC<Props> = ({ query: { filterManager: timelineFilterManager }, } = timelineDataService; - const [eventIdsAddingNotes, setEventIdsAddingNotes] = useState<Set<string>>(new Set()); - - const onToggleShowNotes = useCallback( - (eventId?: string) => { - if (!eventId) return; - const newSet = new Set(eventIdsAddingNotes); - if (newSet.has(eventId)) { - newSet.delete(eventId); - setEventIdsAddingNotes(newSet); - } else { - setEventIdsAddingNotes(newSet.add(eventId)); - } - }, - [eventIdsAddingNotes] - ); - const fieldListSidebarServices: UnifiedFieldListSidebarContainerProps['services'] = useMemo( () => ({ fieldFormats, @@ -373,17 +351,6 @@ const UnifiedTimelineComponent: React.FC<Props> = ({ onFieldEdited(); }, [onFieldEdited]); - const cellContext: TimelineDataGridCellContext = useMemo(() => { - return { - events, - pinnedEventIds, - eventIdsAddingNotes, - onToggleShowNotes, - eventIdToNoteIds, - refetch, - }; - }, [events, pinnedEventIds, eventIdsAddingNotes, onToggleShowNotes, eventIdToNoteIds, refetch]); - return ( <TimelineBodyContainer className="timelineBodyContainer" ref={setSidebarContainer}> <TimelineResizableLayout @@ -460,8 +427,6 @@ const UnifiedTimelineComponent: React.FC<Props> = ({ onFilter={onAddFilter as DocViewFilterFn} trailingControlColumns={trailingControlColumns} leadingControlColumns={leadingControlColumns} - cellContext={cellContext} - eventIdToNoteIds={eventIdToNoteIds} /> </EventDetailsWidthProvider> </DropOverlayWrapper> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/styles.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/styles.tsx index 30001b2ed3a37..78ab042155e7e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/styles.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/styles.tsx @@ -56,6 +56,12 @@ export const StyledMainEuiPanel = styled(EuiPanel).attrs(({ className = '' }) => height: 100%; `; +export const leadingActionsColumnStyles = ` + .udtTimeline .euiDataGridRowCell--controlColumn:nth-child(3) .euiDataGridRowCell__content { + padding: 0; + } +`; + export const StyledTimelineUnifiedDataTable = styled.div.attrs(({ className = '' }) => ({ className: `unifiedDataTable ${className}`, role: 'rowgroup', @@ -167,6 +173,8 @@ export const StyledTimelineUnifiedDataTable = styled.div.attrs(({ className = '' display: flex; align-items: baseline; } + + ${leadingActionsColumnStyles} `; export const UnifiedTimelineGlobalStyles = createGlobalStyle` diff --git a/x-pack/plugins/security_solution/public/timelines/hooks/use_create_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/hooks/use_create_timeline.tsx index 8c30f2485ae09..03f3eba0ab23b 100644 --- a/x-pack/plugins/security_solution/public/timelines/hooks/use_create_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/hooks/use_create_timeline.tsx @@ -22,6 +22,7 @@ import type { TimeRange } from '../../common/store/inputs/model'; import { useDiscoverInTimelineContext } from '../../common/components/discover_in_timeline/use_discover_in_timeline_context'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { defaultUdtHeaders } from '../components/timeline/unified_components/default_headers'; +import { timelineDefaults } from '../store/defaults'; export interface UseCreateTimelineParams { /** @@ -75,6 +76,7 @@ export const useCreateTimeline = ({ selectedPatterns, }) ); + dispatch( timelineActions.createTimeline({ columns: unifiedComponentsInTimelineEnabled ? defaultUdtHeaders : defaultHeaders, @@ -84,6 +86,9 @@ export const useCreateTimeline = ({ show, timelineType, updated: undefined, + excludedRowRendererIds: unifiedComponentsInTimelineEnabled + ? timelineDefaults.excludedRowRendererIds + : [], }) ); diff --git a/x-pack/plugins/security_solution/public/timelines/store/defaults.ts b/x-pack/plugins/security_solution/public/timelines/store/defaults.ts index 55155a30dbbbf..27ca80320ff06 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/defaults.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/defaults.ts @@ -6,7 +6,7 @@ */ import { TimelineTabs } from '../../../common/types/timeline'; -import { TimelineType, TimelineStatus } from '../../../common/api/timeline'; +import { TimelineType, TimelineStatus, RowRendererId } from '../../../common/api/timeline'; import { defaultHeaders } from '../components/timeline/body/column_headers/default_headers'; import { normalizeTimeRange } from '../../common/utils/normalize_time_range'; @@ -35,7 +35,26 @@ export const timelineDefaults: SubsetTimelineModel & }, eventType: 'all', eventIdToNoteIds: {}, - excludedRowRendererIds: [], + excludedRowRendererIds: [ + RowRendererId.alert, + RowRendererId.alerts, + RowRendererId.auditd, + RowRendererId.auditd_file, + RowRendererId.library, + RowRendererId.netflow, + RowRendererId.plain, + RowRendererId.registry, + RowRendererId.suricata, + RowRendererId.system, + RowRendererId.system_dns, + RowRendererId.system_endgame_process, + RowRendererId.system_file, + RowRendererId.system_fim, + RowRendererId.system_security_event, + RowRendererId.system_socket, + RowRendererId.threat_match, + RowRendererId.zeek, + ], expandedDetail: {}, highlightedDropAndProviderId: '', historyIds: [], diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.test.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.test.ts index 624ae513a418f..9fb0585601042 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.test.ts @@ -102,7 +102,10 @@ describe('Timeline note middleware', () => { }, }, }); - expect(selectTimelineById(store.getState(), TimelineId.test).eventIdToNoteIds).toEqual({}); + expect(selectTimelineById(store.getState(), TimelineId.test).eventIdToNoteIds).toEqual({ + // existing note + '1': ['1'], + }); await store.dispatch(updateNote({ note: testNote })); await store.dispatch( addNoteToEvent({ eventId: testEventId, id: TimelineId.test, noteId: testNote.id }) diff --git a/x-pack/plugins/security_solution/public/timelines/store/selectors.ts b/x-pack/plugins/security_solution/public/timelines/store/selectors.ts index d639a8f256740..19281ba06883d 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/selectors.ts @@ -178,3 +178,8 @@ export const selectDataInTimeline = createSelector( return !isEmpty(dataProviders) || !isEmpty(get('filterQuery.kuery.expression', kqlQuery)); } ); + +export const selectExcludedRowRendererIds = createSelector( + selectTimelineById, + (timeline) => timeline?.excludedRowRendererIds +); From 24eb9e6d955a8cb8c991953ea14212543e30990d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= <alejandro.haro@elastic.co> Date: Tue, 2 Jul 2024 18:38:30 +0200 Subject: [PATCH 097/128] Remove `joi` leakage to the browser in `synthetics` (#187308) Co-authored-by: jennypavlova <jennypavlova94@gmail.com> --- .../monitor_management/alert_config.ts | 12 ------------ .../monitor_management/alert_config_schema.ts | 19 +++++++++++++++++++ .../monitor_list_table/monitor_list.tsx | 2 +- .../overview/overview/sort_fields.tsx | 2 +- .../overview/overview/use_infinite_scroll.ts | 2 +- .../lazy_wrapper/monitor_status.tsx | 2 +- .../lib/alert_types/monitor_status.tsx | 8 ++++---- .../synthetics/state/monitor_list/models.ts | 2 +- .../apps/synthetics/state/overview/models.ts | 2 +- .../monitor_cruds/monitor_validation.ts | 2 +- 10 files changed, 30 insertions(+), 23 deletions(-) create mode 100644 x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/alert_config_schema.ts diff --git a/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/alert_config.ts b/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/alert_config.ts index 596292879bfb5..a2beff50149dd 100644 --- a/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/alert_config.ts +++ b/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/alert_config.ts @@ -6,7 +6,6 @@ */ import * as t from 'io-ts'; -import { schema } from '@kbn/config-schema'; export const AlertConfigCodec = t.intersection([ t.interface({ @@ -22,17 +21,6 @@ export const AlertConfigsCodec = t.partial({ status: AlertConfigCodec, }); -export const AlertConfigSchema = schema.object({ - tls: schema.maybe( - schema.object({ - enabled: schema.boolean(), - }) - ), - status: schema.object({ - enabled: schema.boolean(), - }), -}); - export type AlertConfig = t.TypeOf<typeof AlertConfigCodec>; export type AlertConfigs = t.TypeOf<typeof AlertConfigsCodec>; diff --git a/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/alert_config_schema.ts b/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/alert_config_schema.ts new file mode 100644 index 0000000000000..87d2d9ced2f26 --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/alert_config_schema.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 { schema } from '@kbn/config-schema'; + +export const AlertConfigSchema = schema.object({ + tls: schema.maybe( + schema.object({ + enabled: schema.boolean(), + }) + ), + status: schema.object({ + enabled: schema.boolean(), + }), +}); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_list.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_list.tsx index 180b6299bfa4f..7ae4196b5e318 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_list.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_list.tsx @@ -15,7 +15,7 @@ import { useIsWithinMinBreakpoint, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { MonitorListSortField } from '../../../../../../../common/runtime_types/monitor_management/sort_field'; +import type { MonitorListSortField } from '../../../../../../../common/runtime_types/monitor_management/sort_field'; import { DeleteMonitor } from './delete_monitor'; import { IHttpSerializedFetchError } from '../../../../state/utils/http_error'; import { MonitorListPageState } from '../../../../state'; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/sort_fields.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/sort_fields.tsx index 7305e5fcbd2c8..4d1341a805435 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/sort_fields.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/sort_fields.tsx @@ -9,7 +9,7 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { useDispatch, useSelector } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; -import { MonitorListSortField } from '../../../../../../../common/runtime_types/monitor_management/sort_field'; +import type { MonitorListSortField } from '../../../../../../../common/runtime_types/monitor_management/sort_field'; import { ConfigKey } from '../../../../../../../common/runtime_types'; import { selectOverviewState, setOverviewPageStateAction } from '../../../../state/overview'; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/use_infinite_scroll.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/use_infinite_scroll.ts index e30ab8aa952ca..e3e1f48d4e520 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/use_infinite_scroll.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/use_infinite_scroll.ts @@ -9,7 +9,7 @@ import useThrottle from 'react-use/lib/useThrottle'; import { useEffect, useState, MutableRefObject } from 'react'; import useIntersection from 'react-use/lib/useIntersection'; import { useSelector } from 'react-redux'; -import { MonitorListSortField } from '../../../../../../../common/runtime_types/monitor_management/sort_field'; +import type { MonitorListSortField } from '../../../../../../../common/runtime_types/monitor_management/sort_field'; import { useGetUrlParams } from '../../../../hooks'; import { selectOverviewState } from '../../../../state'; import { MonitorOverviewItem } from '../../../../../../../common/runtime_types'; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/lazy_wrapper/monitor_status.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/lazy_wrapper/monitor_status.tsx index c3220ede642fc..7c33dc7aba96e 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/lazy_wrapper/monitor_status.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/lazy_wrapper/monitor_status.tsx @@ -16,7 +16,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { kibanaService } from '../../../../../utils/kibana_service'; import { ClientPluginsStart } from '../../../../../plugin'; import { store } from '../../../state'; -import { StatusRuleParams } from '../../../../../../common/rules/status_rule'; +import type { StatusRuleParams } from '../../../../../../common/rules/status_rule'; interface Props { core: CoreStart; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/monitor_status.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/monitor_status.tsx index df0f160aee3c0..8ee01e185e8c1 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/monitor_status.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/lib/alert_types/monitor_status.tsx @@ -9,14 +9,14 @@ import React from 'react'; import { ALERT_REASON } from '@kbn/rule-data-utils'; -import { ObservabilityRuleTypeModel } from '@kbn/observability-plugin/public'; -import { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; +import type { ObservabilityRuleTypeModel } from '@kbn/observability-plugin/public'; +import type { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; import { getSyntheticsErrorRouteFromMonitorId } from '../../../../../common/utils/get_synthetics_monitor_url'; import { STATE_ID } from '../../../../../common/field_names'; import { SyntheticsMonitorStatusTranslations } from '../../../../../common/rules/synthetics/translations'; -import { StatusRuleParams } from '../../../../../common/rules/status_rule'; +import type { StatusRuleParams } from '../../../../../common/rules/status_rule'; import { SYNTHETICS_ALERT_RULE_TYPES } from '../../../../../common/constants/synthetics_alerts'; -import { AlertTypeInitializer } from '.'; +import type { AlertTypeInitializer } from '.'; const { defaultActionMessage, defaultRecoveryMessage, description } = SyntheticsMonitorStatusTranslations; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/models.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/models.ts index ecd6f40a318c6..4c9a105a4c1fd 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/models.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/models.ts @@ -7,7 +7,7 @@ import { ErrorToastOptions } from '@kbn/core-notifications-browser'; -import { MonitorListSortField } from '../../../../../common/runtime_types/monitor_management/sort_field'; +import type { MonitorListSortField } from '../../../../../common/runtime_types/monitor_management/sort_field'; import { EncryptedSyntheticsMonitor, FetchMonitorManagementListQueryArgs, diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview/models.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview/models.ts index 585e928c6f744..8706ca519d49f 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview/models.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/overview/models.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { MonitorListSortField } from '../../../../../common/runtime_types/monitor_management/sort_field'; +import type { MonitorListSortField } from '../../../../../common/runtime_types/monitor_management/sort_field'; import { ConfigKey, MonitorOverviewResult } from '../../../../../common/runtime_types'; import { IHttpSerializedFetchError } from '../utils/http_error'; diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/monitor_validation.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/monitor_validation.ts index 85714411f92b7..ab5c2c089adb6 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/monitor_validation.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/monitor_validation.ts @@ -11,7 +11,7 @@ import { formatErrors } from '@kbn/securitysolution-io-ts-utils'; import { omit } from 'lodash'; import { schema } from '@kbn/config-schema'; -import { AlertConfigSchema } from '../../../common/runtime_types/monitor_management/alert_config'; +import { AlertConfigSchema } from '../../../common/runtime_types/monitor_management/alert_config_schema'; import { CreateMonitorPayLoad } from './add_monitor/add_monitor_api'; import { flattenAndFormatObject } from '../../synthetics_service/project_monitor/normalizers/common_fields'; import { PrivateLocationAttributes } from '../../runtime_types/private_locations'; From c46cb3a3a4de22647c7a1c6eb5c79d537f119036 Mon Sep 17 00:00:00 2001 From: Vadim Kibana <82822460+vadimkibana@users.noreply.github.com> Date: Tue, 2 Jul 2024 18:47:00 +0200 Subject: [PATCH 098/128] [ES|QL] Add `top` function definition (#187290) ## Summary Closes https://github.com/elastic/kibana/issues/186494 - Adds ~~`top_list`~~ `top` function definition - Adds basic smoke tests ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../src/definitions/aggs.ts | 40 +++++ .../esql_validation_meta_tests.json | 160 ++++++++++++++++++ .../src/validation/validation.test.ts | 98 +++++++++++ 3 files changed, 298 insertions(+) diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts index 08323d121a88a..00f39c2659368 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts @@ -224,4 +224,44 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ 'from index | stats all_sorted_agents=mv_sort(values(agents.keyword))', ], }, + { + name: 'top', + type: 'agg', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.topListDoc', { + defaultMessage: 'Collects top N values per bucket.', + }), + supportedCommands: ['stats', 'metrics'], + signatures: [ + { + params: [ + { + name: 'field', + type: 'any', + noNestingFunctions: true, + optional: false, + }, + { + name: 'limit', + type: 'number', + noNestingFunctions: true, + optional: false, + constantOnly: true, + }, + { + name: 'order', + type: 'string', + noNestingFunctions: true, + optional: false, + constantOnly: true, + literalOptions: ['asc', 'desc'], + }, + ], + returnType: 'any', + }, + ], + examples: [ + `from employees | stats top_salaries = top(salary, 10, "desc")`, + `from employees | stats date = top(hire_date, 2, "asc"), double = top(salary_change, 2, "asc"),`, + ], + }, ]); diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json index 86f0848c3a94f..b75a9506cb766 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json +++ b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json @@ -25901,6 +25901,166 @@ "error": [], "warning": [] }, + { + "query": "from a_index | stats var = top(stringField, 3, \"asc\")", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats top(stringField, 1, \"desc\")", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = top(stringField, 5, \"asc\")", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats top(stringField, 5, \"asc\")", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = top(stringField, 3)", + "error": [ + "Error: [top] function expects exactly 3 arguments, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | stats var = top(stringField)", + "error": [ + "Error: [top] function expects exactly 3 arguments, got 1." + ], + "warning": [] + }, + { + "query": "from a_index | stats var = top(stringField, numberField, \"asc\")", + "error": [ + "Argument of [=] must be a constant, received [top(stringField,numberField,\"asc\")]" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = top(stringField, 100 + numberField, \"asc\")", + "error": [ + "Argument of [=] must be a constant, received [top(stringField,100+numberField,\"asc\")]" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = top(stringField, 1, stringField)", + "error": [ + "Argument of [=] must be a constant, received [top(stringField,1,stringField)]" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = top(stringField, 1, \"asdf\")", + "error": [], + "warning": [ + "Invalid option [\"asdf\"] for top. Supported options: [\"asc\", \"desc\"]." + ] + }, + { + "query": "from a_index | sort top(stringField, numberField, \"asc\")", + "error": [ + "SORT does not support function top" + ], + "warning": [] + }, + { + "query": "from a_index | where top(stringField, numberField, \"asc\")", + "error": [ + "WHERE does not support function top" + ], + "warning": [] + }, + { + "query": "from a_index | where top(stringField, numberField, \"asc\") > 0", + "error": [ + "WHERE does not support function top" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = top(stringField, numberField, \"asc\")", + "error": [ + "EVAL does not support function top" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = top(stringField, numberField, \"asc\") > 0", + "error": [ + "EVAL does not support function top" + ], + "warning": [] + }, + { + "query": "from a_index | eval top(stringField, numberField, \"asc\")", + "error": [ + "EVAL does not support function top" + ], + "warning": [] + }, + { + "query": "from a_index | eval top(stringField, numberField, \"asc\") > 0", + "error": [ + "EVAL does not support function top" + ], + "warning": [] + }, + { + "query": "from a_index | sort top(stringField, 5, \"asc\")", + "error": [ + "SORT does not support function top" + ], + "warning": [] + }, + { + "query": "from a_index | where top(stringField, 5, \"asc\")", + "error": [ + "WHERE does not support function top" + ], + "warning": [] + }, + { + "query": "from a_index | where top(stringField, 5, \"asc\") > 0", + "error": [ + "WHERE does not support function top" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = top(stringField, 5, \"asc\")", + "error": [ + "EVAL does not support function top" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = top(stringField, 5, \"asc\") > 0", + "error": [ + "EVAL does not support function top" + ], + "warning": [] + }, + { + "query": "from a_index | eval top(stringField, 5, \"asc\")", + "error": [ + "EVAL does not support function top" + ], + "warning": [] + }, + { + "query": "from a_index | eval top(stringField, 5, \"asc\") > 0", + "error": [ + "EVAL does not support function top" + ], + "warning": [] + }, { "query": "row var = st_distance(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts index 1d99efd12e9ab..591efd1e413c1 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts @@ -10157,6 +10157,104 @@ describe('validation logic', () => { testErrorsAndWarnings('row nullVar = null | eval repeat(nullVar, nullVar)', []); }); + describe('top', () => { + describe('no errors on correct usage', () => { + testErrorsAndWarnings('from a_index | stats var = top(stringField, 3, "asc")', []); + testErrorsAndWarnings('from a_index | stats top(stringField, 1, "desc")', []); + testErrorsAndWarnings('from a_index | stats var = top(stringField, 5, "asc")', []); + testErrorsAndWarnings('from a_index | stats top(stringField, 5, "asc")', []); + }); + + describe('errors on invalid argument count', () => { + testErrorsAndWarnings('from a_index | stats var = top(stringField, 3)', [ + 'Error: [top] function expects exactly 3 arguments, got 2.', + ]); + testErrorsAndWarnings('from a_index | stats var = top(stringField)', [ + 'Error: [top] function expects exactly 3 arguments, got 1.', + ]); + }); + + describe('limit must be a literal', () => { + testErrorsAndWarnings('from a_index | stats var = top(stringField, numberField, "asc")', [ + 'Argument of [=] must be a constant, received [top(stringField,numberField,"asc")]', + ]); + testErrorsAndWarnings( + 'from a_index | stats var = top(stringField, 100 + numberField, "asc")', + [ + 'Argument of [=] must be a constant, received [top(stringField,100+numberField,"asc")]', + ] + ); + }); + + describe('order must be "asc" or "desc"', () => { + testErrorsAndWarnings('from a_index | stats var = top(stringField, 1, stringField)', [ + 'Argument of [=] must be a constant, received [top(stringField,1,stringField)]', + ]); + testErrorsAndWarnings( + 'from a_index | stats var = top(stringField, 1, "asdf")', + [], + ['Invalid option ["asdf"] for top. Supported options: ["asc", "desc"].'] + ); + }); + + testErrorsAndWarnings('from a_index | sort top(stringField, numberField, "asc")', [ + 'SORT does not support function top', + ]); + + testErrorsAndWarnings('from a_index | where top(stringField, numberField, "asc")', [ + 'WHERE does not support function top', + ]); + + testErrorsAndWarnings('from a_index | where top(stringField, numberField, "asc") > 0', [ + 'WHERE does not support function top', + ]); + + testErrorsAndWarnings('from a_index | eval var = top(stringField, numberField, "asc")', [ + 'EVAL does not support function top', + ]); + + testErrorsAndWarnings( + 'from a_index | eval var = top(stringField, numberField, "asc") > 0', + ['EVAL does not support function top'] + ); + + testErrorsAndWarnings('from a_index | eval top(stringField, numberField, "asc")', [ + 'EVAL does not support function top', + ]); + + testErrorsAndWarnings('from a_index | eval top(stringField, numberField, "asc") > 0', [ + 'EVAL does not support function top', + ]); + + testErrorsAndWarnings('from a_index | sort top(stringField, 5, "asc")', [ + 'SORT does not support function top', + ]); + + testErrorsAndWarnings('from a_index | where top(stringField, 5, "asc")', [ + 'WHERE does not support function top', + ]); + + testErrorsAndWarnings('from a_index | where top(stringField, 5, "asc") > 0', [ + 'WHERE does not support function top', + ]); + + testErrorsAndWarnings('from a_index | eval var = top(stringField, 5, "asc")', [ + 'EVAL does not support function top', + ]); + + testErrorsAndWarnings('from a_index | eval var = top(stringField, 5, "asc") > 0', [ + 'EVAL does not support function top', + ]); + + testErrorsAndWarnings('from a_index | eval top(stringField, 5, "asc")', [ + 'EVAL does not support function top', + ]); + + testErrorsAndWarnings('from a_index | eval top(stringField, 5, "asc") > 0', [ + 'EVAL does not support function top', + ]); + }); + describe('st_distance', () => { testErrorsAndWarnings( 'row var = st_distance(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', From 49c16e0a855617374d64d208200f2120edc57971 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 2 Jul 2024 18:51:51 +0200 Subject: [PATCH 099/128] skip failing test suite (#187314) --- .../apps/integrations/artifact_entries_list.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/apps/integrations/artifact_entries_list.ts b/x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/apps/integrations/artifact_entries_list.ts index 9410b704e6695..6c1bb54caf3df 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/apps/integrations/artifact_entries_list.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/apps/integrations/artifact_entries_list.ts @@ -52,7 +52,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { .set('kbn-xsrf', 'true'); }; - describe('@ess @serverless For each artifact list under management', function () { + // Failing: See https://github.com/elastic/kibana/issues/187314 + describe.skip('@ess @serverless For each artifact list under management', function () { let indexedData: IndexedHostsAndAlertsResponse; let policyInfo: PolicyTestResourceInfo; From 4066f922b52daa94be0dc086b221be50174d7163 Mon Sep 17 00:00:00 2001 From: Joe Reuter <johannes.reuter@elastic.co> Date: Tue, 2 Jul 2024 18:59:45 +0200 Subject: [PATCH 100/128] [Observability Onboarding] OTel logs flow (#183732) # Testing instructions * The k8s flow should work already for logs but won't report any metrics * The linux/mac flow requires the following stopgap measure until everything is ready * Run the provided snippet * It will fail with `No such file or directory` * Run either `touch otel.yml && mkdir otel_samples && curl https://raw.githubusercontent.com/elastic/elastic-agent/871ad33afc5ac1614f0645e86f2a13c05631aa6d/internal/pkg/otel/samples/darwin/platformlogs_hostmetrics.yml -o otel_samples/platformlogs_hostmetrics.yml` for mac or `touch otel.yml && mkdir otel_samples && curl https://raw.githubusercontent.com/elastic/elastic-agent/871ad33afc5ac1614f0645e86f2a13c05631aa6d/internal/pkg/otel/samples/linux/platformlogs_hostmetrics.yml -o otel_samples/platformlogs_hostmetrics.yml` for linux * Re-run the second part of the snippet (the part starting with `rm`) * The rest should work as before Closes https://github.com/elastic/kibana/issues/184433 <img width="931" alt="Screenshot 2024-06-20 at 15 04 19" src="https://github.com/elastic/kibana/assets/1508364/f50392ae-d956-463f-9120-0d01b0fbfa80"> <img width="937" alt="Screenshot 2024-06-20 at 15 04 32" src="https://github.com/elastic/kibana/assets/1508364/a968f615-f173-4210-bb47-6700cc989d5a"> TODOs: * Real kubernetes snippet * Do not show on serverless (disabled for testability) * Remove snapshot version and CDN url --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../public/application/app.tsx | 12 +- .../observability_onboarding_flow.tsx | 5 + .../use_custom_cards_for_category.ts | 36 +- .../custom_logs/api_key_banner.tsx | 2 +- .../quickstart_flows/otel_logs/index.tsx | 901 ++++++++++++++++++ .../multi_integration_install_banner.tsx | 58 ++ .../system_logs/system_integration_banner.tsx | 22 +- ...gration.ts => use_install_integrations.ts} | 32 +- .../observability_onboarding/public/index.ts | 8 + .../observability_onboarding/public/plugin.ts | 5 + .../server/routes/logs/route.ts | 30 +- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 14 files changed, 1077 insertions(+), 37 deletions(-) create mode 100644 x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_logs/index.tsx create mode 100644 x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_logs/multi_integration_install_banner.tsx rename x-pack/plugins/observability_solution/observability_onboarding/public/hooks/{use_install_system_integration.ts => use_install_integrations.ts} (66%) diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/app.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/app.tsx index 835233b424c6f..2134edf1170d8 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/app.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/app.tsx @@ -17,12 +17,11 @@ import { Router } from '@kbn/shared-ux-router'; import React from 'react'; import ReactDOM from 'react-dom'; import { OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT } from '../../common/telemetry_events'; -import { ConfigSchema } from '..'; +import { AppContext, ConfigSchema, ObservabilityOnboardingAppServices } from '..'; import { ObservabilityOnboardingHeaderActionMenu } from './shared/header_action_menu'; import { ObservabilityOnboardingPluginSetupDeps, ObservabilityOnboardingPluginStartDeps, - ObservabilityOnboardingContextValue, } from '../plugin'; import { ObservabilityOnboardingFlow } from './observability_onboarding_flow'; @@ -43,11 +42,17 @@ export function ObservabilityOnboardingAppRoot({ core, corePlugins, config, + context, }: { appMountParameters: AppMountParameters; } & RenderAppProps) { const { history, setHeaderActionMenu, theme$ } = appMountParameters; - const services: ObservabilityOnboardingContextValue = { ...core, ...corePlugins, config }; + const services: ObservabilityOnboardingAppServices = { + ...core, + ...corePlugins, + config, + context, + }; const renderFeedbackLinkAsPortal = !config.serverless.enabled; @@ -101,6 +106,7 @@ interface RenderAppProps { appMountParameters: AppMountParameters; corePlugins: ObservabilityOnboardingPluginStartDeps; config: ConfigSchema; + context: AppContext; } export const renderApp = (props: RenderAppProps) => { diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/observability_onboarding_flow.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/observability_onboarding_flow.tsx index 4177e682d6e77..33e472d841bbc 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/observability_onboarding_flow.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/observability_onboarding_flow.tsx @@ -17,6 +17,7 @@ import { OnboardingFlowForm } from './onboarding_flow_form/onboarding_flow_form' import { Header } from './header/header'; import { SystemLogsPanel } from './quickstart_flows/system_logs'; import { CustomLogsPanel } from './quickstart_flows/custom_logs'; +import { OtelLogsPanel } from './quickstart_flows/otel_logs'; import { AutoDetectPanel } from './quickstart_flows/auto_detect'; import { BackButton } from './shared/back_button'; @@ -65,6 +66,10 @@ export function ObservabilityOnboardingFlow() { <BackButton /> <CustomLogsPanel /> </Route> + <Route path="/otel-logs"> + <BackButton /> + <OtelLogsPanel /> + </Route> <Route> <OnboardingFlowForm /> </Route> diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/use_custom_cards_for_category.ts b/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/use_custom_cards_for_category.ts index 021a3035131bd..5bc63403365cc 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/use_custom_cards_for_category.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/use_custom_cards_for_category.ts @@ -8,7 +8,8 @@ import { reactRouterNavigate, useKibana } from '@kbn/kibana-react-plugin/public'; import { useHistory } from 'react-router-dom'; import { useLocation } from 'react-router-dom-v5-compat'; -import { CustomCard, FeaturedCard } from '../packages_list/types'; +import { ObservabilityOnboardingAppServices } from '../..'; +import { CustomCard, FeaturedCard, VirtualCard } from '../packages_list/types'; import { Category } from './types'; function toFeaturedCard(name: string): FeaturedCard { @@ -22,12 +23,37 @@ export function useCustomCardsForCategory( const history = useHistory(); const location = useLocation(); const { - services: { application, http }, - } = useKibana(); + services: { + application, + http, + context: { isServerless }, + }, + } = useKibana<ObservabilityOnboardingAppServices>(); const getUrlForApp = application?.getUrlForApp; const { href: systemLogsUrl } = reactRouterNavigate(history, `/systemLogs/${location.search}`); const { href: customLogsUrl } = reactRouterNavigate(history, `/customLogs/${location.search}`); + const { href: otelLogsUrl } = reactRouterNavigate(history, `/otel-logs/${location.search}`); + + const otelCard: VirtualCard = { + id: 'otel-logs', + type: 'virtual', + release: 'preview', + title: 'OpenTelemetry', + description: + 'Collect Logs and host metrics using the Elastic distribution of the OpenTelemetry Collector', + name: 'custom-logs-virtual', + categories: ['observability'], + icons: [ + { + type: 'svg', + src: http?.staticAssets.getPluginAssetHref('opentelemetry.svg') ?? '', + }, + ], + url: otelLogsUrl, + version: '', + integration: '', + }; switch (category) { case 'apm': @@ -87,8 +113,8 @@ export function useCustomCardsForCategory( case 'infra': return [ toFeaturedCard('kubernetes'), - toFeaturedCard('prometheus'), toFeaturedCard('docker'), + isServerless ? toFeaturedCard('prometheus') : otelCard, { id: 'azure-virtual', type: 'virtual', @@ -168,7 +194,7 @@ export function useCustomCardsForCategory( version: '', integration: '', }, - toFeaturedCard('nginx'), + isServerless ? toFeaturedCard('nginx') : otelCard, { id: 'azure-logs-virtual', type: 'virtual', diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/custom_logs/api_key_banner.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/custom_logs/api_key_banner.tsx index b5448debbcbfa..7c5cf36a46b30 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/custom_logs/api_key_banner.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/custom_logs/api_key_banner.tsx @@ -32,7 +32,7 @@ export function ApiKeyBanner({ }: { hasPrivileges?: boolean; status: FETCH_STATUS; - payload?: ApiKeyPayload; + payload?: Partial<ApiKeyPayload>; error?: IHttpFetchError<ResponseErrorBody>; }) { const loadingCallout = ( diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_logs/index.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_logs/index.tsx new file mode 100644 index 0000000000000..cac3cd41e06b6 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_logs/index.tsx @@ -0,0 +1,901 @@ +/* + * 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, { useEffect } from 'react'; +import { + EuiBetaBadge, + EuiButton, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiModalBody, + EuiModalHeader, + EuiModalHeaderTitle, + EuiPanel, + EuiSpacer, + EuiSteps, + EuiText, + EuiIcon, + EuiButtonGroup, + EuiCopy, + EuiLink, + EuiImage, + EuiCallOut, +} from '@elastic/eui'; +import { + AllDatasetsLocatorParams, + ALL_DATASETS_LOCATOR_ID, +} from '@kbn/deeplinks-observability/locators'; +import { i18n } from '@kbn/i18n'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import useAsyncFn from 'react-use/lib/useAsyncFn'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { ObservabilityOnboardingAppServices } from '../../..'; +import { ApiKeyBanner } from '../custom_logs/api_key_banner'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { MultiIntegrationInstallBanner } from './multi_integration_install_banner'; + +const HOST_COMMAND = i18n.translate( + 'xpack.observability_onboarding.otelLogsPanel.p.runTheCommandOnYourHostLabel', + { + defaultMessage: + 'Run the following command on your host to download and configure the collector.', + } +); + +export const OtelLogsPanel: React.FC = () => { + const { + data: apiKeyData, + status: apiKeyStatus, + error, + } = useFetcher((callApi) => { + return callApi('POST /internal/observability_onboarding/otel/api_key', {}); + }, []); + + const { data: setup } = useFetcher((callApi) => { + return callApi('GET /internal/observability_onboarding/logs/setup/environment'); + }, []); + + const { + services: { + share, + http, + // context: { isServerless, stackVersion }, + }, + } = useKibana<ObservabilityOnboardingAppServices>(); + + const AGENT_CDN_BASE_URL = 'snapshots.elastic.co/8.15.0-2088c97b/downloads/beats/elastic-agent'; + const agentVersion = '8.15.0-SNAPSHOT'; + // TODO uncomment before merge + // const AGENT_CDN_BASE_URL = 'artifacts.elastic.co/downloads/beats/elastic-agent'; + // const agentVersion = isServerless ? setup?.elasticAgentVersion : stackVersion; + + const allDatasetsLocator = + share.url.locators.get<AllDatasetsLocatorParams>(ALL_DATASETS_LOCATOR_ID); + + const hostsLocator = share.url.locators.get('HOSTS_LOCATOR'); + + const [{ value: deeplinks }, getDeeplinks] = useAsyncFn(async () => { + return { + logs: allDatasetsLocator?.getRedirectUrl({ + type: 'logs', + }), + metrics: hostsLocator?.getRedirectUrl({}), + }; + }, [allDatasetsLocator]); + + useEffect(() => { + getDeeplinks(); + }, [getDeeplinks]); + + const installTabContents = [ + { + id: 'kubernetes', + name: 'Kubernetes', + prompt: ( + <> + <EuiText> + <p> + {i18n.translate( + 'xpack.observability_onboarding.otelLogsPanel.kubernetesApplyCommandPromptLabel', + { + defaultMessage: + 'From the directory where the manifest is downloaded, run the following command to install the collector on every node of your cluster:', + } + )} + </p> + </EuiText> + <CopyableCodeBlock + content={`kubectl create secret generic elastic-secret-otel --from-literal=es_endpoint='${setup?.elasticsearchUrl}' --from-literal=es_api_key='${apiKeyData?.apiKeyEncoded}' + +kubectl apply -f otel-collector-k8s.yml`} + /> + </> + ), + firstStepTitle: i18n.translate( + 'xpack.observability_onboarding.otelLogsPanel.steps.downloadManifest', + { defaultMessage: 'Download the manifest:' } + ), + content: `apiVersion: v1 +kind: ServiceAccount +metadata: + name: elastic-otel-collector-agent + namespace: default + labels: + app.kubernetes.io/name: elastic-opentelemetry-collector + app.kubernetes.io/version: "${agentVersion}" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: elastic-otel-collector-agent + labels: + app.kubernetes.io/name: elastic-opentelemetry-collector + app.kubernetes.io/version: "${agentVersion}" +rules: + - apiGroups: [""] + resources: ["pods", "namespaces", "nodes"] + verbs: ["get", "watch", "list"] + - apiGroups: ["apps"] + resources: ["daemonsets", "deployments", "replicasets", "statefulsets"] + verbs: ["get", "list", "watch"] + - apiGroups: ["extensions"] + resources: ["daemonsets", "deployments", "replicasets"] + verbs: ["get", "list", "watch"] + - apiGroups: [ "" ] + resources: [ "nodes/stats" ] + verbs: [ "get", "watch", "list" ] + - apiGroups: [ "" ] + resources: [ "nodes/proxy" ] + verbs: [ "get" ] + - apiGroups: [ "" ] + resources: ["configmaps"] + verbs: ["get"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: elastic-otel-collector-agent + labels: + app.kubernetes.io/name: elastic-opentelemetry-collector + app.kubernetes.io/version: "${agentVersion}" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: elastic-otel-collector-agent +subjects: + - kind: ServiceAccount + name: elastic-otel-collector-agent + namespace: default +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: elastic-otel-collector-agent + namespace: default + labels: + app.kubernetes.io/name: elastic-opentelemetry-collector + app.kubernetes.io/version: "${agentVersion}" +data: + otel.yaml: | + exporters: + debug: + verbosity: normal + elasticsearch: + endpoints: + - \${env:ES_ENDPOINT} + api_key: \${env:ES_API_KEY} + #logs_index: logs-otel.generic-default + # Metrics are not supported yet + #metrics_index: metrics-otel.generic-default + mapping: + mode: ecs + processors: + elasticinframetrics: + add_system_metrics: true + add_k8s_metrics: true + resourcedetection/eks: + detectors: [env, eks] + timeout: 15s + override: true + eks: + resource_attributes: + k8s.cluster.name: + enabled: true + resourcedetection/gcp: + detectors: [env, gcp] + timeout: 2s + override: false + resource/k8s: + attributes: + - key: service.name + from_attribute: app.label.component + action: insert + attributes/dataset: + actions: + - key: event.dataset + from_attribute: data_stream.dataset + action: upsert + resource/cloud: + attributes: + - key: cloud.instance.id + from_attribute: host.id + action: insert + resource/process: + attributes: + - key: process.executable.name + action: delete + - key: process.executable.path + action: delete + resourcedetection/system: + detectors: ["system", "ec2"] + system: + hostname_sources: [ "os" ] + resource_attributes: + host.name: + enabled: true + host.id: + enabled: false + host.arch: + enabled: true + host.ip: + enabled: true + host.mac: + enabled: true + host.cpu.vendor.id: + enabled: true + host.cpu.family: + enabled: true + host.cpu.model.id: + enabled: true + host.cpu.model.name: + enabled: true + host.cpu.stepping: + enabled: true + host.cpu.cache.l2.size: + enabled: true + os.description: + enabled: true + os.type: + enabled: true + ec2: + resource_attributes: + host.name: + enabled: false + host.id: + enabled: true + k8sattributes: + filter: + node_from_env_var: K8S_NODE_NAME + passthrough: false + pod_association: + - sources: + - from: resource_attribute + name: k8s.pod.ip + - sources: + - from: resource_attribute + name: k8s.pod.uid + - sources: + - from: connection + extract: + metadata: + - "k8s.namespace.name" + - "k8s.deployment.name" + - "k8s.statefulset.name" + - "k8s.daemonset.name" + - "k8s.cronjob.name" + - "k8s.job.name" + - "k8s.node.name" + - "k8s.pod.name" + - "k8s.pod.uid" + - "k8s.pod.start_time" + labels: + - tag_name: app.label.component + key: app.kubernetes.io/component + from: pod + extensions: + file_storage: + directory: /var/lib/otelcol + receivers: + filelog: + retry_on_failure: + enabled: true + start_at: end + exclude: + - /var/log/pods/default_elastic-otel-collector-agent*_*/elastic-opentelemetry-collector/*.log + include: + - /var/log/pods/*/*/*.log + include_file_name: false + include_file_path: true + storage: file_storage + operators: + - id: container-parser + type: container + hostmetrics: + collection_interval: 10s + root_path: /hostfs + scrapers: + cpu: + metrics: + system.cpu.utilization: + enabled: true + system.cpu.logical.count: + enabled: true + memory: + metrics: + system.memory.utilization: + enabled: true + process: + mute_process_exe_error: true + mute_process_io_error: true + mute_process_user_error: true + metrics: + process.threads: + enabled: true + process.open_file_descriptors: + enabled: true + process.memory.utilization: + enabled: true + process.disk.operations: + enabled: true + network: + processes: + load: + disk: + filesystem: + exclude_mount_points: + mount_points: + - /dev/* + - /proc/* + - /sys/* + - /run/k3s/containerd/* + - /var/lib/docker/* + - /var/lib/kubelet/* + - /snap/* + match_type: regexp + exclude_fs_types: + fs_types: + - autofs + - binfmt_misc + - bpf + - cgroup2 + - configfs + - debugfs + - devpts + - devtmpfs + - fusectl + - hugetlbfs + - iso9660 + - mqueue + - nsfs + - overlay + - proc + - procfs + - pstore + - rpc_pipefs + - securityfs + - selinuxfs + - squashfs + - sysfs + - tracefs + match_type: strict + kubeletstats: + auth_type: serviceAccount + collection_interval: 20s + endpoint: \${env:K8S_NODE_NAME}:10250 + node: '\${env:K8S_NODE_NAME}' + # Verify if this can be removed for all CSPs + #insecure_skip_verify: true + k8s_api_config: + auth_type: serviceAccount + metric_groups: + - node + - pod + - node + - volume + metrics: + k8s.pod.cpu.node.utilization: + enabled: true + k8s.container.cpu_limit_utilization: + enabled: true + k8s.pod.cpu_limit_utilization: + enabled: true + k8s.container.cpu_request_utilization: + enabled: true + k8s.container.memory_limit_utilization: + enabled: true + k8s.pod.memory_limit_utilization: + enabled: true + k8s.container.memory_request_utilization: + enabled: true + k8s.node.uptime: + enabled: true + k8s.node.cpu.usage: + enabled: true + k8s.pod.cpu.usage: + enabled: true + extra_metadata_labels: + - container.id + + service: + extensions: [file_storage] + pipelines: + logs: + exporters: + - elasticsearch + - debug + processors: + - k8sattributes + - resourcedetection/system + - resourcedetection/eks + - resourcedetection/gcp + - resource/k8s + - resource/cloud + receivers: + - filelog + metrics: + exporters: + - debug + - elasticsearch + processors: + - k8sattributes + - elasticinframetrics + - resourcedetection/system + - resourcedetection/eks + - resourcedetection/gcp + - resource/k8s + - resource/cloud + - attributes/dataset + - resource/process + receivers: + - kubeletstats + - hostmetrics +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: elastic-otel-collector-agent + namespace: default + labels: + app.kubernetes.io/name: elastic-opentelemetry-collector + app.kubernetes.io/version: "${agentVersion}" +spec: + selector: + matchLabels: + app.kubernetes.io/name: elastic-opentelemetry-collector + app.kubernetes.io/version: "${agentVersion}" + template: + metadata: + labels: + app.kubernetes.io/name: elastic-opentelemetry-collector + app.kubernetes.io/version: "${agentVersion}" + spec: + serviceAccountName: elastic-otel-collector-agent + securityContext: + runAsUser: 0 + runAsGroup: 0 + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet + containers: + - name: elastic-opentelemetry-collector + command: [/usr/share/elastic-agent/elastic-agent] + args: ["otel", "-c", "/etc/elastic-agent/otel.yaml"] + image: docker.elastic.co/beats/elastic-agent:${agentVersion} + imagePullPolicy: IfNotPresent + env: + - name: MY_POD_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + - name: K8S_NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: ES_ENDPOINT + valueFrom: + secretKeyRef: + key: es_endpoint + name: elastic-secret-otel + - name: ES_API_KEY + valueFrom: + secretKeyRef: + key: es_api_key + name: elastic-secret-otel + volumeMounts: + - mountPath: /etc/elastic-agent/otel.yaml + name: opentelemetry-collector-configmap + readOnly: true + subPath: otel.yaml + - name: varlogpods + mountPath: /var/log/pods + readOnly: true + - name: varlibdockercontainers + mountPath: /var/lib/docker/containers + readOnly: true + - name: varlibotelcol + mountPath: /var/lib/otelcol + - name: hostfs + mountPath: /hostfs + readOnly: true + mountPropagation: HostToContainer + + volumes: + - name: opentelemetry-collector-configmap + configMap: + name: elastic-otel-collector-agent + defaultMode: 0640 + - name: varlogpods + hostPath: + path: /var/log/pods + - name: varlibdockercontainers + hostPath: + path: /var/lib/docker/containers + - name: varlibotelcol + hostPath: + path: /var/lib/otelcol + type: DirectoryOrCreate + - name: hostfs + hostPath: + path: /`, + type: 'download', + fileName: 'otel-collector-k8s.yml', + }, + { + id: 'linux', + name: 'Linux', + firstStepTitle: HOST_COMMAND, + content: `arch=$(if ([[ $(arch) == "arm" || $(arch) == "aarch64" ]]); then echo "arm64"; else echo $(arch); fi) + +curl --output elastic-distro-${agentVersion}-linux-$arch.tar.gz --url https://${AGENT_CDN_BASE_URL}/elastic-agent-${agentVersion}-linux-$arch.tar.gz --proto '=https' --tlsv1.2 -fOL && mkdir elastic-distro-${agentVersion}-linux-$arch && tar -xvf elastic-distro-${agentVersion}-linux-$arch.tar.gz -C "elastic-distro-${agentVersion}-linux-$arch" --strip-components=1 && cd elastic-distro-${agentVersion}-linux-$arch + +rm ./otel.yml && cp ./otel_samples/platformlogs_hostmetrics.yml ./otel.yml && sed -i 's#\\\${env:ELASTIC_ENDPOINT}#${setup?.elasticsearchUrl}#g' ./otel.yml && sed -i 's/\\\${env:ELASTIC_API_KEY}/${apiKeyData?.apiKeyEncoded}/g' ./otel.yml`, + start: './otelcol --config otel.yml', + type: 'copy', + }, + { + id: 'mac', + name: 'Mac', + firstStepTitle: HOST_COMMAND, + content: `arch=$(if [[ $(arch) == "arm64" ]]; then echo "aarch64"; else echo $(arch); fi) + +curl --output elastic-distro-${agentVersion}-darwin-$arch.tar.gz --url https://${AGENT_CDN_BASE_URL}/elastic-agent-${agentVersion}-darwin-$arch.tar.gz --proto '=https' --tlsv1.2 -fOL && mkdir "elastic-distro-${agentVersion}-darwin-$arch" && tar -xvf elastic-distro-${agentVersion}-darwin-$arch.tar.gz -C "elastic-distro-${agentVersion}-darwin-$arch" --strip-components=1 && cd elastic-distro-${agentVersion}-darwin-$arch + +rm ./otel.yml && cp ./otel_samples/platformlogs_hostmetrics.yml ./otel.yml && sed -i '' 's#\\\${env:ELASTIC_ENDPOINT}#${setup?.elasticsearchUrl}#g' ./otel.yml && sed -i '' 's/\\\${env:ELASTIC_API_KEY}/${apiKeyData?.apiKeyEncoded}/g' ./otel.yml`, + start: './otelcol --config otel.yml', + type: 'copy', + }, + ]; + + const [selectedTab, setSelectedTab] = React.useState(installTabContents[0].id); + + const selectedContent = installTabContents.find((tab) => tab.id === selectedTab)!; + + return ( + <EuiPanel hasBorder> + <EuiModalHeader> + <EuiModalHeaderTitle> + <EuiFlexGroup gutterSize="l" alignItems="flexStart"> + {http && ( + <EuiFlexItem grow={false}> + <EuiPanel paddingSize="s"> + <EuiIcon + type={http?.staticAssets.getPluginAssetHref('opentelemetry.svg')} + size="xxl" + /> + </EuiPanel> + </EuiFlexItem> + )} + <EuiFlexItem grow> + <EuiFlexGroup gutterSize="m" direction="column"> + <EuiFlexGroup gutterSize="s" alignItems="center"> + <EuiFlexItem grow={false}> + {i18n.translate( + 'xpack.observability_onboarding.otelLogsPanel.otelLogsModalHeaderTitleLabel', + { defaultMessage: 'OpenTelemetry' } + )} + </EuiFlexItem> + + <EuiFlexItem grow={false}> + <EuiBetaBadge + label={i18n.translate( + 'xpack.observability_onboarding.otelLogsPanel.techPreviewBadge.label', + { + defaultMessage: 'Technical preview', + } + )} + size="m" + color="hollow" + tooltipContent={i18n.translate( + 'xpack.observability_onboarding.otelLogsPanel.techPreviewBadge.tooltip', + { + defaultMessage: + 'This functionality is in technical preview and may be changed or removed completely in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.', + } + )} + tooltipPosition={'right'} + /> + </EuiFlexItem> + </EuiFlexGroup> + <EuiText size="s" color="subdued"> + <p> + {i18n.translate( + 'xpack.observability_onboarding.otelLogsPanel.p.collectLogsWithOpenTelemetryLabel', + { + defaultMessage: + 'Collect logs and host metrics using the Elastic distribution of the OTel collector.', + } + )} + </p> + </EuiText> + </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGroup> + </EuiModalHeaderTitle> + </EuiModalHeader> + <EuiModalBody> + <EuiFlexGroup direction="column"> + <MultiIntegrationInstallBanner /> + {error && ( + <EuiFlexItem> + <ApiKeyBanner status={apiKeyStatus} payload={apiKeyData} error={error} /> + </EuiFlexItem> + )} + <EuiSteps + steps={[ + { + title: i18n.translate( + 'xpack.observability_onboarding.otelLogsPanel.steps.platform', + { + defaultMessage: 'Select your platform', + } + ), + + children: ( + <EuiFlexGroup direction="column"> + <EuiButtonGroup + legend={i18n.translate( + 'xpack.observability_onboarding.otelLogsPanel.choosePlatform', + { defaultMessage: 'Choose platform' } + )} + options={installTabContents.map(({ id, name }) => ({ + id, + label: name, + }))} + type="single" + idSelected={selectedTab} + onChange={(id: string) => { + setSelectedTab(id); + }} + /> + <EuiText> + <p>{selectedContent.firstStepTitle}</p> + </EuiText> + <EuiFlexItem> + <EuiCodeBlock language="sh" isCopyable overflowHeight={300}> + {selectedContent.content} + </EuiCodeBlock> + </EuiFlexItem> + <EuiFlexItem align="left"> + <EuiFlexGroup> + {selectedContent.type === 'download' ? ( + <EuiButton + iconType="download" + color="primary" + href={`data:application/yaml;base64,${Buffer.from( + selectedContent.content, + 'utf8' + ).toString('base64')}`} + download={selectedContent.fileName} + target="_blank" + data-test-subj="obltOnboardingOtelDownloadConfig" + > + {i18n.translate( + 'xpack.observability_onboarding.installOtelCollector.configStep.downloadConfigButton', + { defaultMessage: 'Download manifest' } + )} + </EuiButton> + ) : ( + <EuiCopy textToCopy={selectedContent.content}> + {(copy) => ( + <EuiButton + data-test-subj="observabilityOnboardingOtelLogsPanelButton" + iconType="copyClipboard" + onClick={copy} + > + {i18n.translate( + 'xpack.observability_onboarding.installOtelCollector.configStep.copyCommand', + { defaultMessage: 'Copy to clipboard' } + )} + </EuiButton> + )} + </EuiCopy> + )} + </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGroup> + ), + }, + { + title: i18n.translate('xpack.observability_onboarding.otelLogsPanel.steps.start', { + defaultMessage: 'Start the collector', + }), + children: ( + <EuiFlexGroup direction="column"> + <EuiCallOut + title={i18n.translate( + 'xpack.observability_onboarding.otelLogsPanel.historicalDataTitle', + { defaultMessage: 'Historical logs will not be collected' } + )} + color="warning" + iconType="iInCircle" + > + <p> + {i18n.translate( + 'xpack.observability_onboarding.otelLogsPanel.historicalDataDescription', + { + defaultMessage: + 'We only collect new log messages from the setup onward.', + } + )} + </p> + </EuiCallOut> + + {selectedContent.prompt} + {selectedContent.start && ( + <> + <EuiText> + <p> + {i18n.translate( + 'xpack.observability_onboarding.otelLogsPanel.p.startTheCollectorLabel', + { + defaultMessage: 'Run the following command to start the collector', + } + )} + </p> + </EuiText> + <CopyableCodeBlock content={selectedContent.start} /> + </> + )} + </EuiFlexGroup> + ), + }, + { + title: 'Visualize your data', + children: ( + <> + <EuiText> + <p> + {i18n.translate( + 'xpack.observability_onboarding.otelLogsPanel.waitForTheDataLabel', + { + defaultMessage: + 'After running the previous command, come back and view your data.', + } + )} + </p> + </EuiText> + <EuiSpacer /> + <EuiFlexGroup> + <EuiFlexItem grow={false}> + <EuiImage + src={http?.staticAssets.getPluginAssetHref('waterfall_screen.svg')} + width={160} + alt="Illustration" + hasShadow + /> + </EuiFlexItem> + <EuiFlexItem grow> + <EuiFlexGroup direction="column" gutterSize="xs"> + {deeplinks?.logs && ( + <> + <EuiFlexItem grow={false}> + <EuiText size="s"> + {i18n.translate( + 'xpack.observability_onboarding.otelLogsPanel.viewAndAnalyzeYourTextLabel', + { defaultMessage: 'View and analyze your logs' } + )} + </EuiText> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiLink + data-test-subj="obltOnboardingExploreLogs" + href={deeplinks.logs} + > + {i18n.translate( + 'xpack.observability_onboarding.otelLogsPanel.exploreLogs', + { + defaultMessage: 'Open Logs Explorer', + } + )} + </EuiLink> + </EuiFlexItem> + </> + )} + <EuiSpacer size="s" /> + {deeplinks?.metrics && ( + <> + <EuiFlexItem grow={false}> + <EuiText size="s"> + {i18n.translate( + 'xpack.observability_onboarding.otelLogsPanel.viewAndAnalyzeYourMetricsTextLabel', + { defaultMessage: 'View and analyze your metrics' } + )} + </EuiText> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiLink + data-test-subj="obltOnboardingExploreMetrics" + href={deeplinks.metrics} + > + {i18n.translate( + 'xpack.observability_onboarding.otelLogsPanel.exploreMetrics', + { + defaultMessage: 'Open Hosts', + } + )} + </EuiLink> + </EuiFlexItem> + </> + )} + </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer /> + <EuiText size="xs" color="subdued"> + <FormattedMessage + id="xpack.observability_onboarding.otelLogsPanel.troubleshooting" + defaultMessage="Find more details and troubleshooting solution in our documentation. {link}" + values={{ + link: ( + <EuiLink + data-test-subj="observabilityOnboardingOtelLogsPanelDocumentationLink" + href="https://www.elastic.co/guide/en/observability/current/get-started-opentelemetry.html" + target="_blank" + external + > + {i18n.translate( + 'xpack.observability_onboarding.otelLogsPanel.documentationLink', + { defaultMessage: 'Open documentation' } + )} + </EuiLink> + ), + }} + /> + </EuiText> + </> + ), + }, + ]} + /> + </EuiFlexGroup> + </EuiModalBody> + </EuiPanel> + ); +}; + +function CopyableCodeBlock({ content }: { content: string }) { + return ( + <> + <EuiCodeBlock language="yaml">{content}</EuiCodeBlock> + <EuiCopy textToCopy={content}> + {(copy) => ( + <EuiButton + data-test-subj="observabilityOnboardingCopyableCodeBlockCopyToClipboardButton" + iconType="copyClipboard" + onClick={copy} + > + {i18n.translate( + 'xpack.observability_onboarding.installOtelCollector.configStep.copyCommand', + { defaultMessage: 'Copy to clipboard' } + )} + </EuiButton> + )} + </EuiCopy> + </> + ); +} diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_logs/multi_integration_install_banner.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_logs/multi_integration_install_banner.tsx new file mode 100644 index 0000000000000..4696e3af43a84 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_logs/multi_integration_install_banner.tsx @@ -0,0 +1,58 @@ +/* + * 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 { EuiCallOut, EuiCodeBlock, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useCallback, useEffect, useState } from 'react'; +import { + IntegrationInstallationError, + useInstallIntegrations, +} from '../../../hooks/use_install_integrations'; + +export function MultiIntegrationInstallBanner() { + const [error, setError] = useState<IntegrationInstallationError>(); + + const onIntegrationCreationFailure = useCallback((e: IntegrationInstallationError) => { + setError(e); + }, []); + + const { performRequest, requestState } = useInstallIntegrations({ + onIntegrationCreationFailure, + packages: ['system', 'kubernetes'], + }); + + useEffect(() => { + performRequest(); + }, [performRequest]); + + const hasFailedInstallingIntegration = requestState.state === 'rejected'; + + if (hasFailedInstallingIntegration) { + return ( + <EuiFlexItem> + <EuiCallOut + title={i18n.translate('xpack.observability_onboarding.otelLogs.status.failed', { + defaultMessage: 'Integration installation failed', + })} + color="warning" + iconType="warning" + data-test-subj="obltOnboardingOtelLogsIntegrationInstallationFailed" + > + <EuiFlexGroup direction="column"> + <EuiFlexItem> + {i18n.translate('xpack.observability_onboarding.otelLogs.status.failedDetails', { + defaultMessage: 'Incoming data might not be indexed correctly. Details:', + })} + </EuiFlexItem> + <EuiCodeBlock>{error?.message}</EuiCodeBlock> + </EuiFlexGroup> + </EuiCallOut> + </EuiFlexItem> + ); + } + return null; +} diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/system_logs/system_integration_banner.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/system_logs/system_integration_banner.tsx index 3700a82052125..305c921dddfb5 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/system_logs/system_integration_banner.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/system_logs/system_integration_banner.tsx @@ -11,9 +11,9 @@ import { FormattedMessage } from '@kbn/i18n-react'; import React, { useCallback, useEffect, useState } from 'react'; import type { MouseEvent } from 'react'; import { - SystemIntegrationError, - useInstallSystemIntegration, -} from '../../../hooks/use_install_system_integration'; + IntegrationInstallationError, + useInstallIntegrations, +} from '../../../hooks/use_install_integrations'; import { useKibanaNavigation } from '../../../hooks/use_kibana_navigation'; import { PopoverTooltip } from '../shared/popover_tooltip'; @@ -22,29 +22,29 @@ export type SystemIntegrationBannerState = 'pending' | 'resolved' | 'rejected'; export function SystemIntegrationBanner({ onStatusChange, }: { - onStatusChange: (status: SystemIntegrationBannerState) => void; + onStatusChange?: (status: SystemIntegrationBannerState) => void; }) { const { navigateToAppUrl } = useKibanaNavigation(); const [integrationVersion, setIntegrationVersion] = useState<string>(); - const [error, setError] = useState<SystemIntegrationError>(); + const [error, setError] = useState<IntegrationInstallationError>(); const onIntegrationCreationSuccess = useCallback( - ({ version }: { version?: string }) => { - setIntegrationVersion(version); - onStatusChange('resolved'); + ({ versions }: { versions?: string[] }) => { + setIntegrationVersion(versions?.[0]); + onStatusChange?.('resolved'); }, [onStatusChange] ); const onIntegrationCreationFailure = useCallback( - (e: SystemIntegrationError) => { + (e: IntegrationInstallationError) => { setError(e); - onStatusChange('rejected'); + onStatusChange?.('rejected'); }, [onStatusChange] ); - const { performRequest, requestState } = useInstallSystemIntegration({ + const { performRequest, requestState } = useInstallIntegrations({ onIntegrationCreationSuccess, onIntegrationCreationFailure, }); diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_install_system_integration.ts b/x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_install_integrations.ts similarity index 66% rename from x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_install_system_integration.ts rename to x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_install_integrations.ts index d1e65b0c2fad7..018831fac9484 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_install_system_integration.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_install_integrations.ts @@ -12,7 +12,7 @@ import { useKibana } from './use_kibana'; // Errors const UNAUTHORIZED_ERROR = i18n.translate( - 'xpack.observability_onboarding.installSystemIntegration.error.unauthorized', + 'xpack.observability_onboarding.installIntegration.error.unauthorized', { defaultMessage: 'Required kibana privilege {requiredKibanaPrivileges} is missing, please add the required privilege to the role of the authenticated user.', @@ -23,19 +23,21 @@ const UNAUTHORIZED_ERROR = i18n.translate( ); type ErrorType = 'AuthorizationError' | 'UnknownError'; -export interface SystemIntegrationError { +export interface IntegrationInstallationError { type: ErrorType; message: string; } type IntegrationInstallStatus = 'installed' | 'installing' | 'install_failed' | 'not_installed'; -export const useInstallSystemIntegration = ({ +export const useInstallIntegrations = ({ onIntegrationCreationSuccess, onIntegrationCreationFailure, + packages = ['system'], }: { - onIntegrationCreationSuccess: ({ version }: { version?: string }) => void; - onIntegrationCreationFailure: (error: SystemIntegrationError) => void; + onIntegrationCreationSuccess?: ({ versions }: { versions?: string[] }) => void; + onIntegrationCreationFailure: (error: IntegrationInstallationError) => void; + packages?: string[]; }) => { const { services: { http }, @@ -48,20 +50,24 @@ export const useInstallSystemIntegration = ({ headers: { 'Elastic-Api-Version': '2023-10-31' }, }; - const { item: systemIntegration } = await http.get<{ - item: { version: string; status: IntegrationInstallStatus }; - }>('/api/fleet/epm/packages/system', options); + const integrations = []; + for (const packageName of packages) { + const { item: integration } = await http.get<{ + item: { version: string; status: IntegrationInstallStatus }; + }>(`/api/fleet/epm/packages/${packageName}`, options); - if (systemIntegration.status !== 'installed') { - await http.post('/api/fleet/epm/packages/system', options); + if (integration.status !== 'installed') { + await http.post(`/api/fleet/epm/packages/${packageName}`, options); + } + integrations.push(integration); } return { - version: systemIntegration.version, + versions: integrations.map((integration) => integration.version), }; }, - onResolve: ({ version }: { version?: string }) => { - onIntegrationCreationSuccess({ version }); + onResolve: ({ versions }: { versions?: string[] }) => { + onIntegrationCreationSuccess?.({ versions }); }, onReject: (requestError: any) => { if (requestError?.body?.statusCode === 403) { diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/index.ts b/x-pack/plugins/observability_solution/observability_onboarding/public/index.ts index 98174497d6c3e..b84ae734d3859 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/index.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/index.ts @@ -13,6 +13,7 @@ import { PluginInitializer, PluginInitializerContext, } from '@kbn/core/public'; +import { SharePluginStart } from '@kbn/share-plugin/public'; import { ObservabilityOnboardingPlugin, ObservabilityOnboardingPluginSetup, @@ -28,9 +29,16 @@ export interface ConfigSchema { }; } +export interface AppContext { + isServerless: boolean; + stackVersion: string; +} + export interface ObservabilityOnboardingAppServices { application: ApplicationStart; http: HttpStart; + share: SharePluginStart; + context: AppContext; config: ConfigSchema; docLinks: DocLinksStart; chrome: ChromeStart; diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/plugin.ts b/x-pack/plugins/observability_solution/observability_onboarding/public/plugin.ts index be73b77bd336e..2e3dfb201b35e 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/plugin.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/plugin.ts @@ -75,6 +75,7 @@ export class ObservabilityOnboardingPlugin constructor(private readonly ctx: PluginInitializerContext) {} public setup(core: CoreSetup, plugins: ObservabilityOnboardingPluginSetupDeps) { + const stackVersion = this.ctx.env.packageInfo.version; const config = this.ctx.config.get<ObservabilityOnboardingConfig>(); const { ui: { enabled: isObservabilityOnboardingUiEnabled }, @@ -109,6 +110,10 @@ export class ObservabilityOnboardingPlugin appMountParameters, corePlugins: corePlugins as ObservabilityOnboardingPluginStartDeps, config, + context: { + isServerless: Boolean(pluginSetupDeps.cloud?.isServerlessEnabled), + stackVersion, + }, }); }, visibleIn: [], diff --git a/x-pack/plugins/observability_solution/observability_onboarding/server/routes/logs/route.ts b/x-pack/plugins/observability_solution/observability_onboarding/server/routes/logs/route.ts index 4f7c1360dc082..524037c1c4222 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/server/routes/logs/route.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/server/routes/logs/route.ts @@ -7,6 +7,7 @@ import * as t from 'io-ts'; import { createObservabilityOnboardingServerRoute } from '../create_observability_onboarding_server_route'; +import { getFallbackESUrl } from '../../lib/get_fallback_urls'; import { getKibanaUrl } from '../../lib/get_fallback_urls'; import { getAgentVersion } from '../../lib/get_agent_version'; import { hasLogMonitoringPrivileges } from './api_key/has_log_monitoring_privileges'; @@ -38,8 +39,14 @@ const installShipperSetupRoute = createObservabilityOnboardingServerRoute({ apiEndpoint: string; scriptDownloadUrl: string; elasticAgentVersion: string; + elasticsearchUrl: string[]; }> { - const { core, plugins, kibanaVersion } = resources; + const { + core, + plugins, + kibanaVersion, + services: { esLegacyConfigService }, + } = resources; const fleetPluginStart = await plugins.fleet.start(); const elasticAgentVersion = await getAgentVersion(fleetPluginStart, kibanaVersion); @@ -51,14 +58,34 @@ const installShipperSetupRoute = createObservabilityOnboardingServerRoute({ const apiEndpoint = new URL(`${kibanaUrl}/internal/observability_onboarding`).toString(); + const elasticsearchUrl = plugins.cloud?.setup?.elasticsearchUrl + ? [plugins.cloud?.setup?.elasticsearchUrl] + : await getFallbackESUrl(esLegacyConfigService); + return { apiEndpoint, + elasticsearchUrl, scriptDownloadUrl, elasticAgentVersion, }; }, }); +const createAPIKeyRoute = createObservabilityOnboardingServerRoute({ + endpoint: 'POST /internal/observability_onboarding/otel/api_key', + options: { tags: [] }, + params: t.type({}), + async handler(resources): Promise<{ apiKeyEncoded: string }> { + const { context } = resources; + const { + elasticsearch: { client }, + } = await context.core; + const { encoded: apiKeyEncoded } = await createShipperApiKey(client.asCurrentUser, 'otel logs'); + + return { apiKeyEncoded }; + }, +}); + const createFlowRoute = createObservabilityOnboardingServerRoute({ endpoint: 'POST /internal/observability_onboarding/logs/flow', options: { tags: [] }, @@ -110,4 +137,5 @@ export const logsOnboardingRouteRepository = { ...logMonitoringPrivilegesRoute, ...installShipperSetupRoute, ...createFlowRoute, + ...createAPIKeyRoute, }; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 4a7e02dad81ac..04bc9871cb0af 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -29482,7 +29482,6 @@ "xpack.observability_onboarding.installElasticAgent.installStep.description": "Sélectionnez votre plateforme et exécutez la commande install dans votre terminal pour enregistrer, puis démarrez Elastic Agent. Faites ceci pour chaque hôte. Vérifiez {hostRequirementsLink} avant l'installation.", "xpack.observability_onboarding.installElasticAgent.integrationSuccessCallout.title": "Intégration {integrationName} installée.", "xpack.observability_onboarding.installElasticAgent.progress.eaConfig.completedTitle": "La configuration Elastic Agent est écrite dans {configPath}", - "xpack.observability_onboarding.installSystemIntegration.error.unauthorized": "Le privilège Kibana {requiredKibanaPrivileges} requis est manquant. Veuillez ajouter le privilège requis au rôle de l'utilisateur authentifié.", "xpack.observability_onboarding.systemIntegration.installed": "Intégration du système installée. {systemIntegrationTooltip}", "xpack.observability_onboarding.systemIntegration.installed.tooltip.link": "{learnMoreLink} sur les données que vous pouvez collecter à l'aide de l'intégration des systèmes.", "xpack.observability_onboarding.apiKeyBanner.created": "Clé d’API créée.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index ffebfa6c1e4f4..2fed66584a779 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -29459,7 +29459,6 @@ "xpack.observability_onboarding.installElasticAgent.installStep.description": "プラットフォームを選択し、ターミナルでinstallコマンドを実行してElasticエージェントを登録、起動します。各ホストでこの手順を実行します。インストール前に{hostRequirementsLink}を確認してください。", "xpack.observability_onboarding.installElasticAgent.integrationSuccessCallout.title": "{integrationName}統合がインストールされました。", "xpack.observability_onboarding.installElasticAgent.progress.eaConfig.completedTitle": "Elasticエージェント構成が{configPath}に書き込まれました", - "xpack.observability_onboarding.installSystemIntegration.error.unauthorized": "必要なkibana権限{requiredKibanaPrivileges}がありません。認証されたユーザーのロールに必要な権限を追加してください。", "xpack.observability_onboarding.systemIntegration.installed": "システム統合がインストールされました。{systemIntegrationTooltip}", "xpack.observability_onboarding.systemIntegration.installed.tooltip.link": "システム統合を使用して収集できるデータについて{learnMoreLink}。", "xpack.observability_onboarding.apiKeyBanner.created": "APIキーが作成されました。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 14b42d0c36523..8735ee2e0257d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -29499,7 +29499,6 @@ "xpack.observability_onboarding.installElasticAgent.installStep.description": "选择平台并在终端中运行安装命令,以注册并启动 Elastic 代理。对每台主机执行此操作。请在安装之前复查{hostRequirementsLink}。", "xpack.observability_onboarding.installElasticAgent.integrationSuccessCallout.title": "已安装 {integrationName} 集成。", "xpack.observability_onboarding.installElasticAgent.progress.eaConfig.completedTitle": "Elastic 代理配置已写入到 {configPath}", - "xpack.observability_onboarding.installSystemIntegration.error.unauthorized": "缺失所需的 Kibana 权限 {requiredKibanaPrivileges},请将所需权限添加到已通过身份验证的用户的角色。", "xpack.observability_onboarding.systemIntegration.installed": "已安装系统集成。{systemIntegrationTooltip}", "xpack.observability_onboarding.systemIntegration.installed.tooltip.link": "使用系统集成{learnMoreLink}有关您可收集的数据的信息。", "xpack.observability_onboarding.apiKeyBanner.created": "已创建 API 密钥。", From 2afe55eaecb271f4325c95e70ab0165624f9487d Mon Sep 17 00:00:00 2001 From: Gloria Hornero <gloria.hornero@elastic.co> Date: Tue, 2 Jul 2024 19:00:15 +0200 Subject: [PATCH 101/128] [Security Solution] Updates periodic pipeline job names (#187378) ## Summary Currently is not possible to see at first sight which execution is from Cypress and which one from API. <img width="2545" alt="Screenshot 2024-07-02 at 17 18 04" src="https://github.com/elastic/kibana/assets/17427073/c89c204d-e2cf-4661-87f4-1e206ad822d7"> In this PR we are updating the naming to make it easier to find out as well as simplifying the names. --- .../mki_periodic/mki_periodic_defend_workflows.yml | 2 +- .../mki_periodic/mki_periodic_detection_engine.yml | 8 ++++---- .../mki_periodic/mki_periodic_entity_analytics.yml | 4 ++-- .../mki_periodic/mki_periodic_explore.yml | 2 +- .../mki_periodic/mki_periodic_gen_ai.yml | 4 ++-- .../mki_periodic/mki_periodic_investigations.yml | 2 +- .../mki_periodic/mki_periodic_rule_management.yml | 6 +++--- .../mki_quality_gate_defend_workflows.yml | 2 +- .../mki_quality_gate_detection_engine.yml | 8 ++++---- .../mki_quality_gate_entity_analytics.yml | 4 ++-- .../mki_quality_gate/mki_quality_gate_explore.yml | 2 +- .../mki_quality_gate/mki_quality_gate_gen_ai.yml | 4 ++-- .../mki_quality_gate/mki_quality_gate_investigations.yml | 2 +- .../mki_quality_gate/mki_quality_gate_rule_management.yml | 8 ++++---- 14 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_defend_workflows.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_defend_workflows.yml index a20d3c709223f..b880b0a5f2f02 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_defend_workflows.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_defend_workflows.yml @@ -1,6 +1,6 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/edr_workflows/mki_security_solution_defend_workflows.sh cypress:dw:qa:serverless:run - label: "Serverless MKI QA Defend Workflows Cypress Tests on Serverless" + label: "Cypress MKI - Defend Workflows " key: test_defend_workflows agents: image: family/kibana-ubuntu-2004 diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_detection_engine.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_detection_engine.yml index aee2f92b712be..da5aa911a6c29 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_detection_engine.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_detection_engine.yml @@ -1,9 +1,9 @@ steps: - - group: "Serverless MKI QA Detection Engine - Cypress Tests" + - group: "Cypress MKI - Detection Engine" key: cypress_test_detections_engine steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:detection_engine - label: "Serverless MKI QA Detection Engine - Security Solution Cypress Tests" + label: "Cypress MKI - Detection Engine" key: test_detection_engine env: BK_TEST_SUITE_KEY: "serverless-cypress-detection-engine" @@ -22,7 +22,7 @@ steps: limit: 1 - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:detection_engine:exceptions - label: "Serverless MKI QA Detection Engine - Exceptions - Security Solution Cypress Tests" + label: "Cypress MKI - Detection Engine - Exceptions" key: test_detection_engine_exceptions env: BK_TEST_SUITE_KEY: "serverless-cypress-detection-engine" @@ -40,7 +40,7 @@ steps: - exit_status: "-1" limit: 1 - - group: "Serverless MKI QA Detection Engine - API Integration" + - group: "API MKI - Detection Engine - " key: api_test_detections_engine steps: - label: Running exception_lists_items:qa:serverless diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_entity_analytics.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_entity_analytics.yml index 238da924ffd24..f993986aefbb1 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_entity_analytics.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_entity_analytics.yml @@ -1,6 +1,6 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:entity_analytics - label: 'Serverless MKI QA Entity Analytics - Security Solution Cypress Tests' + label: 'Cypress MKI - Entity Analytics' key: test_entity_analytics env: BK_TEST_SUITE_KEY: "serverless-cypress-entity-analytics" @@ -18,7 +18,7 @@ steps: - exit_status: '-1' limit: 1 - - group: "Serverless MKI QA Entity Analytics - API Integration" + - group: "API MKI - Entity Analytics" key: api_test_entity_analytics steps: - label: Running entity_analytics:qa:serverless diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_explore.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_explore.yml index e35f6004ad3e5..7aff13525a2fc 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_explore.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_explore.yml @@ -1,7 +1,7 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:explore key: test_explore - label: 'Serverless MKI QA Explore - Security Solution Cypress Tests' + label: 'Cypress MKI - Explore' env: BK_TEST_SUITE_KEY: "serverless-cypress-explore" agents: diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_gen_ai.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_gen_ai.yml index d6ce8b4a80eb2..2d84e7d4e0315 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_gen_ai.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_gen_ai.yml @@ -1,6 +1,6 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:ai_assistant - label: "Serverless MKI QA AI Assistant - Security Solution Cypress Tests" + label: "Cypress MKI - GenAI key: test_ai_assistant env: BK_TEST_SUITE_KEY: "serverless-cypress-gen-ai" @@ -18,7 +18,7 @@ steps: - exit_status: "-1" limit: 1 - - group: "Serverless MKI QA AI Assistant - API Integration" + - group: "API MKI - GenAI" key: api_test_ai_assistant steps: - label: Running genai:qa:serverless diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_investigations.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_investigations.yml index caa788853c11e..d19d709231e31 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_investigations.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_investigations.yml @@ -1,7 +1,7 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:investigations key: test_investigations - label: 'Serverless MKI QA Investigations - Security Solution Cypress Tests' + label: 'Cypress MKI - Investigations' env: BK_TEST_SUITE_KEY: "serverless-cypress-investigations" agents: diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_rule_management.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_rule_management.yml index 428325ec0a1d0..7f08247a91b86 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_rule_management.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_rule_management.yml @@ -3,7 +3,7 @@ steps: key: cypress_test_rule_management steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:rule_management - label: "Serverless MKI QA Rule Management - Security Solution Cypress Tests" + label: "Cypress MKI - Rule Management" key: test_rule_management env: BK_TEST_SUITE_KEY: "serverless-cypress-rule-management" @@ -22,7 +22,7 @@ steps: limit: 1 - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:rule_management:prebuilt_rules - label: "Serverless MKI QA Rule Management - Prebuilt Rules - Security Solution Cypress Tests" + label: "Cypress MKI - Rule Management - Prebuilt Rules" key: test_rule_management_prebuilt_rules env: BK_TEST_SUITE_KEY: "serverless-cypress-rule-management" @@ -40,7 +40,7 @@ steps: - exit_status: "-1" limit: 1 - - group: "Serverless MKI QA Rule Management - API Integration" + - group: "API MKI - Rule Management" key: api_test_rule_management steps: - label: Running rule_creation:qa:serverless diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_defend_workflows.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_defend_workflows.yml index 96761bb5e9d7f..e59ca507e4003 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_defend_workflows.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_defend_workflows.yml @@ -1,6 +1,6 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/edr_workflows/mki_security_solution_defend_workflows.sh cypress:dw:qa:serverless:run - label: 'Serverless MKI QA Defend Workflows Cypress Tests on Serverless' + label: 'Cypress MKI - Defend Workflows' key: test_defend_workflows agents: image: family/kibana-ubuntu-2004 diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_detection_engine.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_detection_engine.yml index a44847c52b05e..f73ecc6225dcf 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_detection_engine.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_detection_engine.yml @@ -1,9 +1,9 @@ steps: - - group: "Serverless MKI QA Detection Engine - Cypress Tests" + - group: "Cypress MKI - Detection Engine" key: cypress_test_detections_engine steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:detection_engine - label: "Serverless MKI QA Detection Engine - Security Solution Cypress Tests" + label: "Cypress MKI - Detection Engine" key: test_detection_engine env: BK_TEST_SUITE_KEY: "serverless-cypress-detection-engine" @@ -22,7 +22,7 @@ steps: limit: 1 - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:detection_engine:exceptions - label: "Serverless MKI QA Detection Engine - Exceptions - Security Solution Cypress Tests" + label: "Cypress MKI - Detection Engine - Exceptions" key: test_detection_engine_exceptions env: BK_TEST_SUITE_KEY: "serverless-cypress-detection-engine" @@ -40,7 +40,7 @@ steps: - exit_status: "-1" limit: 1 - - group: "Serverless MKI QA Detection Engine - API Integration" + - group: "API MKI - Detection Engine" key: api_test_detections_engine steps: - label: Running exception_lists_items:qa:serverless:release diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_entity_analytics.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_entity_analytics.yml index a3552645ac531..16f2ec688bde4 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_entity_analytics.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_entity_analytics.yml @@ -1,6 +1,6 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:entity_analytics - label: 'Serverless MKI QA Entity Analytics - Security Solution Cypress Tests' + label: 'Cypress MKI - Entity Analytics' key: test_entity_analytics env: BK_TEST_SUITE_KEY: "serverless-cypress-entity-analytics" @@ -18,7 +18,7 @@ steps: - exit_status: '-1' limit: 1 - - group: "Serverless MKI QA Entity Analytics - API Integration" + - group: "API MKI - Entity Analytics" key: api_test_entity_analytics steps: - label: Running entity_analytics:qa:serverless:release diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_explore.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_explore.yml index e51e06a8a0543..e60f4509fcb3e 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_explore.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_explore.yml @@ -1,7 +1,7 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:explore key: test_explore - label: 'Serverless MKI QA Explore - Security Solution Cypress Tests' + label: 'Cypress MKI - Explore' env: BK_TEST_SUITE_KEY: "serverless-cypress-explore" agents: diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_gen_ai.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_gen_ai.yml index 60677728a0481..9ea5755438ef7 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_gen_ai.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_gen_ai.yml @@ -1,6 +1,6 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:ai_assistant - label: "Serverless MKI QA AI Assistant - Security Solution Cypress Tests" + label: "Cypress MKI - GenAI" key: test_ai_assistant env: BK_TEST_SUITE_KEY: "serverless-cypress-gen-ai" @@ -18,7 +18,7 @@ steps: - exit_status: "-1" limit: 1 - - group: "Serverless MKI QA AI Assistant - API Integration" + - group: "API MKI - GenAI" key: api_test_ai_assistant steps: - label: Running genai:qa:serverless:release diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_investigations.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_investigations.yml index 5e5707ad2ea8f..ed46611989b87 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_investigations.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_investigations.yml @@ -1,7 +1,7 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:investigations key: test_investigations - label: 'Serverless MKI QA Investigations - Security Solution Cypress Tests' + label: 'Cypress MKI - Investigations' env: BK_TEST_SUITE_KEY: "serverless-cypress-investigations" agents: diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_rule_management.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_rule_management.yml index ca13baa0bd2ad..5134d96f043c8 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_rule_management.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_rule_management.yml @@ -1,9 +1,9 @@ steps: - - group: "Serverless MKI QA Rule Management - Cypress Test" + - group: "Cypress MKI - Rule Management" key: cypress_test_rule_management steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:rule_management - label: "Serverless MKI QA Rule Management - Security Solution Cypress Tests" + label: "Cypress MKI - Rule Management" key: test_rule_management env: BK_TEST_SUITE_KEY: "serverless-cypress-rule-management" @@ -22,7 +22,7 @@ steps: limit: 1 - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:rule_management:prebuilt_rules - label: "Serverless MKI QA Rule Management - Prebuilt Rules - Security Solution Cypress Tests" + label: "Cypress MKI - Rule Management - Prebuilt Rules key: test_rule_management_prebuilt_rules env: BK_TEST_SUITE_KEY: "serverless-cypress-rule-management" @@ -40,7 +40,7 @@ steps: - exit_status: "-1" limit: 1 - - group: "Serverless MKI QA Rule Management - API Integration" + - group: "API MKI - Rule Management" key: api_test_rule_management steps: - label: Running rule_creation:qa:serverless:release From 274b9fe8505cb5896f21e497f88813dd281a7a42 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 2 Jul 2024 19:16:10 +0200 Subject: [PATCH 102/128] skip failing test suite (#187383) --- .../apps/integrations/artifact_entries_list.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/apps/integrations/artifact_entries_list.ts b/x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/apps/integrations/artifact_entries_list.ts index 6c1bb54caf3df..1c184502244c3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/apps/integrations/artifact_entries_list.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/apps/integrations/artifact_entries_list.ts @@ -53,6 +53,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }; // Failing: See https://github.com/elastic/kibana/issues/187314 + // Failing: See https://github.com/elastic/kibana/issues/187383 describe.skip('@ess @serverless For each artifact list under management', function () { let indexedData: IndexedHostsAndAlertsResponse; let policyInfo: PolicyTestResourceInfo; From 6120167bc3e881a653883f7edf8ba057caa83c4e Mon Sep 17 00:00:00 2001 From: Samiul Monir <150824886+Samiul-TheSoccerFan@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:35:13 -0400 Subject: [PATCH 103/128] Fix form submit issue with key down (#187116) ## Summary This PR fixes an SDH issue where users are writing with a Japanese keyboard and transform the English to Japanese, they have to press ENTER key. Our OnKeyDown functionality automatically submits the form when the user hits the ENTER key from the question input text field. We removed the form submission from the text field. ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../public/components/question_input.test.tsx | 110 ++++++++++++++++++ .../public/components/question_input.tsx | 24 ++-- 2 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 x-pack/plugins/search_playground/public/components/question_input.test.tsx diff --git a/x-pack/plugins/search_playground/public/components/question_input.test.tsx b/x-pack/plugins/search_playground/public/components/question_input.test.tsx new file mode 100644 index 0000000000000..bfd156c9a9228 --- /dev/null +++ b/x-pack/plugins/search_playground/public/components/question_input.test.tsx @@ -0,0 +1,110 @@ +/* + * 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 { EuiButton, EuiForm } from '@elastic/eui'; +import React, { FormEventHandler } from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { QuestionInput } from './question_input'; + +const mockButton = ( + <EuiButton data-test="btn" className="btn" onClick={() => {}}> + Send + </EuiButton> +); + +const handleOnSubmitMock = jest.fn(); + +const MockChatForm = ({ + children, + handleSubmit, +}: { + children: React.ReactElement; + handleSubmit: FormEventHandler; +}) => ( + <EuiForm + component="form" + css={{ display: 'flex', flexGrow: 1 }} + onSubmit={handleSubmit} + data-test-subj="chatPage" + > + {children} + </EuiForm> +); +describe('Question Input', () => { + describe('renders', () => { + it('correctly', () => { + render( + <IntlProvider locale="en"> + <MockChatForm handleSubmit={handleOnSubmitMock}> + <QuestionInput value="" onChange={() => {}} button={mockButton} isDisabled={false} /> + </MockChatForm> + </IntlProvider> + ); + + expect(screen.getByTestId('questionInput')).toBeInTheDocument(); + }); + + it('disabled', () => { + render( + <IntlProvider locale="en"> + <MockChatForm handleSubmit={handleOnSubmitMock}> + <QuestionInput + value="my question" + onChange={() => {}} + button={mockButton} + isDisabled={true} + /> + </MockChatForm> + </IntlProvider> + ); + + expect(screen.getByTestId('questionInput')).toBeDisabled(); + }); + + it('with value', () => { + render( + <IntlProvider locale="en"> + <MockChatForm handleSubmit={handleOnSubmitMock}> + <QuestionInput + value="my question" + onChange={() => {}} + button={mockButton} + isDisabled={false} + /> + </MockChatForm> + </IntlProvider> + ); + + expect(screen.getByTestId('questionInput')).toHaveDisplayValue('my question'); + }); + }); + it('submits form', () => { + render( + <IntlProvider locale="en"> + <MockChatForm handleSubmit={handleOnSubmitMock}> + <QuestionInput value="" onChange={() => {}} button={mockButton} isDisabled={false} /> + </MockChatForm> + </IntlProvider> + ); + + const textArea = screen.getByTestId('questionInput'); + fireEvent.compositionStart(textArea); + fireEvent.keyDown(textArea, { + key: 'Enter', + shiftKey: false, + }); + expect(handleOnSubmitMock).not.toHaveBeenCalled(); + + fireEvent.compositionEnd(textArea); + fireEvent.keyDown(textArea, { + key: 'Enter', + shiftKey: false, + }); + expect(handleOnSubmitMock).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/search_playground/public/components/question_input.tsx b/x-pack/plugins/search_playground/public/components/question_input.tsx index cdc9d347e4ff8..1da424225f2d4 100644 --- a/x-pack/plugins/search_playground/public/components/question_input.tsx +++ b/x-pack/plugins/search_playground/public/components/question_input.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiTextArea, keys, useEuiTheme } from '@elastic/eui'; @@ -25,6 +25,7 @@ export const QuestionInput: React.FC<QuestionInputProps> = ({ button, isDisabled, }) => { + const [isComposing, setIsComposing] = useState(false); const { euiTheme } = useEuiTheme(); const handleChange = useCallback( (e: React.ChangeEvent<HTMLTextAreaElement>) => { @@ -35,13 +36,20 @@ export const QuestionInput: React.FC<QuestionInputProps> = ({ }, [onChange] ); - const handleKeyDown = useCallback((event: React.KeyboardEvent<HTMLTextAreaElement>) => { - if (event.key === keys.ENTER && !event.shiftKey) { - event.preventDefault(); + const handleCompositionStart = () => setIsComposing(true); + const handleCompositionEnd = () => { + setIsComposing(false); + }; + const handleKeyDown = useCallback( + (event: React.KeyboardEvent<HTMLTextAreaElement>) => { + if (event.key === keys.ENTER && !event.shiftKey && !isComposing) { + event.preventDefault(); - event.currentTarget.form?.requestSubmit(); - } - }, []); + event.currentTarget.form?.requestSubmit(); + } + }, + [isComposing] + ); return ( <div css={{ position: 'relative' }}> @@ -67,6 +75,8 @@ export const QuestionInput: React.FC<QuestionInputProps> = ({ disabled={isDisabled} resize="none" data-test-subj="questionInput" + onCompositionStart={handleCompositionStart} + onCompositionEnd={handleCompositionEnd} /> <div From 628994ea3addcca28332ffae81034370a92a6267 Mon Sep 17 00:00:00 2001 From: Elena Stoeva <59341489+ElenaStoeva@users.noreply.github.com> Date: Tue, 2 Jul 2024 18:36:16 +0100 Subject: [PATCH 104/128] [Rollups] Update rollup badges in Data view form (#187235) Closes https://github.com/elastic/kibana/issues/186663 ## Summary Follow-up to https://github.com/elastic/kibana/pull/186321. This PR updates the Rollup badges in the Data view create form so that they show that Rollup is deprecated. <details> <summary>Before:</summary> <img width="1327" alt="Screenshot 2024-07-01 at 11 48 21" src="https://github.com/elastic/kibana/assets/59341489/9f4e1cd1-a34c-42dd-926a-ed6758f06ed7"> </details> <details> <summary>Now:</summary> <img width="1323" alt="Screenshot 2024-07-01 at 17 50 48" src="https://github.com/elastic/kibana/assets/59341489/084873f2-3707-46c4-b4ba-19b4e68c2d87"> <img width="1323" alt="Screenshot 2024-07-01 at 17 50 54" src="https://github.com/elastic/kibana/assets/59341489/d021ef34-3a84-4406-b23e-027aa2e8e868"> <img width="1323" alt="Screenshot 2024-07-01 at 17 50 59" src="https://github.com/elastic/kibana/assets/59341489/ef1246c5-00ed-4eaa-af21-e0515bce956a"> </details> **How to test:** 1. Start Es and Kibana 2. Add the sample data "Sample web logs" 3. Elasticsearch only allows creating a rollup job if there is an existing rollup usage in the cluster. To simulate rollup usage, create a mock rollup index through Console: ``` PUT /mock_rollup_index { "mappings": { "_meta": { "_rollup": { "id": "logs_job" } } } } ``` 4. Create a sample rollup job through Console: ``` PUT _rollup/job/logs_job { "id": "logs_job", "index_pattern": "kibana_sample_data_logs", "rollup_index": "rollup_logstash", "cron": "* * * * * ?", "page_size": 1000, "groups": { "date_histogram": { "interval": "60m", "delay": "7d", "time_zone": "UTC", "field": "@timestamp" }, "terms": { "fields": [ "geo.src", "machine.os.keyword" ] }, "histogram": { "interval": "1003", "fields": [ "bytes", "memory" ] } } } ``` 5. Delete the mock rollup index since it causes issues for the rollup API that we use to fetch rollup indices: `DELETE /mock_rollup_index` 6. Navigate to Stack Management -> Data Views and start creating a new data view. 7. Verify that the rollup badges are updated. <!-- ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../public/components/form_fields/type_field.tsx | 12 +++++++++++- .../preview_panel/indices_list/indices_list.tsx | 13 +++++++++++-- src/plugins/data_view_editor/tsconfig.json | 1 + .../data_views/public/services/get_indices.ts | 4 ++-- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/plugins/data_view_editor/public/components/form_fields/type_field.tsx b/src/plugins/data_view_editor/public/components/form_fields/type_field.tsx index b11d8ac2e03ea..2b043d55a8f24 100644 --- a/src/plugins/data_view_editor/public/components/form_fields/type_field.tsx +++ b/src/plugins/data_view_editor/public/components/form_fields/type_field.tsx @@ -21,6 +21,7 @@ import { } from '@elastic/eui'; import { INDEX_PATTERN_TYPE } from '@kbn/data-views-plugin/public'; +import { RollupDeprecationTooltip } from '@kbn/rollup'; import { UseField } from '../../shared_imports'; import { IndexPatternConfig } from '../../types'; @@ -57,6 +58,15 @@ const rollupSelectItem = ( <EuiBadge color={euiLightVars.euiColorAccent}> <FormattedMessage id="indexPatternEditor.typeSelect.betaLabel" defaultMessage="Beta" /> </EuiBadge> +   + <RollupDeprecationTooltip> + <EuiBadge color="warning"> + <FormattedMessage + id="indexPatternEditor.typeSelect.deprecatedBadge" + defaultMessage="Deprecated" + /> + </EuiBadge> + </RollupDeprecationTooltip> </EuiDescriptionListTitle> <EuiDescriptionListDescription> <FormattedMessage @@ -90,7 +100,7 @@ export const TypeField = ({ onChange }: TypeFieldProps) => { { value: INDEX_PATTERN_TYPE.ROLLUP, inputDisplay: i18n.translate('indexPatternEditor.typeSelect.rollup', { - defaultMessage: 'Rollup', + defaultMessage: 'Rollup (deprecated)', }), dropdownDisplay: rollupSelectItem, }, diff --git a/src/plugins/data_view_editor/public/components/preview_panel/indices_list/indices_list.tsx b/src/plugins/data_view_editor/public/components/preview_panel/indices_list/indices_list.tsx index 1cb5298911785..73aaa8cec2ef2 100644 --- a/src/plugins/data_view_editor/public/components/preview_panel/indices_list/indices_list.tsx +++ b/src/plugins/data_view_editor/public/components/preview_panel/indices_list/indices_list.tsx @@ -27,7 +27,8 @@ import { import { Pager } from '@elastic/eui'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { FormattedMessage } from '@kbn/i18n-react'; -import { MatchedItem, Tag } from '@kbn/data-views-plugin/public'; +import { INDEX_PATTERN_TYPE, MatchedItem, Tag } from '@kbn/data-views-plugin/public'; +import { RollupDeprecationTooltip } from '@kbn/rollup'; export interface IndicesListProps { indices: MatchedItem[]; @@ -205,11 +206,19 @@ export class IndicesList extends React.Component<IndicesListProps, IndicesListSt <EuiTableRowCell>{this.highlightIndexName(index.name, query)}</EuiTableRowCell> <EuiTableRowCell> {index.tags.map((tag: Tag) => { - return ( + const badge = ( <EuiBadge key={`index_${key}_tag_${tag.key}`} color={tag.color}> {tag.name} </EuiBadge> ); + + return tag.key === INDEX_PATTERN_TYPE.ROLLUP ? ( + <> +  <RollupDeprecationTooltip>{badge}</RollupDeprecationTooltip> + </> + ) : ( + badge + ); })} </EuiTableRowCell> </EuiTableRow> diff --git a/src/plugins/data_view_editor/tsconfig.json b/src/plugins/data_view_editor/tsconfig.json index adfd30af81b72..3b15c5555d7b5 100644 --- a/src/plugins/data_view_editor/tsconfig.json +++ b/src/plugins/data_view_editor/tsconfig.json @@ -20,6 +20,7 @@ "@kbn/kibana-utils-plugin", "@kbn/react-kibana-mount", "@kbn/code-editor", + "@kbn/rollup", ], "exclude": [ "target/**/*", diff --git a/src/plugins/data_views/public/services/get_indices.ts b/src/plugins/data_views/public/services/get_indices.ts index fba5004367526..51c370375a7a1 100644 --- a/src/plugins/data_views/public/services/get_indices.ts +++ b/src/plugins/data_views/public/services/get_indices.ts @@ -26,7 +26,7 @@ const frozenLabel = i18n.translate('dataViews.frozenLabel', { }); const rollupLabel = i18n.translate('dataViews.rollupLabel', { - defaultMessage: 'Rollup', + defaultMessage: 'Rollup (deprecated)', }); const getIndexTags = (isRollupIndex: (indexName: string) => boolean) => (indexName: string) => @@ -35,7 +35,7 @@ const getIndexTags = (isRollupIndex: (indexName: string) => boolean) => (indexNa { key: INDEX_PATTERN_TYPE.ROLLUP, name: rollupLabel, - color: 'primary', + color: 'warning', }, ] : []; From a613a53e9bf7b28cb41794d9facca5d5e73efeee Mon Sep 17 00:00:00 2001 From: Katerina <aikaterini.patticha@elastic.co> Date: Tue, 2 Jul 2024 20:40:57 +0300 Subject: [PATCH 105/128] [APM] Unskip apm api test suite (#186870) ## Summary test suite was skipped in https://github.com/elastic/kibana/commit/8c18091792beb7d2ff43e9f621750e210f3eda17 --- .../alerts/error_count_threshold.spec.ts | 11 +- .../alerts/preview_chart_error_count.spec.ts | 458 +++++++++--------- .../alerts/preview_chart_error_rate.spec.ts | 4 +- .../tests/dependencies/top_spans.spec.ts | 4 +- .../tests/diagnostics/data_streams.spec.ts | 13 +- .../tests/diagnostics/index_templates.spec.ts | 2 +- .../tests/errors/group_id_samples.spec.ts | 4 +- .../top_erroneous_transactions.spec.ts | 4 +- .../top_errors_main_stats.spec.ts | 2 +- .../test/apm_api_integration/tests/index.ts | 2 +- .../mobile/crashes/crash_group_list.spec.ts | 2 +- .../tests/mobile/crashes/distribution.spec.ts | 2 +- .../mobile/errors/group_id_samples.spec.ts | 4 +- ...obile_detailed_statistics_by_field.spec.ts | 2 +- .../tests/mobile/mobile_filters.spec.ts | 2 +- .../mobile_http_requests_timeseries.spec.ts | 120 ++--- .../mobile/mobile_location_stats.spec.ts | 2 +- .../mobile_main_statistics_by_field.spec.ts | 2 +- .../mobile/mobile_most_used_chart.spec.ts | 2 +- .../mobile/mobile_sessions_timeseries.spec.ts | 4 +- .../tests/mobile/mobile_stats.spec.ts | 2 +- .../mobile/mobile_terms_by_field.spec.ts | 2 +- 22 files changed, 331 insertions(+), 319 deletions(-) diff --git a/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts index 46d62449de475..45a95db642d2f 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts @@ -8,7 +8,6 @@ import { ApmRuleType } from '@kbn/rule-data-utils'; import { errorCountActionVariables } from '@kbn/apm-plugin/server/routes/alerts/rule_types/error_count/register_error_count_rule_type'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; -import { getErrorGroupingKey } from '@kbn/apm-synthtrace-client/src/lib/apm/instance'; import expect from '@kbn/expect'; import { omit } from 'lodash'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -105,7 +104,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { after(() => apmSynthtraceEsClient.clean()); // FLAKY: https://github.com/elastic/kibana/issues/176948 - describe('create rule without kql filter', () => { + describe.skip('create rule without kql filter', () => { let ruleId: string; let alerts: ApmAlertFields[]; let actionId: string; @@ -214,14 +213,14 @@ export default function ApiTest({ getService }: FtrProviderContext) { serviceName: 'opbeans-php', environment: 'production', transactionName: 'tx-php', - errorGroupingKey: getErrorGroupingKey(phpErrorMessage), + errorGroupingKey: '000000000000000000000a php error', errorGroupingName: phpErrorMessage, }, { serviceName: 'opbeans-java', environment: 'production', transactionName: 'tx-java', - errorGroupingKey: getErrorGroupingKey(javaErrorMessage), + errorGroupingKey: '00000000000000000000a java error', errorGroupingName: javaErrorMessage, }, ]); @@ -254,7 +253,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); // FLAKY: https://github.com/elastic/kibana/issues/176964 - describe('create rule with kql filter for opbeans-php', () => { + describe.skip('create rule with kql filter for opbeans-php', () => { let ruleId: string; before(async () => { @@ -283,7 +282,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('produces one alert for the opbeans-php service', async () => { const alerts = await waitForAlertsForRule({ es, ruleId }); expect(alerts[0]['kibana.alert.reason']).to.be( - 'Error count is 30 in the last 1 hr for service: opbeans-php, env: production, name: tx-php, error key: c85df8159a74b47b461d6ddaa6ba7da38cfc3e74019aef66257d10df74adeb99, error name: a php error. Alert when > 1.' + 'Error count is 30 in the last 1 hr for service: opbeans-php, env: production, name: tx-php, error key: 000000000000000000000a php error, error name: a php error. Alert when > 1.' ); }); }); diff --git a/x-pack/test/apm_api_integration/tests/alerts/preview_chart_error_count.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/preview_chart_error_count.spec.ts index 897f446734444..2ceb5608e3a46 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/preview_chart_error_count.spec.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/preview_chart_error_count.spec.ts @@ -68,7 +68,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); - registry.when(`with data loaded`, { config: 'basic', archives: [] }, () => { + registry.when.skip(`with data loaded`, { config: 'basic', archives: [] }, () => { // FLAKY: https://github.com/elastic/kibana/issues/172769 describe('error_count', () => { beforeEach(async () => { @@ -304,255 +304,259 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); - registry.when(`with data loaded and using KQL filter`, { config: 'basic', archives: [] }, () => { - // FLAKY: https://github.com/elastic/kibana/issues/176975 - describe('error_count', () => { - before(async () => { - await generateErrorData({ serviceName: 'synth-go', start, end, apmSynthtraceEsClient }); - await generateErrorData({ serviceName: 'synth-java', start, end, apmSynthtraceEsClient }); - }); + registry.when.skip( + `with data loaded and using KQL filter`, + { config: 'basic', archives: [] }, + () => { + // FLAKY: https://github.com/elastic/kibana/issues/176975 + describe('error_count', () => { + before(async () => { + await generateErrorData({ serviceName: 'synth-go', start, end, apmSynthtraceEsClient }); + await generateErrorData({ serviceName: 'synth-java', start, end, apmSynthtraceEsClient }); + }); - after(() => apmSynthtraceEsClient.clean()); + after(() => apmSynthtraceEsClient.clean()); - it('with data', async () => { - const options = getOptionsWithFilterQuery(); + it('with data', async () => { + const options = getOptionsWithFilterQuery(); - const response = await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/rule_types/error_count/chart_preview', - ...options, - }); + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/rule_types/error_count/chart_preview', + ...options, + }); - expect(response.status).to.be(200); - expect( - response.body.errorCountChartPreview.series.some((item: PreviewChartResponseItem) => - item.data.some((coordinate) => coordinate.x && coordinate.y) - ) - ).to.equal(true); - }); + expect(response.status).to.be(200); + expect( + response.body.errorCountChartPreview.series.some((item: PreviewChartResponseItem) => + item.data.some((coordinate) => coordinate.x && coordinate.y) + ) + ).to.equal(true); + }); - it('with error grouping key in filter query', async () => { - const options = { - params: { - query: { - ...getOptionsWithFilterQuery().params.query, - searchConfiguration: JSON.stringify({ - query: { - query: `service.name: synth-go and error.grouping_key: ${getErrorGroupingKey( - 'Error 1' - )}`, - language: 'kuery', - }, - }), + it('with error grouping key in filter query', async () => { + const options = { + params: { + query: { + ...getOptionsWithFilterQuery().params.query, + searchConfiguration: JSON.stringify({ + query: { + query: `service.name: synth-go and error.grouping_key: ${getErrorGroupingKey( + 'Error 1' + )}`, + language: 'kuery', + }, + }), + }, }, - }, - }; - - const response = await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/rule_types/error_count/chart_preview', - ...options, + }; + + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/rule_types/error_count/chart_preview', + ...options, + }); + + expect(response.status).to.be(200); + expect( + response.body.errorCountChartPreview.series.map((item: PreviewChartResponseItem) => ({ + name: item.name, + y: item.data[0].y, + })) + ).to.eql([{ name: 'synth-go_production', y: 250 }]); }); - expect(response.status).to.be(200); - expect( - response.body.errorCountChartPreview.series.map((item: PreviewChartResponseItem) => ({ - name: item.name, - y: item.data[0].y, - })) - ).to.eql([{ name: 'synth-go_production', y: 250 }]); - }); - - it('with no group by parameter', async () => { - const options = getOptionsWithFilterQuery(); - const response = await apmApiClient.readUser({ - ...options, - endpoint: 'GET /internal/apm/rule_types/error_count/chart_preview', + it('with no group by parameter', async () => { + const options = getOptionsWithFilterQuery(); + const response = await apmApiClient.readUser({ + ...options, + endpoint: 'GET /internal/apm/rule_types/error_count/chart_preview', + }); + + expect(response.status).to.be(200); + expect(response.body.errorCountChartPreview.series.length).to.equal(1); + expect( + response.body.errorCountChartPreview.series.map((item: PreviewChartResponseItem) => ({ + name: item.name, + y: item.data[0].y, + })) + ).to.eql([{ name: 'synth-go_production', y: 375 }]); }); - expect(response.status).to.be(200); - expect(response.body.errorCountChartPreview.series.length).to.equal(1); - expect( - response.body.errorCountChartPreview.series.map((item: PreviewChartResponseItem) => ({ - name: item.name, - y: item.data[0].y, - })) - ).to.eql([{ name: 'synth-go_production', y: 375 }]); - }); - - it('with default group by fields', async () => { - const options = { - params: { - query: { - ...getOptionsWithFilterQuery().params.query, - groupBy: [SERVICE_NAME, SERVICE_ENVIRONMENT], + it('with default group by fields', async () => { + const options = { + params: { + query: { + ...getOptionsWithFilterQuery().params.query, + groupBy: [SERVICE_NAME, SERVICE_ENVIRONMENT], + }, }, - }, - }; - - const response = await apmApiClient.readUser({ - ...options, - endpoint: 'GET /internal/apm/rule_types/error_count/chart_preview', + }; + + const response = await apmApiClient.readUser({ + ...options, + endpoint: 'GET /internal/apm/rule_types/error_count/chart_preview', + }); + + expect(response.status).to.be(200); + expect(response.body.errorCountChartPreview.series.length).to.equal(1); + expect( + response.body.errorCountChartPreview.series.map((item: PreviewChartResponseItem) => ({ + name: item.name, + y: item.data[0].y, + })) + ).to.eql([{ name: 'synth-go_production', y: 375 }]); }); - expect(response.status).to.be(200); - expect(response.body.errorCountChartPreview.series.length).to.equal(1); - expect( - response.body.errorCountChartPreview.series.map((item: PreviewChartResponseItem) => ({ - name: item.name, - y: item.data[0].y, - })) - ).to.eql([{ name: 'synth-go_production', y: 375 }]); - }); - - it('with group by on error grouping key', async () => { - const options = { - params: { - query: { - ...getOptionsWithFilterQuery().params.query, - groupBy: [SERVICE_NAME, SERVICE_ENVIRONMENT, ERROR_GROUP_ID], + it('with group by on error grouping key', async () => { + const options = { + params: { + query: { + ...getOptionsWithFilterQuery().params.query, + groupBy: [SERVICE_NAME, SERVICE_ENVIRONMENT, ERROR_GROUP_ID], + }, }, - }, - }; - - const response = await apmApiClient.readUser({ - ...options, - endpoint: 'GET /internal/apm/rule_types/error_count/chart_preview', + }; + + const response = await apmApiClient.readUser({ + ...options, + endpoint: 'GET /internal/apm/rule_types/error_count/chart_preview', + }); + + expect(response.status).to.be(200); + expect(response.body.errorCountChartPreview.series.length).to.equal(2); + expect( + response.body.errorCountChartPreview.series.map((item: PreviewChartResponseItem) => ({ + name: item.name, + y: item.data[0].y, + })) + ).to.eql([ + { + name: `synth-go_production_${getErrorGroupingKey('Error 1')}`, + y: 250, + }, + { + name: `synth-go_production_${getErrorGroupingKey('Error 0')}`, + y: 125, + }, + ]); }); - expect(response.status).to.be(200); - expect(response.body.errorCountChartPreview.series.length).to.equal(2); - expect( - response.body.errorCountChartPreview.series.map((item: PreviewChartResponseItem) => ({ - name: item.name, - y: item.data[0].y, - })) - ).to.eql([ - { - name: `synth-go_production_${getErrorGroupingKey('Error 1')}`, - y: 250, - }, - { - name: `synth-go_production_${getErrorGroupingKey('Error 0')}`, - y: 125, - }, - ]); - }); - - it('with group by on error grouping key and filter on error grouping key', async () => { - const options = { - params: { - query: { - ...getOptionsWithFilterQuery().params.query, - searchConfiguration: JSON.stringify({ - query: { - query: `service.name: synth-go and error.grouping_key: ${getErrorGroupingKey( - 'Error 0' - )}`, - language: 'kuery', - }, - }), - groupBy: [SERVICE_NAME, SERVICE_ENVIRONMENT, ERROR_GROUP_ID], + it('with group by on error grouping key and filter on error grouping key', async () => { + const options = { + params: { + query: { + ...getOptionsWithFilterQuery().params.query, + searchConfiguration: JSON.stringify({ + query: { + query: `service.name: synth-go and error.grouping_key: ${getErrorGroupingKey( + 'Error 0' + )}`, + language: 'kuery', + }, + }), + groupBy: [SERVICE_NAME, SERVICE_ENVIRONMENT, ERROR_GROUP_ID], + }, }, - }, - }; - - const response = await apmApiClient.readUser({ - ...options, - endpoint: 'GET /internal/apm/rule_types/error_count/chart_preview', + }; + + const response = await apmApiClient.readUser({ + ...options, + endpoint: 'GET /internal/apm/rule_types/error_count/chart_preview', + }); + + expect(response.status).to.be(200); + expect(response.body.errorCountChartPreview.series.length).to.equal(1); + expect( + response.body.errorCountChartPreview.series.map((item: PreviewChartResponseItem) => ({ + name: item.name, + y: item.data[0].y, + })) + ).to.eql([ + { + name: `synth-go_production_${getErrorGroupingKey('Error 0')}`, + y: 125, + }, + ]); }); - expect(response.status).to.be(200); - expect(response.body.errorCountChartPreview.series.length).to.equal(1); - expect( - response.body.errorCountChartPreview.series.map((item: PreviewChartResponseItem) => ({ - name: item.name, - y: item.data[0].y, - })) - ).to.eql([ - { - name: `synth-go_production_${getErrorGroupingKey('Error 0')}`, - y: 125, - }, - ]); - }); - - it('with empty filter query', async () => { - const options = { - params: { - query: { - ...getOptionsWithFilterQuery().params.query, - searchConfiguration: JSON.stringify({ - query: { - query: '', - language: 'kuery', - }, - }), + it('with empty filter query', async () => { + const options = { + params: { + query: { + ...getOptionsWithFilterQuery().params.query, + searchConfiguration: JSON.stringify({ + query: { + query: '', + language: 'kuery', + }, + }), + }, }, - }, - }; - - const response = await apmApiClient.readUser({ - ...options, - endpoint: 'GET /internal/apm/rule_types/error_count/chart_preview', + }; + + const response = await apmApiClient.readUser({ + ...options, + endpoint: 'GET /internal/apm/rule_types/error_count/chart_preview', + }); + + expect(response.status).to.be(200); + expect( + response.body.errorCountChartPreview.series.map((item: PreviewChartResponseItem) => ({ + name: item.name, + y: item.data[0].y, + })) + ).to.eql([ + { name: 'synth-go_production', y: 375 }, + { name: 'synth-java_production', y: 375 }, + ]); }); - expect(response.status).to.be(200); - expect( - response.body.errorCountChartPreview.series.map((item: PreviewChartResponseItem) => ({ - name: item.name, - y: item.data[0].y, - })) - ).to.eql([ - { name: 'synth-go_production', y: 375 }, - { name: 'synth-java_production', y: 375 }, - ]); - }); - - it('with empty filter query and group by on error grouping key', async () => { - const options = { - params: { - query: { - ...getOptionsWithFilterQuery().params.query, - searchConfiguration: JSON.stringify({ - query: { - query: '', - language: 'kuery', - }, - }), - groupBy: [SERVICE_NAME, SERVICE_ENVIRONMENT, ERROR_GROUP_ID], + it('with empty filter query and group by on error grouping key', async () => { + const options = { + params: { + query: { + ...getOptionsWithFilterQuery().params.query, + searchConfiguration: JSON.stringify({ + query: { + query: '', + language: 'kuery', + }, + }), + groupBy: [SERVICE_NAME, SERVICE_ENVIRONMENT, ERROR_GROUP_ID], + }, }, - }, - }; - - const response = await apmApiClient.readUser({ - ...options, - endpoint: 'GET /internal/apm/rule_types/error_count/chart_preview', + }; + + const response = await apmApiClient.readUser({ + ...options, + endpoint: 'GET /internal/apm/rule_types/error_count/chart_preview', + }); + + expect(response.status).to.be(200); + expect( + response.body.errorCountChartPreview.series.map((item: PreviewChartResponseItem) => ({ + name: item.name, + y: item.data[0].y, + })) + ).to.eql([ + { + name: `synth-go_production_${getErrorGroupingKey('Error 1')}`, + y: 250, + }, + { + name: `synth-java_production_${getErrorGroupingKey('Error 1')}`, + y: 250, + }, + { + name: `synth-go_production_${getErrorGroupingKey('Error 0')}`, + y: 125, + }, + { + name: `synth-java_production_${getErrorGroupingKey('Error 0')}`, + y: 125, + }, + ]); }); - - expect(response.status).to.be(200); - expect( - response.body.errorCountChartPreview.series.map((item: PreviewChartResponseItem) => ({ - name: item.name, - y: item.data[0].y, - })) - ).to.eql([ - { - name: `synth-go_production_${getErrorGroupingKey('Error 1')}`, - y: 250, - }, - { - name: `synth-java_production_${getErrorGroupingKey('Error 1')}`, - y: 250, - }, - { - name: `synth-go_production_${getErrorGroupingKey('Error 0')}`, - y: 125, - }, - { - name: `synth-java_production_${getErrorGroupingKey('Error 0')}`, - y: 125, - }, - ]); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/alerts/preview_chart_error_rate.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/preview_chart_error_rate.spec.ts index bc11d1cb68fd9..8f968a89bac52 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/preview_chart_error_rate.spec.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/preview_chart_error_rate.spec.ts @@ -249,7 +249,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { ).to.eql([{ name: 'synth-go_production_request_GET /apple', y: 25 }]); }); - it('with empty service name, transaction name and transaction type', async () => { + it.skip('with empty service name, transaction name and transaction type', async () => { const options = { params: { query: { @@ -548,7 +548,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { ]); }); - it('with empty filter query and group by on transaction name', async () => { + it.skip('with empty filter query and group by on transaction name', async () => { const options = { params: { query: { diff --git a/x-pack/test/apm_api_integration/tests/dependencies/top_spans.spec.ts b/x-pack/test/apm_api_integration/tests/dependencies/top_spans.spec.ts index b07c7c323ed9c..1002f6dc09eec 100644 --- a/x-pack/test/apm_api_integration/tests/dependencies/top_spans.spec.ts +++ b/x-pack/test/apm_api_integration/tests/dependencies/top_spans.spec.ts @@ -157,7 +157,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(javaSpans.length + goSpans.length).to.eql(spans.length); expect(omit(javaSpans[0], 'spanId', 'traceId', 'transactionId')).to.eql({ - '@timestamp': 1609459200000, + '@timestamp': 1609460040000, agentName: 'java', duration: 100000, serviceName: 'java', @@ -168,7 +168,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); expect(omit(goSpans[0], 'spanId', 'traceId', 'transactionId')).to.eql({ - '@timestamp': 1609459200000, + '@timestamp': 1609460040000, agentName: 'go', duration: 50000, serviceName: 'go', diff --git a/x-pack/test/apm_api_integration/tests/diagnostics/data_streams.spec.ts b/x-pack/test/apm_api_integration/tests/diagnostics/data_streams.spec.ts index 969ce9fabd5a6..80fa34dbaa002 100644 --- a/x-pack/test/apm_api_integration/tests/diagnostics/data_streams.spec.ts +++ b/x-pack/test/apm_api_integration/tests/diagnostics/data_streams.spec.ts @@ -75,17 +75,20 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); expect(status).to.be(200); expect(body.dataStreams).to.eql([ - { name: 'metrics-apm.internal-default', template: 'metrics-apm.internal' }, + { name: 'metrics-apm.internal-default', template: 'metrics-apm.internal@template' }, { name: 'metrics-apm.service_summary.1m-default', - template: 'metrics-apm.service_summary.1m', + template: 'metrics-apm.service_summary.1m@template', }, { name: 'metrics-apm.service_transaction.1m-default', - template: 'metrics-apm.service_transaction.1m', + template: 'metrics-apm.service_transaction.1m@template', }, - { name: 'metrics-apm.transaction.1m-default', template: 'metrics-apm.transaction.1m' }, - { name: 'traces-apm-default', template: 'traces-apm' }, + { + name: 'metrics-apm.transaction.1m-default', + template: 'metrics-apm.transaction.1m@template', + }, + { name: 'traces-apm-default', template: 'traces-apm@template' }, ]); }); diff --git a/x-pack/test/apm_api_integration/tests/diagnostics/index_templates.spec.ts b/x-pack/test/apm_api_integration/tests/diagnostics/index_templates.spec.ts index 5c94de56abb30..1bbc799b3bf78 100644 --- a/x-pack/test/apm_api_integration/tests/diagnostics/index_templates.spec.ts +++ b/x-pack/test/apm_api_integration/tests/diagnostics/index_templates.spec.ts @@ -20,7 +20,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const start = new Date('2021-01-01T00:00:00.000Z').getTime(); const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; - registry.when('Diagnostics: Index Templates', { config: 'basic', archives: [] }, () => { + registry.when.skip('Diagnostics: Index Templates', { config: 'basic', archives: [] }, () => { describe('When there is no data', () => { before(async () => { // delete APM index templates diff --git a/x-pack/test/apm_api_integration/tests/errors/group_id_samples.spec.ts b/x-pack/test/apm_api_integration/tests/errors/group_id_samples.spec.ts index 004f853b6c56a..ea74f1fa622d8 100644 --- a/x-pack/test/apm_api_integration/tests/errors/group_id_samples.spec.ts +++ b/x-pack/test/apm_api_integration/tests/errors/group_id_samples.spec.ts @@ -76,7 +76,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); // FLAKY: https://github.com/elastic/kibana/issues/177397 - registry.when('when samples data is loaded', { config: 'basic', archives: [] }, () => { + registry.when.skip('when samples data is loaded', { config: 'basic', archives: [] }, () => { const { bananaTransaction } = config; describe('error group id', () => { before(async () => { @@ -105,7 +105,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); // FLAKY: https://github.com/elastic/kibana/issues/177383 - registry.when('when error sample data is loaded', { config: 'basic', archives: [] }, () => { + registry.when.skip('when error sample data is loaded', { config: 'basic', archives: [] }, () => { describe('error sample id', () => { before(async () => { await generateData({ serviceName, start, end, apmSynthtraceEsClient }); diff --git a/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts b/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts index ff985e0af388f..53b305f093ce4 100644 --- a/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts +++ b/x-pack/test/apm_api_integration/tests/errors/top_erroneous_transactions/top_erroneous_transactions.spec.ts @@ -65,7 +65,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); // FLAKY: https://github.com/elastic/kibana/issues/177637 - registry.when('when data is loaded', { config: 'basic', archives: [] }, () => { + registry.when.skip('when data is loaded', { config: 'basic', archives: [] }, () => { const { firstTransaction: { name: firstTransactionName, failureRate: firstTransactionFailureRate }, secondTransaction: { name: secondTransactionName, failureRate: secondTransactionFailureRate }, @@ -89,7 +89,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { erroneousTransactions = response.body; }); - it('displays the correct number of occurrences', () => { + it.skip('displays the correct number of occurrences', () => { const { topErroneousTransactions } = erroneousTransactions; expect(topErroneousTransactions.length).to.be(2); diff --git a/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts b/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts index 8e946e081554f..a6476e76a3918 100644 --- a/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts +++ b/x-pack/test/apm_api_integration/tests/errors/top_errors_for_transaction/top_errors_main_stats.spec.ts @@ -59,7 +59,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); // FLAKY: https://github.com/elastic/kibana/issues/177638 - registry.when('when data is loaded', { config: 'basic', archives: [] }, () => { + registry.when.skip('when data is loaded', { config: 'basic', archives: [] }, () => { describe('top errors for transaction', () => { const { firstTransaction: { name: firstTransactionName, failureRate: firstTransactionFailureRate }, diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index ae5b30b175825..3b332fdba0d09 100644 --- a/x-pack/test/apm_api_integration/tests/index.ts +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -30,7 +30,7 @@ export default function apmApiIntegrationTests({ getService, loadTestFile }: Ftr // Skipping here will skip the entire apm api test suite // Instead skip (flaky) tests individually // Failing: See https://github.com/elastic/kibana/issues/176948 - describe.skip('APM API tests', function () { + describe('APM API tests', function () { const filePattern = getGlobPattern(); const tests = globby.sync(filePattern, { cwd }); diff --git a/x-pack/test/apm_api_integration/tests/mobile/crashes/crash_group_list.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/crashes/crash_group_list.spec.ts index 274199437f188..a36036b3ec8e2 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/crashes/crash_group_list.spec.ts +++ b/x-pack/test/apm_api_integration/tests/mobile/crashes/crash_group_list.spec.ts @@ -54,7 +54,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); // FLAKY: https://github.com/elastic/kibana/issues/177651 - registry.when('when data is loaded', { config: 'basic', archives: [] }, () => { + registry.when.skip('when data is loaded', { config: 'basic', archives: [] }, () => { describe('errors group', () => { const appleTransaction = { name: 'GET /apple 🍎 ', diff --git a/x-pack/test/apm_api_integration/tests/mobile/crashes/distribution.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/crashes/distribution.spec.ts index aad32f3490d2a..2fabce70d2696 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/crashes/distribution.spec.ts +++ b/x-pack/test/apm_api_integration/tests/mobile/crashes/distribution.spec.ts @@ -61,7 +61,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); // FLAKY: https://github.com/elastic/kibana/issues/177652 - registry.when('when data is loaded', { config: 'basic', archives: [] }, () => { + registry.when.skip('when data is loaded', { config: 'basic', archives: [] }, () => { describe('errors distribution', () => { const { appleTransaction, bananaTransaction } = config; before(async () => { diff --git a/x-pack/test/apm_api_integration/tests/mobile/errors/group_id_samples.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/errors/group_id_samples.spec.ts index e3e69a540881c..129cbe2a71809 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/errors/group_id_samples.spec.ts +++ b/x-pack/test/apm_api_integration/tests/mobile/errors/group_id_samples.spec.ts @@ -76,7 +76,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); // FLAKY: https://github.com/elastic/kibana/issues/177654 - registry.when('when samples data is loaded', { config: 'basic', archives: [] }, () => { + registry.when.skip('when samples data is loaded', { config: 'basic', archives: [] }, () => { const { bananaTransaction } = config; describe('error group id', () => { before(async () => { @@ -105,7 +105,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); // FLAKY: https://github.com/elastic/kibana/issues/177665 - registry.when('when error sample data is loaded', { config: 'basic', archives: [] }, () => { + registry.when.skip('when error sample data is loaded', { config: 'basic', archives: [] }, () => { describe('error sample id', () => { before(async () => { await generateData({ serviceName, start, end, apmSynthtraceEsClient }); diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_detailed_statistics_by_field.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_detailed_statistics_by_field.spec.ts index 40bf9729bee6e..a8912989e295b 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_detailed_statistics_by_field.spec.ts +++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_detailed_statistics_by_field.spec.ts @@ -73,7 +73,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); // FLAKY: https://github.com/elastic/kibana/issues/177388 - registry.when( + registry.when.skip( 'Mobile detailed statistics when data is loaded', { config: 'basic', archives: [] }, () => { diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_filters.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_filters.spec.ts index 4286266801609..edebde9f0d439 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_filters.spec.ts +++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_filters.spec.ts @@ -178,7 +178,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); // FLAKY: https://github.com/elastic/kibana/issues/177389 - registry.when('Mobile filters', { config: 'basic', archives: [] }, () => { + registry.when.skip('Mobile filters', { config: 'basic', archives: [] }, () => { before(async () => { await generateData({ apmSynthtraceEsClient, diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_http_requests_timeseries.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_http_requests_timeseries.spec.ts index 4c661c9ae14f6..ccd4ddd23ca53 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_http_requests_timeseries.spec.ts +++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_http_requests_timeseries.spec.ts @@ -47,7 +47,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); } - registry.when( + registry.when.skip( 'Mobile HTTP requests without data loaded', { config: 'basic', archives: [] }, () => { @@ -63,75 +63,81 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); // FLAKY: https://github.com/elastic/kibana/issues/177390 - registry.when('Mobile HTTP requests with data loaded', { config: 'basic', archives: [] }, () => { - before(async () => { - await generateMobileData({ - apmSynthtraceEsClient, - start, - end, + registry.when.skip( + 'Mobile HTTP requests with data loaded', + { config: 'basic', archives: [] }, + () => { + before(async () => { + await generateMobileData({ + apmSynthtraceEsClient, + start, + end, + }); }); - }); - after(() => apmSynthtraceEsClient.clean()); + after(() => apmSynthtraceEsClient.clean()); - describe('when data is loaded', () => { - it('returns timeseries for http requests chart', async () => { - const response = await getHttpRequestsChart({ - serviceName: 'synth-android', - offset: '1d', - }); + describe('when data is loaded', () => { + it('returns timeseries for http requests chart', async () => { + const response = await getHttpRequestsChart({ + serviceName: 'synth-android', + offset: '1d', + }); - expect(response.status).to.be(200); - expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql( - true - ); - expect(response.body.previousPeriod.timeseries[0].y).to.eql(0); - }); + expect(response.status).to.be(200); + expect(response.body.currentPeriod.timeseries.some((item) => item.x && item.y)).to.eql( + true + ); + expect(response.body.previousPeriod.timeseries[0].y).to.eql(0); + }); - it('returns only current period timeseries when offset is not available', async () => { - const response = await getHttpRequestsChart({ serviceName: 'synth-android' }); + it('returns only current period timeseries when offset is not available', async () => { + const response = await getHttpRequestsChart({ serviceName: 'synth-android' }); - expect(response.status).to.be(200); - expect( - response.body.currentPeriod.timeseries.some((item) => item.y === 0 && item.x) - ).to.eql(true); + expect(response.status).to.be(200); + expect( + response.body.currentPeriod.timeseries.some((item) => item.y === 0 && item.x) + ).to.eql(true); - expect(response.body.currentPeriod.timeseries[0].y).to.eql(7); - expect(response.body.previousPeriod.timeseries).to.eql([]); + expect(response.body.currentPeriod.timeseries[0].y).to.eql(7); + expect(response.body.previousPeriod.timeseries).to.eql([]); + }); }); - }); - describe('when filters are applied', () => { - it('returns empty state for filters', async () => { - const response = await getHttpRequestsChart({ - serviceName: 'synth-android', - environment: 'production', - kuery: `app.version:"none"`, + describe('when filters are applied', () => { + it('returns empty state for filters', async () => { + const response = await getHttpRequestsChart({ + serviceName: 'synth-android', + environment: 'production', + kuery: `app.version:"none"`, + }); + + expect(response.status).to.be(200); + expect(response.body.currentPeriod.timeseries.every((item) => item.y === 0)).to.eql(true); + expect(response.body.previousPeriod.timeseries.every((item) => item.y === 0)).to.eql( + true + ); }); - expect(response.status).to.be(200); - expect(response.body.currentPeriod.timeseries.every((item) => item.y === 0)).to.eql(true); - expect(response.body.previousPeriod.timeseries.every((item) => item.y === 0)).to.eql(true); - }); + it('returns the correct values when filter is applied', async () => { + const response = await getHttpRequestsChart({ + serviceName: 'synth-android', + environment: 'production', + kuery: `network.connection.type:"wifi"`, + }); - it('returns the correct values when filter is applied', async () => { - const response = await getHttpRequestsChart({ - serviceName: 'synth-android', - environment: 'production', - kuery: `network.connection.type:"wifi"`, - }); + const ntcCell = await getHttpRequestsChart({ + serviceName: 'synth-android', + environment: 'production', + kuery: `network.connection.type:"cell"`, + }); - const ntcCell = await getHttpRequestsChart({ - serviceName: 'synth-android', - environment: 'production', - kuery: `network.connection.type:"cell"`, + expect(response.status).to.be(200); + expect(ntcCell.status).to.be(200); + expect(response.body.currentPeriod.timeseries[0].y).to.eql(5); + expect(ntcCell.body.currentPeriod.timeseries[0].y).to.eql(2); }); - - expect(response.status).to.be(200); - expect(ntcCell.status).to.be(200); - expect(response.body.currentPeriod.timeseries[0].y).to.eql(5); - expect(ntcCell.body.currentPeriod.timeseries[0].y).to.eql(2); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts index 0acf17308b0d8..ec82de406e0e0 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts +++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts @@ -233,7 +233,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); // FLAKY: https://github.com/elastic/kibana/issues/177396 - registry.when('Location stats', { config: 'basic', archives: [] }, () => { + registry.when.skip('Location stats', { config: 'basic', archives: [] }, () => { before(async () => { await generateData({ apmSynthtraceEsClient, diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_main_statistics_by_field.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_main_statistics_by_field.spec.ts index a3f95eaeb495d..945ed5970e000 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_main_statistics_by_field.spec.ts +++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_main_statistics_by_field.spec.ts @@ -179,7 +179,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); // FLAKY: https://github.com/elastic/kibana/issues/177395 - registry.when('Mobile main statistics', { config: 'basic', archives: [] }, () => { + registry.when.skip('Mobile main statistics', { config: 'basic', archives: [] }, () => { before(async () => { await generateData({ apmSynthtraceEsClient, diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_most_used_chart.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_most_used_chart.spec.ts index 497e6987a3e2f..cde19d07344d6 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_most_used_chart.spec.ts +++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_most_used_chart.spec.ts @@ -65,7 +65,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); // FLAKY: https://github.com/elastic/kibana/issues/177394 - registry.when('Mobile stats', { config: 'basic', archives: [] }, () => { + registry.when.skip('Mobile stats', { config: 'basic', archives: [] }, () => { before(async () => { await generateMobileData({ apmSynthtraceEsClient, diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_sessions_timeseries.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_sessions_timeseries.spec.ts index 99f0f245c8c4c..f7f3092935c31 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_sessions_timeseries.spec.ts +++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_sessions_timeseries.spec.ts @@ -47,7 +47,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); } - registry.when('without data loaded', { config: 'basic', archives: [] }, () => { + registry.when.skip('without data loaded', { config: 'basic', archives: [] }, () => { describe('when no data', () => { it('handles empty state', async () => { const response = await getSessionsChart({ serviceName: 'foo' }); @@ -59,7 +59,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); // FLAKY: https://github.com/elastic/kibana/issues/177393 - registry.when('with data loaded', { config: 'basic', archives: [] }, () => { + registry.when.skip('with data loaded', { config: 'basic', archives: [] }, () => { before(async () => { await generateMobileData({ apmSynthtraceEsClient, diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts index 22b7d0c8b8f65..0b1e71471a2b4 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts +++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts @@ -185,7 +185,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); // FLAKY: https://github.com/elastic/kibana/issues/177392 - registry.when('Mobile stats', { config: 'basic', archives: [] }, () => { + registry.when.skip('Mobile stats', { config: 'basic', archives: [] }, () => { before(async () => { await generateData({ apmSynthtraceEsClient, diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_terms_by_field.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_terms_by_field.spec.ts index d50371423c166..3ccdba0a24236 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_terms_by_field.spec.ts +++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_terms_by_field.spec.ts @@ -186,7 +186,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); // FLAKY: https://github.com/elastic/kibana/issues/177498 - registry.when('Mobile terms', { config: 'basic', archives: [] }, () => { + registry.when.skip('Mobile terms', { config: 'basic', archives: [] }, () => { before(async () => { await generateData({ apmSynthtraceEsClient, From dbd1334480a2545dd9f18f8a0cde1a53630e1db7 Mon Sep 17 00:00:00 2001 From: Drew Tate <drew.tate@elastic.co> Date: Tue, 2 Jul 2024 11:55:51 -0600 Subject: [PATCH 106/128] [ES|QL] capitalize things (#186340) ## Summary https://github.com/elastic/kibana/assets/315764/8f8e618c-22e6-4a33-957e-c9d1664cc000 Close https://github.com/elastic/kibana/issues/184238 --------- Co-authored-by: Stratoula Kalafateli <efstratia.kalafateli@elastic.co> --- .../src/utils/append_to_query.test.ts | 14 +++--- .../src/utils/append_to_query.ts | 4 +- .../src/utils/get_initial_esql_query.test.ts | 2 +- .../src/utils/get_initial_esql_query.ts | 2 +- .../src/autocomplete/autocomplete.test.ts | 44 ++++++++++--------- .../src/autocomplete/factories.ts | 18 ++++---- .../src/definitions/helpers.ts | 14 +++--- .../src/shared/helpers.ts | 5 ++- .../src/validation/validation.ts | 2 +- .../create_filters_from_value_click.test.ts | 2 +- .../apps/discover/esql/_esql_view.ts | 8 ++-- .../discover/group6/_sidebar_field_stats.ts | 14 +++--- .../apps/discover/group7/_new_search.ts | 4 +- .../open_lens_config/create_action_helpers.ts | 2 +- .../logs_explorer/public/hooks/use_esql.tsx | 2 +- .../common/discover/esql/_esql_view.ts | 2 +- 16 files changed, 74 insertions(+), 65 deletions(-) diff --git a/packages/kbn-esql-utils/src/utils/append_to_query.test.ts b/packages/kbn-esql-utils/src/utils/append_to_query.test.ts index 2f3d28c467444..f7c69fbb5c687 100644 --- a/packages/kbn-esql-utils/src/utils/append_to_query.test.ts +++ b/packages/kbn-esql-utils/src/utils/append_to_query.test.ts @@ -31,7 +31,7 @@ describe('appendToQuery', () => { appendWhereClauseToESQLQuery('from logstash-* // meow', 'dest', 'tada!', '+', 'string') ).toBe( `from logstash-* // meow -| where \`dest\`=="tada!"` +| WHERE \`dest\`=="tada!"` ); }); it('appends a filter out where clause in an existing query', () => { @@ -39,7 +39,7 @@ describe('appendToQuery', () => { appendWhereClauseToESQLQuery('from logstash-* // meow', 'dest', 'tada!', '-', 'string') ).toBe( `from logstash-* // meow -| where \`dest\`!="tada!"` +| WHERE \`dest\`!="tada!"` ); }); @@ -48,14 +48,14 @@ describe('appendToQuery', () => { appendWhereClauseToESQLQuery('from logstash-* // meow', 'dest', 'tada!', '-', 'ip') ).toBe( `from logstash-* // meow -| where \`dest\`::string!="tada!"` +| WHERE \`dest\`::string!="tada!"` ); }); it('appends a where clause in an existing query with casting to string when the type is not given', () => { expect(appendWhereClauseToESQLQuery('from logstash-* // meow', 'dest', 'tada!', '-')).toBe( `from logstash-* // meow -| where \`dest\`::string!="tada!"` +| WHERE \`dest\`::string!="tada!"` ); }); @@ -70,7 +70,7 @@ describe('appendToQuery', () => { ) ).toBe( `from logstash-* // meow -| where \`dest\` is not null` +| WHERE \`dest\` is not null` ); }); @@ -85,7 +85,7 @@ describe('appendToQuery', () => { ) ).toBe( `from logstash-* // meow -| where \`dest\` is null` +| WHERE \`dest\` is null` ); }); @@ -100,7 +100,7 @@ describe('appendToQuery', () => { ) ).toBe( `from logstash-* | where country == "GR" -and \`dest\`=="Crete"` +AND \`dest\`=="Crete"` ); }); diff --git a/packages/kbn-esql-utils/src/utils/append_to_query.ts b/packages/kbn-esql-utils/src/utils/append_to_query.ts index d1bf0afa33755..0d8de16f03e79 100644 --- a/packages/kbn-esql-utils/src/utils/append_to_query.ts +++ b/packages/kbn-esql-utils/src/utils/append_to_query.ts @@ -85,9 +85,9 @@ export function appendWhereClauseToESQLQuery( } } // filter does not exist in the where clause - const whereClause = `and ${fieldName}${operator}${filterValue}`; + const whereClause = `AND ${fieldName}${operator}${filterValue}`; return appendToESQLQuery(baseESQLQuery, whereClause); } - const whereClause = `| where ${fieldName}${operator}${filterValue}`; + const whereClause = `| WHERE ${fieldName}${operator}${filterValue}`; return appendToESQLQuery(baseESQLQuery, whereClause); } diff --git a/packages/kbn-esql-utils/src/utils/get_initial_esql_query.test.ts b/packages/kbn-esql-utils/src/utils/get_initial_esql_query.test.ts index bb4fe9e1a15da..45aac1344725d 100644 --- a/packages/kbn-esql-utils/src/utils/get_initial_esql_query.test.ts +++ b/packages/kbn-esql-utils/src/utils/get_initial_esql_query.test.ts @@ -10,6 +10,6 @@ import { getInitialESQLQuery } from './get_initial_esql_query'; describe('getInitialESQLQuery', () => { it('should work correctly', () => { - expect(getInitialESQLQuery('logs*')).toBe('from logs* | limit 10'); + expect(getInitialESQLQuery('logs*')).toBe('FROM logs* | LIMIT 10'); }); }); diff --git a/packages/kbn-esql-utils/src/utils/get_initial_esql_query.ts b/packages/kbn-esql-utils/src/utils/get_initial_esql_query.ts index f2ccad78fa55f..302f3c364f1a6 100644 --- a/packages/kbn-esql-utils/src/utils/get_initial_esql_query.ts +++ b/packages/kbn-esql-utils/src/utils/get_initial_esql_query.ts @@ -11,5 +11,5 @@ * @param indexOrIndexPattern */ export function getInitialESQLQuery(indexOrIndexPattern: string): string { - return `from ${indexOrIndexPattern} | limit 10`; + return `FROM ${indexOrIndexPattern} | LIMIT 10`; } diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts index 8edbcd5472593..6f0562d7f118e 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts @@ -186,9 +186,11 @@ function getFunctionSignaturesByReturnType( .sort(({ name: a }, { name: b }) => a.localeCompare(b)) .map(({ type, name, signatures }) => { if (type === 'builtin') { - return signatures.some(({ params }) => params.length > 1) ? `${name} $0` : name; + return signatures.some(({ params }) => params.length > 1) + ? `${name.toUpperCase()} $0` + : name.toUpperCase(); } - return `${name}($0)`; + return `${name.toUpperCase()}($0)`; }); } @@ -337,31 +339,31 @@ describe('autocomplete', () => { describe('New command', () => { testSuggestions( ' ', - sourceCommands.map((name) => name + ' $0') + sourceCommands.map((name) => name.toUpperCase() + ' $0') ); testSuggestions( 'from a | ', commandDefinitions .filter(({ name }) => !sourceCommands.includes(name)) - .map(({ name }) => name + ' $0') + .map(({ name }) => name.toUpperCase() + ' $0') ); testSuggestions( 'from a [metadata _id] | ', commandDefinitions .filter(({ name }) => !sourceCommands.includes(name)) - .map(({ name }) => name + ' $0') + .map(({ name }) => name.toUpperCase() + ' $0') ); testSuggestions( 'from a | eval var0 = a | ', commandDefinitions .filter(({ name }) => !sourceCommands.includes(name)) - .map(({ name }) => name + ' $0') + .map(({ name }) => name.toUpperCase() + ' $0') ); testSuggestions( 'from a [metadata _id] | eval var0 = a | ', commandDefinitions .filter(({ name }) => !sourceCommands.includes(name)) - .map(({ name }) => name + ' $0') + .map(({ name }) => name.toUpperCase() + ' $0') ); }); @@ -371,11 +373,11 @@ describe('autocomplete', () => { // Monaco will filter further down here testSuggestions( 'f', - sourceCommands.map((name) => name + ' $0') + sourceCommands.map((name) => name.toUpperCase() + ' $0') ); testSuggestions('from ', suggestedIndexes); testSuggestions('from a,', suggestedIndexes); - testSuggestions('from a, b ', ['metadata $0', ',', '|']); + testSuggestions('from a, b ', ['METADATA $0', ',', '|']); testSuggestions('from *,', suggestedIndexes); testSuggestions('from index', suggestedIndexes, 5 /* space before index */); testSuggestions('from a, b [metadata ]', METADATA_FIELDS, ' ]'); @@ -403,14 +405,14 @@ describe('autocomplete', () => { }); describe('show', () => { - testSuggestions('show ', ['info']); + testSuggestions('show ', ['INFO']); for (const fn of ['info']) { testSuggestions(`show ${fn} `, ['|']); } }); describe('meta', () => { - testSuggestions('meta ', ['functions']); + testSuggestions('meta ', ['FUNCTIONS']); for (const fn of ['functions']) { testSuggestions(`meta ${fn} `, ['|']); } @@ -522,8 +524,8 @@ describe('autocomplete', () => { ',' ); - testSuggestions('from index | WHERE stringField not ', ['like $0', 'rlike $0', 'in $0']); - testSuggestions('from index | WHERE stringField NOT ', ['like $0', 'rlike $0', 'in $0']); + testSuggestions('from index | WHERE stringField not ', ['LIKE $0', 'RLIKE $0', 'IN $0']); + testSuggestions('from index | WHERE stringField NOT ', ['LIKE $0', 'RLIKE $0', 'IN $0']); testSuggestions('from index | WHERE not ', [ ...getFieldNamesByType('boolean'), ...getFunctionSignaturesByReturnType('eval', 'boolean', { evalMath: true }), @@ -577,7 +579,7 @@ describe('autocomplete', () => { testSuggestions(`from a | ${subExpression} ${command} stringField `, [constantPattern]); testSuggestions( `from a | ${subExpression} ${command} stringField ${constantPattern} `, - (command === 'dissect' ? ['append_separator = $0'] : []).concat(['|']) + (command === 'dissect' ? ['APPEND_SEPARATOR = $0'] : []).concat(['|']) ); if (command === 'dissect') { testSuggestions( @@ -616,7 +618,7 @@ describe('autocomplete', () => { describe('rename', () => { testSuggestions('from a | rename ', getFieldNamesByType('any')); - testSuggestions('from a | rename stringField ', ['as $0']); + testSuggestions('from a | rename stringField ', ['AS $0']); testSuggestions('from a | rename stringField as ', ['var0']); }); @@ -704,7 +706,7 @@ describe('autocomplete', () => { ], '(' ); - testSuggestions('from a | stats a=min(b) ', ['by $0', ',', '|']); + testSuggestions('from a | stats a=min(b) ', ['BY $0', ',', '|']); testSuggestions('from a | stats a=min(b) by ', [ 'var0 =', ...getFieldNamesByType('any'), @@ -737,7 +739,7 @@ describe('autocomplete', () => { ]); // smoke testing with suggestions not at the end of the string - testSuggestions('from a | stats a = min(b) | sort b', ['by $0', ',', '|'], ') '); + testSuggestions('from a | stats a = min(b) | sort b', ['BY $0', ',', '|'], ') '); testSuggestions( 'from a | stats avg(b) by stringField', [ @@ -854,7 +856,7 @@ describe('autocomplete', () => { testSuggestions(`from a ${prevCommand}| enrich _${mode.toUpperCase()}:`, policyNames, ':'); testSuggestions(`from a ${prevCommand}| enrich _${camelCase(mode)}:`, policyNames, ':'); } - testSuggestions(`from a ${prevCommand}| enrich policy `, ['on $0', 'with $0', '|']); + testSuggestions(`from a ${prevCommand}| enrich policy `, ['ON $0', 'WITH $0', '|']); testSuggestions(`from a ${prevCommand}| enrich policy on `, [ 'stringField', 'numberField', @@ -868,7 +870,7 @@ describe('autocomplete', () => { 'any#Char$Field', 'kubernetes.something.something', ]); - testSuggestions(`from a ${prevCommand}| enrich policy on b `, ['with $0', ',', '|']); + testSuggestions(`from a ${prevCommand}| enrich policy on b `, ['WITH $0', ',', '|']); testSuggestions(`from a ${prevCommand}| enrich policy on b with `, [ 'var0 =', ...getPolicyFields('policy'), @@ -915,8 +917,8 @@ describe('autocomplete', () => { ',', '|', ]); - testSuggestions('from index | EVAL stringField not ', ['like $0', 'rlike $0', 'in $0']); - testSuggestions('from index | EVAL stringField NOT ', ['like $0', 'rlike $0', 'in $0']); + testSuggestions('from index | EVAL stringField not ', ['LIKE $0', 'RLIKE $0', 'IN $0']); + testSuggestions('from index | EVAL stringField NOT ', ['LIKE $0', 'RLIKE $0', 'IN $0']); testSuggestions('from index | EVAL numberField in ', ['( $0 )']); testSuggestions( 'from index | EVAL numberField in ( )', diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts index 1bc93193bd39a..53e65c2f95aba 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts @@ -42,7 +42,7 @@ export function getSuggestionFunctionDefinition(fn: FunctionDefinition): Suggest const fullSignatures = getFunctionSignatures(fn); return { label: fullSignatures[0].declaration, - text: `${fn.name}($0)`, + text: `${fn.name.toUpperCase()}($0)`, asSnippet: true, kind: 'Function', detail: fn.description, @@ -60,7 +60,7 @@ export function getSuggestionBuiltinDefinition(fn: FunctionDefinition): Suggesti const hasArgs = fn.signatures.some(({ params }) => params.length > 1); return { label: fn.name, - text: hasArgs ? `${fn.name} $0` : fn.name, + text: hasArgs ? `${fn.name.toUpperCase()} $0` : fn.name.toUpperCase(), asSnippet: hasArgs, kind: 'Operator', detail: fn.description, @@ -103,10 +103,10 @@ export function getSuggestionCommandDefinition( const commandDefinition = getCommandDefinition(command.name); const commandSignature = getCommandSignature(commandDefinition); return { - label: commandDefinition.name, + label: commandDefinition.name.toUpperCase(), text: commandDefinition.signature.params.length - ? `${commandDefinition.name} $0` - : commandDefinition.name, + ? `${commandDefinition.name.toUpperCase()} $0` + : commandDefinition.name.toUpperCase(), asSnippet: true, kind: 'Method', detail: commandDefinition.description, @@ -247,14 +247,16 @@ export const buildOptionDefinition = ( isAssignType: boolean = false ) => { const completeItem: SuggestionRawDefinition = { - label: option.name, - text: option.name, + label: option.name.toUpperCase(), + text: option.name.toUpperCase(), kind: 'Reference', detail: option.description, sortText: '1', }; if (isAssignType || option.signature.params.length) { - completeItem.text = isAssignType ? `${option.name} = $0` : `${option.name} $0`; + completeItem.text = isAssignType + ? `${option.name.toUpperCase()} = $0` + : `${option.name.toUpperCase()} $0`; completeItem.asSnippet = true; completeItem.command = TRIGGER_SUGGESTION_COMMAND; } diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/helpers.ts index 7d70d1acc9631..0ccb7855b284b 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/helpers.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/helpers.ts @@ -56,12 +56,16 @@ export function getCommandSignature( { withTypes }: { withTypes: boolean } = { withTypes: true } ) { return { - declaration: `${name} ${printCommandArguments(signature, withTypes)} ${options.map( + declaration: `${name.toUpperCase()} ${printCommandArguments( + signature, + withTypes + )} ${options.map( (option) => - `${option.wrapped ? option.wrapped[0] : ''}${option.name} ${printCommandArguments( - option.signature, - withTypes - )}${option.wrapped ? option.wrapped[1] : ''}` + `${ + option.wrapped ? option.wrapped[0] : '' + }${option.name.toUpperCase()} ${printCommandArguments(option.signature, withTypes)}${ + option.wrapped ? option.wrapped[1] : '' + }` )}`, examples, }; diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts index 4a89a6b72d166..effd6b1b16ddd 100644 --- a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts +++ b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts @@ -127,7 +127,7 @@ export function isComma(char: string) { } export function isSourceCommand({ label }: { label: string }) { - return ['from', 'row', 'show'].includes(String(label)); + return ['FROM', 'ROW', 'SHOW'].includes(label); } let fnLookups: Map<string, FunctionDefinition> | undefined; @@ -290,12 +290,13 @@ export function areFieldAndVariableTypesCompatible( return fieldType === variableType; } -export function printFunctionSignature(arg: ESQLFunction): string { +export function printFunctionSignature(arg: ESQLFunction, useCaps = true): string { const fnDef = getFunctionDefinition(arg.name); if (fnDef) { const signature = getFunctionSignatures( { ...fnDef, + name: useCaps ? fnDef.name.toUpperCase() : fnDef.name, signatures: [ { ...fnDef?.signatures[0], diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.ts index fcd17d5451825..e3c94aee3f482 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.ts @@ -220,7 +220,7 @@ function validateNestedFunctionArg( values: { name: astFunction.name, argType: parameterDefinition.type, - value: printFunctionSignature(actualArg) || actualArg.name, + value: printFunctionSignature(actualArg, false) || actualArg.name, givenType: argFn.signatures[0].returnType, }, locations: actualArg.location, diff --git a/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts index fab4d18af400b..16c2983c7a9cf 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts @@ -169,7 +169,7 @@ describe('createFiltersFromClickEvent', () => { }); expect(queryString).toEqual(`from meow -| where \`columnA\`=="2048"`); +| WHERE \`columnA\`=="2048"`); }); }); }); diff --git a/test/functional/apps/discover/esql/_esql_view.ts b/test/functional/apps/discover/esql/_esql_view.ts index 9f60f4991ab4c..cea3b6ecce044 100644 --- a/test/functional/apps/discover/esql/_esql_view.ts +++ b/test/functional/apps/discover/esql/_esql_view.ts @@ -303,7 +303,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const historyItems = await esql.getHistoryItems(); log.debug(historyItems); const queryAdded = historyItems.some((item) => { - return item[1] === 'from logstash-* | limit 10'; + return item[1] === 'FROM logstash-* | LIMIT 10'; }); expect(queryAdded).to.be(true); @@ -564,7 +564,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const editorValue = await monacoEditor.getCodeEditorValue(); expect(editorValue).to.eql( - `from logstash-* | sort @timestamp desc | limit 10000 | stats countB = count(bytes) by geo.dest | sort countB\n| where \`geo.dest\`=="BT"` + `from logstash-* | sort @timestamp desc | limit 10000 | stats countB = count(bytes) by geo.dest | sort countB\n| WHERE \`geo.dest\`=="BT"` ); // negate @@ -575,7 +575,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const newValue = await monacoEditor.getCodeEditorValue(); expect(newValue).to.eql( - `from logstash-* | sort @timestamp desc | limit 10000 | stats countB = count(bytes) by geo.dest | sort countB\n| where \`geo.dest\`!="BT"` + `from logstash-* | sort @timestamp desc | limit 10000 | stats countB = count(bytes) by geo.dest | sort countB\n| WHERE \`geo.dest\`!="BT"` ); }); @@ -597,7 +597,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const editorValue = await monacoEditor.getCodeEditorValue(); expect(editorValue).to.eql( - `from logstash-* | sort @timestamp desc | limit 10000 | stats countB = count(bytes) by geo.dest | sort countB | where countB > 0\nand \`geo.dest\`=="BT"` + `from logstash-* | sort @timestamp desc | limit 10000 | stats countB = count(bytes) by geo.dest | sort countB | where countB > 0\nAND \`geo.dest\`=="BT"` ); }); }); diff --git a/test/functional/apps/discover/group6/_sidebar_field_stats.ts b/test/functional/apps/discover/group6/_sidebar_field_stats.ts index cbeb128036ab6..dd148e43d2d6e 100644 --- a/test/functional/apps/discover/group6/_sidebar_field_stats.ts +++ b/test/functional/apps/discover/group6/_sidebar_field_stats.ts @@ -172,7 +172,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('TextBasedLangEditor-expand'); const editorValue = await monacoEditor.getCodeEditorValue(); expect(editorValue).to.eql( - `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| where \`bytes\`==0` + `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`bytes\`==0` ); await PageObjects.unifiedFieldList.closeFieldPopover(); }); @@ -193,7 +193,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('TextBasedLangEditor-expand'); const editorValue = await monacoEditor.getCodeEditorValue(); expect(editorValue).to.eql( - `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| where \`extension.raw\`=="css"` + `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`extension.raw\`=="css"` ); await PageObjects.unifiedFieldList.closeFieldPopover(); @@ -215,7 +215,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('TextBasedLangEditor-expand'); const editorValue = await monacoEditor.getCodeEditorValue(); expect(editorValue).to.eql( - `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| where \`clientip\`::string=="216.126.255.31"` + `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`clientip\`::string=="216.126.255.31"` ); await PageObjects.unifiedFieldList.closeFieldPopover(); @@ -241,7 +241,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('TextBasedLangEditor-expand'); const editorValue = await monacoEditor.getCodeEditorValue(); expect(editorValue).to.eql( - `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| where \`@timestamp\` is not null` + `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`@timestamp\` is not null` ); await testSubjects.missingOrFail('dscFieldStats-statsFooter'); await PageObjects.unifiedFieldList.closeFieldPopover(); @@ -277,7 +277,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('TextBasedLangEditor-expand'); const editorValue = await monacoEditor.getCodeEditorValue(); expect(editorValue).to.eql( - `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| where \`extension\`=="css"` + `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`extension\`=="css"` ); await PageObjects.unifiedFieldList.closeFieldPopover(); @@ -317,7 +317,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('TextBasedLangEditor-expand'); const editorValue = await monacoEditor.getCodeEditorValue(); expect(editorValue).to.eql( - `from logstash-* | sort @timestamp desc | limit 50 | stats avg(bytes) by geo.dest | limit 3\n| where \`avg(bytes)\`==5453` + `from logstash-* | sort @timestamp desc | limit 50 | stats avg(bytes) by geo.dest | limit 3\n| WHERE \`avg(bytes)\`==5453` ); await PageObjects.unifiedFieldList.closeFieldPopover(); @@ -345,7 +345,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.unifiedFieldList.clickFieldListMinusFilter('enabled', 'true'); await testSubjects.click('TextBasedLangEditor-expand'); const editorValue = await monacoEditor.getCodeEditorValue(); - expect(editorValue).to.eql(`row enabled = true\n| where \`enabled\`!=true`); + expect(editorValue).to.eql(`row enabled = true\n| WHERE \`enabled\`!=true`); await PageObjects.unifiedFieldList.closeFieldPopover(); }); }); diff --git a/test/functional/apps/discover/group7/_new_search.ts b/test/functional/apps/discover/group7/_new_search.ts index 265602db217e2..14632d6e2618b 100644 --- a/test/functional/apps/discover/group7/_new_search.ts +++ b/test/functional/apps/discover/group7/_new_search.ts @@ -105,7 +105,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.clickNewSearchButton(); await PageObjects.discover.waitUntilSearchingHasFinished(); - expect(await monacoEditor.getCodeEditorValue()).to.be('from logstash-* | limit 10'); + expect(await monacoEditor.getCodeEditorValue()).to.be('FROM logstash-* | LIMIT 10'); expect(await PageObjects.discover.getVisContextSuggestionType()).to.be('histogramForESQL'); expect(await PageObjects.discover.getHitCount()).to.be('10'); }); @@ -126,7 +126,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.clickNewSearchButton(); await PageObjects.discover.waitUntilSearchingHasFinished(); - expect(await monacoEditor.getCodeEditorValue()).to.be('from logstash-* | limit 10'); + expect(await monacoEditor.getCodeEditorValue()).to.be('FROM logstash-* | LIMIT 10'); expect(await PageObjects.discover.getHitCount()).to.be('10'); }); }); diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts index 387349039fed0..d11a32bbd5e06 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts @@ -63,7 +63,7 @@ export async function executeCreateAction({ const defaultIndex = dataView.getIndexPattern(); const defaultEsqlQuery = { - esql: `from ${defaultIndex} | limit 10`, + esql: `FROM ${defaultIndex} | LIMIT 10`, }; // For the suggestions api we need only the columns diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_esql.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_esql.tsx index e26474b6165e6..65140b4c1e4f2 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_esql.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_esql.tsx @@ -35,7 +35,7 @@ export const useEsql = ({ dataSourceSelection }: EsqlContextDeps): UseEsqlResult const discoverLinkParams = { query: { - esql: `from ${esqlPattern} | limit 10`, + esql: `FROM ${esqlPattern} | LIMIT 10`, }, }; diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts b/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts index 8e7b921d9655d..600ce61167c74 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts @@ -308,7 +308,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const historyItems = await esql.getHistoryItems(); log.debug(historyItems); const queryAdded = historyItems.some((item) => { - return item[1] === 'from logstash-* | limit 10'; + return item[1] === 'FROM logstash-* | LIMIT 10'; }); expect(queryAdded).to.be(true); From 75874ca942c2c5e57411a754aa153ee1b3f12fb4 Mon Sep 17 00:00:00 2001 From: Sandra G <neptunian@users.noreply.github.com> Date: Tue, 2 Jul 2024 14:11:56 -0400 Subject: [PATCH 107/128] [APM] limit service map scripted metric agg based on shard count (#186417) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary https://github.com/elastic/kibana/issues/179229 This PR addresses the need to limit the amount of data that the scripted metric aggregation in the service map processes in one request which can lead to timeouts and OOMs, often resulting in the user seeing [parent circuit breaker](https://www.elastic.co/guide/en/elasticsearch/reference/current/circuit-breaker.html#parent-circuit-breaker) errors and no service map visualization. This query can fire up to 20 times max depending on how many trace ids are fetched in subsequent query, contributing more to exceeding the total allowable memory. These changes will not remove the possibility of OOMs or circuit breaker errors. It doesn't control for multiple users or other processes happening in kibana, rather we are removing the current state of querying for an unknown number of documents by providing a hard limit and a way to easily tweak that limit. ## Changes - Make get_service_paths_from_trace_ids "shard aware" by adding an initial query, `get_trace_ids_shard_data` without the aggregations and only the trace id filter and other filters in order to see how many shards were searched - Use a baseline of 2_576_980_377 bytes max from new config `serverlessServiceMapMaxAvailableBytes`, for all get_service_paths_from_trace_ids queries when hitting the `/internal/apm/service-map` - Calculate how many docs we should retrieve per shard and set that to `terminateAfter` and also as part of the map phase to ensure we never send more than this number to reduce - Calculation is: ((serverlessServiceMapMaxAvailableBytes / average document size) / totalRequests) / numberOfShards Eg: 2_576_980_377 / 495 avg doc size = 5,206,020 total docs 5,206,020 total docs / 10 requests = 520,602 docs per query 520,602 docs per query / 3 shards = **173,534 docs per shard** Since 173,534 is greater than the default setting `serviceMapTerminateAfter`, docs per shard is 100k - Ensure that `map_script` phase won't process duplicate events - Refactor the `processAndReturnEvent` function to replace recursion with a loop to mitigate risks of stack overflow and excessive memory consumption when processing deep trees ## Testing ### Testing that the scripted metric agg query does not exceed the request circuit breaker - start elasticsearch with default settings - on `main`, without these changes, update the request circuit breaker limit to be 2mb: ``` PUT /_cluster/settings { "persistent": { "indices.breaker.request.limit": "2mb" } } ``` - run synthtrace `node scripts/synthtrace.js service_map_oom --from=now-15m --to=now --clean` - Go to the service map, and you should see this error: <img width="305" alt="Screenshot 2024-06-20 at 2 41 18 PM" src="https://github.com/elastic/kibana/assets/1676003/517709e5-f5c0-46bf-a06f-5817458fe292"> - checkout this PR - set the apm kibana setting to 2mb(binary): `xpack.apm.serverlessServiceMapMaxAvailableBytes: 2097152`. this represents the available space for the [request circuit breaker](https://www.elastic.co/guide/en/elasticsearch/reference/current/circuit-breaker.html#request-circuit-breaker), since we aren't grabbing that dynamically. - navigate to the service map and you should not get this error and the service map should appear --------- Co-authored-by: Carlos Crespo <carloshenrique.leonelcrespo@elastic.co> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../apm/server/index.ts | 1 + .../calculate_docs_per_shard.test.ts | 31 ++ .../service_map/calculate_docs_per_shard.ts | 34 ++ .../fetch_service_paths_from_trace_ids.ts | 377 +++++++++++------- .../routes/service_map/get_service_map.ts | 2 + .../get_service_map_from_trace_ids.ts | 6 + 6 files changed, 314 insertions(+), 137 deletions(-) create mode 100644 x-pack/plugins/observability_solution/apm/server/routes/service_map/calculate_docs_per_shard.test.ts create mode 100644 x-pack/plugins/observability_solution/apm/server/routes/service_map/calculate_docs_per_shard.ts diff --git a/x-pack/plugins/observability_solution/apm/server/index.ts b/x-pack/plugins/observability_solution/apm/server/index.ts index 44d447ce4c110..f3ebcec582a46 100644 --- a/x-pack/plugins/observability_solution/apm/server/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/index.ts @@ -26,6 +26,7 @@ const configSchema = schema.object({ serviceMapFingerprintGlobalBucketSize: schema.number({ defaultValue: 1000, }), + serviceMapMaxAllowableBytes: schema.number({ defaultValue: 2_576_980_377 }), // 2.4GB serviceMapTraceIdBucketSize: schema.number({ defaultValue: 65 }), serviceMapTraceIdGlobalBucketSize: schema.number({ defaultValue: 6 }), serviceMapMaxTracesPerRequest: schema.number({ defaultValue: 50 }), diff --git a/x-pack/plugins/observability_solution/apm/server/routes/service_map/calculate_docs_per_shard.test.ts b/x-pack/plugins/observability_solution/apm/server/routes/service_map/calculate_docs_per_shard.test.ts new file mode 100644 index 0000000000000..cf32db83f2dac --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/server/routes/service_map/calculate_docs_per_shard.test.ts @@ -0,0 +1,31 @@ +/* + * 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 { calculateDocsPerShard } from './calculate_docs_per_shard'; + +describe('calculateDocsPerShard', () => { + it('calculates correct docs per shard', () => { + expect( + calculateDocsPerShard({ + serviceMapMaxAllowableBytes: 2_576_980_377, + avgDocSizeInBytes: 495, + totalShards: 3, + numOfRequests: 10, + }) + ).toBe(173534); + }); + it('handles zeros', () => { + expect(() => + calculateDocsPerShard({ + serviceMapMaxAllowableBytes: 0, + avgDocSizeInBytes: 0, + totalShards: 0, + numOfRequests: 0, + }) + ).toThrow('all parameters must be > 0'); + }); +}); diff --git a/x-pack/plugins/observability_solution/apm/server/routes/service_map/calculate_docs_per_shard.ts b/x-pack/plugins/observability_solution/apm/server/routes/service_map/calculate_docs_per_shard.ts new file mode 100644 index 0000000000000..de9145d7542f6 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/server/routes/service_map/calculate_docs_per_shard.ts @@ -0,0 +1,34 @@ +/* + * 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. + */ + +interface Params { + serviceMapMaxAllowableBytes: number; + avgDocSizeInBytes: number; + totalShards: number; + numOfRequests: number; +} + +export const calculateDocsPerShard = ({ + serviceMapMaxAllowableBytes, + avgDocSizeInBytes, + totalShards, + numOfRequests, +}: Params): number => { + if ( + serviceMapMaxAllowableBytes <= 0 || + avgDocSizeInBytes <= 0 || + totalShards <= 0 || + numOfRequests <= 0 + ) { + throw new Error('all parameters must be > 0'); + } + const bytesPerRequest = Math.floor(serviceMapMaxAllowableBytes / numOfRequests); + const totalNumDocsAllowed = Math.floor(bytesPerRequest / avgDocSizeInBytes); + const numDocsPerShardAllowed = Math.floor(totalNumDocsAllowed / totalShards); + + return numDocsPerShardAllowed; +}; diff --git a/x-pack/plugins/observability_solution/apm/server/routes/service_map/fetch_service_paths_from_trace_ids.ts b/x-pack/plugins/observability_solution/apm/server/routes/service_map/fetch_service_paths_from_trace_ids.ts index 5467606954844..70d51a56c6177 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/service_map/fetch_service_paths_from_trace_ids.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/service_map/fetch_service_paths_from_trace_ids.ts @@ -7,13 +7,38 @@ import { rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { TRACE_ID } from '../../../common/es_fields/apm'; +import { + AGENT_NAME, + PARENT_ID, + PROCESSOR_EVENT, + SERVICE_ENVIRONMENT, + SERVICE_NAME, + SPAN_DESTINATION_SERVICE_RESOURCE, + SPAN_SUBTYPE, + SPAN_TYPE, + TRACE_ID, +} from '../../../common/es_fields/apm'; import { ConnectionNode, ExternalConnectionNode, ServiceConnectionNode, } from '../../../common/service_map'; import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; +import { calculateDocsPerShard } from './calculate_docs_per_shard'; + +const SCRIPTED_METRICS_FIELDS_TO_COPY = [ + PARENT_ID, + SERVICE_NAME, + SERVICE_ENVIRONMENT, + SPAN_DESTINATION_SERVICE_RESOURCE, + TRACE_ID, + PROCESSOR_EVENT, + SPAN_TYPE, + SPAN_SUBTYPE, + AGENT_NAME, +]; + +const AVG_BYTES_PER_FIELD = 55; export async function fetchServicePathsFromTraceIds({ apmEventClient, @@ -21,12 +46,16 @@ export async function fetchServicePathsFromTraceIds({ start, end, terminateAfter, + serviceMapMaxAllowableBytes, + numOfRequests, }: { apmEventClient: APMEventClient; traceIds: string[]; start: number; end: number; terminateAfter: number; + serviceMapMaxAllowableBytes: number; + numOfRequests: number; }) { // make sure there's a range so ES can skip shards const dayInMs = 24 * 60 * 60 * 1000; @@ -37,8 +66,8 @@ export async function fetchServicePathsFromTraceIds({ apm: { events: [ProcessorEvent.span, ProcessorEvent.transaction], }, - terminate_after: terminateAfter, body: { + terminate_after: terminateAfter, track_total_hits: false, size: 0, query: { @@ -53,178 +82,252 @@ export async function fetchServicePathsFromTraceIds({ ], }, }, - aggs: { - service_map: { - scripted_metric: { - init_script: { - lang: 'painless', - source: `state.eventsById = new HashMap(); - - String[] fieldsToCopy = new String[] { - 'parent.id', - 'service.name', - 'service.environment', - 'span.destination.service.resource', - 'trace.id', - 'processor.event', - 'span.type', - 'span.subtype', - 'agent.name' - }; - state.fieldsToCopy = fieldsToCopy;`, - }, - map_script: { - lang: 'painless', - source: `def id; - id = $('span.id', null); - if (id == null) { - id = $('transaction.id', null); - } - - def copy = new HashMap(); - copy.id = id; - - for(key in state.fieldsToCopy) { - def value = $(key, null); - if (value != null) { - copy[key] = value; - } + }, + }; + // fetch without aggs to get shard count, first + const serviceMapQueryDataResponse = await apmEventClient.search( + 'get_trace_ids_shard_data', + serviceMapParams + ); + /* + * Calculate how many docs we can fetch per shard. + * Used in both terminate_after and tracking in the map script of the scripted_metric agg + * to ensure we don't fetch more than we can handle. + * + * 1. Use serviceMapMaxAllowableBytes setting, which represents our baseline request circuit breaker limit. + * 2. Divide by numOfRequests we fire off simultaneously to calculate bytesPerRequest. + * 3. Divide bytesPerRequest by the average doc size to get totalNumDocsAllowed. + * 4. Divide totalNumDocsAllowed by totalShards to get numDocsPerShardAllowed. + * 5. Use the lesser of numDocsPerShardAllowed or terminateAfter. + */ + + const avgDocSizeInBytes = SCRIPTED_METRICS_FIELDS_TO_COPY.length * AVG_BYTES_PER_FIELD; // estimated doc size in bytes + const totalShards = serviceMapQueryDataResponse._shards.total; + + const calculatedDocs = calculateDocsPerShard({ + serviceMapMaxAllowableBytes, + avgDocSizeInBytes, + totalShards, + numOfRequests, + }); + + const numDocsPerShardAllowed = calculatedDocs > terminateAfter ? terminateAfter : calculatedDocs; + + const serviceMapAggs = { + service_map: { + scripted_metric: { + params: { + limit: numDocsPerShardAllowed, + fieldsToCopy: SCRIPTED_METRICS_FIELDS_TO_COPY, + }, + init_script: { + lang: 'painless', + source: ` + state.docCount = 0; + state.limit = params.limit; + state.eventsById = new HashMap(); + state.fieldsToCopy = params.fieldsToCopy;`, + }, + map_script: { + lang: 'painless', + source: ` + if (state.docCount >= state.limit) { + // Stop processing if the document limit is reached + return; + } + + def id = $('span.id', null); + if (id == null) { + id = $('transaction.id', null); + } + + // Ensure same event isn't processed twice + if (id != null && !state.eventsById.containsKey(id)) { + def copy = new HashMap(); + copy.id = id; + + for(key in state.fieldsToCopy) { + def value = $(key, null); + if (value != null) { + copy[key] = value; } - - state.eventsById[id] = copy`, - }, - combine_script: { - lang: 'painless', - source: `return state.eventsById;`, - }, - reduce_script: { - lang: 'painless', - source: ` - def getDestination ( def event ) { - def destination = new HashMap(); - destination['span.destination.service.resource'] = event['span.destination.service.resource']; - destination['span.type'] = event['span.type']; - destination['span.subtype'] = event['span.subtype']; - return destination; } - def processAndReturnEvent(def context, def eventId) { - if (context.processedEvents[eventId] != null) { - return context.processedEvents[eventId]; - } - - def event = context.eventsById[eventId]; - - if (event == null) { - return null; + state.eventsById[id] = copy; + state.docCount++; + } + `, + }, + combine_script: { + lang: 'painless', + source: `return state;`, + }, + reduce_script: { + lang: 'painless', + source: ` + def getDestination(def event) { + def destination = new HashMap(); + destination['span.destination.service.resource'] = event['span.destination.service.resource']; + destination['span.type'] = event['span.type']; + destination['span.subtype'] = event['span.subtype']; + return destination; + } + + def processAndReturnEvent(def context, def eventId) { + def stack = new Stack(); + def reprocessQueue = new LinkedList(); + + // Avoid reprocessing the same event + def visited = new HashSet(); + + stack.push(eventId); + + while (!stack.isEmpty()) { + def currentEventId = stack.pop(); + def event = context.eventsById.get(currentEventId); + + if (event == null || context.processedEvents.get(currentEventId) != null) { + continue; } + visited.add(currentEventId); def service = new HashMap(); service['service.name'] = event['service.name']; service['service.environment'] = event['service.environment']; service['agent.name'] = event['agent.name']; - + def basePath = new ArrayList(); - def parentId = event['parent.id']; - def parent; - - if (parentId != null && parentId != event['id']) { - parent = processAndReturnEvent(context, parentId); - if (parent != null) { - /* copy the path from the parent */ - basePath.addAll(parent.path); - /* flag parent path for removal, as it has children */ - context.locationsToRemove.add(parent.path); - - /* if the parent has 'span.destination.service.resource' set, and the service is different, - we've discovered a service */ - - if (parent['span.destination.service.resource'] != null - && parent['span.destination.service.resource'] != "" - && (parent['service.name'] != event['service.name'] - || parent['service.environment'] != event['service.environment'] - ) - ) { - def parentDestination = getDestination(parent); - context.externalToServiceMap.put(parentDestination, service); + + if (parentId != null && !parentId.equals(currentEventId)) { + def parent = context.processedEvents.get(parentId); + + if (parent == null) { + + // Only adds the parentId to the stack if it hasn't been visited to prevent infinite loop scenarios + // if the parent is null, it means it hasn't been processed yet or it could also mean that the current event + // doesn't have a parent, in which case we should skip it + if (!visited.contains(parentId)) { + stack.push(parentId); + // Add currentEventId to be reprocessed once its parent is processed + reprocessQueue.add(currentEventId); } + + + continue; } - } + // copy the path from the parent + basePath.addAll(parent.path); + // flag parent path for removal, as it has children + context.locationsToRemove.add(parent.path); + + // if the parent has 'span.destination.service.resource' set, and the service is different, we've discovered a service + if (parent['span.destination.service.resource'] != null + && !parent['span.destination.service.resource'].equals("") + && (!parent['service.name'].equals(event['service.name']) + || !parent['service.environment'].equals(event['service.environment']) + ) + ) { + def parentDestination = getDestination(parent); + context.externalToServiceMap.put(parentDestination, service); + } + } + def lastLocation = basePath.size() > 0 ? basePath[basePath.size() - 1] : null; - def currentLocation = service; - - /* only add the current location to the path if it's different from the last one*/ + + // only add the current location to the path if it's different from the last one if (lastLocation == null || !lastLocation.equals(currentLocation)) { basePath.add(currentLocation); } - - /* if there is an outgoing span, create a new path */ + + // if there is an outgoing span, create a new path if (event['span.destination.service.resource'] != null - && event['span.destination.service.resource'] != '') { + && !event['span.destination.service.resource'].equals("")) { + def outgoingLocation = getDestination(event); def outgoingPath = new ArrayList(basePath); outgoingPath.add(outgoingLocation); context.paths.add(outgoingPath); } - + event.path = basePath; + context.processedEvents[currentEventId] = event; - context.processedEvents[eventId] = event; - return event; - } - - def context = new HashMap(); - - context.processedEvents = new HashMap(); - context.eventsById = new HashMap(); - - context.paths = new HashSet(); - context.externalToServiceMap = new HashMap(); - context.locationsToRemove = new HashSet(); - - for (state in states) { - context.eventsById.putAll(state); - } - - for (entry in context.eventsById.entrySet()) { - processAndReturnEvent(context, entry.getKey()); - } - - def paths = new HashSet(); - - for(foundPath in context.paths) { - if (!context.locationsToRemove.contains(foundPath)) { - paths.add(foundPath); + // reprocess events which were waiting for their parents to be processed + while (!reprocessQueue.isEmpty()) { + stack.push(reprocessQueue.remove()); } } - def response = new HashMap(); - response.paths = paths; - - def discoveredServices = new HashSet(); - - for(entry in context.externalToServiceMap.entrySet()) { - def map = new HashMap(); - map.from = entry.getKey(); - map.to = entry.getValue(); - discoveredServices.add(map); + return null; + } + + def context = new HashMap(); + + context.processedEvents = new HashMap(); + context.eventsById = new HashMap(); + context.paths = new HashSet(); + context.externalToServiceMap = new HashMap(); + context.locationsToRemove = new HashSet(); + + for (state in states) { + context.eventsById.putAll(state.eventsById); + state.eventsById.clear(); + } + + states.clear(); + + for (entry in context.eventsById.entrySet()) { + processAndReturnEvent(context, entry.getKey()); + } + + context.processedEvents.clear(); + context.eventsById.clear(); + + def response = new HashMap(); + response.paths = new HashSet(); + response.discoveredServices = new HashSet(); + + for (foundPath in context.paths) { + if (!context.locationsToRemove.contains(foundPath)) { + response.paths.add(foundPath); } - response.discoveredServices = discoveredServices; - - return response;`, - }, - }, - } as const, + } + + context.locationsToRemove.clear(); + context.paths.clear(); + + for (entry in context.externalToServiceMap.entrySet()) { + def map = new HashMap(); + map.from = entry.getKey(); + map.to = entry.getValue(); + response.discoveredServices.add(map); + } + + context.externalToServiceMap.clear(); + + return response; + `, + }, }, + } as const, + }; + + const serviceMapParamsWithAggs = { + ...serviceMapParams, + body: { + ...serviceMapParams.body, + size: 1, + terminate_after: numDocsPerShardAllowed, + aggs: serviceMapAggs, }, }; const serviceMapFromTraceIdsScriptResponse = await apmEventClient.search( 'get_service_paths_from_trace_ids', - serviceMapParams + serviceMapParamsWithAggs ); return serviceMapFromTraceIdsScriptResponse as { diff --git a/x-pack/plugins/observability_solution/apm/server/routes/service_map/get_service_map.ts b/x-pack/plugins/observability_solution/apm/server/routes/service_map/get_service_map.ts index 61fc849996a6e..69a000f4c2a8f 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/service_map/get_service_map.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/service_map/get_service_map.ts @@ -82,6 +82,8 @@ async function getConnectionData({ start, end, terminateAfter: config.serviceMapTerminateAfter, + serviceMapMaxAllowableBytes: config.serviceMapMaxAllowableBytes, + numOfRequests: chunks.length, logger, }) ) diff --git a/x-pack/plugins/observability_solution/apm/server/routes/service_map/get_service_map_from_trace_ids.ts b/x-pack/plugins/observability_solution/apm/server/routes/service_map/get_service_map_from_trace_ids.ts index 1b2cc6070d87f..d1bec0076d8f2 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/service_map/get_service_map_from_trace_ids.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/service_map/get_service_map_from_trace_ids.ts @@ -46,6 +46,8 @@ export async function getServiceMapFromTraceIds({ start, end, terminateAfter, + serviceMapMaxAllowableBytes, + numOfRequests, logger, }: { apmEventClient: APMEventClient; @@ -53,6 +55,8 @@ export async function getServiceMapFromTraceIds({ start: number; end: number; terminateAfter: number; + serviceMapMaxAllowableBytes: number; + numOfRequests: number; logger: Logger; }) { const serviceMapFromTraceIdsScriptResponse = await fetchServicePathsFromTraceIds({ @@ -61,6 +65,8 @@ export async function getServiceMapFromTraceIds({ start, end, terminateAfter, + serviceMapMaxAllowableBytes, + numOfRequests, }); logger.debug('Received scripted metric agg response'); From 5cb60aa23fc6319eae83c5d8724a5701e6b34865 Mon Sep 17 00:00:00 2001 From: Gabriel Landau <42078554+gabriellandau@users.noreply.github.com> Date: Tue, 2 Jul 2024 14:28:32 -0400 Subject: [PATCH 108/128] Defend Advanced Policy Options for Registry Event Filtering Enforcement (#186564) ## Summary Adds a Defend Advanced Policy option to allow 8.15.0 users to opt out of Registry Event Filtering. ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../pages/policy/models/advanced_policy_schema.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts index 05ea215f65c50..b6333f949c761 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts @@ -1908,4 +1908,15 @@ export const AdvancedPolicySchema: AdvancedPolicySchemaType[] = [ } ), }, + { + key: 'windows.advanced.events.registry.enforce_registry_filters', + first_supported_version: '8.15', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.events.registry.enforce_registry_filters', + { + defaultMessage: + 'Reduce data volume by filtering out registry events which are not relevant to behavioral protections. Default: true', + } + ), + }, ]; From 4007283a84fdd2f9accd374af614c4b388bb71cb Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin <aleh.zasypkin@elastic.co> Date: Tue, 2 Jul 2024 22:26:25 +0300 Subject: [PATCH 109/128] fix(dev, serverless): do not inject mock SAML IdP configuration if conflicting configuration is provided via CLI arguments (#187337) ## Summary Our functional test server provides Kibana configuration via CLI arguments that the code configuring the mock SAML IdP realm in dev mode didn't account for. This means that when we run the test server locally, both FTR and Kibana try to configure the mock SAML IdP, which crashes the local Kibana. This issue only affects those who run functional tests locally and doesn't impact CI, where we use the built version of Kibana to run tests. This built version doesn't include the mock SAML IdP, delegating the mock SAML IdP configuration solely to FTR. This PR updates the code that attempts to automatically configure the mock SAML IdP in dev mode to check the configuration from both config files and CLI arguments to determine whether automatic configuration is possible. --- src/cli/serve/serve.js | 47 ++++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js index b29e054b7ed95..bb2d6f6684683 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -118,7 +118,11 @@ export function applyConfigOverrides(rawConfig, opts, extraCliOptions, keystoreC if (opts.dev) { if (opts.serverless) { setServerlessKibanaDevServiceAccountIfPossible(get, set, opts); - isServerlessSamlSupported = tryConfigureServerlessSamlProvider(rawConfig, opts); + isServerlessSamlSupported = tryConfigureServerlessSamlProvider( + rawConfig, + opts, + extraCliOptions + ); } if (!has('elasticsearch.serviceAccountToken') && opts.devCredentials !== false) { @@ -342,9 +346,10 @@ function mergeAndReplaceArrays(objValue, srcValue) { * Tries to configure SAML provider in serverless mode and applies the necessary configuration. * @param rawConfig Full configuration object. * @param opts CLI options. + * @param extraCliOptions Extra CLI options. * @returns {boolean} True if SAML provider was successfully configured. */ -function tryConfigureServerlessSamlProvider(rawConfig, opts) { +function tryConfigureServerlessSamlProvider(rawConfig, opts, extraCliOptions) { if (!MOCK_IDP_PLUGIN_SUPPORTED || opts.ssl === false) { return false; } @@ -353,22 +358,32 @@ function tryConfigureServerlessSamlProvider(rawConfig, opts) { // eslint-disable-next-line import/no-dynamic-require const { MOCK_IDP_REALM_NAME } = require(MOCK_IDP_PLUGIN_PATH); - // Check if there are any custom authentication providers already configure with the order `0` reserved for the - // Serverless SAML provider. + // Check if there are any custom authentication providers already configured with the order `0` reserved for the + // Serverless SAML provider or if there is an existing SAML provider with the name MOCK_IDP_REALM_NAME. We check + // both rawConfig and extraCliOptions because the latter can be used to override the former. let hasBasicOrTokenProviderConfigured = false; - const providersConfig = _.get(rawConfig, 'xpack.security.authc.providers', {}); - for (const [providerType, providers] of Object.entries(providersConfig)) { - if (providerType === 'basic' || providerType === 'token') { - hasBasicOrTokenProviderConfigured = true; - } + for (const configSource of [rawConfig, extraCliOptions]) { + const providersConfig = _.get(configSource, 'xpack.security.authc.providers', {}); + for (const [providerType, providers] of Object.entries(providersConfig)) { + if (providerType === 'basic' || providerType === 'token') { + hasBasicOrTokenProviderConfigured = true; + } - for (const [providerName, provider] of Object.entries(providers)) { - if (provider.order === 0) { - console.warn( - `The serverless SAML authentication provider won't be configured because the order "0" is already used by the custom authentication provider "${providerType}/${providerName}".` + - `Please update the custom provider to use a different order or remove it to allow the serverless SAML provider to be configured.` - ); - return false; + for (const [providerName, provider] of Object.entries(providers)) { + if (provider.order === 0) { + console.warn( + `The serverless SAML authentication provider won't be configured because the order "0" is already used by the custom authentication provider "${providerType}/${providerName}".` + + `Please update the custom provider to use a different order or remove it to allow the serverless SAML provider to be configured.` + ); + return false; + } + + if (providerType === 'saml' && providerName === MOCK_IDP_REALM_NAME) { + console.warn( + `The serverless SAML authentication provider won't be configured because the SAML provider with "${MOCK_IDP_REALM_NAME}" name is already configured".` + ); + return false; + } } } } From e654e464665f0d6b071149d27b4293ceb4de3ba3 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher <kerry.gallagher@elastic.co> Date: Tue, 2 Jul 2024 20:29:32 +0100 Subject: [PATCH 110/128] [Logs] Add Log Sources advanced setting and client / server access services (#186468) ## Summary Implements part 1 of https://github.com/elastic/observability-dev/issues/3498 (adds an advanced setting and data access services for consumers). ## Reviewer notes - Please see note comments inline with the code. - The `limits.yml` change was generated by `node scripts/build_kibana_platform_plugins.js --update-limits`. - There are no consumers using this yet, so you'll need to make some minor adjustments if you'd like to test the access services. In a plugin (`infra` for example) the server side access could look like this: ```ts const [, { logsDataAccess }] = await getStartServices(); const logSourcesService = await logsDataAccess.services.getLogSourcesService(request); await logSourcesService.setLogSources([ { indexPattern: 'logs-*-*' }, { indexPattern: 'test-logs-*' }, ]); ``` Public access could look like this: ```ts const logSourcesService = plugins.logsDataAccess.services.logSourcesService; logSourcesService.setLogSources([{ indexPattern: 'client-side-logs-*' }]); ``` - I haven't added any tests here yet as any unit tests would more or less just be re-testing a UI settings mock. Functional tests will be valuable once there are consumers. ## UI <img width="1425" alt="Screenshot 2024-06-20 at 10 41 16" src="https://github.com/elastic/kibana/assets/471693/30608dcf-4c34-4d29-9a22-441b06757c28"> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../settings/setting_ids/index.ts | 1 + packages/kbn-optimizer/limits.yml | 1 + .../server/collectors/management/schema.ts | 7 ++++ .../server/collectors/management/types.ts | 1 + src/plugins/telemetry/schema/oss_plugins.json | 9 +++++ x-pack/.i18nrc.json | 1 + .../logs_data_access/common/constants.ts | 8 ++++ .../logs_data_access/common/types.ts | 10 +++++ .../logs_data_access/common/ui_settings.ts | 32 +++++++++++++++ .../logs_data_access/kibana.jsonc | 2 +- .../logs_data_access/public/index.ts | 23 +++++++++++ .../logs_data_access/public/plugin.ts | 39 +++++++++++++++++++ .../services/log_sources_service/index.ts | 28 +++++++++++++ .../public/services/register_services.ts | 21 ++++++++++ .../logs_data_access/public/types.ts | 12 ++++++ .../logs_data_access/server/plugin.ts | 10 ++++- .../services/log_sources_service/index.ts | 35 +++++++++++++++++ .../server/services/register_services.ts | 9 ++++- .../logs_data_access/tsconfig.json | 10 ++++- 19 files changed, 254 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugins/observability_solution/logs_data_access/common/constants.ts create mode 100644 x-pack/plugins/observability_solution/logs_data_access/common/types.ts create mode 100644 x-pack/plugins/observability_solution/logs_data_access/common/ui_settings.ts create mode 100644 x-pack/plugins/observability_solution/logs_data_access/public/index.ts create mode 100644 x-pack/plugins/observability_solution/logs_data_access/public/plugin.ts create mode 100644 x-pack/plugins/observability_solution/logs_data_access/public/services/log_sources_service/index.ts create mode 100644 x-pack/plugins/observability_solution/logs_data_access/public/services/register_services.ts create mode 100644 x-pack/plugins/observability_solution/logs_data_access/public/types.ts create mode 100644 x-pack/plugins/observability_solution/logs_data_access/server/services/log_sources_service/index.ts diff --git a/packages/kbn-management/settings/setting_ids/index.ts b/packages/kbn-management/settings/setting_ids/index.ts index 32df8ac3240c4..94d56372a17c4 100644 --- a/packages/kbn-management/settings/setting_ids/index.ts +++ b/packages/kbn-management/settings/setting_ids/index.ts @@ -142,6 +142,7 @@ export const OBSERVABILITY_APM_ENABLE_SERVICE_INVENTORY_TABLE_SEARCH_BAR = export const OBSERVABILITY_LOGS_EXPLORER_ALLOWED_DATA_VIEWS_ID = 'observability:logsExplorer:allowedDataViews'; export const OBSERVABILITY_APM_ENABLE_MULTI_SIGNAL = 'observability:apmEnableMultiSignal'; +export const OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID = 'observability:logSources'; // Reporting settings export const XPACK_REPORTING_CUSTOM_PDF_LOGO_ID = 'xpackReporting:customPdfLogo'; diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 73d9f0e23af29..c4958003bcfb6 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -95,6 +95,7 @@ pageLoadAssetSize: licensing: 29004 links: 44490 lists: 22900 + logsDataAccess: 16759 logsExplorer: 60000 logsShared: 281060 logstash: 53548 diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index cf62f45ce3156..8285a0aee6b01 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -499,6 +499,13 @@ export const stackManagementSchema: MakeSchemaFrom<UsageStats> = { _meta: { description: 'Non-default value of setting.' }, }, }, + 'observability:logSources': { + type: 'array', + items: { + type: 'keyword', + _meta: { description: 'Non-default value of setting.' }, + }, + }, 'banners:placement': { type: 'keyword', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index 6a5983df9ccfd..95c72298a9b0e 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -53,6 +53,7 @@ export interface UsageStats { 'observability:apmEnableTableSearchBar': boolean; 'observability:apmEnableServiceInventoryTableSearchBar': boolean; 'observability:logsExplorer:allowedDataViews': string[]; + 'observability:logSources': string[]; 'observability:aiAssistantLogsIndexPattern': string; 'observability:aiAssistantResponseLanguage': string; 'observability:aiAssistantSimulatedFunctionCalling': boolean; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 1e2c942ba2a5c..22e75e5d4b658 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -10343,6 +10343,15 @@ } } }, + "observability:logSources": { + "type": "array", + "items": { + "type": "keyword", + "_meta": { + "description": "Non-default value of setting." + } + } + }, "banners:placement": { "type": "keyword", "_meta": { diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index dd3409451e704..ed7720dda0fdd 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -47,6 +47,7 @@ "xpack.idxMgmtPackage": "packages/index-management", "xpack.indexLifecycleMgmt": "plugins/index_lifecycle_management", "xpack.infra": "plugins/observability_solution/infra", + "xpack.logsDataAccess": "plugins/observability_solution/logs_data_access", "xpack.logsExplorer": "plugins/observability_solution/logs_explorer", "xpack.logsShared": "plugins/observability_solution/logs_shared", "xpack.fleet": "plugins/fleet", diff --git a/x-pack/plugins/observability_solution/logs_data_access/common/constants.ts b/x-pack/plugins/observability_solution/logs_data_access/common/constants.ts new file mode 100644 index 0000000000000..83acb8bcfff15 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_data_access/common/constants.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export const DEFAULT_LOG_SOURCES = ['logs-*-*']; diff --git a/x-pack/plugins/observability_solution/logs_data_access/common/types.ts b/x-pack/plugins/observability_solution/logs_data_access/common/types.ts new file mode 100644 index 0000000000000..d021617f294ae --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_data_access/common/types.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export interface LogSource { + indexPattern: string; +} diff --git a/x-pack/plugins/observability_solution/logs_data_access/common/ui_settings.ts b/x-pack/plugins/observability_solution/logs_data_access/common/ui_settings.ts new file mode 100644 index 0000000000000..500011231ee38 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_data_access/common/ui_settings.ts @@ -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 { schema } from '@kbn/config-schema'; +import { UiSettingsParams } from '@kbn/core-ui-settings-common'; +import { i18n } from '@kbn/i18n'; +import { OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID } from '@kbn/management-settings-ids'; +import { DEFAULT_LOG_SOURCES } from './constants'; + +/** + * uiSettings definitions for the logs_data_access plugin. + */ +export const uiSettings: Record<string, UiSettingsParams> = { + [OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID]: { + category: ['observability'], + name: i18n.translate('xpack.logsDataAccess.logSources', { + defaultMessage: 'Log sources', + }), + value: DEFAULT_LOG_SOURCES, + description: i18n.translate('xpack.logsDataAccess.logSourcesDescription', { + defaultMessage: + 'Sources to be used for logs data. If the data contained in these indices is not logs data, you may experience degraded functionality.', + }), + type: 'array', + schema: schema.arrayOf(schema.string()), + requiresPageReload: true, + }, +}; diff --git a/x-pack/plugins/observability_solution/logs_data_access/kibana.jsonc b/x-pack/plugins/observability_solution/logs_data_access/kibana.jsonc index 0636aac3e5c96..56d8e556affc4 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/kibana.jsonc +++ b/x-pack/plugins/observability_solution/logs_data_access/kibana.jsonc @@ -5,7 +5,7 @@ "plugin": { "id": "logsDataAccess", "server": true, - "browser": false, + "browser": true, "requiredPlugins": [ "data", "dataViews" diff --git a/x-pack/plugins/observability_solution/logs_data_access/public/index.ts b/x-pack/plugins/observability_solution/logs_data_access/public/index.ts new file mode 100644 index 0000000000000..ed4a2be8a1b09 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_data_access/public/index.ts @@ -0,0 +1,23 @@ +/* + * 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 { PluginInitializer } from '@kbn/core/public'; +import { + LogsDataAccessPlugin, + LogsDataAccessPluginSetup, + LogsDataAccessPluginStart, +} from './plugin'; +import { LogsDataAccessPluginSetupDeps, LogsDataAccessPluginStartDeps } from './types'; + +export const plugin: PluginInitializer< + LogsDataAccessPluginSetup, + LogsDataAccessPluginStart, + LogsDataAccessPluginSetupDeps, + LogsDataAccessPluginStartDeps +> = () => { + return new LogsDataAccessPlugin(); +}; diff --git a/x-pack/plugins/observability_solution/logs_data_access/public/plugin.ts b/x-pack/plugins/observability_solution/logs_data_access/public/plugin.ts new file mode 100644 index 0000000000000..b68d3734ee695 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_data_access/public/plugin.ts @@ -0,0 +1,39 @@ +/* + * 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 { CoreStart } from '@kbn/core/public'; +import { Plugin } from '@kbn/core/public'; +import { registerServices } from './services/register_services'; +import { LogsDataAccessPluginSetupDeps, LogsDataAccessPluginStartDeps } from './types'; +export type LogsDataAccessPluginSetup = ReturnType<LogsDataAccessPlugin['setup']>; +export type LogsDataAccessPluginStart = ReturnType<LogsDataAccessPlugin['start']>; + +export class LogsDataAccessPlugin + implements + Plugin< + LogsDataAccessPluginSetup, + LogsDataAccessPluginStart, + LogsDataAccessPluginSetupDeps, + LogsDataAccessPluginStartDeps + > +{ + public setup() {} + + public start(core: CoreStart, plugins: LogsDataAccessPluginStartDeps) { + const services = registerServices({ + deps: { + uiSettings: core.uiSettings, + }, + }); + + return { + services, + }; + } + + public stop() {} +} diff --git a/x-pack/plugins/observability_solution/logs_data_access/public/services/log_sources_service/index.ts b/x-pack/plugins/observability_solution/logs_data_access/public/services/log_sources_service/index.ts new file mode 100644 index 0000000000000..3fd4674ea5509 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_data_access/public/services/log_sources_service/index.ts @@ -0,0 +1,28 @@ +/* + * 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 { OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID } from '@kbn/management-settings-ids'; +import { LogSource } from '../../../common/types'; +import { RegisterServicesParams } from '../register_services'; + +export function createLogSourcesService(params: RegisterServicesParams) { + const { uiSettings } = params.deps; + return { + getLogSources: (): LogSource[] => { + const logSources = uiSettings.get<string[]>(OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID); + return logSources.map((logSource) => ({ + indexPattern: logSource, + })); + }, + setLogSources: async (sources: LogSource[]) => { + return await uiSettings.set( + OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID, + sources.map((source) => source.indexPattern) + ); + }, + }; +} diff --git a/x-pack/plugins/observability_solution/logs_data_access/public/services/register_services.ts b/x-pack/plugins/observability_solution/logs_data_access/public/services/register_services.ts new file mode 100644 index 0000000000000..73ce189106287 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_data_access/public/services/register_services.ts @@ -0,0 +1,21 @@ +/* + * 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 { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; +import { createLogSourcesService } from './log_sources_service'; + +export interface RegisterServicesParams { + deps: { + uiSettings: IUiSettingsClient; + }; +} + +export function registerServices(params: RegisterServicesParams) { + return { + logSourcesService: createLogSourcesService(params), + }; +} diff --git a/x-pack/plugins/observability_solution/logs_data_access/public/types.ts b/x-pack/plugins/observability_solution/logs_data_access/public/types.ts new file mode 100644 index 0000000000000..a330a295c17ce --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_data_access/public/types.ts @@ -0,0 +1,12 @@ +/* + * 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. + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface LogsDataAccessPluginSetupDeps {} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface LogsDataAccessPluginStartDeps {} diff --git a/x-pack/plugins/observability_solution/logs_data_access/server/plugin.ts b/x-pack/plugins/observability_solution/logs_data_access/server/plugin.ts index 13977e869b233..74d56a794b3fe 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/server/plugin.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/server/plugin.ts @@ -12,6 +12,7 @@ import type { Plugin, PluginInitializerContext, } from '@kbn/core/server'; +import { uiSettings } from '../common/ui_settings'; import { registerServices } from './services/register_services'; import { LogsDataAccessPluginStartDeps, LogsDataAccessPluginSetupDeps } from './types'; @@ -32,12 +33,17 @@ export class LogsDataAccessPlugin constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); } - public setup(core: CoreSetup, plugins: LogsDataAccessPluginSetupDeps) {} + public setup(core: CoreSetup, plugins: LogsDataAccessPluginSetupDeps) { + core.uiSettings.register(uiSettings); + } public start(core: CoreStart, plugins: LogsDataAccessPluginStartDeps) { const services = registerServices({ logger: this.logger, - deps: {}, + deps: { + savedObjects: core.savedObjects, + uiSettings: core.uiSettings, + }, }); return { diff --git a/x-pack/plugins/observability_solution/logs_data_access/server/services/log_sources_service/index.ts b/x-pack/plugins/observability_solution/logs_data_access/server/services/log_sources_service/index.ts new file mode 100644 index 0000000000000..c6075d1d20834 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_data_access/server/services/log_sources_service/index.ts @@ -0,0 +1,35 @@ +/* + * 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 { KibanaRequest } from '@kbn/core-http-server'; +import { OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID } from '@kbn/management-settings-ids'; +import { LogSource } from '../../../common/types'; +import { RegisterServicesParams } from '../register_services'; + +export function createGetLogSourcesService(params: RegisterServicesParams) { + return async (request: KibanaRequest) => { + const { savedObjects, uiSettings } = params.deps; + const soClient = savedObjects.getScopedClient(request); + const uiSettingsClient = uiSettings.asScopedToClient(soClient); + return { + getLogSources: async (): Promise<LogSource[]> => { + const logSources = await uiSettingsClient.get<string[]>( + OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID + ); + return logSources.map((logSource) => ({ + indexPattern: logSource, + })); + }, + setLogSources: async (sources: LogSource[]) => { + return await uiSettingsClient.set( + OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID, + sources.map((source) => source.indexPattern) + ); + }, + }; + }; +} diff --git a/x-pack/plugins/observability_solution/logs_data_access/server/services/register_services.ts b/x-pack/plugins/observability_solution/logs_data_access/server/services/register_services.ts index c35b30783b5f4..26435a5657be9 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/server/services/register_services.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/server/services/register_services.ts @@ -5,16 +5,23 @@ * 2.0. */ +import { SavedObjectsServiceStart } from '@kbn/core-saved-objects-server'; +import { UiSettingsServiceStart } from '@kbn/core-ui-settings-server'; import { Logger } from '@kbn/logging'; import { createGetLogsRatesService } from './get_logs_rates_service'; +import { createGetLogSourcesService } from './log_sources_service'; export interface RegisterServicesParams { logger: Logger; - deps: {}; + deps: { + savedObjects: SavedObjectsServiceStart; + uiSettings: UiSettingsServiceStart; + }; } export function registerServices(params: RegisterServicesParams) { return { getLogsRatesService: createGetLogsRatesService(params), + getLogSourcesService: createGetLogSourcesService(params), }; } diff --git a/x-pack/plugins/observability_solution/logs_data_access/tsconfig.json b/x-pack/plugins/observability_solution/logs_data_access/tsconfig.json index 9bd4031c7a39e..1bc17c4f8814a 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/tsconfig.json +++ b/x-pack/plugins/observability_solution/logs_data_access/tsconfig.json @@ -3,12 +3,20 @@ "compilerOptions": { "outDir": "target/types" }, - "include": ["common/**/*", "server/**/*", "jest.config.js"], + "include": ["common/**/*", "server/**/*", "public/**/*", "jest.config.js"], "exclude": ["target/**/*"], "kbn_references": [ "@kbn/core", "@kbn/logging", "@kbn/data-plugin", "@kbn/data-views-plugin", + "@kbn/core-http-server", + "@kbn/management-settings-ids", + "@kbn/config-schema", + "@kbn/core-ui-settings-common", + "@kbn/i18n", + "@kbn/core-saved-objects-server", + "@kbn/core-ui-settings-server", + "@kbn/core-ui-settings-browser", ] } From 2aa94a27f05b0b72fdbb01f8b929e28452974929 Mon Sep 17 00:00:00 2001 From: Ryland Herrick <ryalnd@gmail.com> Date: Tue, 2 Jul 2024 14:33:11 -0500 Subject: [PATCH 111/128] [Detection Engine] Adds Alert Suppression to ML Rules (#181926) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR introduces Alert Suppression for ML Detection Rules. This feature is behaviorally similar to alerting suppression for other Detection Engine Rule types, and nearly identical to the analogous features for EQL rules. There are some additional UI behaviors introduced here as well, mainly intended to cover the shortcomings discovered in https://github.com/elastic/kibana/issues/183100. Those behaviors are: 1. Populating the suppression field list with fields from the anomaly index(es). 1. Disabling the suppression UI if no selected ML jobs are running (because we cannot populate the list of fields on which they'll be suppressing). 1. Warning the user if _some_ selected ML jobs are not running (because the list of suppression fields may be incomplete). See screenshots below for more info. ### Intermediate Serverless Deployment As per the "intermediate deployment" requirements for serverless, while the schema (and declared alert SO mappings) will be extended to allow this functionality, the user-facing features are currently hidden behind a feature flag. Once this is merged and released, we can issue a "final" deployment in which the feature flag is enabled, and the feature effectively released. ## Screenshots * Overview of new UI fields <img width="1044" alt="Screenshot 2024-05-16 at 3 22 02 PM" src="https://github.com/elastic/kibana/assets/657252/8c07700d-5860-4d1e-a701-eac84fc35558"> * Example of Anomaly fields in suppression combobox <img width="881" alt="Screenshot 2024-06-06 at 5 14 17 PM" src="https://github.com/rylnd/kibana/assets/657252/9aa6ed99-1e02-44a0-ad1b-785136510d68"> * Suppression disabled due to no jobs running <img width="668" alt="Screenshot 2024-06-17 at 11 23 39 PM" src="https://github.com/elastic/kibana/assets/657252/a8636a52-31bd-4579-9bcd-d59d93c26984"> * Warning due to not all jobs running <img width="776" alt="Screenshot 2024-06-17 at 11 26 16 PM" src="https://github.com/elastic/kibana/assets/657252/f44c2400-570e-4fde-adce-e5841a2de08d"> ## Steps to Review 1. Review the Test Plan for an overview of behavior 2. Review Integration tests for an overview of implementation and edge cases 3. Review Cypress tests for an overview of UX changes 4. Testing on [Demo Instance](https://rylnd-pr-181926-ml-rule-alert-suppression.kbndev.co/) (elastic/changeme) 1. This instance has the relevant feature flag enabled, has some sample auditbeat data, as well as the [anomalies archive data](https://github.com/elastic/kibana/tree/main/x-pack/test/functional/es_archives/security_solution/anomalies) for the purposes of exercising an ML rule against "real" anomalies 1. There are a few example rules in the default space: 1. A simple [query rule](https://rylnd-pr-181926-ml-rule-alert-suppression.kbndev.co/app/security/rules/id/f6f5960d-7e4b-40c1-ae15-501112822130) against auditbeat data 1. An [ML rule](https://rylnd-pr-181926-ml-rule-alert-suppression.kbndev.co/app/security/rules/id/9122669e-b2e1-41ce-af25-eeae15aa9ece) with per-execution suppression on both `by_field_name` and `by_field_value` (which ends up not actually suppressing anything) 1. An [ML rule](https://rylnd-pr-181926-ml-rule-alert-suppression.kbndev.co/app/security/rules/id/0aabc280-00bd-42d4-82e6-65997c751797) with per-execution suppression on `by_field_name` (which suppresses all anomalies into a single alert) ## Related Issues - This feature was temporarily blocked by https://github.com/elastic/kibana/issues/183100, but those changes are now in this PR. ## Checklist - [x] Functional changes are hidden behind a feature flag. If not hidden, the PR explains why these changes are being implemented in a long-living feature branch. - [x] Functional changes are covered with a test plan and automated tests. * [Test Plan](https://github.com/elastic/security-team/pull/9279) - [x] Stability of new and changed tests is verified using the [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner) in both ESS and Serverless. By default, use 200 runs for ESS and 200 runs for Serverless. * [ESS - Cypress x 200](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6449) * [Serverless - Cypress x 200](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6450) * [ESS - API x 200](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6447) * [Serverless - API x 200](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6448) - [ ] Comprehensive manual testing is done by two engineers: the PR author and one of the PR reviewers. Changes are tested in both ESS and Serverless. - [ ] Mapping changes are accompanied by a technical design document. It can be a GitHub issue or an RFC explaining the changes. The design document is shared with and approved by the appropriate teams and individual stakeholders. - [ ] (OPTIONAL) OpenAPI specs changes include detailed descriptions and examples of usage and are ready to be released on https://docs.elastic.co/api-reference. NOTE: This is optional because at the moment we don't have yet any OpenAPI specs that would be fully "documented" and "GA-ready" for publishing on https://docs.elastic.co/api-reference. - [ ] Functional changes are communicated to the Docs team. A ticket is opened in https://github.com/elastic/security-docs using the [Internal documentation request (Elastic employees)](https://github.com/elastic/security-docs/issues/new?assignees=&labels=&projects=&template=docs-request-internal.yaml&title=%5BRequest%5D+) template. The following information is included: feature flags used, target ESS version, planned timing for ESS and Serverless releases. --------- Co-authored-by: Nastasha Solomon <79124755+nastasha-solomon@users.noreply.github.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../project_roles/security/roles.yml | 21 + ...s_upgrade_and_rollback_checks.test.ts.snap | 46 + .../rule_schema/rule_request_schema.test.ts | 1 + .../model/rule_schema/rule_schemas.gen.ts | 17 +- .../rule_schema/rule_schemas.schema.yaml | 9 + .../common/detection_engine/constants.ts | 1 + .../common/detection_engine/utils.test.ts | 16 +- .../common/experimental_features.ts | 5 + .../components/ml/hooks/use_ml_rule_config.ts | 62 + .../ml/hooks/use_ml_rule_validations.test.ts | 102 ++ .../ml/hooks/use_ml_rule_validations.ts | 41 + .../common/components/ml_popover/api.mock.ts | 10 + .../hooks/use_security_jobs_helpers.tsx | 13 +- .../description_step/index.test.tsx | 73 +- .../components/step_define_rule/index.tsx | 106 +- .../step_define_rule/translations.tsx | 15 + ...e_experimental_feature_fields_transform.ts | 10 +- .../pages/rule_creation/helpers.test.ts | 26 + .../pages/rule_creation/helpers.ts | 1 + .../logic/use_alert_suppression.test.tsx | 38 +- .../logic/use_alert_suppression.tsx | 15 +- .../rule_management/logic/use_rule_fields.ts | 36 + .../components/alerts_table/actions.tsx | 52 +- .../es_serverless_resources/roles.yml | 18 + .../normalization/rule_converters.test.ts | 117 +- .../normalization/rule_converters.ts | 4 + .../rule_schema/model/rule_schemas.ts | 1 + .../rule_types/ml/create_ml_alert_type.ts | 39 +- .../detection_engine/rule_types/ml/ml.test.ts | 60 +- .../lib/detection_engine/rule_types/ml/ml.ts | 84 +- .../lib/detection_engine/rule_types/types.ts | 4 +- .../utils/wrap_suppressed_alerts.ts | 11 +- .../delete_all_anomalies.ts | 36 + .../detections_response/index.ts | 1 + .../security_solution/anomalies/mappings.json | 9 +- .../config/ess/config.base.ts | 1 + .../configs/serverless.config.ts | 1 + .../execution_logic/index.ts | 1 + .../execution_logic/machine_learning.ts | 4 +- .../machine_learning_alert_suppression.ts | 1106 +++++++++++++++++ .../machine_learning_setup.ts | 3 +- ..._generated_properties_including_rule_id.ts | 10 +- .../test/security_solution_cypress/config.ts | 1 + ...ws_suppression_serverless_essentials.cy.ts | 7 +- .../common_flows_supression_ess_basic.cy.ts | 4 + .../machine_learning_rule_suppression.cy.ts | 198 +++ .../rule_edit/machine_learning_rule.cy.ts | 178 +++ .../prebuilt_rules_preview.cy.ts | 28 +- .../cypress/support/machine_learning.ts | 64 + .../serverless_config.ts | 1 + .../project_controller_security_roles.yml | 18 + 51 files changed, 2503 insertions(+), 222 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_validations.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_validations.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_fields.ts create mode 100644 x-pack/test/common/utils/security_solution/detections_response/delete_all_anomalies.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning_alert_suppression.ts create mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/machine_learning_rule_suppression.cy.ts create mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/machine_learning_rule.cy.ts diff --git a/packages/kbn-es/src/serverless_resources/project_roles/security/roles.yml b/packages/kbn-es/src/serverless_resources/project_roles/security/roles.yml index e47cc78eadc33..3c118688f6429 100644 --- a/packages/kbn-es/src/serverless_resources/project_roles/security/roles.yml +++ b/packages/kbn-es/src/serverless_resources/project_roles/security/roles.yml @@ -35,6 +35,7 @@ viewer: - '.fleet-actions*' - 'risk-score.risk-score-*' - '.asset-criticality.asset-criticality-*' + - '.ml-anomalies-*' privileges: - read applications: @@ -100,6 +101,10 @@ editor: - 'read' - 'write' allow_restricted_indices: false + - names: + - '.ml-anomalies-*' + privileges: + - read applications: - application: 'kibana-.kibana' privileges: @@ -154,6 +159,7 @@ t1_analyst: - '.fleet-actions*' - risk-score.risk-score-* - .asset-criticality.asset-criticality-* + - '.ml-anomalies-*' privileges: - read applications: @@ -201,6 +207,7 @@ t2_analyst: - .fleet-agents* - .fleet-actions* - risk-score.risk-score-* + - '.ml-anomalies-*' privileges: - read - names: @@ -262,6 +269,7 @@ t3_analyst: - .fleet-agents* - .fleet-actions* - risk-score.risk-score-* + - '.ml-anomalies-*' privileges: - read applications: @@ -331,6 +339,7 @@ threat_intelligence_analyst: - .fleet-agents* - .fleet-actions* - risk-score.risk-score-* + - '.ml-anomalies-*' privileges: - read applications: @@ -389,6 +398,7 @@ rule_author: - .fleet-agents* - .fleet-actions* - risk-score.risk-score-* + - '.ml-anomalies-*' privileges: - read applications: @@ -453,6 +463,7 @@ soc_manager: - .fleet-agents* - .fleet-actions* - risk-score.risk-score-* + - '.ml-anomalies-*' privileges: - read applications: @@ -513,6 +524,7 @@ detections_admin: - metrics-endpoint.metadata_current_* - .fleet-agents* - .fleet-actions* + - '.ml-anomalies-*' privileges: - read - names: @@ -570,6 +582,10 @@ platform_engineer: privileges: - read - write + - names: + - '.ml-anomalies-*' + privileges: + - read applications: - application: 'kibana-.kibana' privileges: @@ -620,6 +636,7 @@ endpoint_operations_analyst: - .lists* - .items* - risk-score.risk-score-* + - '.ml-anomalies-*' privileges: - read - names: @@ -710,6 +727,10 @@ endpoint_policy_manager: - read - write - manage + - names: + - '.ml-anomalies-*' + privileges: + - read applications: - application: 'kibana-.kibana' privileges: diff --git a/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap b/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap index 456b7e00dd1ed..932daa1fed69d 100644 --- a/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap +++ b/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap @@ -7127,6 +7127,52 @@ Object { }, Object { "properties": Object { + "alertSuppression": Object { + "additionalProperties": false, + "properties": Object { + "duration": Object { + "additionalProperties": false, + "properties": Object { + "unit": Object { + "enum": Array [ + "s", + "m", + "h", + ], + "type": "string", + }, + "value": Object { + "minimum": 1, + "type": "integer", + }, + }, + "required": Array [ + "value", + "unit", + ], + "type": "object", + }, + "groupBy": Object { + "items": Object { + "type": "string", + }, + "maxItems": 3, + "minItems": 1, + "type": "array", + }, + "missingFieldsStrategy": Object { + "enum": Array [ + "doNotSuppress", + "suppress", + ], + "type": "string", + }, + }, + "required": Array [ + "groupBy", + ], + "type": "object", + }, "anomalyThreshold": Object { "minimum": 0, "type": "integer", diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts index b7435c7dd86e8..a22886b287c7f 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts @@ -1272,6 +1272,7 @@ describe('rules schema', () => { { ruleType: 'saved_query', ruleMock: getCreateSavedQueryRulesSchemaMock() }, { ruleType: 'eql', ruleMock: getCreateEqlRuleSchemaMock() }, { ruleType: 'new_terms', ruleMock: getCreateNewTermsRulesSchemaMock() }, + { ruleType: 'machine_learning', ruleMock: getCreateMachineLearningRulesSchemaMock() }, ]; cases.forEach(({ ruleType, ruleMock }) => { diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.gen.ts index 9bb1b26fafd95..83bf6778ec3e3 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.gen.ts @@ -468,14 +468,25 @@ export const MachineLearningRuleRequiredFields = z.object({ machine_learning_job_id: MachineLearningJobId, }); +export type MachineLearningRuleOptionalFields = z.infer<typeof MachineLearningRuleOptionalFields>; +export const MachineLearningRuleOptionalFields = z.object({ + alert_suppression: AlertSuppression.optional(), +}); + export type MachineLearningRulePatchFields = z.infer<typeof MachineLearningRulePatchFields>; -export const MachineLearningRulePatchFields = MachineLearningRuleRequiredFields.partial(); +export const MachineLearningRulePatchFields = MachineLearningRuleRequiredFields.partial().merge( + MachineLearningRuleOptionalFields +); export type MachineLearningRuleResponseFields = z.infer<typeof MachineLearningRuleResponseFields>; -export const MachineLearningRuleResponseFields = MachineLearningRuleRequiredFields; +export const MachineLearningRuleResponseFields = MachineLearningRuleRequiredFields.merge( + MachineLearningRuleOptionalFields +); export type MachineLearningRuleCreateFields = z.infer<typeof MachineLearningRuleCreateFields>; -export const MachineLearningRuleCreateFields = MachineLearningRuleRequiredFields; +export const MachineLearningRuleCreateFields = MachineLearningRuleRequiredFields.merge( + MachineLearningRuleOptionalFields +); export type MachineLearningRule = z.infer<typeof MachineLearningRule>; export const MachineLearningRule = SharedResponseProps.merge(MachineLearningRuleResponseFields); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml index de424af505c1f..4ade72c15fbb9 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml @@ -686,18 +686,27 @@ components: - machine_learning_job_id - anomaly_threshold + MachineLearningRuleOptionalFields: + type: object + properties: + alert_suppression: + $ref: './common_attributes.schema.yaml#/components/schemas/AlertSuppression' + MachineLearningRulePatchFields: allOf: - $ref: '#/components/schemas/MachineLearningRuleRequiredFields' x-modify: partial + - $ref: '#/components/schemas/MachineLearningRuleOptionalFields' MachineLearningRuleResponseFields: allOf: - $ref: '#/components/schemas/MachineLearningRuleRequiredFields' + - $ref: '#/components/schemas/MachineLearningRuleOptionalFields' MachineLearningRuleCreateFields: allOf: - $ref: '#/components/schemas/MachineLearningRuleRequiredFields' + - $ref: '#/components/schemas/MachineLearningRuleOptionalFields' MachineLearningRule: allOf: 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 54c81cf93568f..8e06f46f1f46d 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/constants.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/constants.ts @@ -47,6 +47,7 @@ export const SUPPRESSIBLE_ALERT_RULES: Type[] = [ 'new_terms', 'threat_match', 'eql', + 'machine_learning', ]; export const SUPPRESSIBLE_ALERT_RULES_GA: Type[] = ['saved_query', 'query']; diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts index 2e5ac39936fa3..a4db006a67463 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts @@ -236,9 +236,7 @@ describe('Alert Suppression Rules', () => { expect(isSuppressibleAlertRule('threat_match')).toBe(true); expect(isSuppressibleAlertRule('new_terms')).toBe(true); expect(isSuppressibleAlertRule('eql')).toBe(true); - - // Rule types that don't support alert suppression: - expect(isSuppressibleAlertRule('machine_learning')).toBe(false); + expect(isSuppressibleAlertRule('machine_learning')).toBe(true); }); test('should return false for an unknown rule type', () => { @@ -273,9 +271,7 @@ describe('Alert Suppression Rules', () => { expect(isSuppressionRuleConfiguredWithDuration('threat_match')).toBe(true); expect(isSuppressionRuleConfiguredWithDuration('new_terms')).toBe(true); expect(isSuppressionRuleConfiguredWithDuration('eql')).toBe(true); - - // Rule types that don't support alert suppression: - expect(isSuppressionRuleConfiguredWithDuration('machine_learning')).toBe(false); + expect(isSuppressionRuleConfiguredWithDuration('machine_learning')).toBe(true); }); test('should return false for an unknown rule type', () => { @@ -294,9 +290,7 @@ describe('Alert Suppression Rules', () => { expect(isSuppressionRuleConfiguredWithGroupBy('threat_match')).toBe(true); expect(isSuppressionRuleConfiguredWithGroupBy('new_terms')).toBe(true); expect(isSuppressionRuleConfiguredWithGroupBy('eql')).toBe(true); - - // Rule types that don't support alert suppression: - expect(isSuppressionRuleConfiguredWithGroupBy('machine_learning')).toBe(false); + expect(isSuppressionRuleConfiguredWithGroupBy('machine_learning')).toBe(true); }); test('should return false for a threshold rule type', () => { @@ -320,9 +314,7 @@ describe('Alert Suppression Rules', () => { expect(isSuppressionRuleConfiguredWithMissingFields('threat_match')).toBe(true); expect(isSuppressionRuleConfiguredWithMissingFields('new_terms')).toBe(true); expect(isSuppressionRuleConfiguredWithMissingFields('eql')).toBe(true); - - // Rule types that don't support alert suppression: - expect(isSuppressionRuleConfiguredWithMissingFields('machine_learning')).toBe(false); + expect(isSuppressionRuleConfiguredWithMissingFields('machine_learning')).toBe(true); }); test('should return false for a threshold rule type', () => { diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 0a7558515226f..66b5f4bd948a1 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -175,6 +175,11 @@ export const allowedExperimentalValues = Object.freeze({ */ riskEnginePrivilegesRouteEnabled: true, + /** + * Enables alerts suppression for machine learning rules + */ + alertSuppressionForMachineLearningRuleEnabled: false, + /** * Enables experimental Experimental S1 integration data to be available in Analyzer */ diff --git a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts new file mode 100644 index 0000000000000..86551ad64b43a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts @@ -0,0 +1,62 @@ +/* + * 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 { useMemo } from 'react'; +import type { DataViewFieldBase } from '@kbn/es-query'; + +import { getTermsAggregationFields } from '../../../../detection_engine/rule_creation_ui/components/step_define_rule/utils'; +import { useRuleFields } from '../../../../detection_engine/rule_management/logic/use_rule_fields'; +import type { BrowserField } from '../../../containers/source'; +import { useMlCapabilities } from './use_ml_capabilities'; +import { useMlRuleValidations } from './use_ml_rule_validations'; +import { hasMlAdminPermissions } from '../../../../../common/machine_learning/has_ml_admin_permissions'; +import { hasMlLicense } from '../../../../../common/machine_learning/has_ml_license'; + +export interface UseMlRuleConfigReturn { + hasMlAdminPermissions: boolean; + hasMlLicense: boolean; + mlFields: DataViewFieldBase[]; + mlFieldsLoading: boolean; + mlSuppressionFields: BrowserField[]; + noMlJobsStarted: boolean; + someMlJobsStarted: boolean; +} + +/** + * This hook is used to retrieve the various configurations and status needed for creating/editing an ML Rule in the Detection Engine UI. It composes several other ML hooks. + * + * @param machineLearningJobId The ID(s) of the ML job to retrieve the configuration for + * + * @returns {UseMlRuleConfigReturn} An object containing the various configurations and statuses needed for creating/editing an ML Rule + * + */ +export const useMLRuleConfig = ({ + machineLearningJobId, +}: { + machineLearningJobId: string[]; +}): UseMlRuleConfigReturn => { + const mlCapabilities = useMlCapabilities(); + const { someJobsStarted: someMlJobsStarted, noJobsStarted: noMlJobsStarted } = + useMlRuleValidations({ machineLearningJobId }); + const { loading: mlFieldsLoading, fields: mlFields } = useRuleFields({ + machineLearningJobId, + }); + const mlSuppressionFields = useMemo( + () => getTermsAggregationFields(mlFields as BrowserField[]), + [mlFields] + ); + + return { + hasMlAdminPermissions: hasMlAdminPermissions(mlCapabilities), + hasMlLicense: hasMlLicense(mlCapabilities), + mlFields, + mlFieldsLoading, + mlSuppressionFields, + noMlJobsStarted, + someMlJobsStarted, + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_validations.test.ts b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_validations.test.ts new file mode 100644 index 0000000000000..6f14d6fe2a736 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_validations.test.ts @@ -0,0 +1,102 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { TestProviders } from '../../../mock'; +import { buildMockJobsSummary, getJobsSummaryResponseMock } from '../../ml_popover/api.mock'; +import { useInstalledSecurityJobs } from './use_installed_security_jobs'; + +import { useMlRuleValidations } from './use_ml_rule_validations'; + +jest.mock('./use_installed_security_jobs'); + +describe('useMlRuleValidations', () => { + const machineLearningJobId = ['test_job', 'test_job_2']; + + beforeEach(() => { + (useInstalledSecurityJobs as jest.Mock).mockReturnValue({ + loading: true, + jobs: [], + }); + }); + + it('returns loading state from inner hook', () => { + const { result, rerender } = renderHook(() => useMlRuleValidations({ machineLearningJobId }), { + wrapper: TestProviders, + }); + expect(result.current).toEqual(expect.objectContaining({ loading: true })); + + (useInstalledSecurityJobs as jest.Mock).mockReturnValueOnce({ + loading: false, + jobs: [], + }); + + rerender(); + + expect(result.current).toEqual(expect.objectContaining({ loading: false })); + }); + + it('returns no jobs started when no jobs are started', () => { + const { result } = renderHook(() => useMlRuleValidations({ machineLearningJobId }), { + wrapper: TestProviders, + }); + + expect(result.current).toEqual( + expect.objectContaining({ noJobsStarted: true, someJobsStarted: false }) + ); + }); + + it('returns some jobs started when some jobs are started', () => { + (useInstalledSecurityJobs as jest.Mock).mockReturnValueOnce({ + loading: false, + jobs: getJobsSummaryResponseMock([ + buildMockJobsSummary({ + id: machineLearningJobId[0], + jobState: 'opened', + datafeedState: 'started', + }), + buildMockJobsSummary({ + id: machineLearningJobId[1], + }), + ]), + }); + + const { result } = renderHook(() => useMlRuleValidations({ machineLearningJobId }), { + wrapper: TestProviders, + }); + + expect(result.current).toEqual( + expect.objectContaining({ noJobsStarted: false, someJobsStarted: true }) + ); + }); + + it('returns neither "no jobs started" nor "some jobs started" when all jobs are started', () => { + (useInstalledSecurityJobs as jest.Mock).mockReturnValueOnce({ + loading: false, + jobs: getJobsSummaryResponseMock([ + buildMockJobsSummary({ + id: machineLearningJobId[0], + jobState: 'opened', + datafeedState: 'started', + }), + buildMockJobsSummary({ + id: machineLearningJobId[1], + jobState: 'opened', + datafeedState: 'started', + }), + ]), + }); + + const { result } = renderHook(() => useMlRuleValidations({ machineLearningJobId }), { + wrapper: TestProviders, + }); + + expect(result.current).toEqual( + expect.objectContaining({ noJobsStarted: false, someJobsStarted: false }) + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_validations.ts b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_validations.ts new file mode 100644 index 0000000000000..81897c5d29b82 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_validations.ts @@ -0,0 +1,41 @@ +/* + * 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 { isJobStarted } from '../../../../../common/machine_learning/helpers'; +import { useInstalledSecurityJobs } from './use_installed_security_jobs'; + +export interface UseMlRuleValidationsParams { + machineLearningJobId: string[] | undefined; +} + +export interface UseMlRuleValidationsReturn { + loading: boolean; + noJobsStarted: boolean; + someJobsStarted: boolean; +} + +/** + * Hook to encapsulate some of our validation checks for ML rules. + * + * @param machineLearningJobId the ML Job IDs of the rule + * @returns validation state about the rule, relative to its ML jobs. + */ +export const useMlRuleValidations = ({ + machineLearningJobId, +}: UseMlRuleValidationsParams): UseMlRuleValidationsReturn => { + const { jobs: installedJobs, loading } = useInstalledSecurityJobs(); + const ruleMlJobs = installedJobs.filter((installedJob) => + (machineLearningJobId ?? []).includes(installedJob.id) + ); + const numberOfRuleMlJobsStarted = ruleMlJobs.filter((job) => + isJobStarted(job.jobState, job.datafeedState) + ).length; + const noMlJobsStarted = numberOfRuleMlJobsStarted === 0; + const someMlJobsStarted = !noMlJobsStarted && numberOfRuleMlJobsStarted !== ruleMlJobs.length; + + return { loading, noJobsStarted: noMlJobsStarted, someJobsStarted: someMlJobsStarted }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts index 2000db1807cbf..fdd9d66ebaf90 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts @@ -100,6 +100,16 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [ }, ]; +export const getJobsSummaryResponseMock = (additionalJobs: MlSummaryJob[]): MlSummaryJob[] => [ + ...mockJobsSummaryResponse, + ...additionalJobs, +]; + +export const buildMockJobsSummary = (overrides: Partial<MlSummaryJob>): MlSummaryJob => ({ + ...mockJobsSummaryResponse[0], + ...overrides, +}); + export const mockGetModuleResponse: Module[] = [ { id: 'security_linux_v3', diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.tsx index 8d0b63d8b32fe..567d7e038b5ad 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.tsx @@ -6,6 +6,7 @@ */ import type { MlSummaryJob } from '@kbn/ml-plugin/public'; +import { isSecurityJob } from '../../../../../common/machine_learning/is_security_job'; import type { AugmentedSecurityJobFields, Module, @@ -111,13 +112,11 @@ export const getInstalledJobs = ( moduleJobs: SecurityJob[], compatibleModuleIds: string[] ): SecurityJob[] => - jobSummaryData - .filter(({ groups }) => groups.includes('siem') || groups.includes('security')) - .map<SecurityJob>((jobSummary) => ({ - ...jobSummary, - ...getAugmentedFields(jobSummary.id, moduleJobs, compatibleModuleIds), - isInstalled: true, - })); + jobSummaryData.filter(isSecurityJob).map((jobSummary) => ({ + ...jobSummary, + ...getAugmentedFields(jobSummary.id, moduleJobs, compatibleModuleIds), + isInstalled: true, + })); /** * Combines installed jobs + moduleSecurityJobs that don't overlap and sorts by name asc diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx index 8695041697120..f5a7e39634359 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx @@ -14,7 +14,6 @@ import { buildListItems, getDescriptionItem, } from '.'; -import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import { FilterManager, UI_SETTINGS } from '@kbn/data-plugin/public'; import type { Filter } from '@kbn/es-query'; @@ -575,7 +574,6 @@ describe('description_step', () => { }); describe('alert suppression', () => { - const ruleTypesWithoutSuppression: Type[] = ['machine_learning']; const suppressionFields = { groupByDuration: { unit: 'm', @@ -587,23 +585,6 @@ describe('description_step', () => { suppressionMissingFields: 'suppress', }; describe('groupByDuration', () => { - ruleTypesWithoutSuppression.forEach((ruleType) => { - test(`should be empty if rule is ${ruleType}`, () => { - const result: ListItems[] = getDescriptionItem( - 'groupByDuration', - 'label', - { - ruleType, - ...suppressionFields, - }, - mockFilterManager, - mockLicenseService - ); - - expect(result).toEqual([]); - }); - }); - ['query', 'saved_query'].forEach((ruleType) => { test(`should be empty if groupByFields empty for ${ruleType} rule`, () => { const result: ListItems[] = getDescriptionItem( @@ -686,22 +667,21 @@ describe('description_step', () => { }); describe('groupByFields', () => { - [...ruleTypesWithoutSuppression, 'threshold'].forEach((ruleType) => { - test(`should be empty if rule is ${ruleType}`, () => { - const result: ListItems[] = getDescriptionItem( - 'groupByFields', - 'label', - { - ruleType, - ...suppressionFields, - }, - mockFilterManager, - mockLicenseService - ); + test(`should be empty if rule type is 'threshold'`, () => { + const result: ListItems[] = getDescriptionItem( + 'groupByFields', + 'label', + { + ruleType: 'threshold', + ...suppressionFields, + }, + mockFilterManager, + mockLicenseService + ); - expect(result).toEqual([]); - }); + expect(result).toEqual([]); }); + ['query', 'saved_query'].forEach((ruleType) => { test(`should return item for ${ruleType} rule`, () => { const result: ListItems[] = getDescriptionItem( @@ -720,22 +700,21 @@ describe('description_step', () => { }); describe('suppressionMissingFields', () => { - [...ruleTypesWithoutSuppression, 'threshold'].forEach((ruleType) => { - test(`should be empty if rule is ${ruleType}`, () => { - const result: ListItems[] = getDescriptionItem( - 'suppressionMissingFields', - 'label', - { - ruleType, - ...suppressionFields, - }, - mockFilterManager, - mockLicenseService - ); + test(`should be empty if rule type is 'threshold'`, () => { + const result: ListItems[] = getDescriptionItem( + 'suppressionMissingFields', + 'label', + { + ruleType: 'threshold', + ...suppressionFields, + }, + mockFilterManager, + mockLicenseService + ); - expect(result).toEqual([]); - }); + expect(result).toEqual([]); }); + ['query', 'saved_query'].forEach((ruleType) => { test(`should return item for ${ruleType} rule`, () => { const result: ListItems[] = getDescriptionItem( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx index 98fe3bae27f5e..df6152c7069df 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx @@ -36,9 +36,6 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { useSetFieldValueWithCallback } from '../../../../common/utils/use_set_field_value_cb'; import { useRuleFromTimeline } from '../../../../detections/containers/detection_engine/rules/use_rule_from_timeline'; import { isMlRule } from '../../../../../common/machine_learning/helpers'; -import { hasMlAdminPermissions } from '../../../../../common/machine_learning/has_ml_admin_permissions'; -import { hasMlLicense } from '../../../../../common/machine_learning/has_ml_license'; -import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml_capabilities'; import type { EqlOptionsSelected, FieldsEqlOptions } from '../../../../../common/search_strategy'; import { filterRuleFieldsForType, getStepDataDataSource } from '../../pages/rule_creation/helpers'; import type { @@ -105,6 +102,7 @@ import { useAllEsqlRuleFields } from '../../hooks'; import { useAlertSuppression } from '../../../rule_management/logic/use_alert_suppression'; import { AiAssistant } from '../ai_assistant'; import { RelatedIntegrations } from '../../../rule_creation/components/related_integrations'; +import { useMLRuleConfig } from '../../../../common/components/ml/hooks/use_ml_rule_config'; const CommonUseField = getUseField({ component: Field }); @@ -169,41 +167,53 @@ const IntendedRuleTypeEuiFormRow = styled(RuleTypeEuiFormRow)` // eslint-disable-next-line complexity const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({ - isLoading, - isUpdateView = false, - kibanaDataViews, - indicesConfig, - threatIndicesConfig, + browserFields, + dataSourceType, defaultSavedQuery, + enableThresholdSuppression, form, - optionsSelected, - setOptionsSelected, + groupByFields, + index, indexPattern, + indicesConfig, isIndexPatternLoading, - browserFields, + isLoading, isQueryBarValid, + isUpdateView = false, + kibanaDataViews, + optionsSelected, + queryBarSavedId, + queryBarTitle, + ruleType, setIsQueryBarValid, setIsThreatQueryBarValid, - ruleType, - index, - threatIndex, - groupByFields, - dataSourceType, + setOptionsSelected, shouldLoadQueryDynamically, - queryBarTitle, - queryBarSavedId, + threatIndex, + threatIndicesConfig, thresholdFields, - enableThresholdSuppression, }) => { const queryClient = useQueryClient(); const { isSuppressionEnabled: isAlertSuppressionEnabled } = useAlertSuppression(ruleType); - const mlCapabilities = useMlCapabilities(); const [openTimelineSearch, setOpenTimelineSearch] = useState(false); const [indexModified, setIndexModified] = useState(false); const [threatIndexModified, setThreatIndexModified] = useState(false); const license = useLicense(); + const [{ machineLearningJobId }] = useFormData<DefineStepRule>({ + form, + watch: ['machineLearningJobId'], + }); + const { + hasMlAdminPermissions, + hasMlLicense, + mlFieldsLoading, + mlSuppressionFields, + noMlJobsStarted, + someMlJobsStarted, + } = useMLRuleConfig({ machineLearningJobId }); + const esqlQueryRef = useRef<DefineStepRule['queryBar'] | undefined>(undefined); const isAlertSuppressionLicenseValid = license.isAtLeast(MINIMUM_LICENSE_FOR_SUPPRESSION); @@ -474,6 +484,24 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({ isEqlSequenceQuery(queryBar?.query?.query as string) && groupByFields.length === 0; + const isSuppressionGroupByDisabled = + !isAlertSuppressionLicenseValid || + areSuppressionFieldsDisabledBySequence || + isEsqlSuppressionLoading || + (isMlRule(ruleType) && (noMlJobsStarted || mlFieldsLoading || !mlSuppressionFields.length)); + + const suppressionGroupByDisabledText = areSuppressionFieldsDisabledBySequence + ? i18n.EQL_SEQUENCE_SUPPRESSION_DISABLE_TOOLTIP + : isMlRule(ruleType) && noMlJobsStarted + ? i18n.MACHINE_LEARNING_SUPPRESSION_DISABLED_LABEL + : alertSuppressionUpsellingMessage; + + const suppressionGroupByFields = isEsqlRule(ruleType) + ? esqlSuppressionFields + : isMlRule(ruleType) + ? mlSuppressionFields + : termsAggregationFields; + /** * Component that allows selection of suppression intervals disabled: * - if suppression license is not valid(i.e. less than platinum) @@ -868,10 +896,10 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({ () => ({ describedByIds: ['detectionEngineStepDefineRuleType'], isUpdateView, - hasValidLicense: hasMlLicense(mlCapabilities), - isMlAdmin: hasMlAdminPermissions(mlCapabilities), + hasValidLicense: hasMlLicense, + isMlAdmin: hasMlAdminPermissions, }), - [isUpdateView, mlCapabilities] + [hasMlAdminPermissions, hasMlLicense, isUpdateView] ); return ( @@ -1078,22 +1106,22 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({ </EuiText> } > - <UseField - path="groupByFields" - component={MultiSelectFieldsAutocomplete} - componentProps={{ - browserFields: isEsqlRule(ruleType) - ? esqlSuppressionFields - : termsAggregationFields, - isDisabled: - !isAlertSuppressionLicenseValid || - areSuppressionFieldsDisabledBySequence || - isEsqlSuppressionLoading, - disabledText: areSuppressionFieldsDisabledBySequence - ? i18n.EQL_SEQUENCE_SUPPRESSION_DISABLE_TOOLTIP - : alertSuppressionUpsellingMessage, - }} - /> + <> + <UseField + path="groupByFields" + component={MultiSelectFieldsAutocomplete} + componentProps={{ + browserFields: suppressionGroupByFields, + isDisabled: isSuppressionGroupByDisabled, + disabledText: suppressionGroupByDisabledText, + }} + /> + {someMlJobsStarted && ( + <EuiText size="xs" color="warning"> + {i18n.MACHINE_LEARNING_SUPPRESSION_INCOMPLETE_LABEL} + </EuiText> + )} + </> </RuleTypeEuiFormRow> <IntendedRuleTypeEuiFormRow diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/translations.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/translations.tsx index ef2a6adcc57c6..7d7bb9c4a9253 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/translations.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/translations.tsx @@ -234,6 +234,21 @@ export const EQL_SEQUENCE_SUPPRESSION_GROUPBY_VALIDATION_TEXT = i18n.translate( } ); +export const MACHINE_LEARNING_SUPPRESSION_DISABLED_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.machineLearningSuppressionDisabledLabel', + { + defaultMessage: 'To enable alert suppression, start relevant Machine Learning jobs.', + } +); + +export const MACHINE_LEARNING_SUPPRESSION_INCOMPLETE_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.machineLearningSuppressionIncompleteLabel', + { + defaultMessage: + 'This list of fields might be incomplete as some Machine Learning jobs are not running. Start all relevant jobs for a complete list.', + } +); + export const GROUP_BY_TECH_PREVIEW_LABEL_APPEND = i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupByFieldsTechPreviewLabelAppend', { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/use_experimental_feature_fields_transform.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/use_experimental_feature_fields_transform.ts index c92c35688dd3b..1bca12d461111 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/use_experimental_feature_fields_transform.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/use_experimental_feature_fields_transform.ts @@ -8,7 +8,7 @@ import { useCallback } from 'react'; import type { DefineStepRule } from '../../../../detections/pages/detection_engine/rules/types'; import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; -import { isEsqlRule } from '../../../../../common/detection_engine/utils'; +import { isEsqlRule, isMlRule } from '../../../../../common/detection_engine/utils'; /** * transforms DefineStepRule fields according to experimental feature flags @@ -16,6 +16,9 @@ import { isEsqlRule } from '../../../../../common/detection_engine/utils'; export const useExperimentalFeatureFieldsTransform = <T extends Partial<DefineStepRule>>(): (( fields: T ) => T) => { + const isAlertSuppressionForMachineLearningRuleEnabled = useIsExperimentalFeatureEnabled( + 'alertSuppressionForMachineLearningRuleEnabled' + ); const isAlertSuppressionForEsqlRuleEnabled = useIsExperimentalFeatureEnabled( 'alertSuppressionForEsqlRuleEnabled' ); @@ -23,7 +26,8 @@ export const useExperimentalFeatureFieldsTransform = <T extends Partial<DefineSt const transformer = useCallback( (fields: T) => { const isSuppressionDisabled = - isEsqlRule(fields.ruleType) && !isAlertSuppressionForEsqlRuleEnabled; + (isMlRule(fields.ruleType) && !isAlertSuppressionForMachineLearningRuleEnabled) || + (isEsqlRule(fields.ruleType) && !isAlertSuppressionForEsqlRuleEnabled); // reset any alert suppression values hidden behind feature flag if (isSuppressionDisabled) { @@ -38,7 +42,7 @@ export const useExperimentalFeatureFieldsTransform = <T extends Partial<DefineSt return fields; }, - [isAlertSuppressionForEsqlRuleEnabled] + [isAlertSuppressionForEsqlRuleEnabled, isAlertSuppressionForMachineLearningRuleEnabled] ); return transformer; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts index b61cdbc386ee1..5154f0aaffba3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts @@ -587,6 +587,32 @@ describe('helpers', () => { expect(result).toEqual(expected); }); + + it('returns suppression fields for machine_learning rules', () => { + const mockStepData: DefineStepRule = { + ...mockData, + ruleType: 'machine_learning', + machineLearningJobId: ['some_jobert_id'], + anomalyThreshold: 44, + groupByFields: ['event.type'], + groupByRadioSelection: GroupByOptions.PerTimePeriod, + groupByDuration: { value: 10, unit: 'm' }, + }; + const result = formatDefineStepData(mockStepData); + + const expected: DefineStepRuleJson = { + machine_learning_job_id: ['some_jobert_id'], + anomaly_threshold: 44, + type: 'machine_learning', + alert_suppression: { + group_by: ['event.type'], + duration: { value: 10, unit: 'm' }, + missing_fields_strategy: 'suppress', + }, + }; + + expect(result).toEqual(expect.objectContaining(expected)); + }); }); describe('formatScheduleStepData', () => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts index f281b3b6b4a2b..8cda58eeeb541 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts @@ -439,6 +439,7 @@ export const formatDefineStepData = (defineStepData: DefineStepRule): DefineStep ? { anomaly_threshold: ruleFields.anomalyThreshold, machine_learning_job_id: ruleFields.machineLearningJobId, + ...alertSuppressionFields, } : isThresholdFields(ruleFields) ? { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_alert_suppression.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_alert_suppression.test.tsx index d12a5ff97d50a..fb00b73e88ffd 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_alert_suppression.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_alert_suppression.test.tsx @@ -37,18 +37,38 @@ describe('useAlertSuppression', () => { expect(result.current.isSuppressionEnabled).toBe(false); }); - it('should return isSuppressionEnabled false if ES|QL Feature Flag is disabled', () => { - const { result } = renderHook(() => useAlertSuppression('esql')); + describe('ML rules', () => { + it('is true if the feature flag is enabled', () => { + jest + .spyOn(useIsExperimentalFeatureEnabledMock, 'useIsExperimentalFeatureEnabled') + .mockReset() + .mockReturnValue(true); + const { result } = renderHook(() => useAlertSuppression('machine_learning')); - expect(result.current.isSuppressionEnabled).toBe(false); + expect(result.current.isSuppressionEnabled).toBe(true); + }); + + it('is false if the feature flag is disabled', () => { + const { result } = renderHook(() => useAlertSuppression('machine_learning')); + + expect(result.current.isSuppressionEnabled).toBe(false); + }); }); - it('should return isSuppressionEnabled true if ES|QL Feature Flag is enabled', () => { - jest - .spyOn(useIsExperimentalFeatureEnabledMock, 'useIsExperimentalFeatureEnabled') - .mockImplementation((flag) => flag === 'alertSuppressionForEsqlRuleEnabled'); - const { result } = renderHook(() => useAlertSuppression('esql')); + describe('ES|QL rules', () => { + it('should return isSuppressionEnabled false if ES|QL Feature Flag is disabled', () => { + const { result } = renderHook(() => useAlertSuppression('esql')); + + expect(result.current.isSuppressionEnabled).toBe(false); + }); + + it('should return isSuppressionEnabled true if ES|QL Feature Flag is enabled', () => { + jest + .spyOn(useIsExperimentalFeatureEnabledMock, 'useIsExperimentalFeatureEnabled') + .mockImplementation((flag) => flag === 'alertSuppressionForEsqlRuleEnabled'); + const { result } = renderHook(() => useAlertSuppression('esql')); - expect(result.current.isSuppressionEnabled).toBe(true); + expect(result.current.isSuppressionEnabled).toBe(true); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_alert_suppression.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_alert_suppression.tsx index 1c9f139633c8c..6d0ecefe8345d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_alert_suppression.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_alert_suppression.tsx @@ -6,7 +6,7 @@ */ import { useCallback } from 'react'; import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; -import { isSuppressibleAlertRule } from '../../../../common/detection_engine/utils'; +import { isMlRule, isSuppressibleAlertRule } from '../../../../common/detection_engine/utils'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; export interface UseAlertSuppressionReturn { @@ -14,6 +14,9 @@ export interface UseAlertSuppressionReturn { } export const useAlertSuppression = (ruleType: Type | undefined): UseAlertSuppressionReturn => { + const isAlertSuppressionForMachineLearningRuleEnabled = useIsExperimentalFeatureEnabled( + 'alertSuppressionForMachineLearningRuleEnabled' + ); const isAlertSuppressionForEsqlRuleEnabled = useIsExperimentalFeatureEnabled( 'alertSuppressionForEsqlRuleEnabled' ); @@ -27,8 +30,16 @@ export const useAlertSuppression = (ruleType: Type | undefined): UseAlertSuppres return isSuppressibleAlertRule(ruleType) && isAlertSuppressionForEsqlRuleEnabled; } + if (isMlRule(ruleType)) { + return isSuppressibleAlertRule(ruleType) && isAlertSuppressionForMachineLearningRuleEnabled; + } + return isSuppressibleAlertRule(ruleType); - }, [ruleType, isAlertSuppressionForEsqlRuleEnabled]); + }, [ + isAlertSuppressionForEsqlRuleEnabled, + isAlertSuppressionForMachineLearningRuleEnabled, + ruleType, + ]); return { isSuppressionEnabled: isSuppressionEnabledForRuleType(), diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_fields.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_fields.ts new file mode 100644 index 0000000000000..c0f34c5502f94 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_fields.ts @@ -0,0 +1,36 @@ +/* + * 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 { DataViewFieldBase } from '@kbn/es-query'; + +import { useRuleIndices } from './use_rule_indices'; +import { useFetchIndex } from '../../../common/containers/source'; + +interface UseRuleFieldParams { + machineLearningJobId?: string[]; + indexPattern?: string[]; +} + +interface UseRuleFieldsReturn { + loading: boolean; + fields: DataViewFieldBase[]; +} + +export const useRuleFields = ({ + machineLearningJobId, + indexPattern, +}: UseRuleFieldParams): UseRuleFieldsReturn => { + const { ruleIndices } = useRuleIndices(machineLearningJobId, indexPattern); + const [ + loading, + { + indexPatterns: { fields }, + }, + ] = useFetchIndex(ruleIndices); + + return { loading, fields }; +}; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index c1465be7e67e0..b88ca5ff6ab83 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -30,6 +30,7 @@ import { TIMESTAMP, } from '@kbn/rule-data-utils'; +import type { Type as RuleType } from '@kbn/securitysolution-io-ts-alerting-types'; import { lastValueFrom } from 'rxjs'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import type { DataTableModel } from '@kbn/securitysolution-data-table'; @@ -42,7 +43,13 @@ import { ALERT_NEW_TERMS, ALERT_RULE_INDICES, } from '../../../../common/field_maps/field_names'; -import { isEqlRule, isEsqlRule } from '../../../../common/detection_engine/utils'; +import { + isEqlRule, + isEsqlRule, + isMlRule, + isNewTermsRule, + isThresholdRule, +} from '../../../../common/detection_engine/utils'; import type { TimelineResult } from '../../../../common/api/timeline'; import { TimelineId } from '../../../../common/types/timeline'; import { TimelineStatus, TimelineType } from '../../../../common/api/timeline'; @@ -266,31 +273,16 @@ export const isEqlAlertWithGroupId = (ecsData: Ecs): boolean => { return isEql && groupId?.length > 0; }; -export const isThresholdAlert = (ecsData: Ecs): boolean => { - const ruleType = getField(ecsData, ALERT_RULE_TYPE); - return ( - ruleType === 'threshold' || - (Array.isArray(ruleType) && ruleType.length > 0 && ruleType[0] === 'threshold') - ); -}; - -export const isEqlAlert = (ecsData: Ecs): boolean => { +const getRuleType = (ecsData: Ecs): RuleType | undefined => { const ruleType = getField(ecsData, ALERT_RULE_TYPE); - return isEqlRule(ruleType) || (Array.isArray(ruleType) && isEqlRule(ruleType[0])); + return Array.isArray(ruleType) ? ruleType[0] : ruleType; }; -export const isEsqlAlert = (ecsData: Ecs): boolean => { - const ruleType = getField(ecsData, ALERT_RULE_TYPE); - return isEsqlRule(ruleType) || (Array.isArray(ruleType) && isEsqlRule(ruleType[0])); -}; - -export const isNewTermsAlert = (ecsData: Ecs): boolean => { - const ruleType = getField(ecsData, ALERT_RULE_TYPE); - return ( - ruleType === 'new_terms' || - (Array.isArray(ruleType) && ruleType.length > 0 && ruleType[0] === 'new_terms') - ); -}; +const isNewTermsAlert = (ecsData: Ecs): boolean => isNewTermsRule(getRuleType(ecsData)); +const isEsqlAlert = (ecsData: Ecs): boolean => isEsqlRule(getRuleType(ecsData)); +const isEqlAlert = (ecsData: Ecs): boolean => isEqlRule(getRuleType(ecsData)); +const isThresholdAlert = (ecsData: Ecs): boolean => isThresholdRule(getRuleType(ecsData)); +const isMlAlert = (ecsData: Ecs): boolean => isMlRule(getRuleType(ecsData)); const isSuppressedAlert = (ecsData: Ecs): boolean => { return getField(ecsData, ALERT_SUPPRESSION_DOCS_COUNT) != null; @@ -1035,7 +1027,12 @@ export const sendAlertToTimelineAction = async ({ getExceptionFilter ); // The Query field should remain unpopulated with the suppressed EQL/ES|QL alert. - } else if (isSuppressedAlert(ecsData) && !isEqlAlert(ecsData) && !isEsqlAlert(ecsData)) { + } else if ( + isSuppressedAlert(ecsData) && + !isEqlAlert(ecsData) && + !isEsqlAlert(ecsData) && + !isMlAlert(ecsData) + ) { return createSuppressedTimeline( ecsData, createTimeline, @@ -1106,7 +1103,12 @@ export const sendAlertToTimelineAction = async ({ } else if (isNewTermsAlert(ecsData)) { return createNewTermsTimeline(ecsData, createTimeline, noteContent, {}, getExceptionFilter); // The Query field should remain unpopulated with the suppressed EQL/ES|QL alert. - } else if (isSuppressedAlert(ecsData) && !isEqlAlert(ecsData) && !isEsqlAlert(ecsData)) { + } else if ( + isSuppressedAlert(ecsData) && + !isEqlAlert(ecsData) && + !isEsqlAlert(ecsData) && + !isMlAlert(ecsData) + ) { return createSuppressedTimeline(ecsData, createTimeline, noteContent, {}, getExceptionFilter); } else { let { dataProviders, filters } = buildTimelineDataProviderOrFilter( diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/es_serverless_resources/roles.yml b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/es_serverless_resources/roles.yml index 3bc3320b96026..c94d4a9a31d8e 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/es_serverless_resources/roles.yml +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/es_serverless_resources/roles.yml @@ -53,6 +53,7 @@ viewer: - ".fleet-actions*" - "risk-score.risk-score-*" - ".asset-criticality.asset-criticality-*" + - ".ml-anomalies-*" privileges: - read applications: @@ -119,6 +120,10 @@ editor: - "read" - "write" allow_restricted_indices: false + - names: + - ".ml-anomalies-*" + privileges: + - read applications: - application: "kibana-.kibana" privileges: @@ -174,6 +179,7 @@ t1_analyst: - ".fleet-actions*" - risk-score.risk-score-* - .asset-criticality.asset-criticality-* + - ".ml-anomalies-*" privileges: - read applications: @@ -222,6 +228,7 @@ t2_analyst: - .fleet-agents* - .fleet-actions* - risk-score.risk-score-* + - ".ml-anomalies-*" privileges: - read - names: @@ -284,6 +291,7 @@ t3_analyst: - .fleet-agents* - .fleet-actions* - risk-score.risk-score-* + - ".ml-anomalies-*" privileges: - read applications: @@ -349,6 +357,7 @@ threat_intelligence_analyst: - .fleet-agents* - .fleet-actions* - risk-score.risk-score-* + - ".ml-anomalies-*" privileges: - read applications: @@ -408,6 +417,7 @@ rule_author: - .fleet-agents* - .fleet-actions* - risk-score.risk-score-* + - ".ml-anomalies-*" privileges: - read applications: @@ -473,6 +483,7 @@ soc_manager: - .fleet-agents* - .fleet-actions* - risk-score.risk-score-* + - ".ml-anomalies-*" privileges: - read applications: @@ -534,6 +545,7 @@ detections_admin: - metrics-endpoint.metadata_current_* - .fleet-agents* - .fleet-actions* + - ".ml-anomalies-*" privileges: - read - names: @@ -592,6 +604,10 @@ platform_engineer: privileges: - read - write + - names: + - ".ml-anomalies-*" + privileges: + - read applications: - application: "kibana-.kibana" privileges: @@ -643,6 +659,7 @@ endpoint_operations_analyst: - .lists* - .items* - risk-score.risk-score-* + - ".ml-anomalies-*" privileges: - read - names: @@ -711,6 +728,7 @@ endpoint_policy_manager: - packetbeat-* - winlogbeat-* - risk-score.risk-score-* + - ".ml-anomalies-*" privileges: - read - names: diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.test.ts index 537d7b6abaf8a..5df02371befa2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.test.ts @@ -110,6 +110,51 @@ describe('rule_converters', () => { }); }); + describe('machine learning rules', () => { + test('should accept machine learning params when existing rule type is machine learning', () => { + const patchParams = { + anomaly_threshold: 5, + }; + const rule = getMlRuleParams(); + const patchedParams = patchTypeSpecificSnakeToCamel(patchParams, rule); + expect(patchedParams).toEqual( + expect.objectContaining({ + anomalyThreshold: 5, + }) + ); + }); + + test('should reject invalid machine learning params when existing rule type is machine learning', () => { + const patchParams = { + anomaly_threshold: 'invalid', + } as PatchRuleRequestBody; + const rule = getMlRuleParams(); + expect(() => patchTypeSpecificSnakeToCamel(patchParams, rule)).toThrowError( + 'anomaly_threshold: Expected number, received string' + ); + }); + + it('accepts suppression params', () => { + const patchParams = { + alert_suppression: { + group_by: ['agent.name'], + missing_fields_strategy: 'suppress' as const, + }, + }; + const rule = getMlRuleParams(); + const patchedParams = patchTypeSpecificSnakeToCamel(patchParams, rule); + + expect(patchedParams).toEqual( + expect.objectContaining({ + alertSuppression: { + groupBy: ['agent.name'], + missingFieldsStrategy: 'suppress', + }, + }) + ); + }); + }); + test('should accept threat match params when existing rule type is threat match', () => { const patchParams = { threat_indicator_path: 'my.indicator', @@ -298,29 +343,6 @@ describe('rule_converters', () => { ); }); - test('should accept machine learning params when existing rule type is machine learning', () => { - const patchParams = { - anomaly_threshold: 5, - }; - const rule = getMlRuleParams(); - const patchedParams = patchTypeSpecificSnakeToCamel(patchParams, rule); - expect(patchedParams).toEqual( - expect.objectContaining({ - anomalyThreshold: 5, - }) - ); - }); - - test('should reject invalid machine learning params when existing rule type is machine learning', () => { - const patchParams = { - anomaly_threshold: 'invalid', - } as PatchRuleRequestBody; - const rule = getMlRuleParams(); - expect(() => patchTypeSpecificSnakeToCamel(patchParams, rule)).toThrowError( - 'anomaly_threshold: Expected number, received string' - ); - }); - test('should accept new terms params when existing rule type is new terms', () => { const patchParams = { new_terms_fields: ['event.new_field'], @@ -344,6 +366,7 @@ describe('rule_converters', () => { ); }); }); + describe('typeSpecificCamelToSnake', () => { describe('EQL', () => { test('should accept EQL params when existing rule type is EQL', () => { @@ -396,6 +419,54 @@ describe('rule_converters', () => { ); }); }); + + describe('machine learning rules', () => { + it('accepts normal params', () => { + const params = { + anomalyThreshold: 74, + machineLearningJobId: ['job-1'], + }; + const ruleParams = { ...getMlRuleParams(), ...params }; + const transformedParams = typeSpecificCamelToSnake(ruleParams); + expect(transformedParams).toEqual( + expect.objectContaining({ + anomaly_threshold: 74, + machine_learning_job_id: ['job-1'], + }) + ); + }); + + it('accepts suppression params', () => { + const params = { + anomalyThreshold: 74, + machineLearningJobId: ['job-1'], + alertSuppression: { + groupBy: ['event.type'], + duration: { + value: 10, + unit: 'm', + } as AlertSuppressionDuration, + missingFieldsStrategy: 'suppress' as AlertSuppressionMissingFieldsStrategy, + }, + }; + const ruleParams = { ...getMlRuleParams(), ...params }; + const transformedParams = typeSpecificCamelToSnake(ruleParams); + expect(transformedParams).toEqual( + expect.objectContaining({ + anomaly_threshold: 74, + machine_learning_job_id: ['job-1'], + alert_suppression: { + group_by: ['event.type'], + duration: { + value: 10, + unit: 'm', + }, + missing_fields_strategy: 'suppress', + }, + }) + ); + }); + }); }); describe('commonParamsCamelToSnake', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts index 7aac52dfe52c4..db815f32fb5ed 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts @@ -191,6 +191,7 @@ export const typeSpecificSnakeToCamel = ( type: params.type, anomalyThreshold: params.anomaly_threshold, machineLearningJobId: normalizeMachineLearningJobIds(params.machine_learning_job_id), + alertSuppression: convertAlertSuppressionToCamel(params.alert_suppression), }; } case 'new_terms': { @@ -338,6 +339,8 @@ const patchMachineLearningParams = ( machineLearningJobId: params.machine_learning_job_id ? normalizeMachineLearningJobIds(params.machine_learning_job_id) : existingRule.machineLearningJobId, + alertSuppression: + convertAlertSuppressionToCamel(params.alert_suppression) ?? existingRule.alertSuppression, }; }; @@ -706,6 +709,7 @@ export const typeSpecificCamelToSnake = ( type: params.type, anomaly_threshold: params.anomalyThreshold, machine_learning_job_id: params.machineLearningJobId, + alert_suppression: convertAlertSuppressionToSnake(params.alertSuppression), }; } case 'new_terms': { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts index 48637e898dda3..b3000edf895dc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts @@ -268,6 +268,7 @@ export const MachineLearningSpecificRuleParams = z.object({ type: z.literal('machine_learning'), anomalyThreshold: AnomalyThreshold, machineLearningJobId: z.array(z.string()), + alertSuppression: AlertSuppressionCamel.optional(), }); export type MachineLearningRuleParams = BaseRuleParams & MachineLearningSpecificRuleParams; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts index ca0edac6fca4e..2d38b16e94b5f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts @@ -11,13 +11,15 @@ import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; import { SERVER_APP_ID } from '../../../../../common/constants'; import { MachineLearningRuleParams } from '../../rule_schema'; +import { getIsAlertSuppressionActive } from '../utils/get_is_alert_suppression_active'; import { mlExecutor } from './ml'; -import type { CreateRuleOptions, SecurityAlertType } from '../types'; +import type { CreateRuleOptions, SecurityAlertType, WrapSuppressedHits } from '../types'; +import { wrapSuppressedAlerts } from '../utils/wrap_suppressed_alerts'; export const createMlAlertType = ( createOptions: CreateRuleOptions ): SecurityAlertType<MachineLearningRuleParams, {}, {}, 'default'> => { - const { ml } = createOptions; + const { experimentalFeatures, ml, licensing } = createOptions; return { id: ML_RULE_TYPE_ID, name: 'Machine Learning Rule', @@ -56,11 +58,39 @@ export const createMlAlertType = ( wrapHits, exceptionFilter, unprocessedExceptions, + mergeStrategy, + alertTimestampOverride, + publicBaseUrl, + alertWithSuppression, + primaryTimestamp, + secondaryTimestamp, }, services, + spaceId, state, } = execOptions; + const isAlertSuppressionActive = await getIsAlertSuppressionActive({ + alertSuppression: completeRule.ruleParams.alertSuppression, + isFeatureDisabled: !experimentalFeatures.alertSuppressionForMachineLearningRuleEnabled, + licensing, + }); + + const wrapSuppressedHits: WrapSuppressedHits = (events, buildReasonMessage) => + wrapSuppressedAlerts({ + events, + spaceId, + completeRule, + mergeStrategy, + indicesToQuery: [], + buildReasonMessage, + alertTimestampOverride, + ruleExecutionLogger, + publicBaseUrl, + primaryTimestamp, + secondaryTimestamp, + }); + const result = await mlExecutor({ completeRule, tuple, @@ -72,6 +102,11 @@ export const createMlAlertType = ( wrapHits, exceptionFilter, unprocessedExceptions, + wrapSuppressedHits, + alertTimestampOverride, + alertWithSuppression, + isAlertSuppressionActive, + experimentalFeatures, }); return { ...result, state }; }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/ml.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/ml.test.ts index c357a7e077bb2..59a0204ef9545 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/ml.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/ml.test.ts @@ -9,6 +9,7 @@ import dateMath from '@kbn/datemath'; import type { RuleExecutorServicesMock } from '@kbn/alerting-plugin/server/mocks'; import { alertsMock } from '@kbn/alerting-plugin/server/mocks'; import { mlExecutor } from './ml'; +import type { ExperimentalFeatures } from '../../../../../common'; import { getCompleteRuleMock, getMlRuleParams } from '../../rule_schema/mocks'; import { getListClientMock } from '@kbn/lists-plugin/server/services/lists/list_client.mock'; import { findMlSignals } from './find_ml_signals'; @@ -21,6 +22,7 @@ jest.mock('./find_ml_signals'); jest.mock('./bulk_create_ml_signals'); describe('ml_executor', () => { + let mockExperimentalFeatures: jest.Mocked<ExperimentalFeatures>; let jobsSummaryMock: jest.Mock; let forceStartDatafeedsMock: jest.Mock; let stopDatafeedsMock: jest.Mock; @@ -37,6 +39,7 @@ describe('ml_executor', () => { const listClient = getListClientMock(); beforeEach(() => { + mockExperimentalFeatures = {} as jest.Mocked<ExperimentalFeatures>; jobsSummaryMock = jest.fn(); mlMock = mlPluginServerMock.createSetupContract(); mlMock.jobServiceProvider.mockReturnValue({ @@ -59,7 +62,7 @@ describe('ml_executor', () => { }); (bulkCreateMlSignals as jest.Mock).mockResolvedValue({ success: true, - bulkCreateDuration: 0, + bulkCreateDuration: 21, createdItemsCount: 0, errors: [], createdItems: [], @@ -80,6 +83,11 @@ describe('ml_executor', () => { wrapHits: jest.fn(), exceptionFilter: undefined, unprocessedExceptions: [], + wrapSuppressedHits: jest.fn(), + alertTimestampOverride: undefined, + alertWithSuppression: jest.fn(), + isAlertSuppressionActive: true, + experimentalFeatures: mockExperimentalFeatures, }) ).rejects.toThrow('ML plugin unavailable during rule execution'); }); @@ -97,6 +105,11 @@ describe('ml_executor', () => { wrapHits: jest.fn(), exceptionFilter: undefined, unprocessedExceptions: [], + wrapSuppressedHits: jest.fn(), + alertTimestampOverride: undefined, + alertWithSuppression: jest.fn(), + isAlertSuppressionActive: true, + experimentalFeatures: mockExperimentalFeatures, }); expect(ruleExecutionLogger.warn).toHaveBeenCalled(); expect(ruleExecutionLogger.warn.mock.calls[0][0]).toContain( @@ -125,6 +138,11 @@ describe('ml_executor', () => { wrapHits: jest.fn(), exceptionFilter: undefined, unprocessedExceptions: [], + wrapSuppressedHits: jest.fn(), + alertTimestampOverride: undefined, + alertWithSuppression: jest.fn(), + isAlertSuppressionActive: true, + experimentalFeatures: mockExperimentalFeatures, }); expect(ruleExecutionLogger.warn).toHaveBeenCalled(); expect(ruleExecutionLogger.warn.mock.calls[0][0]).toContain( @@ -149,9 +167,49 @@ describe('ml_executor', () => { wrapHits: jest.fn(), exceptionFilter: undefined, unprocessedExceptions: [], + wrapSuppressedHits: jest.fn(), + alertTimestampOverride: undefined, + alertWithSuppression: jest.fn(), + isAlertSuppressionActive: true, + experimentalFeatures: mockExperimentalFeatures, }); expect(result.userError).toEqual(true); expect(result.success).toEqual(false); expect(result.errors).toEqual(['my_test_job_name missing']); }); + + it('returns some timing information as part of the result', async () => { + // ensure our mock corresponds to the job that the rule uses + jobsSummaryMock.mockResolvedValue( + mlCompleteRule.ruleParams.machineLearningJobId.map((jobId) => ({ + id: jobId, + jobState: 'opened', + datafeedState: 'started', + })) + ); + + const result = await mlExecutor({ + completeRule: mlCompleteRule, + tuple, + ml: mlMock, + services: alertServices, + ruleExecutionLogger, + listClient, + bulkCreate: jest.fn(), + wrapHits: jest.fn(), + exceptionFilter: undefined, + unprocessedExceptions: [], + wrapSuppressedHits: jest.fn(), + alertTimestampOverride: undefined, + alertWithSuppression: jest.fn(), + isAlertSuppressionActive: true, + experimentalFeatures: mockExperimentalFeatures, + }); + + expect(result).toEqual( + expect.objectContaining({ + bulkCreateTimes: expect.arrayContaining([expect.any(Number)]), + }) + ); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/ml.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/ml.ts index 641a9dab05cb2..4b7de9b27a667 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/ml.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/ml.ts @@ -8,6 +8,7 @@ /* eslint require-atomic-updates: ["error", { "allowProperties": true }] */ import type { KibanaRequest } from '@kbn/core/server'; +import type { SuppressedAlertService } from '@kbn/rule-registry-plugin/server'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import type { AlertInstanceContext, @@ -17,11 +18,12 @@ import type { import type { ListClient } from '@kbn/lists-plugin/server'; import type { Filter } from '@kbn/es-query'; import { isJobStarted } from '../../../../../common/machine_learning/helpers'; +import type { ExperimentalFeatures } from '../../../../../common/experimental_features'; import type { CompleteRule, MachineLearningRuleParams } from '../../rule_schema'; import { bulkCreateMlSignals } from './bulk_create_ml_signals'; import { filterEventsAgainstList } from '../utils/large_list_filters/filter_events_against_list'; import { findMlSignals } from './find_ml_signals'; -import type { BulkCreate, RuleRangeTuple, WrapHits } from '../types'; +import type { BulkCreate, RuleRangeTuple, WrapHits, WrapSuppressedHits } from '../types'; import { addToSearchAfterReturn, createErrorsFromShard, @@ -33,6 +35,26 @@ import type { SetupPlugins } from '../../../../plugin'; import { withSecuritySpan } from '../../../../utils/with_security_span'; import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring'; import type { AnomalyResults } from '../../../machine_learning'; +import { bulkCreateSuppressedAlertsInMemory } from '../utils/bulk_create_suppressed_alerts_in_memory'; +import { buildReasonMessageForMlAlert } from '../utils/reason_formatters'; + +interface MachineLearningRuleExecutorParams { + completeRule: CompleteRule<MachineLearningRuleParams>; + tuple: RuleRangeTuple; + ml: SetupPlugins['ml']; + listClient: ListClient; + services: RuleExecutorServices<AlertInstanceState, AlertInstanceContext, 'default'>; + ruleExecutionLogger: IRuleExecutionLogForExecutors; + bulkCreate: BulkCreate; + wrapHits: WrapHits; + exceptionFilter: Filter | undefined; + unprocessedExceptions: ExceptionListItemSchema[]; + wrapSuppressedHits: WrapSuppressedHits; + alertTimestampOverride: Date | undefined; + alertWithSuppression: SuppressedAlertService; + isAlertSuppressionActive: boolean; + experimentalFeatures: ExperimentalFeatures; +} export const mlExecutor = async ({ completeRule, @@ -45,18 +67,12 @@ export const mlExecutor = async ({ wrapHits, exceptionFilter, unprocessedExceptions, -}: { - completeRule: CompleteRule<MachineLearningRuleParams>; - tuple: RuleRangeTuple; - ml: SetupPlugins['ml']; - listClient: ListClient; - services: RuleExecutorServices<AlertInstanceState, AlertInstanceContext, 'default'>; - ruleExecutionLogger: IRuleExecutionLogForExecutors; - bulkCreate: BulkCreate; - wrapHits: WrapHits; - exceptionFilter: Filter | undefined; - unprocessedExceptions: ExceptionListItemSchema[]; -}) => { + isAlertSuppressionActive, + wrapSuppressedHits, + alertTimestampOverride, + alertWithSuppression, + experimentalFeatures, +}: MachineLearningRuleExecutorParams) => { const result = createSearchAfterReturnType(); const ruleParams = completeRule.ruleParams; @@ -120,6 +136,7 @@ export const mlExecutor = async ({ return result; } + // TODO we add the max_signals warning _before_ filtering the anomalies against the exceptions list. Is that correct? if ( anomalyResults.hits.total && typeof anomalyResults.hits.total !== 'number' && @@ -140,17 +157,36 @@ export const mlExecutor = async ({ ruleExecutionLogger.debug(`Found ${anomalyCount} signals from ML anomalies`); } - const createResult = await bulkCreateMlSignals({ - anomalyHits: filteredAnomalyHits, - completeRule, - services, - ruleExecutionLogger, - id: completeRule.alertId, - signalsIndex: ruleParams.outputIndex, - bulkCreate, - wrapHits, - }); - addToSearchAfterReturn({ current: result, next: createResult }); + if (anomalyCount && isAlertSuppressionActive) { + await bulkCreateSuppressedAlertsInMemory({ + enrichedEvents: filteredAnomalyHits, + toReturn: result, + wrapHits, + bulkCreate, + services, + buildReasonMessage: buildReasonMessageForMlAlert, + ruleExecutionLogger, + tuple, + alertSuppression: completeRule.ruleParams.alertSuppression, + wrapSuppressedHits, + alertTimestampOverride, + alertWithSuppression, + experimentalFeatures, + }); + } else { + const createResult = await bulkCreateMlSignals({ + anomalyHits: filteredAnomalyHits, + completeRule, + services, + ruleExecutionLogger, + id: completeRule.alertId, + signalsIndex: ruleParams.outputIndex, + bulkCreate, + wrapHits, + }); + addToSearchAfterReturn({ current: result, next: createResult }); + } + const shardFailures = anomalyResults._shards.failures ?? []; const searchErrors = createErrorsFromShard({ errors: shardFailures, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts index 31aa1797234bf..8f7a50b195e4f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts @@ -37,7 +37,7 @@ import type { LicensingPluginSetup } from '@kbn/licensing-plugin/server'; import type { RuleResponseAction } from '../../../../common/api/detection_engine/model/rule_response_actions'; import type { ConfigType } from '../../../config'; import type { SetupPlugins } from '../../../plugin'; -import type { CompleteRule, EqlRuleParams, RuleParams, ThreatRuleParams } from '../rule_schema'; +import type { CompleteRule, RuleParams } from '../rule_schema'; import type { ExperimentalFeatures } from '../../../../common/experimental_features'; import type { ITelemetryEventsSender } from '../../telemetry/sender'; import type { IRuleExecutionLogForExecutors, IRuleMonitoringService } from '../rule_monitoring'; @@ -401,5 +401,3 @@ export interface OverrideBodyQuery { _source?: estypes.SearchSourceConfig; fields?: estypes.Fields; } - -export type RuleWithInMemorySuppression = ThreatRuleParams | EqlRuleParams; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/wrap_suppressed_alerts.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/wrap_suppressed_alerts.ts index 89328f176567d..70fee20116fc4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/wrap_suppressed_alerts.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/wrap_suppressed_alerts.ts @@ -9,14 +9,19 @@ import objectHash from 'object-hash'; import { TIMESTAMP } from '@kbn/rule-data-utils'; import type { SuppressionFieldsLatest } from '@kbn/rule-registry-plugin/common/schemas'; -import type { RuleWithInMemorySuppression, SignalSourceHit } from '../types'; +import type { SignalSourceHit } from '../types'; import type { BaseFieldsLatest, WrappedFieldsLatest, } from '../../../../../common/api/detection_engine/model/alerts'; import type { ConfigType } from '../../../../config'; -import type { CompleteRule } from '../../rule_schema'; +import type { + CompleteRule, + EqlRuleParams, + MachineLearningRuleParams, + ThreatRuleParams, +} from '../../rule_schema'; import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring'; import { buildBulkBody } from '../factories/utils/build_bulk_body'; import { getSuppressionAlertFields, getSuppressionTerms } from './suppression_utils'; @@ -24,6 +29,8 @@ import { generateId } from './utils'; import type { BuildReasonMessage } from './reason_formatters'; +type RuleWithInMemorySuppression = ThreatRuleParams | EqlRuleParams | MachineLearningRuleParams; + /** * wraps suppressed alerts * creates instanceId hash, which is used to search on time interval alerts diff --git a/x-pack/test/common/utils/security_solution/detections_response/delete_all_anomalies.ts b/x-pack/test/common/utils/security_solution/detections_response/delete_all_anomalies.ts new file mode 100644 index 0000000000000..1f9df710c5d5d --- /dev/null +++ b/x-pack/test/common/utils/security_solution/detections_response/delete_all_anomalies.ts @@ -0,0 +1,36 @@ +/* + * 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 { ToolingLog } from '@kbn/tooling-log'; +import type { Client } from '@elastic/elasticsearch'; + +import { countDownTest } from './count_down_test'; + +export const deleteAllAnomalies = async ( + log: ToolingLog, + es: Client, + index: string[] = ['.ml-anomalies-*'] +): Promise<void> => { + await countDownTest( + async () => { + await es.deleteByQuery({ + index, + body: { + query: { + match_all: {}, + }, + }, + refresh: true, + }); + return { + passed: true, + }; + }, + 'deleteAllAnomalies', + log + ); +}; diff --git a/x-pack/test/common/utils/security_solution/detections_response/index.ts b/x-pack/test/common/utils/security_solution/detections_response/index.ts index d6a06f8e57797..43c2a54900c15 100644 --- a/x-pack/test/common/utils/security_solution/detections_response/index.ts +++ b/x-pack/test/common/utils/security_solution/detections_response/index.ts @@ -7,6 +7,7 @@ export * from './rules'; export * from './alerts'; +export * from './delete_all_anomalies'; export * from './count_down_test'; export * from './route_with_namespace'; export * from './wait_for'; diff --git a/x-pack/test/functional/es_archives/security_solution/anomalies/mappings.json b/x-pack/test/functional/es_archives/security_solution/anomalies/mappings.json index 484e0f3fc9aa0..56a26b937a49b 100644 --- a/x-pack/test/functional/es_archives/security_solution/anomalies/mappings.json +++ b/x-pack/test/functional/es_archives/security_solution/anomalies/mappings.json @@ -2,22 +2,21 @@ "type": "index", "value": { "aliases": { - ".ml-anomalies-.write-linux_anomalous_network_activity_ecs": { + ".ml-anomalies-.write-v3_linux_anomalous_network_activity": { "is_hidden": true }, - ".ml-anomalies-linux_anomalous_network_activity_ecs": { + ".ml-anomalies-v3_linux_anomalous_network_activity": { "filter": { "term": { "job_id": { - "boost": 1, - "value": "linux_anomalous_network_activity_ecs" + "value": "v3_linux_anomalous_network_activity" } } }, "is_hidden": true } }, - "index": ".ml-anomalies-custom-linux_anomalous_network_activity_ecs", + "index": ".ml-anomalies-custom-v3_linux_anomalous_network_activity", "mappings": { "_meta": { "version": "8.0.0" diff --git a/x-pack/test/security_solution_api_integration/config/ess/config.base.ts b/x-pack/test/security_solution_api_integration/config/ess/config.base.ts index 7256432174e3c..a47e43bd426e6 100644 --- a/x-pack/test/security_solution_api_integration/config/ess/config.base.ts +++ b/x-pack/test/security_solution_api_integration/config/ess/config.base.ts @@ -84,6 +84,7 @@ export function createTestConfig(options: CreateTestConfigOptions, testFiles?: s 'riskScoringPersistence', 'riskScoringRoutesEnabled', 'bulkCustomHighlightedFieldsEnabled', + 'alertSuppressionForMachineLearningRuleEnabled', 'manualRuleRunEnabled', ])}`, '--xpack.task_manager.poll_interval=1000', diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/serverless.config.ts index 76c73ff71cc18..825d6a0e5833b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/serverless.config.ts @@ -19,6 +19,7 @@ export default createTestConfig({ ])}`, // See tests within the file "ignore_fields.ts" which use these values in "alertIgnoreFields" `--xpack.securitySolution.enableExperimental=${JSON.stringify([ 'bulkCustomHighlightedFieldsEnabled', + 'alertSuppressionForMachineLearningRuleEnabled', 'alertSuppressionForEsqlRuleEnabled', ])}`, ], diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/index.ts index 3ea2c4e6c9359..5d0e8f4db4061 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/index.ts @@ -14,6 +14,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./esql')); loadTestFile(require.resolve('./esql_suppression')); loadTestFile(require.resolve('./machine_learning')); + loadTestFile(require.resolve('./machine_learning_alert_suppression')); loadTestFile(require.resolve('./new_terms')); loadTestFile(require.resolve('./new_terms_alert_suppression')); loadTestFile(require.resolve('./saved_query')); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts index 3fb077df86a38..5d73249e576f4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts @@ -151,7 +151,7 @@ export default ({ getService }: FtrProviderContext) => { [SPACE_IDS]: ['default'], [ALERT_SEVERITY]: 'critical', [ALERT_RISK_SCORE]: 50, - [ALERT_RULE_PARAMETERS]: { + [ALERT_RULE_PARAMETERS]: expect.objectContaining({ anomaly_threshold: 30, author: [], description: 'Test ML rule description', @@ -174,7 +174,7 @@ export default ({ getService }: FtrProviderContext) => { to: 'now', type: 'machine_learning', version: 1, - }, + }), [ALERT_DEPTH]: 1, [ALERT_REASON]: `event with process store, by root on mothra created critical alert Test ML rule.`, [ALERT_ORIGINAL_TIME]: expect.any(String), diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning_alert_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning_alert_suppression.ts new file mode 100644 index 0000000000000..b29ce8abbb8ef --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning_alert_suppression.ts @@ -0,0 +1,1106 @@ +/* + * 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 { expect } from 'expect'; + +import { + MachineLearningRuleCreateProps, + RuleExecutionStatusEnum, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; +import type { Anomaly } from '@kbn/security-solution-plugin/server/lib/machine_learning'; +import { + ALERT_LAST_DETECTED, + ALERT_START, + ALERT_SUPPRESSION_DOCS_COUNT, + ALERT_SUPPRESSION_END, + ALERT_SUPPRESSION_START, + ALERT_SUPPRESSION_TERMS, + TIMESTAMP, +} from '@kbn/rule-data-utils'; +import { ALERT_ORIGINAL_TIME } from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { DETECTION_ENGINE_SIGNALS_STATUS_URL as DETECTION_ENGINE_ALERTS_STATUS_URL } from '@kbn/security-solution-plugin/common/constants'; +import { EsArchivePathBuilder } from '../../../../../../es_archive_path_builder'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { + dataGeneratorFactory, + executeSetupModuleRequest, + forceStartDatafeeds, + getAlerts, + getOpenAlerts, + getPreviewAlerts, + patchRule, + previewRule, + previewRuleWithExceptionEntries, + setAlertStatus, +} from '../../../../utils'; +import { + createRule, + deleteAllAlerts, + deleteAllAnomalies, + deleteAllRules, +} from '../../../../../../../common/utils/security_solution'; +import { deleteAllExceptions } from '../../../../../lists_and_exception_lists/utils'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + const log = getService('log'); + const config = getService('config'); + + const isServerless = config.get('serverless'); + const dataPathBuilder = new EsArchivePathBuilder(isServerless); + const auditbeatArchivePath = dataPathBuilder.getPath('auditbeat/hosts'); + + const { indexListOfDocuments } = dataGeneratorFactory({ + es, + index: '.ml-anomalies-custom-v3_linux_anomalous_network_activity', + log, + }); + + const mlModuleName = 'security_linux_v3'; + const mlJobId = 'v3_linux_anomalous_network_activity'; + const baseRuleProps: MachineLearningRuleCreateProps = { + name: 'Test ML rule', + description: 'Test ML rule description', + risk_score: 50, + severity: 'critical', + type: 'machine_learning', + anomaly_threshold: 40, + machine_learning_job_id: mlJobId, + from: '1900-01-01T00:00:00.000Z', + rule_id: 'ml-rule-id', + }; + let ruleProps: MachineLearningRuleCreateProps; + const baseAnomaly: Partial<Anomaly> = { + is_interim: false, + record_score: 43, // exceeds anomaly_threshold above + result_type: 'record', + job_id: mlJobId, + 'user.name': ['root'], + }; + + // The tests described in this file rely on the + // 'alertSuppressionForMachineLearningRuleEnabled' feature flag, and are thus + // skipped in MKI + describe('@ess @serverless @skipInServerlessMKI Machine Learning Detection Rule - Alert Suppression', () => { + describe('with an active ML Job', () => { + before(async () => { + // Order is critical here: auditbeat data must be loaded before attempting to start the ML job, + // as the job looks for certain indices on start + await esArchiver.load(auditbeatArchivePath); + await executeSetupModuleRequest({ module: mlModuleName, rspCode: 200, supertest }); + await forceStartDatafeeds({ jobId: mlJobId, rspCode: 200, supertest }); + await esArchiver.load('x-pack/test/functional/es_archives/security_solution/anomalies'); + await deleteAllAnomalies(log, es); + }); + + after(async () => { + await esArchiver.load(auditbeatArchivePath); + await esArchiver.unload('x-pack/test/functional/es_archives/security_solution/anomalies'); + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + await deleteAllAnomalies(log, es); + }); + + describe('with per-execution suppression duration', () => { + beforeEach(() => { + ruleProps = { + ...baseRuleProps, + alert_suppression: { + group_by: ['user.name'], + missing_fields_strategy: 'suppress', + }, + }; + }); + + it('performs no suppression if a single alert is generated', async () => { + const timestamp = new Date().toISOString(); + const anomaly = { + ...baseAnomaly, + timestamp, + }; + await indexListOfDocuments([anomaly]); + const createdRule = await createRule(supertest, log, ruleProps); + const alerts = await getAlerts(supertest, log, es, createdRule); + + expect(alerts.hits.hits).toHaveLength(1); + expect(alerts.hits.hits[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [{ field: 'user.name', value: ['root'] }], + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, + }) + ); + }); + + it('suppresses alerts within a single execution', async () => { + const timestamp = new Date().toISOString(); + const anomaly = { + ...baseAnomaly, + timestamp, + }; + await indexListOfDocuments([anomaly, anomaly]); + + const createdRule = await createRule(supertest, log, { + ...ruleProps, + from: timestamp, + }); + + const alerts = await getAlerts(supertest, log, es, createdRule); + expect(alerts.hits.hits).toHaveLength(1); + expect(alerts.hits.hits[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'user.name', + value: ['root'], + }, + ], + [ALERT_SUPPRESSION_START]: timestamp, + [ALERT_SUPPRESSION_END]: timestamp, + [ALERT_ORIGINAL_TIME]: timestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }) + ); + }); + + it('deduplicates previously suppressed alerts if rule has overlapping execution windows', async () => { + const firstTimestamp = new Date().toISOString(); + const firstAnomaly = { + ...baseAnomaly, + timestamp: firstTimestamp, + }; + await indexListOfDocuments([firstAnomaly]); + + const createdRule = await createRule(supertest, log, { + ...ruleProps, + from: firstTimestamp, + }); + const alerts = await getAlerts(supertest, log, es, createdRule); + + expect(alerts.hits.hits).toHaveLength(1); + expect(alerts.hits.hits[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'user.name', + value: ['root'], + }, + ], + // suppression boundaries equal to original event time, since no alert been suppressed + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: firstTimestamp, + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, + }) + ); + + const secondTimestamp = new Date().toISOString(); + const secondAnomaly = { + ...baseAnomaly, + timestamp: secondTimestamp, + }; + + // Add more anomalies, then disable and re-enable to trigger another + // rule run. The second anomaly should trigger an update to the + // existing alert without changing the timestamp + await indexListOfDocuments([secondAnomaly, secondAnomaly]); + await patchRule(supertest, log, { id: createdRule.id, enabled: false }); + await patchRule(supertest, log, { id: createdRule.id, enabled: true }); + const secondAlerts = await getOpenAlerts( + supertest, + log, + es, + createdRule, + RuleExecutionStatusEnum.succeeded, + undefined, + new Date() + ); + + expect(secondAlerts.hits.hits).toHaveLength(2); + expect(secondAlerts.hits.hits[1]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'user.name', + value: ['root'], + }, + ], + [ALERT_ORIGINAL_TIME]: secondTimestamp, + [ALERT_SUPPRESSION_START]: secondTimestamp, + [ALERT_SUPPRESSION_END]: secondTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, // 1 of the two new anomalies was suppressed on this execution + }) + ); + }); + }); + + describe('with interval suppression duration', () => { + beforeEach(() => { + ruleProps = { + ...baseRuleProps, + alert_suppression: { + duration: { + value: 300, + unit: 'm', + }, + group_by: ['user.name'], + missing_fields_strategy: 'suppress', + }, + }; + }); + + it('performs no suppression if a single alert is generated', async () => { + const timestamp = new Date().toISOString(); + const anomaly = { + ...baseAnomaly, + timestamp, + }; + await indexListOfDocuments([anomaly]); + const createdRule = await createRule(supertest, log, ruleProps); + const alerts = await getAlerts(supertest, log, es, createdRule); + + expect(alerts.hits.hits).toHaveLength(1); + expect(alerts.hits.hits[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [{ field: 'user.name', value: ['root'] }], + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, + }) + ); + }); + + it('suppresses alerts across two executions', async () => { + const firstTimestamp = new Date().toISOString(); + const firstAnomaly = { + ...baseAnomaly, + timestamp: firstTimestamp, + }; + await indexListOfDocuments([firstAnomaly]); + + const createdRule = await createRule(supertest, log, { + ...ruleProps, + from: firstTimestamp, + }); + const alerts = await getAlerts(supertest, log, es, createdRule); + + expect(alerts.hits.hits).toHaveLength(1); + expect(alerts.hits.hits[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'user.name', + value: ['root'], + }, + ], + // suppression boundaries equal to original event time, since no alert been suppressed + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: firstTimestamp, + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, + }) + ); + + const secondTimestamp = new Date().toISOString(); + const secondAnomaly = { + ...baseAnomaly, + timestamp: secondTimestamp, + }; + + // Add more anomalies, then disable and re-enable to trigger another + // rule run. The second anomaly should trigger an update to the + // existing alert without changing the timestamp + await indexListOfDocuments([secondAnomaly, secondAnomaly]); + await patchRule(supertest, log, { id: createdRule.id, enabled: false }); + await patchRule(supertest, log, { id: createdRule.id, enabled: true }); + const secondAlerts = await getOpenAlerts( + supertest, + log, + es, + createdRule, + RuleExecutionStatusEnum.succeeded, + undefined, + new Date() + ); + + expect(secondAlerts.hits.hits).toHaveLength(1); + expect(secondAlerts.hits.hits[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'user.name', + value: ['root'], + }, + ], + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: secondTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 2, // 1 of the two new anomalies was suppressed on this execution + }) + ); + }); + + describe('with anomalies spanning multiple rule execution windows', () => { + const firstTimestamp = '2020-10-28T05:45:00.000Z'; + const secondTimestamp = '2020-10-28T06:15:00.000Z'; + const thirdTimestamp = '2020-10-28T06:45:00.000Z'; + const afterThirdTimestamp = '2020-10-28T07:00:00.000Z'; + + beforeEach(async () => { + const firstAnomaly = { + ...baseAnomaly, + timestamp: firstTimestamp, + }; + const secondAnomaly = { + ...baseAnomaly, + timestamp: secondTimestamp, + }; + const thirdAnomaly = { + ...baseAnomaly, + timestamp: thirdTimestamp, + }; + + await indexListOfDocuments([ + firstAnomaly, + firstAnomaly, + secondAnomaly, + secondAnomaly, + thirdAnomaly, + ]); + }); + + it('suppresses alerts across three executions', async () => { + const rule = { ...ruleProps, interval: '30m' }; + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date(afterThirdTimestamp), + invocationCount: 3, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: [ALERT_ORIGINAL_TIME], + }); + + expect(previewAlerts.length).toEqual(1); + expect(previewAlerts[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'user.name', + value: ['root'], + }, + ], + [TIMESTAMP]: '2020-10-28T06:00:00.000Z', + [ALERT_LAST_DETECTED]: afterThirdTimestamp, + [ALERT_START]: '2020-10-28T06:00:00.000Z', + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: thirdTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 4, // in total 4 alert got suppressed: 1 from the first run, 2 from the second, 1 from the third + }) + ); + }); + + it('suppresses alerts across multiple, sparse executions', async () => { + const fifthTimestamp = '2020-10-28T07:45:00.000Z'; + const afterFifthTimestamp = '2020-10-28T08:00:00.000Z'; + const fifthAnomaly = { ...baseAnomaly, timestamp: fifthTimestamp }; + // no anomaly for fourth execution + await indexListOfDocuments([fifthAnomaly]); + + const rule = { ...ruleProps, interval: '30m' }; + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date(afterFifthTimestamp), + invocationCount: 5, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: [ALERT_ORIGINAL_TIME], + }); + + expect(previewAlerts.length).toEqual(1); + expect(previewAlerts[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'user.name', + value: ['root'], + }, + ], + [TIMESTAMP]: '2020-10-28T06:00:00.000Z', + [ALERT_LAST_DETECTED]: afterFifthTimestamp, + [ALERT_START]: '2020-10-28T06:00:00.000Z', + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: fifthTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 5, // in total 5 alerts were suppressed: 1 from the first run, 2 from the second, 1 from the third run, none from the fourth, and one from the fifth. + }) + ); + }); + }); + + it('suppresses alerts on multiple fields', async () => { + const timestamp = new Date().toISOString(); + const anomaly = { + ...baseAnomaly, + timestamp, + 'process.name': ['auditbeat'], + }; + await indexListOfDocuments([anomaly, anomaly]); + + const rule = { + ...ruleProps, + alert_suppression: { + ...ruleProps.alert_suppression, + group_by: ['user.name', 'process.name'], + }, + }; + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date(timestamp), + invocationCount: 1, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: [ALERT_ORIGINAL_TIME], + }); + + expect(previewAlerts.length).toEqual(1); + expect(previewAlerts[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'user.name', + value: ['root'], + }, + { + field: 'process.name', + value: ['auditbeat'], + }, + ], + [TIMESTAMP]: timestamp, + [ALERT_START]: timestamp, + [ALERT_ORIGINAL_TIME]: timestamp, + [ALERT_SUPPRESSION_START]: timestamp, + [ALERT_SUPPRESSION_END]: timestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }) + ); + }); + + it('suppresses alerts with missing fields, if configured to do so', async () => { + const timestamp = new Date().toISOString(); + const anomaly = { + ...baseAnomaly, + timestamp, + 'host.name': ['relevant'], + }; + const anomalyWithoutSuppressionField = { + ...baseAnomaly, + timestamp, + }; + await indexListOfDocuments([anomaly, anomaly, anomalyWithoutSuppressionField]); + + const rule = { + ...ruleProps, + alert_suppression: { + ...ruleProps.alert_suppression, + group_by: ['host.name'], + }, + }; + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date(timestamp), + invocationCount: 1, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: [ALERT_SUPPRESSION_DOCS_COUNT], + }); + + expect(previewAlerts.length).toEqual(2); + expect(previewAlerts[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: null, + }, + ], + [TIMESTAMP]: timestamp, + [ALERT_START]: timestamp, + [ALERT_ORIGINAL_TIME]: timestamp, + [ALERT_SUPPRESSION_START]: timestamp, + [ALERT_SUPPRESSION_END]: timestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, + }) + ); + + expect(previewAlerts[1]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['relevant'], + }, + ], + [TIMESTAMP]: timestamp, + [ALERT_START]: timestamp, + [ALERT_ORIGINAL_TIME]: timestamp, + [ALERT_SUPPRESSION_START]: timestamp, + [ALERT_SUPPRESSION_END]: timestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, // the anomaly without `host.name` is not represented here + }) + ); + }); + + it('does not suppress alerts with missing fields, if not configured to do so', async () => { + const rule = { + ...ruleProps, + alert_suppression: { + ...ruleProps.alert_suppression, + group_by: ['host.name'], + missing_fields_strategy: 'doNotSuppress' as const, + }, + }; + const timestamp = new Date().toISOString(); + const anomaly = { + ...baseAnomaly, + timestamp, + 'host.name': ['relevant'], + }; + const anomalyWithoutSuppressionField = { + ...baseAnomaly, + timestamp, + 'user.name': ['irrelevant'], + }; + await indexListOfDocuments([ + anomaly, + anomaly, + anomalyWithoutSuppressionField, + anomalyWithoutSuppressionField, + ]); + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date(timestamp), + invocationCount: 1, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: [ALERT_ORIGINAL_TIME], + }); + + expect(previewAlerts.length).toEqual(3); + expect(previewAlerts[0]._source).toEqual( + expect.objectContaining({ + 'user.name': ['irrelevant'], + [TIMESTAMP]: timestamp, + [ALERT_START]: timestamp, + }) + ); + + expect(previewAlerts[0]._source).toEqual( + expect.not.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: expect.anything(), + [ALERT_ORIGINAL_TIME]: expect.anything(), + [ALERT_SUPPRESSION_START]: expect.anything(), + [ALERT_SUPPRESSION_END]: expect.anything(), + [ALERT_SUPPRESSION_DOCS_COUNT]: expect.anything(), + }) + ); + + expect(previewAlerts[1]._source).toEqual( + expect.objectContaining({ + 'user.name': ['irrelevant'], + [TIMESTAMP]: timestamp, + [ALERT_START]: timestamp, + }) + ); + expect(previewAlerts[1]._source).toEqual( + expect.not.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: expect.anything(), + [ALERT_ORIGINAL_TIME]: expect.anything(), + [ALERT_SUPPRESSION_START]: expect.anything(), + [ALERT_SUPPRESSION_END]: expect.anything(), + [ALERT_SUPPRESSION_DOCS_COUNT]: expect.anything(), + }) + ); + + expect(previewAlerts[2]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['relevant'], + }, + ], + [TIMESTAMP]: timestamp, + [ALERT_START]: timestamp, + [ALERT_ORIGINAL_TIME]: timestamp, + [ALERT_SUPPRESSION_START]: timestamp, + [ALERT_SUPPRESSION_END]: timestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, // the anomaly without `host.name` is not represented here + }) + ); + }); + + it('does not suppress into a closed alert', async () => { + const firstTimestamp = new Date().toISOString(); + const firstAnomaly = { + ...baseAnomaly, + timestamp: firstTimestamp, + }; + await indexListOfDocuments([firstAnomaly]); + + const createdRule = await createRule(supertest, log, { + ...ruleProps, + from: firstTimestamp, + }); + const alerts = await getAlerts(supertest, log, es, createdRule); + + expect(alerts.hits.hits).toHaveLength(1); + const alertId = alerts.hits.hits[0]._id!; + + // close generated alert + await supertest + .post(DETECTION_ENGINE_ALERTS_STATUS_URL) + .set('kbn-xsrf', 'true') + .send(setAlertStatus({ alertIds: [alertId], status: 'closed' })) + .expect(200); + + const secondTimestamp = new Date().toISOString(); + const secondAnomaly = { + ...baseAnomaly, + timestamp: secondTimestamp, + }; + + // Add more anomalies, then disable and re-enable to trigger another + // rule run. The second anomalies should create a new alert, since the existing alert is closed. + await indexListOfDocuments([secondAnomaly, secondAnomaly]); + await patchRule(supertest, log, { id: createdRule.id, enabled: false }); + await patchRule(supertest, log, { id: createdRule.id, enabled: true }); + const secondAlerts = await getOpenAlerts( + supertest, + log, + es, + createdRule, + RuleExecutionStatusEnum.succeeded, + undefined, + new Date() + ); + + expect(secondAlerts.hits.hits).toHaveLength(1); + expect(secondAlerts.hits.hits[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'user.name', + value: ['root'], + }, + ], + [ALERT_ORIGINAL_TIME]: secondTimestamp, + [ALERT_SUPPRESSION_START]: secondTimestamp, + [ALERT_SUPPRESSION_END]: secondTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }) + ); + }); + + it('does not suppress into an unsuppressed alert', async () => { + const firstTimestamp = new Date().toISOString(); + const firstAnomaly = { + ...baseAnomaly, + timestamp: firstTimestamp, + }; + await indexListOfDocuments([firstAnomaly]); + + const ruleWithoutSuppression = { ...ruleProps, alert_suppression: undefined }; + const createdRule = await createRule(supertest, log, { + ...ruleWithoutSuppression, + from: firstTimestamp, + }); + const alerts = await getAlerts(supertest, log, es, createdRule); + + expect(alerts.hits.hits).toHaveLength(1); + + // update the rule to include suppression + await patchRule(supertest, log, { + id: createdRule.id, + alert_suppression: ruleProps.alert_suppression, + }); + + const secondTimestamp = new Date().toISOString(); + const secondAnomaly = { + ...baseAnomaly, + timestamp: secondTimestamp, + }; + + // Add more anomalies, then disable and re-enable to trigger another + // rule run. The second anomalies should create a new suppressed alert, since the original was not suppressed. + await indexListOfDocuments([secondAnomaly, secondAnomaly, secondAnomaly]); + await patchRule(supertest, log, { id: createdRule.id, enabled: false }); + await patchRule(supertest, log, { id: createdRule.id, enabled: true }); + const secondAlerts = await getOpenAlerts( + supertest, + log, + es, + createdRule, + RuleExecutionStatusEnum.succeeded, + undefined, + new Date() + ); + + expect(secondAlerts.hits.hits).toHaveLength(2); + // assert that the first alert does not have suppression fields + expect(secondAlerts.hits.hits[0]._source).toEqual( + expect.not.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: expect.anything(), + [ALERT_ORIGINAL_TIME]: expect.anything(), + [ALERT_SUPPRESSION_START]: expect.anything(), + [ALERT_SUPPRESSION_END]: expect.anything(), + [ALERT_SUPPRESSION_DOCS_COUNT]: expect.anything(), + }) + ); + + expect(secondAlerts.hits.hits[1]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'user.name', + value: ['root'], + }, + ], + [ALERT_ORIGINAL_TIME]: secondTimestamp, + [ALERT_SUPPRESSION_START]: secondTimestamp, + [ALERT_SUPPRESSION_END]: secondTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 2, + }) + ); + }); + + it('suppresses alerts that would be _created_ within the suppression duration window, even if the original anomalies were outside that suppression duration window', async () => { + const rule = { + ...ruleProps, + interval: '30m', + alert_suppression: { + ...ruleProps.alert_suppression, + duration: { + value: 1, + unit: 'm', + }, + }, + } as MachineLearningRuleCreateProps; + const firstTimestamp = '2020-10-28T06:00:00.000Z'; + const secondTimestamp = '2020-10-28T06:15:00.000Z'; + const firstAnomaly = { ...baseAnomaly, timestamp: firstTimestamp }; + const secondAnomaly = { ...baseAnomaly, timestamp: secondTimestamp }; + await indexListOfDocuments([firstAnomaly, secondAnomaly]); + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date(secondTimestamp), + invocationCount: 1, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: [ALERT_ORIGINAL_TIME], + }); + + expect(previewAlerts.length).toEqual(1); + expect(previewAlerts[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'user.name', + value: ['root'], + }, + ], + [TIMESTAMP]: secondTimestamp, + [ALERT_LAST_DETECTED]: secondTimestamp, + [ALERT_START]: secondTimestamp, + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: secondTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }) + ); + }); + + it('does not suppress across multiple runs if the suppression interval is less than the rule interval ', async () => { + const rule = { + ...ruleProps, + interval: '5m', + alert_suppression: { + ...ruleProps.alert_suppression, + duration: { + value: 1, + unit: 'm', + }, + }, + } as MachineLearningRuleCreateProps; + const firstTimestamp = '2020-10-28T06:00:00.000Z'; + const secondTimestamp = '2020-10-28T06:15:00.000Z'; + const firstAnomaly = { ...baseAnomaly, timestamp: firstTimestamp }; + const secondAnomaly = { ...baseAnomaly, timestamp: secondTimestamp }; + await indexListOfDocuments([firstAnomaly, secondAnomaly]); + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date(secondTimestamp), + invocationCount: 3, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: [ALERT_ORIGINAL_TIME], + }); + + expect(previewAlerts.length).toEqual(2); + expect(previewAlerts[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'user.name', + value: ['root'], + }, + ], + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: firstTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, + }) + ); + expect(previewAlerts[1]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'user.name', + value: ['root'], + }, + ], + [ALERT_ORIGINAL_TIME]: secondTimestamp, + [ALERT_SUPPRESSION_START]: secondTimestamp, + [ALERT_SUPPRESSION_END]: secondTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, + }) + ); + }); + + it('suppresses alerts within a single execution', async () => { + const timestamp = new Date().toISOString(); + const anomaly = { + ...baseAnomaly, + timestamp, + }; + await indexListOfDocuments([anomaly, anomaly]); + + const createdRule = await createRule(supertest, log, { + ...ruleProps, + from: timestamp, + }); + + const alerts = await getAlerts(supertest, log, es, createdRule); + expect(alerts.hits.hits).toHaveLength(1); + expect(alerts.hits.hits[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'user.name', + value: ['root'], + }, + ], + [ALERT_SUPPRESSION_START]: timestamp, + [ALERT_SUPPRESSION_END]: timestamp, + [ALERT_ORIGINAL_TIME]: timestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }) + ); + }); + + it('deduplicates previously suppressed alerts if rule has overlapping execution windows', async () => { + const firstTimestamp = new Date().toISOString(); + const firstAnomaly = { + ...baseAnomaly, + timestamp: firstTimestamp, + }; + await indexListOfDocuments([firstAnomaly]); + + const createdRule = await createRule(supertest, log, { + ...ruleProps, + from: firstTimestamp, + }); + const alerts = await getAlerts(supertest, log, es, createdRule); + + expect(alerts.hits.hits).toHaveLength(1); + expect(alerts.hits.hits[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'user.name', + value: ['root'], + }, + ], + // suppression boundaries equal to original event time, since no alert been suppressed + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: firstTimestamp, + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, + }) + ); + + const secondTimestamp = new Date().toISOString(); + const secondAnomaly = { + ...baseAnomaly, + timestamp: secondTimestamp, + }; + + // Add more anomalies, then disable and re-enable to trigger another + // rule run. The second anomaly should trigger an update to the + // existing alert without changing the timestamp + await indexListOfDocuments([secondAnomaly, secondAnomaly]); + await patchRule(supertest, log, { id: createdRule.id, enabled: false }); + await patchRule(supertest, log, { id: createdRule.id, enabled: true }); + const secondAlerts = await getOpenAlerts( + supertest, + log, + es, + createdRule, + RuleExecutionStatusEnum.succeeded, + undefined, + new Date() + ); + + expect(secondAlerts.hits.hits).toHaveLength(1); + expect(secondAlerts.hits.hits[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'user.name', + value: ['root'], + }, + ], + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: secondTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 2, // both new anomalies were suppressed into the original + }) + ); + }); + + it('suppresses alerts with array field values', async () => { + const timestamp = new Date().toISOString(); + const anomaly = { + ...baseAnomaly, + 'user.name': ['host1', 'host2'], + timestamp, + }; + await indexListOfDocuments([anomaly, anomaly]); + + const createdRule = await createRule(supertest, log, { + ...ruleProps, + from: timestamp, + }); + + const alerts = await getAlerts(supertest, log, es, createdRule); + expect(alerts.hits.hits).toHaveLength(1); + expect(alerts.hits.hits[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'user.name', + value: ['host1', 'host2'], + }, + ], + [ALERT_SUPPRESSION_START]: timestamp, + [ALERT_SUPPRESSION_END]: timestamp, + [ALERT_ORIGINAL_TIME]: timestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }) + ); + }); + + describe('with exceptions', () => { + beforeEach(async () => { + await deleteAllExceptions(supertest, log); + }); + + it('applies exceptions before suppression', async () => { + const timestamp = new Date().toISOString(); + const anomaly = { + ...baseAnomaly, + timestamp, + }; + const anomalyWithExceptionField = { + ...anomaly, + 'process.name': ['auditbeat'], + }; + await indexListOfDocuments([anomaly, anomalyWithExceptionField]); + + const { previewId } = await previewRuleWithExceptionEntries({ + supertest, + rule: ruleProps, + log, + timeframeEnd: new Date(timestamp), + entries: [ + [ + { + field: 'process.name', + operator: 'included', + type: 'match', + value: 'auditbeat', + }, + ], + ], + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: [ALERT_ORIGINAL_TIME], + }); + + expect(previewAlerts.length).toEqual(1); + expect(previewAlerts[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'user.name', + value: ['root'], + }, + ], + [TIMESTAMP]: timestamp, + [ALERT_START]: timestamp, + [ALERT_ORIGINAL_TIME]: timestamp, + [ALERT_SUPPRESSION_START]: timestamp, + [ALERT_SUPPRESSION_END]: timestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, // the anomaly with the exception field was not suppressed but omitted due to the exception + }) + ); + }); + }); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/machine_learning/machine_learning_setup.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/machine_learning/machine_learning_setup.ts index a9b9bf1c8ce5b..fa0c6fa4f78b5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/machine_learning/machine_learning_setup.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/machine_learning/machine_learning_setup.ts @@ -6,6 +6,7 @@ */ import type SuperTest from 'supertest'; +import { ML_GROUP_ID } from '@kbn/security-solution-plugin/common/constants'; import { getCommonRequestHeader } from '../../../../../functional/services/ml/common_api'; export const executeSetupModuleRequest = async ({ @@ -22,7 +23,7 @@ export const executeSetupModuleRequest = async ({ .set(getCommonRequestHeader('1')) .send({ prefix: '', - groups: ['auditbeat'], + groups: [ML_GROUP_ID], indexPatternName: 'auditbeat-*', startDatafeed: false, useDedicatedIndex: true, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/remove_server_generated_properties_including_rule_id.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/remove_server_generated_properties_including_rule_id.ts index 1b57b5663ec23..176ce575a6457 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/remove_server_generated_properties_including_rule_id.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/remove_server_generated_properties_including_rule_id.ts @@ -7,7 +7,10 @@ import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { removeServerGeneratedProperties } from './remove_server_generated_properties'; +import { + removeServerGeneratedProperties, + type RuleWithoutServerGeneratedProperties, +} from './remove_server_generated_properties'; /** * This will remove server generated properties such as date times, etc... including the rule_id @@ -15,9 +18,8 @@ import { removeServerGeneratedProperties } from './remove_server_generated_prope */ export const removeServerGeneratedPropertiesIncludingRuleId = ( rule: RuleResponse -): Partial<RuleResponse> => { +): Omit<RuleWithoutServerGeneratedProperties, 'rule_id'> => { const ruleWithRemovedProperties = removeServerGeneratedProperties(rule); - // eslint-disable-next-line @typescript-eslint/naming-convention - const { rule_id, ...additionalRuledIdRemoved } = ruleWithRemovedProperties; + const { rule_id: _, ...additionalRuledIdRemoved } = ruleWithRemovedProperties; return additionalRuledIdRemoved; }; diff --git a/x-pack/test/security_solution_cypress/config.ts b/x-pack/test/security_solution_cypress/config.ts index 6e65ab15324a6..092fe4b79d38f 100644 --- a/x-pack/test/security_solution_cypress/config.ts +++ b/x-pack/test/security_solution_cypress/config.ts @@ -47,6 +47,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--xpack.securitySolution.enableExperimental=${JSON.stringify([ 'alertSuppressionForEsqlRuleEnabled', 'bulkCustomHighlightedFieldsEnabled', + 'alertSuppressionForMachineLearningRuleEnabled', 'manualRuleRunEnabled', ])}`, // mock cloud to enable the guided onboarding tour in e2e tests diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_suppression_serverless_essentials.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_suppression_serverless_essentials.cy.ts index d6f23687cf418..946e0190bc1f8 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_suppression_serverless_essentials.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_suppression_serverless_essentials.cy.ts @@ -15,6 +15,7 @@ import { login } from '../../../../tasks/login'; import { visit } from '../../../../tasks/navigation'; import { ALERT_SUPPRESSION_FIELDS_INPUT, + MACHINE_LEARNING_TYPE, THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX, } from '../../../../screens/create_new_rule'; import { CREATE_RULE_URL } from '../../../../urls/navigation'; @@ -22,7 +23,7 @@ import { CREATE_RULE_URL } from '../../../../urls/navigation'; describe( 'Detection rules, Alert Suppression for Essentials tier', { - // skipped in MKI as it depends on feature flag alertSuppressionForEsqlRuleEnabled + // skipped in MKI as it depends on feature flag alertSuppressionForEsqlRuleEnabled, alertSuppressionForMachineLearningRuleEnabled tags: ['@serverless', '@skipInServerlessMKI'], env: { ftrConfig: { @@ -35,6 +36,7 @@ describe( kbnServerArgs: [ `--xpack.securitySolution.enableExperimental=${JSON.stringify([ 'alertSuppressionForEsqlRuleEnabled', + 'alertSuppressionForMachineLearningRuleEnabled', ])}`, ], }, @@ -60,6 +62,9 @@ describe( selectEsqlRuleType(); cy.get(ALERT_SUPPRESSION_FIELDS_INPUT).should('be.enabled'); + + // ML Rules require Complete tier + cy.get(MACHINE_LEARNING_TYPE).get('button').should('be.disabled'); }); } ); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_supression_ess_basic.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_supression_ess_basic.cy.ts index 1f86d6d0dd789..a4e7a7dabb5fe 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_supression_ess_basic.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_supression_ess_basic.cy.ts @@ -8,6 +8,7 @@ import { THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX, ALERT_SUPPRESSION_DURATION_INPUT, + MACHINE_LEARNING_TYPE, } from '../../../../screens/create_new_rule'; import { @@ -52,6 +53,9 @@ describe( selectEsqlRuleType(); openSuppressionFieldsTooltipAndCheckLicense(); + // ML Rules require Platinum license + cy.get(MACHINE_LEARNING_TYPE).get('button').should('be.disabled'); + selectThresholdRuleType(); cy.get(THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX).should('be.disabled'); cy.get(THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX).parent().trigger('mouseover'); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/machine_learning_rule_suppression.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/machine_learning_rule_suppression.cy.ts new file mode 100644 index 0000000000000..befa75fce93ff --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/machine_learning_rule_suppression.cy.ts @@ -0,0 +1,198 @@ +/* + * 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 { getMachineLearningRule } from '../../../../objects/rule'; +import { TOOLTIP } from '../../../../screens/common'; +import { + ALERT_SUPPRESSION_FIELDS, + ALERT_SUPPRESSION_FIELDS_INPUT, +} from '../../../../screens/create_new_rule'; +import { + DEFINITION_DETAILS, + DETAILS_TITLE, + SUPPRESS_BY_DETAILS, + SUPPRESS_FOR_DETAILS, + SUPPRESS_MISSING_FIELD, +} from '../../../../screens/rule_details'; +import { + executeSetupModuleRequest, + forceStartDatafeeds, + forceStopAndCloseJob, +} from '../../../../support/machine_learning'; +import { + continueFromDefineStep, + fillAlertSuppressionFields, + fillDefineMachineLearningRule, + selectMachineLearningRuleType, + selectAlertSuppressionPerInterval, + setAlertSuppressionDuration, + selectDoNotSuppressForMissingFields, + skipScheduleRuleAction, + fillAboutRuleMinimumAndContinue, + createRuleWithoutEnabling, +} from '../../../../tasks/create_new_rule'; +import { login } from '../../../../tasks/login'; +import { visit } from '../../../../tasks/navigation'; +import { getDetails } from '../../../../tasks/rule_details'; +import { CREATE_RULE_URL } from '../../../../urls/navigation'; + +describe( + 'Machine Learning Detection Rules - Creation', + { + // Skipped in MKI as tests depend on feature flag alertSuppressionForMachineLearningRuleEnabled + tags: ['@ess', '@serverless', '@skipInServerlessMKI'], + env: { + ftrConfig: { + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'alertSuppressionForMachineLearningRuleEnabled', + ])}`, + ], + }, + }, + }, + () => { + let mlRule: ReturnType<typeof getMachineLearningRule>; + const jobId = 'v3_linux_anomalous_network_activity'; + const suppressByFields = ['by_field_name', 'by_field_value']; + + beforeEach(() => { + login(); + visit(CREATE_RULE_URL); + }); + + describe('with Alert Suppression', () => { + describe('when no ML jobs have run', () => { + before(() => { + const machineLearningJobIds = ([] as string[]).concat( + getMachineLearningRule().machine_learning_job_id + ); + // ensure no ML jobs are started before the suite + machineLearningJobIds.forEach((j) => forceStopAndCloseJob({ jobId: j })); + }); + + beforeEach(() => { + mlRule = getMachineLearningRule(); + selectMachineLearningRuleType(); + fillDefineMachineLearningRule(mlRule); + }); + + it('disables the suppression fields and displays a message', () => { + cy.get(ALERT_SUPPRESSION_FIELDS_INPUT).should('be.disabled'); + cy.get(ALERT_SUPPRESSION_FIELDS_INPUT).realHover(); + cy.get(TOOLTIP).should( + 'contain.text', + 'To enable alert suppression, start relevant Machine Learning jobs.' + ); + }); + }); + + describe('when ML jobs have run', () => { + before(() => { + cy.task('esArchiverLoad', { archiveName: '../auditbeat/hosts', type: 'ftr' }); + executeSetupModuleRequest({ moduleName: 'security_linux_v3' }); + forceStartDatafeeds({ jobIds: [jobId] }); + cy.task('esArchiverLoad', { archiveName: 'anomalies', type: 'ftr' }); + }); + + after(() => { + cy.task('esArchiverUnload', { archiveName: 'anomalies', type: 'ftr' }); + cy.task('esArchiverUnload', { archiveName: '../auditbeat/hosts', type: 'ftr' }); + }); + + describe('when not all jobs are running', () => { + beforeEach(() => { + mlRule = getMachineLearningRule(); + selectMachineLearningRuleType(); + fillDefineMachineLearningRule(mlRule); + }); + + it('displays a warning message on the suppression fields', () => { + cy.get(ALERT_SUPPRESSION_FIELDS_INPUT).should('be.enabled'); + cy.get(ALERT_SUPPRESSION_FIELDS).should( + 'contain.text', + 'This list of fields might be incomplete as some Machine Learning jobs are not running. Start all relevant jobs for a complete list.' + ); + }); + }); + + describe('when all jobs are running', () => { + beforeEach(() => { + mlRule = getMachineLearningRule({ machine_learning_job_id: [jobId] }); + selectMachineLearningRuleType(); + fillDefineMachineLearningRule(mlRule); + }); + + it('allows a rule with per-execution suppression to be created and displayed', () => { + fillAlertSuppressionFields(suppressByFields); + continueFromDefineStep(); + + // ensures details preview works correctly + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_BY_DETAILS).should('have.text', suppressByFields.join('')); + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', 'One rule execution'); + getDetails(SUPPRESS_MISSING_FIELD).should( + 'have.text', + 'Suppress and group alerts for events with missing fields' + ); + + // suppression functionality should be under Tech Preview + cy.contains(DETAILS_TITLE, SUPPRESS_FOR_DETAILS).contains('Technical Preview'); + }); + + fillAboutRuleMinimumAndContinue(mlRule); + skipScheduleRuleAction(); + createRuleWithoutEnabling(); + + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_BY_DETAILS).should('have.text', suppressByFields.join('')); + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', 'One rule execution'); + getDetails(SUPPRESS_MISSING_FIELD).should( + 'have.text', + 'Suppress and group alerts for events with missing fields' + ); + }); + }); + + it('allows a rule with interval suppression to be created and displayed', () => { + fillAlertSuppressionFields(suppressByFields); + selectAlertSuppressionPerInterval(); + setAlertSuppressionDuration(45, 'm'); + selectDoNotSuppressForMissingFields(); + continueFromDefineStep(); + + // ensures details preview works correctly + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_BY_DETAILS).should('have.text', suppressByFields.join('')); + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '45m'); + getDetails(SUPPRESS_MISSING_FIELD).should( + 'have.text', + 'Do not suppress alerts for events with missing fields' + ); + + // suppression functionality should be under Tech Preview + cy.contains(DETAILS_TITLE, SUPPRESS_FOR_DETAILS).contains('Technical Preview'); + }); + + fillAboutRuleMinimumAndContinue(mlRule); + skipScheduleRuleAction(); + createRuleWithoutEnabling(); + + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_BY_DETAILS).should('have.text', suppressByFields.join('')); + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '45m'); + getDetails(SUPPRESS_MISSING_FIELD).should( + 'have.text', + 'Do not suppress alerts for events with missing fields' + ); + }); + }); + }); + }); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/machine_learning_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/machine_learning_rule.cy.ts new file mode 100644 index 0000000000000..5e6cd673070ba --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/machine_learning_rule.cy.ts @@ -0,0 +1,178 @@ +/* + * 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 { getMachineLearningRule } from '../../../../objects/rule'; +import { + ALERT_SUPPRESSION_DURATION_INPUT, + ALERT_SUPPRESSION_FIELDS, + ALERT_SUPPRESSION_MISSING_FIELDS_DO_NOT_SUPPRESS, +} from '../../../../screens/create_new_rule'; +import { + DEFINITION_DETAILS, + DETAILS_TITLE, + SUPPRESS_BY_DETAILS, + SUPPRESS_FOR_DETAILS, + SUPPRESS_MISSING_FIELD, +} from '../../../../screens/rule_details'; +import { + executeSetupModuleRequest, + forceStartDatafeeds, + forceStopAndCloseJob, +} from '../../../../support/machine_learning'; +import { editFirstRule } from '../../../../tasks/alerts_detection_rules'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { createRule } from '../../../../tasks/api_calls/rules'; +import { + clearAlertSuppressionFields, + fillAlertSuppressionFields, + selectAlertSuppressionPerInterval, + selectAlertSuppressionPerRuleExecution, + setAlertSuppressionDuration, +} from '../../../../tasks/create_new_rule'; +import { saveEditedRule } from '../../../../tasks/edit_rule'; +import { login } from '../../../../tasks/login'; +import { visit } from '../../../../tasks/navigation'; +import { assertDetailsNotExist, getDetails } from '../../../../tasks/rule_details'; +import { RULES_MANAGEMENT_URL } from '../../../../urls/rules_management'; + +describe( + 'Machine Learning Detection Rules - Editing', + { + // Skipping in MKI as it depends on feature flag alertSuppressionForMachineLearningRuleEnabled + tags: ['@ess', '@serverless', '@skipInServerlessMKI'], + env: { + ftrConfig: { + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'alertSuppressionForMachineLearningRuleEnabled', + ])}`, + ], + }, + }, + }, + () => { + let mlRule: ReturnType<typeof getMachineLearningRule>; + const suppressByFields = ['by_field_name', 'by_field_value']; + const jobId = 'v3_linux_anomalous_network_activity'; + + before(() => { + const machineLearningJobIds = ([] as string[]).concat( + getMachineLearningRule().machine_learning_job_id + ); + // ensure no ML jobs are started before the test + machineLearningJobIds.forEach((j) => forceStopAndCloseJob({ jobId: j })); + }); + + beforeEach(() => { + login(); + deleteAlertsAndRules(); + cy.task('esArchiverLoad', { archiveName: '../auditbeat/hosts', type: 'ftr' }); + executeSetupModuleRequest({ moduleName: 'security_linux_v3' }); + forceStartDatafeeds({ jobIds: [jobId] }); + cy.task('esArchiverLoad', { archiveName: 'anomalies', type: 'ftr' }); + }); + + describe('without Alert Suppression', () => { + beforeEach(() => { + mlRule = getMachineLearningRule({ machine_learning_job_id: [jobId] }); + createRule(mlRule); + visit(RULES_MANAGEMENT_URL); + editFirstRule(); + }); + + it('allows editing of a rule to add suppression configuration', () => { + fillAlertSuppressionFields(suppressByFields); + selectAlertSuppressionPerInterval(); + setAlertSuppressionDuration(2, 'h'); + + saveEditedRule(); + + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_BY_DETAILS).should('have.text', suppressByFields.join('')); + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '2h'); + getDetails(SUPPRESS_MISSING_FIELD).should( + 'have.text', + 'Suppress and group alerts for events with missing fields' + ); + + // suppression functionality should be under Tech Preview + cy.contains(DETAILS_TITLE, SUPPRESS_FOR_DETAILS).contains('Technical Preview'); + }); + }); + }); + + describe('with Alert Suppression', () => { + beforeEach(() => { + mlRule = { + ...getMachineLearningRule({ machine_learning_job_id: [jobId] }), + alert_suppression: { + group_by: suppressByFields, + duration: { value: 360, unit: 's' }, + missing_fields_strategy: 'doNotSuppress', + }, + }; + + createRule(mlRule); + visit(RULES_MANAGEMENT_URL); + editFirstRule(); + }); + + it('allows editing of a rule to change its suppression configuration', () => { + // check saved suppression settings + cy.get(ALERT_SUPPRESSION_DURATION_INPUT) + .eq(0) + .should('be.enabled') + .should('have.value', 360); + cy.get(ALERT_SUPPRESSION_DURATION_INPUT) + .eq(1) + .should('be.enabled') + .should('have.value', 's'); + + cy.get(ALERT_SUPPRESSION_FIELDS).should('contain', suppressByFields.join('')); + cy.get(ALERT_SUPPRESSION_MISSING_FIELDS_DO_NOT_SUPPRESS).should('be.checked'); + + // set new duration first to overcome some flaky racing conditions during form save + setAlertSuppressionDuration(2, 'h'); + selectAlertSuppressionPerRuleExecution(); + + saveEditedRule(); + + // check execution duration has changed + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', 'One rule execution'); + }); + }); + + it('allows editing of a rule to remove suppression configuration', () => { + // check saved suppression settings + cy.get(ALERT_SUPPRESSION_DURATION_INPUT) + .eq(0) + .should('be.enabled') + .should('have.value', 360); + cy.get(ALERT_SUPPRESSION_DURATION_INPUT) + .eq(1) + .should('be.enabled') + .should('have.value', 's'); + + cy.get(ALERT_SUPPRESSION_FIELDS).should('contain', suppressByFields.join('')); + cy.get(ALERT_SUPPRESSION_MISSING_FIELDS_DO_NOT_SUPPRESS).should('be.checked'); + + // set new duration first to overcome some flaky racing conditions during form save + setAlertSuppressionDuration(2, 'h'); + + clearAlertSuppressionFields(); + saveEditedRule(); + + // check suppression is now absent + cy.get(DEFINITION_DETAILS).within(() => { + assertDetailsNotExist(SUPPRESS_FOR_DETAILS); + assertDetailsNotExist(SUPPRESS_BY_DETAILS); + }); + }); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/prebuilt_rules_preview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/prebuilt_rules_preview.cy.ts index 4b4a9542ff1bc..fca78851ddf03 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/prebuilt_rules_preview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/prebuilt_rules_preview.cy.ts @@ -220,9 +220,17 @@ describe('Detection rules, Prebuilt Rules Installation and Update workflow', () type: 'machine_learning', anomaly_threshold: 65, machine_learning_job_id: ['auth_high_count_logon_events', 'auth_high_count_logon_fails'], + alert_suppression: { + group_by: ['host.name'], + duration: { unit: 'm', value: 5 }, + missing_fields_strategy: 'suppress', + }, }), ['security-rule.query', 'security-rule.language'] - ) as typeof CUSTOM_QUERY_INDEX_PATTERN_RULE; + ) as Omit< + ReturnType<typeof createRuleAssetSavedObject>, + 'security-rule.query' | 'security-rule.language' + >; const THRESHOLD_RULE_INDEX_PATTERN = createRuleAssetSavedObject({ name: 'Threshold index pattern rule', @@ -500,24 +508,30 @@ describe('Detection rules, Prebuilt Rules Installation and Update workflow', () }); it('Machine learning rule properties', function () { - clickAddElasticRulesButton(); - - openRuleInstallPreview(MACHINE_LEARNING_RULE['security-rule'].name); - - assertCommonPropertiesShown(commonProperties); - const { + name, + alert_suppression: alertSuppression, anomaly_threshold: anomalyThreshold, machine_learning_job_id: machineLearningJobIds, } = MACHINE_LEARNING_RULE['security-rule'] as { + name: string; anomaly_threshold: number; machine_learning_job_id: string[]; + alert_suppression: AlertSuppression; }; + + clickAddElasticRulesButton(); + openRuleInstallPreview(name); + + assertCommonPropertiesShown(commonProperties); + assertMachineLearningPropertiesShown( anomalyThreshold, machineLearningJobIds, this.mlModules ); + + assertAlertSuppressionPropertiesShown(alertSuppression); }); it('Threshold rule properties', () => { diff --git a/x-pack/test/security_solution_cypress/cypress/support/machine_learning.ts b/x-pack/test/security_solution_cypress/cypress/support/machine_learning.ts index e562a693865e3..5fb869cebc29f 100644 --- a/x-pack/test/security_solution_cypress/cypress/support/machine_learning.ts +++ b/x-pack/test/security_solution_cypress/cypress/support/machine_learning.ts @@ -5,8 +5,72 @@ * 2.0. */ +import { ML_GROUP_ID } from '@kbn/security-solution-plugin/common/constants'; import { rootRequest } from '../tasks/api_calls/common'; +/** + * + * Calls the internal ML Module API to set up a module, which installs the jobs + * contained in that module. + * @param moduleName the name of the ML module to set up + * @returns the response from the setup module request + */ +export const executeSetupModuleRequest = ({ moduleName }: { moduleName: string }) => + rootRequest({ + headers: { + 'elastic-api-version': 1, + }, + method: 'POST', + url: `/internal/ml/modules/setup/${moduleName}`, + failOnStatusCode: true, + body: { + prefix: '', + groups: [ML_GROUP_ID], + indexPatternName: 'auditbeat-*', + startDatafeed: false, + useDedicatedIndex: true, + applyToAllSpaces: true, + }, + }); + +/** + * + * Calls the internal ML Jobs API to force start the datafeeds for the given job IDs. Necessary to get them in the "started" state for the purposes of the detection engine + * @param jobIds the job IDs for which to force start datafeeds + * @returns the response from the force start datafeeds request + */ +export const forceStartDatafeeds = ({ jobIds }: { jobIds: string[] }) => + rootRequest({ + headers: { + 'elastic-api-version': 1, + }, + method: 'POST', + url: '/internal/ml/jobs/force_start_datafeeds', + failOnStatusCode: true, + body: { + datafeedIds: jobIds.map((jobId) => `datafeed-${jobId}`), + start: new Date().getUTCMilliseconds(), + }, + }); + +/** + * Calls the internal ML Jobs API to stop the datafeeds for the given job IDs. + * @param jobIds the job IDs for which to stop datafeeds + * @returns the response from the stop datafeeds request + */ +export const stopDatafeeds = ({ jobIds }: { jobIds: string[] }) => + rootRequest({ + headers: { + 'elastic-api-version': 1, + }, + method: 'POST', + url: '/internal/ml/jobs/stop_datafeeds', + failOnStatusCode: true, + body: { + datafeedIds: jobIds.map((jobId) => `datafeed-${jobId}`), + }, + }); + /** * Calls the internal ML Jobs API to force stop the datafeed of, and force close * the job with the given ID. diff --git a/x-pack/test/security_solution_cypress/serverless_config.ts b/x-pack/test/security_solution_cypress/serverless_config.ts index ebdd5d1b333c9..b9f153028e5c8 100644 --- a/x-pack/test/security_solution_cypress/serverless_config.ts +++ b/x-pack/test/security_solution_cypress/serverless_config.ts @@ -37,6 +37,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--xpack.securitySolution.enableExperimental=${JSON.stringify([ 'alertSuppressionForEsqlRuleEnabled', 'bulkCustomHighlightedFieldsEnabled', + 'alertSuppressionForMachineLearningRuleEnabled', 'manualRuleRunEnabled', ])}`, ], diff --git a/x-pack/test_serverless/shared/lib/security/kibana_roles/project_controller_security_roles.yml b/x-pack/test_serverless/shared/lib/security/kibana_roles/project_controller_security_roles.yml index 9f3220959c486..5f86c7a23ca27 100644 --- a/x-pack/test_serverless/shared/lib/security/kibana_roles/project_controller_security_roles.yml +++ b/x-pack/test_serverless/shared/lib/security/kibana_roles/project_controller_security_roles.yml @@ -34,6 +34,7 @@ viewer: - ".fleet-actions*" - "risk-score.risk-score-*" - ".asset-criticality.asset-criticality-*" + - ".ml-anomalies-*" privileges: - read applications: @@ -100,6 +101,10 @@ editor: - "read" - "write" allow_restricted_indices: false + - names: + - ".ml-anomalies-*" + privileges: + - read applications: - application: "kibana-.kibana" privileges: @@ -155,6 +160,7 @@ t1_analyst: - ".fleet-actions*" - risk-score.risk-score-* - .asset-criticality.asset-criticality-* + - ".ml-anomalies-*" privileges: - read applications: @@ -203,6 +209,7 @@ t2_analyst: - .fleet-agents* - .fleet-actions* - risk-score.risk-score-* + - ".ml-anomalies-*" privileges: - read - names: @@ -265,6 +272,7 @@ t3_analyst: - .fleet-agents* - .fleet-actions* - risk-score.risk-score-* + - ".ml-anomalies-*" privileges: - read applications: @@ -330,6 +338,7 @@ threat_intelligence_analyst: - .fleet-agents* - .fleet-actions* - risk-score.risk-score-* + - ".ml-anomalies-*" privileges: - read applications: @@ -389,6 +398,7 @@ rule_author: - .fleet-agents* - .fleet-actions* - risk-score.risk-score-* + - ".ml-anomalies-*" privileges: - read applications: @@ -454,6 +464,7 @@ soc_manager: - .fleet-agents* - .fleet-actions* - risk-score.risk-score-* + - ".ml-anomalies-*" privileges: - read applications: @@ -515,6 +526,7 @@ detections_admin: - metrics-endpoint.metadata_current_* - .fleet-agents* - .fleet-actions* + - ".ml-anomalies-*" privileges: - read - names: @@ -573,6 +585,10 @@ platform_engineer: privileges: - read - write + - names: + - ".ml-anomalies-*" + privileges: + - read applications: - application: "kibana-.kibana" privileges: @@ -624,6 +640,7 @@ endpoint_operations_analyst: - .lists* - .items* - risk-score.risk-score-* + - ".ml-anomalies-*" privileges: - read - names: @@ -692,6 +709,7 @@ endpoint_policy_manager: - packetbeat-* - winlogbeat-* - risk-score.risk-score-* + - ".ml-anomalies-*" privileges: - read - names: From ad4fe8407838309fc24d92837be058fabc1c8b3b Mon Sep 17 00:00:00 2001 From: Steph Milovic <stephanie.milovic@elastic.co> Date: Tue, 2 Jul 2024 13:42:22 -0600 Subject: [PATCH 112/128] [Security solution] Assistant race condition bug fixing (#187186) --- .../assistant_header_flyout.tsx | 3 ++ .../assistant/chat_send/use_chat_send.tsx | 2 +- .../impl/assistant/index.test.tsx | 37 ++++++++++++------- .../impl/assistant/index.tsx | 25 ++++++------- 4 files changed, 39 insertions(+), 28 deletions(-) diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/assistant_header_flyout.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/assistant_header_flyout.tsx index d9ba27b96655f..bd75e80aef0ca 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/assistant_header_flyout.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_header/assistant_header_flyout.tsx @@ -125,6 +125,7 @@ export const AssistantHeaderFlyout: React.FC<Props> = ({ `, onClick: showDestroyModal, icon: 'refresh', + 'data-test-subj': 'clear-chat', }, ], }, @@ -243,6 +244,7 @@ export const AssistantHeaderFlyout: React.FC<Props> = ({ aria-label="test" iconType="boxesVertical" onClick={onButtonClick} + data-test-subj="chat-context-menu" /> } isOpen={isPopoverOpen} @@ -266,6 +268,7 @@ export const AssistantHeaderFlyout: React.FC<Props> = ({ confirmButtonText={i18n.RESET_BUTTON_TEXT} buttonColor="danger" defaultFocusedButton="confirm" + data-test-subj="reset-conversation-modal" > <p>{i18n.CLEAR_CHAT_CONFIRMATION}</p> </EuiConfirmModal> diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.tsx index 020822821d163..f42fe17242d86 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/chat_send/use_chat_send.tsx @@ -35,7 +35,7 @@ export interface UseChatSendProps { export interface UseChatSend { abortStream: () => void; - handleOnChatCleared: () => void; + handleOnChatCleared: () => Promise<void>; handlePromptChange: (prompt: string) => void; handleSendMessage: (promptText: string) => void; handleRegenerateResponse: () => void; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx index c023970803da4..b25945dd247bf 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { act, fireEvent, render, screen, within } from '@testing-library/react'; +import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react'; import { Assistant } from '.'; import type { IHttpFetchError } from '@kbn/core/public'; @@ -63,15 +63,25 @@ const mockData = { }, }; const mockDeleteConvo = jest.fn(); +const clearConversation = jest.fn(); const mockUseConversation = { + clearConversation: clearConversation.mockResolvedValue(mockData.welcome_id), getConversation: jest.fn(), getDefaultConversation: jest.fn().mockReturnValue(mockData.welcome_id), deleteConversation: mockDeleteConvo, setApiConfig: jest.fn().mockResolvedValue({}), }; +const refetchResults = jest.fn(); + describe('Assistant', () => { - beforeAll(() => { + let persistToLocalStorage: jest.Mock; + let persistToSessionStorage: jest.Mock; + + beforeEach(() => { + jest.clearAllMocks(); + persistToLocalStorage = jest.fn(); + persistToSessionStorage = jest.fn(); (useConversation as jest.Mock).mockReturnValue(mockUseConversation); jest.mocked(useConnectorSetup).mockReturnValue({ comments: [], @@ -89,13 +99,14 @@ describe('Assistant', () => { ]; jest.mocked(useLoadConnectors).mockReturnValue({ isFetched: true, + isFetchedAfterMount: true, data: connectors, } as unknown as UseQueryResult<AIConnector[], IHttpFetchError>); jest.mocked(useFetchCurrentUserConversations).mockReturnValue({ data: mockData, isLoading: false, - refetch: jest.fn().mockResolvedValue({ + refetch: refetchResults.mockResolvedValue({ isLoading: false, data: { ...mockData, @@ -107,16 +118,6 @@ describe('Assistant', () => { }), isFetched: true, } as unknown as DefinedUseQueryResult<Record<string, Conversation>, unknown>); - }); - - let persistToLocalStorage: jest.Mock; - let persistToSessionStorage: jest.Mock; - - beforeEach(() => { - jest.clearAllMocks(); - persistToLocalStorage = jest.fn(); - persistToSessionStorage = jest.fn(); - jest .mocked(useLocalStorage) .mockReturnValue([undefined, persistToLocalStorage] as unknown as ReturnType< @@ -234,6 +235,16 @@ describe('Assistant', () => { }); expect(mockDeleteConvo).toHaveBeenCalledWith(mockData.welcome_id.id); }); + it('should refetchConversationsState after clear chat history button click', async () => { + renderAssistant({ isFlyoutMode: true }); + fireEvent.click(screen.getByTestId('chat-context-menu')); + fireEvent.click(screen.getByTestId('clear-chat')); + fireEvent.click(screen.getByTestId('confirmModalConfirmButton')); + await waitFor(() => { + expect(clearConversation).toHaveBeenCalled(); + expect(refetchResults).toHaveBeenCalled(); + }); + }); }); describe('when selected conversation changes and some connectors are loaded', () => { it('should persist the conversation id to local storage', async () => { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx index 830c5d2b7080a..907e4d70accd5 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx @@ -220,7 +220,7 @@ const AssistantComponent: React.FC<Props> = ({ ); useEffect(() => { - if (conversationsLoaded && Object.keys(conversations).length > 0) { + if (areConnectorsFetched && conversationsLoaded && Object.keys(conversations).length > 0) { setCurrentConversation((prev) => { const nextConversation = (currentConversationId && conversations[currentConversationId]) || @@ -256,13 +256,13 @@ const AssistantComponent: React.FC<Props> = ({ }); } }, [ + areConnectorsFetched, conversationTitle, conversations, - getDefaultConversation, - getLastConversationId, conversationsLoaded, - currentConversation?.id, currentConversationId, + getDefaultConversation, + getLastConversationId, isAssistantEnabled, isFlyoutMode, ]); @@ -549,7 +549,7 @@ const AssistantComponent: React.FC<Props> = ({ const { abortStream, - handleOnChatCleared, + handleOnChatCleared: onChatCleared, handlePromptChange, handleSendMessage, handleRegenerateResponse, @@ -567,6 +567,11 @@ const AssistantComponent: React.FC<Props> = ({ setCurrentConversation, }); + const handleOnChatCleared = useCallback(async () => { + await onChatCleared(); + await refetchResults(); + }, [onChatCleared, refetchResults]); + const handleChatSend = useCallback( async (promptText: string) => { await handleSendMessage(promptText); @@ -733,15 +738,7 @@ const AssistantComponent: React.FC<Props> = ({ } } })(); - }, [ - currentConversation, - defaultConnector, - refetchConversationsState, - setApiConfig, - showMissingConnectorCallout, - areConnectorsFetched, - mutateAsync, - ]); + }, [areConnectorsFetched, currentConversation, mutateAsync]); const handleCreateConversation = useCallback(async () => { const newChatExists = find(conversations, ['title', NEW_CHAT]); From 9fae9c536ad54113f376e12885339189fc243266 Mon Sep 17 00:00:00 2001 From: Rickyanto Ang <rickyangwyn@gmail.com> Date: Tue, 2 Jul 2024 13:07:33 -0700 Subject: [PATCH 113/128] [Cloud Security] Update CSP Version to 1.9.0 for Test (#186657) ## Summary With 8.14 released, we want to make sure our CSP is using the latest CSP version for our test environment --- .../cloud_security_posture/common/constants.ts | 2 +- .../components/package_policy_input_var_field.tsx | 1 + .../page_objects/add_cis_integration_form_page.ts | 5 +++++ .../cis_integrations/cspm/cis_integration_aws.ts | 4 ++++ .../cis_integrations/cspm/cis_integration_azure.ts | 12 ++---------- .../cis_integrations/kspm/cis_integration_eks.ts | 2 ++ .../config.cloud_security_posture.essentials.ts | 3 ++- 7 files changed, 17 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/common/constants.ts b/x-pack/plugins/cloud_security_posture/common/constants.ts index 57f1e465ea73b..27fc64d44966f 100644 --- a/x-pack/plugins/cloud_security_posture/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/common/constants.ts @@ -199,6 +199,6 @@ export const TEMPLATE_URL_ACCOUNT_TYPE_ENV_VAR = 'ACCOUNT_TYPE'; export const ORGANIZATION_ACCOUNT = 'organization-account'; export const SINGLE_ACCOUNT = 'single-account'; -export const CLOUD_SECURITY_PLUGIN_VERSION = '1.8.1'; +export const CLOUD_SECURITY_PLUGIN_VERSION = '1.9.0'; // Cloud Credentials Template url was implemented in 1.10.0-preview01. See PR - https://github.com/elastic/integrations/pull/9828 export const CLOUD_CREDENTIALS_PACKAGE_VERSION = '1.10.0-preview01'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx index 653986a7128de..114d973f32541 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx @@ -420,6 +420,7 @@ function SecretInputField({ iconType="refresh" iconSide="left" size="xs" + data-test-subj={`button-replace-${fieldTestSelector}`} > <FormattedMessage id="xpack.fleet.editPackagePolicy.stepConfigure.fieldSecretValueSetEditButton" diff --git a/x-pack/test/cloud_security_posture_functional/page_objects/add_cis_integration_form_page.ts b/x-pack/test/cloud_security_posture_functional/page_objects/add_cis_integration_form_page.ts index 0df5147574d28..1b2eda553dba2 100644 --- a/x-pack/test/cloud_security_posture_functional/page_objects/add_cis_integration_form_page.ts +++ b/x-pack/test/cloud_security_posture_functional/page_objects/add_cis_integration_form_page.ts @@ -278,6 +278,10 @@ export function AddCisIntegrationFormPageProvider({ return await (await checkBox.findByCssSelector(`input[id='${id}']`)).getAttribute('checked'); }; + const getReplaceSecretButton = async (secretField: string) => { + return await testSubjects.find(`button-replace-${secretField}`); + }; + return { cisAzure, cisAws, @@ -311,5 +315,6 @@ export function AddCisIntegrationFormPageProvider({ getValueInEditPage, isOptionChecked, checkIntegrationPliAuthBlockExists, + getReplaceSecretButton, }; } diff --git a/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts b/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts index cae23e9bfe8a4..5523e05120912 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts @@ -124,6 +124,7 @@ export default function (providerContext: FtrProviderContext) { (await cisIntegration.getFieldValueInEditPage(DIRECT_ACCESS_KEY_ID_TEST_ID)) === directAccessKeyId ).to.be(true); + expect(await cisIntegration.getReplaceSecretButton('secret-access-key')).to.not.be(null); }); }); @@ -159,6 +160,7 @@ export default function (providerContext: FtrProviderContext) { (await cisIntegration.getValueInEditPage(TEMP_ACCESS_SESSION_TOKEN_TEST_ID)) === tempAccessSessionToken ).to.be(true); + expect(await cisIntegration.getReplaceSecretButton('secret-access-key')).to.not.be(null); }); }); @@ -247,6 +249,7 @@ export default function (providerContext: FtrProviderContext) { (await cisIntegration.getFieldValueInEditPage(DIRECT_ACCESS_KEY_ID_TEST_ID)) === directAccessKeyId ).to.be(true); + expect(await cisIntegration.getReplaceSecretButton('secret-access-key')).to.not.be(null); }); }); @@ -283,6 +286,7 @@ export default function (providerContext: FtrProviderContext) { (await cisIntegration.getValueInEditPage(TEMP_ACCESS_SESSION_TOKEN_TEST_ID)) === tempAccessSessionToken ).to.be(true); + expect(await cisIntegration.getReplaceSecretButton('secret-access-key')).to.not.be(null); }); }); diff --git a/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_azure.ts b/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_azure.ts index 6bd117c36a85d..99cc57905d46e 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_azure.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_azure.ts @@ -111,11 +111,7 @@ export default function (providerContext: FtrProviderContext) { CIS_AZURE_INPUT_FIELDS_TEST_SUBJECTS.TENANT_ID )) === tenantId ).to.be(true); - expect( - (await cisIntegration.getValueInEditPage( - CIS_AZURE_INPUT_FIELDS_TEST_SUBJECTS.CLIENT_SECRET - )) === clientSecret - ).to.be(true); + expect(await cisIntegration.getReplaceSecretButton('client-secret')).to.not.be(null); }); }); @@ -227,11 +223,7 @@ export default function (providerContext: FtrProviderContext) { CIS_AZURE_INPUT_FIELDS_TEST_SUBJECTS.TENANT_ID )) === tenantId ).to.be(true); - expect( - (await cisIntegration.getValueInEditPage( - CIS_AZURE_INPUT_FIELDS_TEST_SUBJECTS.CLIENT_SECRET - )) === clientSecret - ).to.be(true); + expect(await cisIntegration.getReplaceSecretButton('client-secret')).to.not.be(null); }); }); diff --git a/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts b/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts index ec4a7c61239ea..db567d335b0ff 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts @@ -73,6 +73,7 @@ export default function (providerContext: FtrProviderContext) { (await cisIntegration.getFieldValueInEditPage(DIRECT_ACCESS_KEY_ID_TEST_ID)) === directAccessKeyId ).to.be(true); + expect(await cisIntegration.getReplaceSecretButton('secret-access-key')).to.not.be(null); }); }); @@ -107,6 +108,7 @@ export default function (providerContext: FtrProviderContext) { (await cisIntegration.getValueInEditPage(TEMP_ACCESS_SESSION_TOKEN_TEST_ID)) === tempAccessSessionToken ).to.be(true); + expect(await cisIntegration.getReplaceSecretButton('secret-access-key')).to.not.be(null); }); }); diff --git a/x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.essentials.ts b/x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.essentials.ts index 320b130e63e91..11b4f056841f1 100644 --- a/x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.essentials.ts +++ b/x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.essentials.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CLOUD_SECURITY_PLUGIN_VERSION } from '@kbn/cloud-security-posture-plugin/common/constants'; import { createTestConfig } from '../../config.base'; export default createTestConfig({ @@ -14,7 +15,7 @@ export default createTestConfig({ }, kbnServerArgs: [ `--xpack.fleet.packages.0.name=cloud_security_posture`, - `--xpack.fleet.packages.0.version=1.5.2`, + `--xpack.fleet.packages.0.version=${CLOUD_SECURITY_PLUGIN_VERSION}`, `--xpack.securitySolutionServerless.productTypes=${JSON.stringify([ { product_line: 'security', product_tier: 'essentials' }, { product_line: 'endpoint', product_tier: 'essentials' }, From 32e7bf98284d5afff87d69a54721234a540b11f4 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita <nikita.khristinin@elastic.co> Date: Tue, 2 Jul 2024 23:04:54 +0200 Subject: [PATCH 114/128] Telemetry for manual rule run (#186364) ## Summary report following events: - open modal window for manual rule run - execute manual rule run + save time range in ms - cancel backfill job - filter in event log by run type - show source event date range Epic - https://github.com/elastic/security-team/issues/2840 ### How to test enable feature flag - `manualRuleRunEnabled` You can see feature demo here - https://github.com/elastic/kibana/pull/184500 Check that events appears here after some time - https://telemetry-v2-staging.elastic.dev/s/securitysolution/app/r/s/7YYlg --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../public/common/lib/telemetry/constants.ts | 5 + .../lib/telemetry/events/event_log/index.ts | 37 +++++ .../lib/telemetry/events/event_log/types.ts | 29 ++++ .../telemetry/events/manual_rule_run/index.ts | 79 +++++++++++ .../telemetry/events/manual_rule_run/types.ts | 43 ++++++ .../lib/telemetry/events/telemetry_events.ts | 11 ++ .../lib/telemetry/telemetry_client.mock.ts | 5 + .../common/lib/telemetry/telemetry_client.ts | 27 ++++ .../public/common/lib/telemetry/types.ts | 34 ++++- .../execution_log_table.test.tsx | 45 ++++++- .../execution_log_table.tsx | 21 ++- .../components/rule_backfills_info/index.tsx | 2 +- .../stop_backfill.test.tsx | 126 ++++++++++++++++++ .../rule_backfills_info/stop_backfill.tsx | 12 +- .../logic/use_schedule_rule_run.test.tsx | 78 ++++++++++- .../rule_gaps/logic/use_schedule_rule_run.ts | 14 +- .../bulk_actions/use_bulk_actions.tsx | 11 ++ .../rules_table/use_rules_table_actions.tsx | 8 +- .../execution_run_type_filter/index.test.tsx | 47 +++++++ .../execution_run_type_filter/index.tsx | 13 +- .../rule_actions_overflow/index.test.tsx | 27 ++++ .../rules/rule_actions_overflow/index.tsx | 9 +- 22 files changed, 665 insertions(+), 18 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/lib/telemetry/events/event_log/index.ts create mode 100644 x-pack/plugins/security_solution/public/common/lib/telemetry/events/event_log/types.ts create mode 100644 x-pack/plugins/security_solution/public/common/lib/telemetry/events/manual_rule_run/index.ts create mode 100644 x-pack/plugins/security_solution/public/common/lib/telemetry/events/manual_rule_run/types.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/stop_backfill.test.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/execution_run_type_filter/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/constants.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/constants.ts index 4d7c8f180d0ec..bc9004c8d99c7 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/constants.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/constants.ts @@ -79,6 +79,11 @@ export enum TelemetryEventTypes { OnboardingHubStepOpen = 'Onboarding Hub Step Open', OnboardingHubStepFinished = 'Onboarding Hub Step Finished', OnboardingHubStepLinkClicked = 'Onboarding Hub Step Link Clicked', + ManualRuleRunOpenModal = 'Manual Rule Run Open Modal', + ManualRuleRunExecute = 'Manual Rule Run Execute', + ManualRuleRunCancelJob = 'Manual Rule Run Cancel Job', + EventLogFilterByRunType = 'Event Log Filter By Run Type', + EventLogShowSourceEventDateRange = 'Event Log -> Show Source -> Event Date Range', } export enum ML_JOB_TELEMETRY_STATUS { diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/event_log/index.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/event_log/index.ts new file mode 100644 index 0000000000000..c30efcee10cfc --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/event_log/index.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EventLogTelemetryEvent } from './types'; +import { TelemetryEventTypes } from '../../constants'; + +export const eventLogFilterByRunTypeEvent: EventLogTelemetryEvent = { + eventType: TelemetryEventTypes.EventLogFilterByRunType, + schema: { + runType: { + type: 'array', + items: { + type: 'keyword', + _meta: { + description: 'Filter event log by run type', + }, + }, + }, + }, +}; + +export const eventLogShowSourceEventDateRangeEvent: EventLogTelemetryEvent = { + eventType: TelemetryEventTypes.EventLogShowSourceEventDateRange, + schema: { + isVisible: { + type: 'boolean', + _meta: { + description: 'Show source event date range', + optional: false, + }, + }, + }, +}; diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/event_log/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/event_log/types.ts new file mode 100644 index 0000000000000..b196c9010b258 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/event_log/types.ts @@ -0,0 +1,29 @@ +/* + * 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 { RootSchema } from '@kbn/core/public'; +import type { TelemetryEventTypes } from '../../constants'; + +export interface ReportEventLogFilterByRunTypeParams { + runType: string[]; +} +export interface ReportEventLogShowSourceEventDateRangeParams { + isVisible: boolean; +} + +export type ReportEventLogTelemetryEventParams = + | ReportEventLogFilterByRunTypeParams + | ReportEventLogShowSourceEventDateRangeParams; + +export type EventLogTelemetryEvent = + | { + eventType: TelemetryEventTypes.EventLogFilterByRunType; + schema: RootSchema<ReportEventLogFilterByRunTypeParams>; + } + | { + eventType: TelemetryEventTypes.EventLogShowSourceEventDateRange; + schema: RootSchema<ReportEventLogShowSourceEventDateRangeParams>; + }; diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/manual_rule_run/index.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/manual_rule_run/index.ts new file mode 100644 index 0000000000000..a1476944d9806 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/manual_rule_run/index.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { TelemetryEvent } from '../../types'; +import { TelemetryEventTypes } from '../../constants'; + +export const manualRuleRunOpenModalEvent: TelemetryEvent = { + eventType: TelemetryEventTypes.ManualRuleRunOpenModal, + schema: { + type: { + type: 'keyword', + _meta: { + description: 'Open manual rule run modal (single|bulk)', + optional: false, + }, + }, + }, +}; + +export const manualRuleRunExecuteEvent: TelemetryEvent = { + eventType: TelemetryEventTypes.ManualRuleRunExecute, + schema: { + rangeInMs: { + type: 'integer', + _meta: { + description: + 'The time range (expressed in milliseconds) against which the manual rule run was executed', + optional: false, + }, + }, + status: { + type: 'keyword', + _meta: { + description: + 'Outcome state of the manual rule run. Possible values are "success" and "error"', + optional: false, + }, + }, + rulesCount: { + type: 'integer', + _meta: { + description: 'Number of rules that were executed in the manual rule run', + optional: false, + }, + }, + }, +}; + +export const manualRuleRunCancelJobEvent: TelemetryEvent = { + eventType: TelemetryEventTypes.ManualRuleRunCancelJob, + schema: { + totalTasks: { + type: 'integer', + _meta: { + description: + 'Total number of scheduled tasks (rule executions) at the moment of backfill cancellation', + optional: false, + }, + }, + completedTasks: { + type: 'integer', + _meta: { + description: 'Number of completed rule executions at the moment of backfill cancellation', + optional: false, + }, + }, + errorTasks: { + type: 'integer', + _meta: { + description: 'Number of error rule executions at the moment of backfill cancellation', + optional: false, + }, + }, + }, +}; diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/manual_rule_run/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/manual_rule_run/types.ts new file mode 100644 index 0000000000000..a58b0adf45503 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/manual_rule_run/types.ts @@ -0,0 +1,43 @@ +/* + * 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 { RootSchema } from '@kbn/core/public'; +import type { TelemetryEventTypes } from '../../constants'; + +export interface ReportManualRuleRunOpenModalParams { + type: 'single' | 'bulk'; +} + +export interface ReportManualRuleRunExecuteParams { + rangeInMs: number; + rulesCount: number; + status: 'success' | 'error'; +} + +export interface ReportManualRuleRunCancelJobParams { + totalTasks: number; + completedTasks: number; + errorTasks: number; +} + +export type ReportManualRuleRunTelemetryEventParams = + | ReportManualRuleRunOpenModalParams + | ReportManualRuleRunExecuteParams + | ReportManualRuleRunCancelJobParams; + +export type ManualRuleRunTelemetryEvent = + | { + eventType: TelemetryEventTypes.ManualRuleRunOpenModal; + schema: RootSchema<ReportManualRuleRunOpenModalParams>; + } + | { + eventType: TelemetryEventTypes.ManualRuleRunExecute; + schema: RootSchema<ReportManualRuleRunExecuteParams>; + } + | { + eventType: TelemetryEventTypes.ManualRuleRunCancelJob; + schema: RootSchema<ReportManualRuleRunCancelJobParams>; + }; diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/telemetry_events.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/telemetry_events.ts index 8fe949fc783e7..3cf5fb9b37818 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/telemetry_events.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/telemetry_events.ts @@ -38,6 +38,12 @@ import { onboardingHubStepLinkClickedEvent, onboardingHubStepOpenEvent, } from './onboarding'; +import { + manualRuleRunCancelJobEvent, + manualRuleRunExecuteEvent, + manualRuleRunOpenModalEvent, +} from './manual_rule_run'; +import { eventLogFilterByRunTypeEvent, eventLogShowSourceEventDateRangeEvent } from './event_log'; const mlJobUpdateEvent: TelemetryEvent = { eventType: TelemetryEventTypes.MLJobUpdate, @@ -175,4 +181,9 @@ export const telemetryEvents = [ onboardingHubStepOpenEvent, onboardingHubStepLinkClickedEvent, onboardingHubStepFinishedEvent, + manualRuleRunCancelJobEvent, + manualRuleRunExecuteEvent, + manualRuleRunOpenModalEvent, + eventLogFilterByRunTypeEvent, + eventLogShowSourceEventDateRangeEvent, ]; diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts index 747a0a3a57770..24057982ed588 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts @@ -35,4 +35,9 @@ export const createTelemetryClientMock = (): jest.Mocked<TelemetryClientStart> = reportAssetCriticalityCsvPreviewGenerated: jest.fn(), reportAssetCriticalityFileSelected: jest.fn(), reportAssetCriticalityCsvImported: jest.fn(), + reportEventLogFilterByRunType: jest.fn(), + reportEventLogShowSourceEventDateRange: jest.fn(), + reportManualRuleRunCancelJob: jest.fn(), + reportManualRuleRunExecute: jest.fn(), + reportManualRuleRunOpenModal: jest.fn(), }); diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts index 266b3c737eb62..130bbc7817034 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts @@ -35,6 +35,11 @@ import type { OnboardingHubStepLinkClickedParams, OnboardingHubStepOpenParams, OnboardingHubStepFinishedParams, + ReportManualRuleRunCancelJobParams, + ReportManualRuleRunExecuteParams, + ReportManualRuleRunOpenModalParams, + ReportEventLogShowSourceEventDateRangeParams, + ReportEventLogFilterByRunTypeParams, } from './types'; import { TelemetryEventTypes } from './constants'; @@ -168,4 +173,26 @@ export class TelemetryClient implements TelemetryClientStart { public reportOnboardingHubStepLinkClicked = (params: OnboardingHubStepLinkClickedParams) => { this.analytics.reportEvent(TelemetryEventTypes.OnboardingHubStepLinkClicked, params); }; + + public reportManualRuleRunOpenModal = (params: ReportManualRuleRunOpenModalParams) => { + this.analytics.reportEvent(TelemetryEventTypes.ManualRuleRunOpenModal, params); + }; + + public reportManualRuleRunExecute = (params: ReportManualRuleRunExecuteParams) => { + this.analytics.reportEvent(TelemetryEventTypes.ManualRuleRunExecute, params); + }; + + public reportManualRuleRunCancelJob = (params: ReportManualRuleRunCancelJobParams) => { + this.analytics.reportEvent(TelemetryEventTypes.ManualRuleRunCancelJob, params); + }; + + public reportEventLogFilterByRunType = (params: ReportEventLogFilterByRunTypeParams) => { + this.analytics.reportEvent(TelemetryEventTypes.EventLogFilterByRunType, params); + }; + + public reportEventLogShowSourceEventDateRange( + params: ReportEventLogShowSourceEventDateRangeParams + ): void { + this.analytics.reportEvent(TelemetryEventTypes.EventLogShowSourceEventDateRange, params); + } } diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts index 9e7a49a91497e..3aba8176d9f67 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts @@ -53,6 +53,19 @@ import type { OnboardingHubStepOpenParams, OnboardingHubTelemetryEvent, } from './events/onboarding/types'; +import type { + ManualRuleRunTelemetryEvent, + ReportManualRuleRunOpenModalParams, + ReportManualRuleRunExecuteParams, + ReportManualRuleRunCancelJobParams, + ReportManualRuleRunTelemetryEventParams, +} from './events/manual_rule_run/types'; +import type { + EventLogTelemetryEvent, + ReportEventLogFilterByRunTypeParams, + ReportEventLogShowSourceEventDateRangeParams, + ReportEventLogTelemetryEventParams, +} from './events/event_log/types'; export * from './events/ai_assistant/types'; export * from './events/alerts_grouping/types'; @@ -70,6 +83,8 @@ export type { ReportAssetCriticalityCsvImportedParams, } from './events/entity_analytics/types'; export * from './events/document_details/types'; +export * from './events/manual_rule_run/types'; +export * from './events/event_log/types'; export interface TelemetryServiceSetupParams { analytics: AnalyticsServiceSetup; @@ -112,7 +127,9 @@ export type TelemetryEventParams = | ReportDocumentDetailsTelemetryEventParams | OnboardingHubStepOpenParams | OnboardingHubStepFinishedParams - | OnboardingHubStepLinkClickedParams; + | OnboardingHubStepLinkClickedParams + | ReportManualRuleRunTelemetryEventParams + | ReportEventLogTelemetryEventParams; export interface TelemetryClientStart { reportAlertsGroupingChanged(params: ReportAlertsGroupingChangedParams): void; @@ -155,6 +172,17 @@ export interface TelemetryClientStart { reportOnboardingHubStepOpen(params: OnboardingHubStepOpenParams): void; reportOnboardingHubStepFinished(params: OnboardingHubStepFinishedParams): void; reportOnboardingHubStepLinkClicked(params: OnboardingHubStepLinkClickedParams): void; + + // manual rule run + reportManualRuleRunOpenModal(params: ReportManualRuleRunOpenModalParams): void; + reportManualRuleRunExecute(params: ReportManualRuleRunExecuteParams): void; + reportManualRuleRunCancelJob(params: ReportManualRuleRunCancelJobParams): void; + + // event log + reportEventLogFilterByRunType(params: ReportEventLogFilterByRunTypeParams): void; + reportEventLogShowSourceEventDateRange( + params: ReportEventLogShowSourceEventDateRangeParams + ): void; } export type TelemetryEvent = @@ -179,4 +207,6 @@ export type TelemetryEvent = eventType: TelemetryEventTypes.BreadcrumbClicked; schema: RootSchema<ReportBreadcrumbClickedParams>; } - | OnboardingHubTelemetryEvent; + | OnboardingHubTelemetryEvent + | ManualRuleRunTelemetryEvent + | EventLogTelemetryEvent; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.test.tsx index 584a9a4e49026..3a221836e3a35 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { noop } from 'lodash/fp'; -import { render, screen } from '@testing-library/react'; +import { render, screen, fireEvent } from '@testing-library/react'; import { coreMock } from '@kbn/core/public/mocks'; import { TestProviders } from '../../../../../common/mock'; @@ -18,10 +18,30 @@ import { useExecutionResults } from '../../../../rule_monitoring'; import { useSourcererDataView } from '../../../../../sourcerer/containers'; import { useRuleDetailsContext } from '../rule_details_context'; import { ExecutionLogTable } from './execution_log_table'; +import { useKibana } from '../../../../../common/lib/kibana'; +import { useKibana as mockUseKibana } from '../../../../../common/lib/kibana/__mocks__'; jest.mock('../../../../../sourcerer/containers'); jest.mock('../../../../rule_monitoring/components/execution_results_table/use_execution_results'); jest.mock('../rule_details_context'); +jest.mock('../../../../../common/lib/kibana'); +jest.mock('../../../../../common/hooks/use_experimental_features', () => { + return { + useIsExperimentalFeatureEnabled: jest.fn().mockReturnValue(true), + }; +}); + +const mockTelemetry = { + reportEventLogShowSourceEventDateRange: jest.fn(), +}; + +const mockedUseKibana = { + ...mockUseKibana(), + services: { + ...mockUseKibana().services, + telemetry: mockTelemetry, + }, +}; const coreStart = coreMock.createStart(); @@ -42,6 +62,11 @@ mockUseRuleExecutionEvents.mockReturnValue({ }); describe('ExecutionLogTable', () => { + beforeEach(() => { + jest.clearAllMocks(); + (useKibana as jest.Mock).mockReturnValue(mockedUseKibana); + }); + test('Shows total events returned', () => { const ruleDetailsContext = useRuleDetailsContextMock.create(); (useRuleDetailsContext as jest.Mock).mockReturnValue(ruleDetailsContext); @@ -50,4 +75,22 @@ describe('ExecutionLogTable', () => { }); expect(screen.getByTestId('executionsShowing')).toHaveTextContent('Showing 7 rule executions'); }); + + test('should call telemetry when the "Show Source Event Time Range" switch is toggled', async () => { + const ruleDetailsContext = useRuleDetailsContextMock.create(); + (useRuleDetailsContext as jest.Mock).mockReturnValue(ruleDetailsContext); + + const { getByText } = render( + <ExecutionLogTable ruleId={'0'} selectAlertsTab={noop} {...coreStart} />, + { + wrapper: TestProviders, + } + ); + + const switchButton = getByText('Show source event time range'); + + fireEvent.click(switchButton); + + expect(mockTelemetry.reportEventLogShowSourceEventDateRange).toHaveBeenCalled(); + }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx index 981f80f36f744..37037719f8e42 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx @@ -9,7 +9,12 @@ import React, { useCallback, useMemo, useRef } from 'react'; import { useDispatch } from 'react-redux'; import styled from 'styled-components'; import moment from 'moment'; -import type { OnTimeChangeProps, OnRefreshProps, OnRefreshChangeProps } from '@elastic/eui'; +import type { + OnTimeChangeProps, + OnRefreshProps, + OnRefreshChangeProps, + EuiSwitchEvent, +} from '@elastic/eui'; import { EuiTextColor, EuiFlexGroup, @@ -120,6 +125,7 @@ const ExecutionLogTableComponent: React.FC<ExecutionLogTableProps> = ({ }, storage, timelines, + telemetry, } = useKibana().services; const isManualRuleRunEnabled = useIsExperimentalFeatureEnabled('manualRuleRunEnabled'); @@ -453,6 +459,17 @@ const ExecutionLogTableComponent: React.FC<ExecutionLogTableProps> = ({ renderItem: renderExpandedItem, }); + const handleShowSourceEventTimeRange = useCallback( + (e: EuiSwitchEvent) => { + const isVisible = e.target.checked; + onShowSourceEventTimeRange(isVisible); + telemetry.reportEventLogShowSourceEventDateRange({ + isVisible, + }); + }, + [onShowSourceEventTimeRange, telemetry] + ); + const executionLogColumns = useMemo(() => { const columns = [...EXECUTION_LOG_COLUMNS].filter((item) => { if ('field' in item) { @@ -569,7 +586,7 @@ const ExecutionLogTableComponent: React.FC<ExecutionLogTableProps> = ({ label={i18n.RULE_EXECUTION_LOG_SHOW_SOURCE_EVENT_TIME_RANGE} checked={showSourceEventTimeRange} compressed={true} - onChange={(e) => onShowSourceEventTimeRange(e.target.checked)} + onChange={handleShowSourceEventTimeRange} /> )} <UtilitySwitch diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/index.tsx index 3a2a608d84431..2bacc44b15a76 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/index.tsx @@ -35,7 +35,7 @@ const DEFAULT_PAGE_SIZE = 10; const getBackfillsTableColumns = (hasCRUDPermissions: boolean) => { const stopAction = { name: i18n.BACKFILLS_TABLE_COLUMN_ACTION, - render: (item: BackfillRow) => <StopBackfill id={item.id} />, + render: (item: BackfillRow) => <StopBackfill backfill={item} />, width: '10%', }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/stop_backfill.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/stop_backfill.test.tsx new file mode 100644 index 0000000000000..b2cdd83d6f43f --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/stop_backfill.test.tsx @@ -0,0 +1,126 @@ +/* + * 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, fireEvent, waitFor } from '@testing-library/react'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { useDeleteBackfill } from '../../api/hooks/use_delete_backfill'; +import { StopBackfill } from './stop_backfill'; +import { TestProviders } from '../../../../common/mock'; +import { useKibana } from '../../../../common/lib/kibana'; +import * as i18n from '../../translations'; +import type { BackfillRow } from '../../types'; + +jest.mock('../../../../common/hooks/use_app_toasts'); +jest.mock('../../api/hooks/use_delete_backfill'); +jest.mock('../../../../common/lib/kibana'); + +const mockUseAppToasts = useAppToasts as jest.Mock; +const mockUseDeleteBackfill = useDeleteBackfill as jest.Mock; +const mockUseKibana = useKibana as jest.Mock; + +describe('StopBackfill', () => { + const mockTelemetry = { + reportManualRuleRunCancelJob: jest.fn(), + }; + + const addSuccess = jest.fn(); + const addError = jest.fn(); + + const backfill = { + id: 'backfill-id', + total: 10, + complete: 5, + error: 1, + duration: '1h', + enabled: true, + running: 1, + pending: 1, + timeout: 1, + end: '2024-06-28T12:05:38.955Z', + start: '2024-06-28T12:00:00.000Z', + status: 'pending', + created_at: '2024-06-28T12:05:42.572Z', + space_id: 'default', + rule: { + name: 'Rule', + }, + schedule: [ + { + run_at: '2024-06-28T13:00:00.000Z', + status: 'pending', + interval: '1h', + }, + ], + } as BackfillRow; + + beforeEach(() => { + jest.clearAllMocks(); + + mockUseAppToasts.mockReturnValue({ + addSuccess, + addError, + }); + + mockUseKibana.mockReturnValue({ + services: { + telemetry: mockTelemetry, + }, + }); + }); + + it('should call deleteBackfillMutation and telemetry when confirmed', async () => { + mockUseDeleteBackfill.mockImplementation((options) => ({ + mutate: () => { + if (options.onSuccess) { + options.onSuccess(); + } + }, + })); + + const { getByTestId } = render(<StopBackfill backfill={backfill} />, { + wrapper: TestProviders, + }); + + fireEvent.click(getByTestId('rule-backfills-delete-button')); + fireEvent.click(getByTestId('confirmModalConfirmButton')); + + await waitFor(() => { + expect(mockTelemetry.reportManualRuleRunCancelJob).toHaveBeenCalledWith({ + totalTasks: backfill.total, + completedTasks: backfill.complete, + errorTasks: backfill.error, + }); + }); + + expect(addSuccess).toHaveBeenCalledWith(i18n.BACKFILLS_TABLE_STOP_CONFIRMATION_SUCCESS); + }); + + it('should call addError on deleteBackfillMutation error', async () => { + mockUseDeleteBackfill.mockImplementation((options) => ({ + mutate: () => { + if (options.onError) { + options.onError(new Error('Error stopping backfill')); + } + }, + })); + + const { getByTestId } = render(<StopBackfill backfill={backfill} />, { + wrapper: TestProviders, + }); + + fireEvent.click(getByTestId('rule-backfills-delete-button')); + fireEvent.click(getByTestId('confirmModalConfirmButton')); + + await waitFor(() => { + expect(addError).toHaveBeenCalledWith(expect.any(Error), { + title: i18n.BACKFILLS_TABLE_STOP_CONFIRMATION_ERROR, + toastMessage: 'Error stopping backfill', + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/stop_backfill.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/stop_backfill.tsx index 6dfca1922d2a4..84acf0b014d60 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/stop_backfill.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/components/rule_backfills_info/stop_backfill.tsx @@ -10,12 +10,20 @@ import { EuiButtonEmpty, EuiConfirmModal } from '@elastic/eui'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useDeleteBackfill } from '../../api/hooks/use_delete_backfill'; import * as i18n from '../../translations'; +import type { BackfillRow } from '../../types'; +import { useKibana } from '../../../../common/lib/kibana'; -export const StopBackfill = ({ id }: { id: string }) => { +export const StopBackfill = ({ backfill }: { backfill: BackfillRow }) => { + const { telemetry } = useKibana().services; const { addSuccess, addError } = useAppToasts(); const deleteBackfillMutation = useDeleteBackfill({ onSuccess: () => { closeModal(); + telemetry.reportManualRuleRunCancelJob({ + totalTasks: backfill.total, + completedTasks: backfill.complete, + errorTasks: backfill.error, + }); addSuccess(i18n.BACKFILLS_TABLE_STOP_CONFIRMATION_SUCCESS); }, onError: (error) => { @@ -29,7 +37,7 @@ export const StopBackfill = ({ id }: { id: string }) => { const showModal = () => setIsModalVisible(true); const closeModal = () => setIsModalVisible(false); const onConfirm = () => { - deleteBackfillMutation.mutate({ backfillId: id }); + deleteBackfillMutation.mutate({ backfillId: backfill.id }); }; return ( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.test.tsx index 94c3f7e36acdb..36bdf8a8bf821 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.test.tsx @@ -5,20 +5,38 @@ * 2.0. */ -import { INTERNAL_ALERTING_BACKFILL_SCHEDULE_API_PATH } from '@kbn/alerting-plugin/common'; import { act, renderHook } from '@testing-library/react-hooks'; import moment from 'moment'; import { useKibana } from '../../../common/lib/kibana'; +import { useKibana as mockUseKibana } from '../../../common/lib/kibana/__mocks__'; import { TestProviders } from '../../../common/mock'; import { useScheduleRuleRun } from './use_schedule_rule_run'; +const mockUseScheduleRuleRunMutation = jest.fn(); + jest.mock('../../../common/lib/kibana'); +jest.mock('../api/hooks/use_schedule_rule_run_mutation', () => ({ + useScheduleRuleRunMutation: () => { + return { + mutateAsync: mockUseScheduleRuleRunMutation, + }; + }, +})); -const useKibanaMock = useKibana as jest.MockedFunction<typeof useKibana>; +const mockedUseKibana = { + ...mockUseKibana(), + services: { + ...mockUseKibana().services, + telemetry: { + reportManualRuleRunExecute: jest.fn(), + }, + }, +}; describe('When using the `useScheduleRuleRun()` hook', () => { beforeEach(() => { jest.clearAllMocks(); + (useKibana as jest.Mock).mockReturnValue(mockedUseKibana); }); it('should send schedule rule run request', async () => { @@ -31,13 +49,61 @@ describe('When using the `useScheduleRuleRun()` hook', () => { result.current.scheduleRuleRun({ ruleIds: ['rule-1'], timeRange }); }); - await waitFor(() => (useKibanaMock().services.http.fetch as jest.Mock).mock.calls.length > 0); + await waitFor(() => { + return mockUseScheduleRuleRunMutation.mock.calls.length > 0; + }); - expect(useKibanaMock().services.http.fetch).toHaveBeenCalledWith( - INTERNAL_ALERTING_BACKFILL_SCHEDULE_API_PATH, + expect(mockUseScheduleRuleRunMutation).toHaveBeenCalledWith( expect.objectContaining({ - body: `[{"rule_id":"rule-1","start":"${timeRange.startDate.toISOString()}","end":"${timeRange.endDate.toISOString()}"}]`, + ruleIds: ['rule-1'], + timeRange, }) ); }); + + it('should call reportManualRuleRunExecute with success status on success', async () => { + const { result, waitFor } = renderHook(() => useScheduleRuleRun(), { + wrapper: TestProviders, + }); + + const timeRange = { startDate: moment().subtract(1, 'd'), endDate: moment() }; + mockUseScheduleRuleRunMutation.mockResolvedValueOnce([{ id: 'rule-1' }]); + + act(() => { + result.current.scheduleRuleRun({ ruleIds: ['rule-1'], timeRange }); + }); + + await waitFor(() => { + return mockUseScheduleRuleRunMutation.mock.calls.length > 0; + }); + + expect(mockedUseKibana.services.telemetry.reportManualRuleRunExecute).toHaveBeenCalledWith({ + rangeInMs: timeRange.endDate.diff(timeRange.startDate), + status: 'success', + rulesCount: 1, + }); + }); + + it('should call reportManualRuleRunExecute with error status on failure', async () => { + const { result, waitFor } = renderHook(() => useScheduleRuleRun(), { + wrapper: TestProviders, + }); + + const timeRange = { startDate: moment().subtract(1, 'd'), endDate: moment() }; + mockUseScheduleRuleRunMutation.mockRejectedValueOnce(new Error('Error scheduling rule run')); + + act(() => { + result.current.scheduleRuleRun({ ruleIds: ['rule-1'], timeRange }); + }); + + await waitFor(() => { + return mockUseScheduleRuleRunMutation.mock.calls.length > 0; + }); + + expect(mockedUseKibana.services.telemetry.reportManualRuleRunExecute).toHaveBeenCalledWith({ + rangeInMs: timeRange.endDate.diff(timeRange.startDate), + status: 'error', + rulesCount: 1, + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.ts index 7c00c4294acdc..7599d8685d3c0 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.ts @@ -7,6 +7,7 @@ import { useCallback } from 'react'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import { useKibana } from '../../../common/lib/kibana'; import { useScheduleRuleRunMutation } from '../api/hooks/use_schedule_rule_run_mutation'; import type { ScheduleBackfillProps } from '../types'; @@ -15,18 +16,29 @@ import * as i18n from '../translations'; export function useScheduleRuleRun() { const { mutateAsync } = useScheduleRuleRunMutation(); const { addError, addSuccess } = useAppToasts(); + const { telemetry } = useKibana().services; const scheduleRuleRun = useCallback( async (options: ScheduleBackfillProps) => { try { const results = await mutateAsync(options); + telemetry.reportManualRuleRunExecute({ + rangeInMs: options.timeRange.endDate.diff(options.timeRange.startDate), + status: 'success', + rulesCount: options.ruleIds.length, + }); addSuccess(i18n.BACKFILL_SCHEDULE_SUCCESS(results.length)); return results; } catch (error) { addError(error, { title: i18n.BACKFILL_SCHEDULE_ERROR_TITLE }); + telemetry.reportManualRuleRunExecute({ + rangeInMs: options.timeRange.endDate.diff(options.timeRange.startDate), + status: 'error', + rulesCount: options.ruleIds.length, + }); } }, - [addError, addSuccess, mutateAsync] + [addError, addSuccess, mutateAsync, telemetry] ); return { scheduleRuleRun }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx index 5ea5d3d456f15..c93e5040d7aca 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx @@ -239,6 +239,9 @@ export const useBulkActions = ({ } const modalManualRuleRunConfirmationResult = await showManualRuleRunConfirmation(); + startServices.telemetry.reportManualRuleRunOpenModal({ + type: 'bulk', + }); if (modalManualRuleRunConfirmationResult === null) { return; } @@ -253,6 +256,14 @@ export const useBulkActions = ({ end_date: modalManualRuleRunConfirmationResult.endDate.toISOString(), }, }); + + startServices.telemetry.reportManualRuleRunExecute({ + rangeInMs: modalManualRuleRunConfirmationResult.endDate.diff( + modalManualRuleRunConfirmationResult.startDate + ), + status: 'success', + rulesCount: enabledIds.length, + }); }; const handleBulkEdit = (bulkEditActionType: BulkActionEditType) => async () => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx index 4af2fdd7ef356..984df06342a1a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx @@ -36,7 +36,10 @@ export const useRulesTableActions = ({ showManualRuleRunConfirmation: () => Promise<TimeRange | null>; confirmDeletion: () => Promise<boolean>; }): Array<DefaultItemAction<Rule>> => { - const { navigateToApp } = useKibana().services.application; + const { + application: { navigateToApp }, + telemetry, + } = useKibana().services; const hasActionsPrivileges = useHasActionsPrivileges(); const { startTransaction } = useStartTransaction(); const { executeBulkAction } = useExecuteBulkAction(); @@ -129,6 +132,9 @@ export const useRulesTableActions = ({ onClick: async (rule: Rule) => { startTransaction({ name: SINGLE_RULE_ACTIONS.MANUAL_RULE_RUN }); const modalManualRuleRunConfirmationResult = await showManualRuleRunConfirmation(); + telemetry.reportManualRuleRunOpenModal({ + type: 'single', + }); if (modalManualRuleRunConfirmationResult === null) { return; } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/execution_run_type_filter/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/execution_run_type_filter/index.test.tsx new file mode 100644 index 0000000000000..50c35e7a6e529 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/execution_run_type_filter/index.test.tsx @@ -0,0 +1,47 @@ +/* + * 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, screen, fireEvent } from '@testing-library/react'; +import { ExecutionRunTypeFilter } from '.'; +import { RuleRunTypeEnum } from '../../../../../../../common/api/detection_engine/rule_monitoring'; +import { useKibana } from '../../../../../../common/lib/kibana'; + +jest.mock('../../../../../../common/lib/kibana'); + +const mockTelemetry = { + reportEventLogFilterByRunType: jest.fn(), +}; + +const mockUseKibana = useKibana as jest.Mock; + +mockUseKibana.mockReturnValue({ + services: { + telemetry: mockTelemetry, + }, +}); + +const items = [RuleRunTypeEnum.backfill, RuleRunTypeEnum.standard]; + +describe('ExecutionRunTypeFilter', () => { + it('calls telemetry.reportEventLogFilterByRunType on selection change', () => { + const handleChange = jest.fn(); + + render(<ExecutionRunTypeFilter items={items} selectedItems={[]} onChange={handleChange} />); + + const select = screen.getByText('Run type'); + fireEvent.click(select); + + const manualRun = screen.getByText('Manual'); + fireEvent.click(manualRun); + + expect(handleChange).toHaveBeenCalledWith([RuleRunTypeEnum.backfill]); + expect(mockTelemetry.reportEventLogFilterByRunType).toHaveBeenCalledWith({ + runType: [RuleRunTypeEnum.backfill], + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/execution_run_type_filter/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/execution_run_type_filter/index.tsx index 773e64e71ffc9..9f144410a7590 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/execution_run_type_filter/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/execution_run_type_filter/index.tsx @@ -14,6 +14,7 @@ import { RULE_EXECUTION_TYPE_BACKFILL, RULE_EXECUTION_TYPE_STANDARD, } from '../../../../../../common/translations'; +import { useKibana } from '../../../../../../common/lib/kibana'; interface ExecutionRunTypeFilterProps { items: RuleRunType[]; @@ -26,6 +27,8 @@ const ExecutionRunTypeFilterComponent: React.FC<ExecutionRunTypeFilterProps> = ( selectedItems, onChange, }) => { + const { telemetry } = useKibana().services; + const renderItem = useCallback((item: RuleRunType) => { if (item === RuleRunTypeEnum.backfill) { return RULE_EXECUTION_TYPE_BACKFILL; @@ -36,13 +39,21 @@ const ExecutionRunTypeFilterComponent: React.FC<ExecutionRunTypeFilterProps> = ( } }, []); + const handleSelectionChange = useCallback( + (types: RuleRunType[]) => { + onChange(types); + telemetry.reportEventLogFilterByRunType({ runType: types }); + }, + [onChange, telemetry] + ); + return ( <MultiselectFilter<RuleRunType> dataTestSubj="ExecutionRunTypeFilter" title={i18n.FILTER_TITLE} items={items} selectedItems={selectedItems} - onSelectionChange={onChange} + onSelectionChange={handleSelectionChange} renderItem={renderItem} /> ); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx index 43a9246d8d5c9..298ae1c503533 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx @@ -28,12 +28,17 @@ jest.mock('../../../../detection_engine/rule_management/logic/bulk_actions/use_b jest.mock('../../../../detection_engine/rule_gaps/logic/use_schedule_rule_run'); jest.mock('../../../../common/lib/apm/use_start_transaction'); jest.mock('../../../../common/hooks/use_app_toasts'); +const mockReportManualRuleRunOpenModal = jest.fn(); jest.mock('../../../../common/lib/kibana', () => { const actual = jest.requireActual('../../../../common/lib/kibana'); return { ...actual, useKibana: jest.fn().mockReturnValue({ services: { + telemetry: { + reportManualRuleRunOpenModal: (params: { type: 'single' | 'bulk' }) => + mockReportManualRuleRunOpenModal(params), + }, application: { navigateToApp: jest.fn(), }, @@ -287,5 +292,27 @@ describe('RuleActionsOverflow', () => { expect(getByTestId('rules-details-menu-panel')).not.toHaveTextContent('Manual run'); }); + + test('it calls telemetry.reportManualRuleRunOpenModal when rules-details-manual-rule-run is clicked', async () => { + const { getByTestId } = render( + <RuleActionsOverflow + showBulkDuplicateExceptionsConfirmation={showBulkDuplicateExceptionsConfirmation} + showManualRuleRunConfirmation={showManualRuleRunConfirmation} + rule={mockRule('id')} + userHasPermissions + canDuplicateRuleWithActions={true} + confirmDeletion={() => Promise.resolve(true)} + />, + { wrapper: TestProviders } + ); + fireEvent.click(getByTestId('rules-details-popover-button-icon')); + fireEvent.click(getByTestId('rules-details-manual-rule-run')); + + await waitFor(() => { + expect(mockReportManualRuleRunOpenModal).toHaveBeenCalledWith({ + type: 'single', + }); + }); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx index 6ed110483ecc4..f1889efd1a556 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx @@ -68,7 +68,10 @@ const RuleActionsOverflowComponent = ({ confirmDeletion, }: RuleActionsOverflowComponentProps) => { const [isPopoverOpen, , closePopover, togglePopover] = useBoolState(); - const { navigateToApp } = useKibana().services.application; + const { + application: { navigateToApp }, + telemetry, + } = useKibana().services; const { startTransaction } = useStartTransaction(); const { executeBulkAction } = useExecuteBulkAction({ suppressSuccessToast: true }); const { bulkExport } = useBulkExport(); @@ -166,6 +169,9 @@ const RuleActionsOverflowComponent = ({ closePopover(); const modalManualRuleRunConfirmationResult = await showManualRuleRunConfirmation(); + telemetry.reportManualRuleRunOpenModal({ + type: 'single', + }); if (modalManualRuleRunConfirmationResult === null) { return; } @@ -221,6 +227,7 @@ const RuleActionsOverflowComponent = ({ confirmDeletion, scheduleRuleRun, isManualRuleRunEnabled, + telemetry, ] ); From 43058ca97b7518ffe362a67c58981bfff128ccc9 Mon Sep 17 00:00:00 2001 From: Ash <1849116+ashokaditya@users.noreply.github.com> Date: Tue, 2 Jul 2024 23:29:01 +0200 Subject: [PATCH 115/128] [Serverless][SecuritySolution][Endpoint] Update `serverless` tests for scan w.r.to. PLIs (#187376) ## Summary As `scan` response action is categorized now with Endpoint complete PLI include `scan` action in serverless tests using `responseActionScanEnabled` feature flag. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed --- .../project_roles/security/roles.yml | 1 + .../serverless/feature_access/complete.cy.ts | 19 ++++++++++++------- .../complete_with_endpoint.cy.ts | 15 +++++++-------- .../feature_access/essentials.cy.ts | 15 +++++++++------ .../essentials_with_endpoint.cy.ts | 15 +++++++++------ .../roles/complete_with_endpoint_roles.cy.ts | 8 +++++++- .../management/cypress/screens/responder.ts | 6 ++---- .../cypress/tasks/response_actions.ts | 6 ++++++ .../es_serverless_resources/roles.yml | 1 + .../endpoint/common/roles_users/t3_analyst.ts | 1 + .../project_controller_security_roles.yml | 1 + 11 files changed, 56 insertions(+), 32 deletions(-) diff --git a/packages/kbn-es/src/serverless_resources/project_roles/security/roles.yml b/packages/kbn-es/src/serverless_resources/project_roles/security/roles.yml index 3c118688f6429..b05bb0de2f2c8 100644 --- a/packages/kbn-es/src/serverless_resources/project_roles/security/roles.yml +++ b/packages/kbn-es/src/serverless_resources/project_roles/security/roles.yml @@ -289,6 +289,7 @@ t3_analyst: - feature_siem.process_operations_all - feature_siem.actions_log_management_all # Response actions history - feature_siem.file_operations_all + - feature_siem.scan_operations_all - feature_securitySolutionCases.all - feature_securitySolutionAssistant.all - feature_actions.read diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/complete.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/complete.cy.ts index 57b2820921dd9..21a7253109d4a 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/complete.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/complete.cy.ts @@ -16,7 +16,14 @@ describe( { tags: ['@serverless', '@skipInServerlessMKI'], env: { - ftrConfig: { productTypes: [{ product_line: 'security', product_tier: 'complete' }] }, + ftrConfig: { + productTypes: [{ product_line: 'security', product_tier: 'complete' }], + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'responseActionScanEnabled', + ])}`, + ], + }, }, }, () => { @@ -53,10 +60,9 @@ describe( } // No access to response actions (except `unisolate`) - // TODO: update tests when `scan` is included in PLIs for (const actionName of RESPONSE_ACTION_API_COMMANDS_NAMES.filter( - (apiName) => apiName !== 'scan' - ).filter((apiName) => apiName !== 'unisolate')) { + (apiName) => apiName !== 'unisolate' + )) { it(`should not allow access to Response Action: ${actionName}`, () => { ensureResponseActionAuthzAccess('none', actionName, username, password); }); @@ -79,10 +85,9 @@ describe( }); // No access to response actions (except `unisolate`) - // TODO: update tests when `scan` is included in PLIs for (const actionName of RESPONSE_ACTION_API_COMMANDS_NAMES.filter( - (apiName) => apiName !== 'scan' - ).filter((apiName) => apiName !== 'unisolate')) { + (apiName) => apiName !== 'unisolate' + )) { it(`should not allow access to Response Action: ${actionName}`, () => { ensureResponseActionAuthzAccess('none', actionName, username, password); }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/complete_with_endpoint.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/complete_with_endpoint.cy.ts index da17beb14d760..54d5b688aa1d1 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/complete_with_endpoint.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/complete_with_endpoint.cy.ts @@ -24,6 +24,11 @@ describe( { product_line: 'security', product_tier: 'complete' }, { product_line: 'endpoint', product_tier: 'complete' }, ], + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'responseActionScanEnabled', + ])}`, + ], }, }, }, @@ -47,10 +52,7 @@ describe( }); } - // TODO: update tests when `scan` is included in PLIs - for (const actionName of RESPONSE_ACTION_API_COMMANDS_NAMES.filter( - (apiName) => apiName !== 'scan' - )) { + for (const actionName of RESPONSE_ACTION_API_COMMANDS_NAMES) { it(`should allow access to Response Action: ${actionName}`, () => { ensureResponseActionAuthzAccess('all', actionName, username, password); }); @@ -73,10 +75,7 @@ describe( }); }); - // TODO: update tests when `scan` is included in PLIs - for (const actionName of RESPONSE_ACTION_API_COMMANDS_NAMES.filter( - (apiName) => apiName !== 'scan' - )) { + for (const actionName of RESPONSE_ACTION_API_COMMANDS_NAMES) { it(`should allow access to Response Action: ${actionName}`, () => { ensureResponseActionAuthzAccess('all', actionName, username, password); }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/essentials.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/essentials.cy.ts index e4388924f05fc..7be22d7e7e5b5 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/essentials.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/essentials.cy.ts @@ -18,6 +18,11 @@ describe( env: { ftrConfig: { productTypes: [{ product_line: 'security', product_tier: 'essentials' }], + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'responseActionScanEnabled', + ])}`, + ], }, }, }, @@ -55,10 +60,9 @@ describe( } // No access to response actions (except `unisolate`) - // TODO: update tests when `scan` is included in PLIs for (const actionName of RESPONSE_ACTION_API_COMMANDS_NAMES.filter( - (apiName) => apiName !== 'scan' - ).filter((apiName) => apiName !== 'unisolate')) { + (apiName) => apiName !== 'unisolate' + )) { it(`should not allow access to Response Action: ${actionName}`, () => { ensureResponseActionAuthzAccess('none', actionName, username, password); }); @@ -81,10 +85,9 @@ describe( }); // No access to response actions (except `unisolate`) - // TODO: update tests when `scan` is included in PLIs for (const actionName of RESPONSE_ACTION_API_COMMANDS_NAMES.filter( - (apiName) => apiName !== 'scan' - ).filter((apiName) => apiName !== 'unisolate')) { + (apiName) => apiName !== 'unisolate' + )) { it(`should not allow access to Response Action: ${actionName}`, () => { ensureResponseActionAuthzAccess('none', actionName, username, password); }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/essentials_with_endpoint.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/essentials_with_endpoint.cy.ts index 4a37f1089e897..a57102e7a2b2c 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/essentials_with_endpoint.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/feature_access/essentials_with_endpoint.cy.ts @@ -24,6 +24,11 @@ describe( { product_line: 'security', product_tier: 'essentials' }, { product_line: 'endpoint', product_tier: 'essentials' }, ], + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'responseActionScanEnabled', + ])}`, + ], }, }, }, @@ -62,10 +67,9 @@ describe( }); } - // TODO: update tests when `scan` is included in PLIs for (const actionName of RESPONSE_ACTION_API_COMMANDS_NAMES.filter( - (apiName) => apiName !== 'scan' - ).filter((apiName) => apiName !== 'unisolate')) { + (apiName) => apiName !== 'unisolate' + )) { it(`should not allow access to Response Action: ${actionName}`, () => { ensureResponseActionAuthzAccess('none', actionName, username, password); }); @@ -92,10 +96,9 @@ describe( }); }); - // TODO: update tests when `scan` is included in PLIs for (const actionName of RESPONSE_ACTION_API_COMMANDS_NAMES.filter( - (apiName) => apiName !== 'scan' - ).filter((apiName) => apiName !== 'unisolate')) { + (apiName) => apiName !== 'unisolate' + )) { it(`should not allow access to Response Action: ${actionName}`, () => { ensureResponseActionAuthzAccess('none', actionName, username, password); }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/roles/complete_with_endpoint_roles.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/roles/complete_with_endpoint_roles.cy.ts index 8d2b564e9dd1a..a31ae854aa059 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/roles/complete_with_endpoint_roles.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/roles/complete_with_endpoint_roles.cy.ts @@ -40,6 +40,11 @@ describe( { product_line: 'security', product_tier: 'complete' }, { product_line: 'endpoint', product_tier: 'complete' }, ], + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'responseActionScanEnabled', + ])}`, + ], }, }, }, @@ -118,7 +123,8 @@ describe( 'kill-process', 'suspend-process', 'get-file', - 'upload' + 'upload', + 'scan' ); const deniedResponseActions = pick(consoleHelpPanelResponseActionsTestSubj, 'execute'); diff --git a/x-pack/plugins/security_solution/public/management/cypress/screens/responder.ts b/x-pack/plugins/security_solution/public/management/cypress/screens/responder.ts index 7e920772374c5..19a86eb153bc2 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/screens/responder.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/screens/responder.ts @@ -14,9 +14,8 @@ const TEST_SUBJ = Object.freeze({ actionLogFlyout: 'responderActionLogFlyout', }); -// TODO: 8.15 Include `scan` in return type when responseActionsScanEnabled when `scan` is categorized in PLIs export const getConsoleHelpPanelResponseActionTestSubj = (): Record< - Exclude<ConsoleResponseActionCommands, 'scan'>, + ConsoleResponseActionCommands, string > => { return { @@ -28,8 +27,7 @@ export const getConsoleHelpPanelResponseActionTestSubj = (): Record< 'get-file': 'endpointResponseActionsConsole-commandList-Responseactions-get-file', execute: 'endpointResponseActionsConsole-commandList-Responseactions-execute', upload: 'endpointResponseActionsConsole-commandList-Responseactions-upload', - // TODO: 8.15 Include `scan` in return type when responseActionsScanEnabled when `scan` is categorized in PLIs - // scan: 'endpointResponseActionsConsole-commandList-Responseactions-scan', + scan: 'endpointResponseActionsConsole-commandList-Responseactions-scan', }; }; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts index 488742ac945c8..a55e385b4b1d0 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts @@ -16,6 +16,7 @@ import { GET_PROCESSES_ROUTE, ISOLATE_HOST_ROUTE_V2, KILL_PROCESS_ROUTE, + SCAN_ROUTE, SUSPEND_PROCESS_ROUTE, UNISOLATE_HOST_ROUTE_V2, UPLOAD_ROUTE, @@ -243,6 +244,11 @@ export const ensureResponseActionAuthzAccess = ( } break; + case 'scan': + url = SCAN_ROUTE; + Object.assign(apiPayload, { parameters: { path: 'scan/two' } }); + break; + default: throw new Error(`Response action [${responseAction}] has no API payload defined`); } diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/es_serverless_resources/roles.yml b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/es_serverless_resources/roles.yml index c94d4a9a31d8e..12214295817a5 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/es_serverless_resources/roles.yml +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/es_serverless_resources/roles.yml @@ -311,6 +311,7 @@ t3_analyst: - feature_siem.process_operations_all - feature_siem.actions_log_management_all # Response actions history - feature_siem.file_operations_all + - feature_siem.scan_operations_all - feature_securitySolutionCases.all - feature_securitySolutionAssistant.all - feature_actions.read diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/t3_analyst.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/t3_analyst.ts index 304c4e6d744ee..872cb1c352fd3 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/t3_analyst.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/t3_analyst.ts @@ -31,6 +31,7 @@ export const getT3Analyst: () => Omit<Role, 'name'> = () => { 'process_operations_all', 'actions_log_management_all', 'file_operations_all', + 'scan_operations_all', ], }, }, diff --git a/x-pack/test_serverless/shared/lib/security/kibana_roles/project_controller_security_roles.yml b/x-pack/test_serverless/shared/lib/security/kibana_roles/project_controller_security_roles.yml index 5f86c7a23ca27..9e1e542df8e87 100644 --- a/x-pack/test_serverless/shared/lib/security/kibana_roles/project_controller_security_roles.yml +++ b/x-pack/test_serverless/shared/lib/security/kibana_roles/project_controller_security_roles.yml @@ -292,6 +292,7 @@ t3_analyst: - feature_siem.process_operations_all - feature_siem.actions_log_management_all # Response actions history - feature_siem.file_operations_all + - feature_siem.scan_operations_all - feature_securitySolutionCases.all - feature_securitySolutionAssistant.all - feature_actions.read From 08017ae2dce04e08ec5c1d42896fafef224bf429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= <weltenwort@users.noreply.github.com> Date: Wed, 3 Jul 2024 00:37:49 +0200 Subject: [PATCH 116/128] [Logs UI] Fix log entry fly-out when response is slow (#187303) This disables a change in polling behavior of the ESE search strategy, which was introduced with https://github.com/elastic/kibana/pull/178921. The response processing and progress reporting depends on it. --- .../log_entries_search_strategy.ts | 11 +- .../log_entry_search_strategy.test.ts | 255 ++++++++++-------- .../log_entries/log_entry_search_strategy.ts | 11 +- 3 files changed, 165 insertions(+), 112 deletions(-) diff --git a/x-pack/plugins/observability_solution/logs_shared/server/services/log_entries/log_entries_search_strategy.ts b/x-pack/plugins/observability_solution/logs_shared/server/services/log_entries/log_entries_search_strategy.ts index 587e0cd753f6a..f886060964b5f 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/services/log_entries/log_entries_search_strategy.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/services/log_entries/log_entries_search_strategy.ts @@ -118,7 +118,16 @@ export const logEntriesSearchStrategyProvider = ({ const searchResponse$ = concat(recoveredRequest$, initialRequest$).pipe( take(1), - concatMap((esRequest) => esSearchStrategy.search(esRequest, options, dependencies)) + concatMap((esRequest) => + esSearchStrategy.search( + esRequest, + { + ...options, + retrieveResults: true, // the subsequent processing requires the actual search results + }, + dependencies + ) + ) ); return combineLatest([searchResponse$, resolvedLogView$, messageFormattingRules$]).pipe( diff --git a/x-pack/plugins/observability_solution/logs_shared/server/services/log_entries/log_entry_search_strategy.test.ts b/x-pack/plugins/observability_solution/logs_shared/server/services/log_entries/log_entry_search_strategy.test.ts index 9a82a743e8e53..7c46fe37d649e 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/services/log_entries/log_entry_search_strategy.test.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/services/log_entries/log_entry_search_strategy.test.ts @@ -5,17 +5,21 @@ * 2.0. */ -import { errors } from '@elastic/elasticsearch'; -import { lastValueFrom, of, throwError } from 'rxjs'; +import { errors, TransportResult } from '@elastic/elasticsearch'; +import { AsyncSearchSubmitResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { elasticsearchServiceMock, httpServerMock, savedObjectsClientMock, uiSettingsServiceMock, } from '@kbn/core/server/mocks'; -import { IEsSearchRequest, IEsSearchResponse } from '@kbn/search-types'; -import { ISearchStrategy, SearchStrategyDependencies } from '@kbn/data-plugin/server'; +import { getMockSearchConfig } from '@kbn/data-plugin/config.mock'; +import { ISearchStrategy } from '@kbn/data-plugin/server'; +import { enhancedEsSearchStrategyProvider } from '@kbn/data-plugin/server/search'; import { createSearchSessionsClientMock } from '@kbn/data-plugin/server/search/mocks'; +import { KbnSearchError } from '@kbn/data-plugin/server/search/report_search_error'; +import { loggerMock } from '@kbn/logging-mocks'; +import { EMPTY, lastValueFrom } from 'rxjs'; import { createResolvedLogViewMock } from '../../../common/log_views/resolved_log_view.mock'; import { createLogViewsClientMock } from '../log_views/log_views_client.mock'; import { createLogViewsServiceStartMock } from '../log_views/log_views_service.mock'; @@ -26,23 +30,34 @@ import { describe('LogEntry search strategy', () => { it('handles initial search requests', async () => { - const esSearchStrategyMock = createEsSearchStrategyMock({ - id: 'ASYNC_REQUEST_ID', - isRunning: true, - rawResponse: { - took: 0, - _shards: { total: 1, failed: 0, skipped: 0, successful: 0 }, - timed_out: false, - hits: { total: 0, max_score: 0, hits: [] }, + const esSearchStrategy = createEsSearchStrategy(); + const mockDependencies = createSearchStrategyDependenciesMock(); + const esClient = mockDependencies.esClient.asCurrentUser; + esClient.asyncSearch.submit.mockResolvedValueOnce({ + body: { + id: 'ASYNC_REQUEST_ID', + response: { + took: 0, + _shards: { total: 1, failed: 0, skipped: 0, successful: 0 }, + timed_out: false, + hits: { total: 0, max_score: 0, hits: [] }, + }, + is_partial: false, + is_running: false, + expiration_time_in_millis: 0, + start_time_in_millis: 0, }, - }); + statusCode: 200, + headers: {}, + warnings: [], + meta: {} as any, + } as TransportResult<AsyncSearchSubmitResponse> as any); // type inference for the mock fails - const dataMock = createDataPluginMock(esSearchStrategyMock); + const dataMock = createDataPluginMock(esSearchStrategy); const logViewsClientMock = createLogViewsClientMock(); logViewsClientMock.getResolvedLogView.mockResolvedValue(createResolvedLogViewMock()); const logViewsMock = createLogViewsServiceStartMock(); logViewsMock.getScopedClient.mockReturnValue(logViewsClientMock); - const mockDependencies = createSearchStrategyDependenciesMock(); const logEntrySearchStrategy = logEntrySearchStrategyProvider({ data: dataMock, @@ -62,72 +77,88 @@ describe('LogEntry search strategy', () => { ) ); + // ensure log view was resolved expect(logViewsMock.getScopedClient).toHaveBeenCalled(); expect(logViewsClientMock.getResolvedLogView).toHaveBeenCalled(); - expect(esSearchStrategyMock.search).toHaveBeenCalledWith( - { - params: expect.objectContaining({ - index: 'log-indices-*', - body: expect.objectContaining({ - track_total_hits: false, - terminate_after: 1, - query: { - ids: { - values: ['LOG_ENTRY_ID'], - }, + + // ensure search request was made + expect(esClient.asyncSearch.submit).toHaveBeenCalledWith( + expect.objectContaining({ + index: 'log-indices-*', + body: expect.objectContaining({ + track_total_hits: false, + terminate_after: 1, + query: { + ids: { + values: ['LOG_ENTRY_ID'], }, - runtime_mappings: { - runtime_field: { - type: 'keyword', - script: { - source: 'emit("runtime value")', - }, + }, + runtime_mappings: { + runtime_field: { + type: 'keyword', + script: { + source: 'emit("runtime value")', }, }, - }), + }, }), - }, - expect.anything(), + }), expect.anything() ); + + // ensure response content is as expected expect(response.id).toEqual(expect.any(String)); - expect(response.isRunning).toBe(true); + expect(response.isRunning).toBe(false); }); it('handles subsequent polling requests', async () => { const date = new Date(1605116827143).toISOString(); - const esSearchStrategyMock = createEsSearchStrategyMock({ - id: 'ASYNC_REQUEST_ID', - isRunning: false, - rawResponse: { - took: 1, - _shards: { total: 1, failed: 0, skipped: 0, successful: 1 }, - timed_out: false, - hits: { - total: 0, - max_score: 0, - hits: [ - { - _id: 'HIT_ID', - _index: 'HIT_INDEX', - _score: 0, - _source: null, - fields: { - '@timestamp': [date], - message: ['HIT_MESSAGE'], + const esSearchStrategy = createEsSearchStrategy(); + const mockDependencies = createSearchStrategyDependenciesMock(); + const esClient = mockDependencies.esClient.asCurrentUser; + + // set up response to polling request + esClient.asyncSearch.get.mockResolvedValueOnce({ + body: { + id: 'ASYNC_REQUEST_ID', + response: { + took: 0, + _shards: { total: 1, failed: 0, skipped: 0, successful: 0 }, + timed_out: false, + hits: { + total: 1, + max_score: 0, + hits: [ + { + _id: 'HIT_ID', + _index: 'HIT_INDEX', + _score: 0, + _source: null, + fields: { + '@timestamp': [date], + message: ['HIT_MESSAGE'], + }, + sort: [date as any, 1 as any], // incorrectly typed as string upstream }, - sort: [date as any, 1 as any], // incorrectly typed as string upstream - }, - ], + ], + }, }, + is_partial: false, + is_running: false, + expiration_time_in_millis: 0, + start_time_in_millis: 0, }, - }); - const dataMock = createDataPluginMock(esSearchStrategyMock); + statusCode: 200, + headers: {}, + warnings: [], + meta: {} as any, + } as TransportResult<AsyncSearchSubmitResponse> as any); + + const dataMock = createDataPluginMock(esSearchStrategy); const logViewsClientMock = createLogViewsClientMock(); logViewsClientMock.getResolvedLogView.mockResolvedValue(createResolvedLogViewMock()); const logViewsMock = createLogViewsServiceStartMock(); logViewsMock.getScopedClient.mockReturnValue(logViewsClientMock); - const mockDependencies = createSearchStrategyDependenciesMock(); const logEntrySearchStrategy = logEntrySearchStrategyProvider({ data: dataMock, @@ -151,9 +182,18 @@ describe('LogEntry search strategy', () => { ) ); + // ensure search was polled using the get API + expect(esClient.asyncSearch.get).toHaveBeenCalledWith( + expect.objectContaining({ id: 'ASYNC_REQUEST_ID' }), + expect.anything() + ); + expect(esClient.asyncSearch.status).not.toHaveBeenCalled(); + + // ensure log view was not resolved again expect(logViewsMock.getScopedClient).not.toHaveBeenCalled(); expect(logViewsClientMock.getResolvedLogView).not.toHaveBeenCalled(); - expect(esSearchStrategyMock.search).toHaveBeenCalled(); + + // ensure response content is as expected expect(response.id).toEqual(requestId); expect(response.isRunning).toBe(false); expect(response.rawResponse.data).toEqual({ @@ -171,22 +211,30 @@ describe('LogEntry search strategy', () => { }); it('forwards errors from the underlying search strategy', async () => { - const esSearchStrategyMock = createEsSearchStrategyMock({ - id: 'ASYNC_REQUEST_ID', - isRunning: false, - rawResponse: { - took: 1, - _shards: { total: 1, failed: 0, skipped: 0, successful: 1 }, - timed_out: false, - hits: { total: 0, max_score: 0, hits: [] }, - }, - }); - const dataMock = createDataPluginMock(esSearchStrategyMock); + const esSearchStrategy = createEsSearchStrategy(); + const mockDependencies = createSearchStrategyDependenciesMock(); + const esClient = mockDependencies.esClient.asCurrentUser; + + // set up failing response + esClient.asyncSearch.get.mockRejectedValueOnce( + new errors.ResponseError({ + body: { + error: { + type: 'mock_error', + }, + }, + headers: {}, + meta: {} as any, + statusCode: 404, + warnings: [], + }) + ); + + const dataMock = createDataPluginMock(esSearchStrategy); const logViewsClientMock = createLogViewsClientMock(); logViewsClientMock.getResolvedLogView.mockResolvedValue(createResolvedLogViewMock()); const logViewsMock = createLogViewsServiceStartMock(); logViewsMock.getScopedClient.mockReturnValue(logViewsClientMock); - const mockDependencies = createSearchStrategyDependenciesMock(); const logEntrySearchStrategy = logEntrySearchStrategyProvider({ data: dataMock, @@ -205,26 +253,24 @@ describe('LogEntry search strategy', () => { mockDependencies ); - await expect(response.toPromise()).rejects.toThrowError(errors.ResponseError); + await expect(lastValueFrom(response)).rejects.toThrowError(KbnSearchError); }); it('forwards cancellation to the underlying search strategy', async () => { - const esSearchStrategyMock = createEsSearchStrategyMock({ - id: 'ASYNC_REQUEST_ID', - isRunning: false, - rawResponse: { - took: 1, - _shards: { total: 1, failed: 0, skipped: 0, successful: 1 }, - timed_out: false, - hits: { total: 0, max_score: 0, hits: [] }, - }, + const esSearchStrategy = createEsSearchStrategy(); + const mockDependencies = createSearchStrategyDependenciesMock(); + const esClient = mockDependencies.esClient.asCurrentUser; + + // set up response to cancellation request + esClient.asyncSearch.delete.mockResolvedValueOnce({ + acknowledged: true, }); - const dataMock = createDataPluginMock(esSearchStrategyMock); + + const dataMock = createDataPluginMock(esSearchStrategy); const logViewsClientMock = createLogViewsClientMock(); logViewsClientMock.getResolvedLogView.mockResolvedValue(createResolvedLogViewMock()); const logViewsMock = createLogViewsServiceStartMock(); logViewsMock.getScopedClient.mockReturnValue(logViewsClientMock); - const mockDependencies = createSearchStrategyDependenciesMock(); const logEntrySearchStrategy = logEntrySearchStrategyProvider({ data: dataMock, @@ -236,34 +282,23 @@ describe('LogEntry search strategy', () => { await logEntrySearchStrategy.cancel?.(requestId, {}, mockDependencies); - expect(esSearchStrategyMock.cancel).toHaveBeenCalled(); + // ensure cancellation request is forwarded + expect(esClient.asyncSearch.delete).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'ASYNC_REQUEST_ID', + }) + ); }); }); -const createEsSearchStrategyMock = (esSearchResponse: IEsSearchResponse) => ({ - search: jest.fn((esSearchRequest: IEsSearchRequest) => { - if (typeof esSearchRequest.id === 'string') { - if (esSearchRequest.id === esSearchResponse.id) { - return of(esSearchResponse); - } else { - return throwError( - new errors.ResponseError({ - body: {}, - headers: {}, - meta: {} as any, - statusCode: 404, - warnings: [], - }) - ); - } - } else { - return of(esSearchResponse); - } - }), - cancel: jest.fn().mockResolvedValue(undefined), -}); +const createEsSearchStrategy = () => { + const legacyConfig$ = EMPTY; + const searchConfig = getMockSearchConfig({}); + const logger = loggerMock.create(); + return enhancedEsSearchStrategyProvider(legacyConfig$, searchConfig, logger); +}; -const createSearchStrategyDependenciesMock = (): SearchStrategyDependencies => ({ +const createSearchStrategyDependenciesMock = () => ({ uiSettingsClient: uiSettingsServiceMock.createClient(), esClient: elasticsearchServiceMock.createScopedClusterClient(), savedObjectsClient: savedObjectsClientMock.create(), diff --git a/x-pack/plugins/observability_solution/logs_shared/server/services/log_entries/log_entry_search_strategy.ts b/x-pack/plugins/observability_solution/logs_shared/server/services/log_entries/log_entry_search_strategy.ts index ee0a112fc3a63..635322383ffdd 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/services/log_entries/log_entry_search_strategy.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/services/log_entries/log_entry_search_strategy.ts @@ -82,7 +82,16 @@ export const logEntrySearchStrategyProvider = ({ return concat(recoveredRequest$, initialRequest$).pipe( take(1), - concatMap((esRequest) => esSearchStrategy.search(esRequest, options, dependencies)), + concatMap((esRequest) => + esSearchStrategy.search( + esRequest, + { + ...options, + retrieveResults: true, // without it response will not contain progress information + }, + dependencies + ) + ), map((esResponse) => ({ ...esResponse, rawResponse: decodeOrThrow(getLogEntryResponseRT)(esResponse.rawResponse), From 53ebc68f72b3f3608f05a3d0803faa56043f04fb Mon Sep 17 00:00:00 2001 From: Tiago Costa <tiago.costa@elastic.co> Date: Wed, 3 Jul 2024 04:59:18 +0100 Subject: [PATCH 117/128] skip flaky suite (#187421) --- .../tests/correlations/field_candidates.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/apm_api_integration/tests/correlations/field_candidates.spec.ts b/x-pack/test/apm_api_integration/tests/correlations/field_candidates.spec.ts index 1984f7f652c27..2fc54f5cb2659 100644 --- a/x-pack/test/apm_api_integration/tests/correlations/field_candidates.spec.ts +++ b/x-pack/test/apm_api_integration/tests/correlations/field_candidates.spec.ts @@ -25,7 +25,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, }); - registry.when('field candidates without data', { config: 'trial', archives: [] }, () => { + // FLAKY: https://github.com/elastic/kibana/issues/187421 + registry.when.skip('field candidates without data', { config: 'trial', archives: [] }, () => { it('handles the empty state', async () => { const response = await apmApiClient.readUser({ endpoint, From df1264fd5089d9b1a89664f209107d0d58491594 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 3 Jul 2024 06:01:24 +0200 Subject: [PATCH 118/128] skip failing test suite (#187388) --- x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts index e1aa13f739140..99d17c480af85 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts @@ -43,7 +43,8 @@ import { import { ServerlessRoleName } from '../../support/roles'; import { getAdvancedButton } from '../../screens/integrations'; -describe('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { +// Failing: See https://github.com/elastic/kibana/issues/187388 +describe.skip('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { let caseId: string; before(() => { From f7f841efac3905f610daf6a213c4ddaac181c76d Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 3 Jul 2024 06:55:56 +0200 Subject: [PATCH 119/128] [api-docs] 2024-07-03 Daily api_docs build (#187437) Generated by https://buildkite.com/elastic/kibana-api-docs-daily/builds/757 --- api_docs/actions.mdx | 2 +- api_docs/advanced_settings.mdx | 2 +- .../ai_assistant_management_selection.mdx | 2 +- api_docs/aiops.mdx | 2 +- api_docs/alerting.mdx | 2 +- api_docs/apm.devdocs.json | 6 +- api_docs/apm.mdx | 2 +- api_docs/apm_data_access.mdx | 2 +- api_docs/assets_data_access.mdx | 2 +- api_docs/banners.mdx | 2 +- api_docs/bfetch.mdx | 2 +- api_docs/canvas.mdx | 2 +- api_docs/cases.devdocs.json | 132 +++++++++--------- api_docs/cases.mdx | 2 +- api_docs/charts.mdx | 2 +- api_docs/cloud.mdx | 2 +- api_docs/cloud_data_migration.mdx | 2 +- api_docs/cloud_defend.mdx | 2 +- api_docs/cloud_experiments.mdx | 2 +- api_docs/cloud_security_posture.mdx | 2 +- api_docs/console.mdx | 2 +- api_docs/content_management.mdx | 2 +- api_docs/controls.mdx | 2 +- api_docs/custom_integrations.mdx | 2 +- api_docs/dashboard.mdx | 2 +- api_docs/dashboard_enhanced.mdx | 2 +- api_docs/data.devdocs.json | 8 ++ api_docs/data.mdx | 2 +- api_docs/data_quality.mdx | 2 +- api_docs/data_query.mdx | 2 +- api_docs/data_search.mdx | 2 +- api_docs/data_view_editor.mdx | 2 +- api_docs/data_view_field_editor.mdx | 2 +- api_docs/data_view_management.mdx | 2 +- api_docs/data_views.mdx | 2 +- api_docs/data_visualizer.mdx | 2 +- api_docs/dataset_quality.mdx | 2 +- api_docs/deprecations_by_api.mdx | 6 +- api_docs/deprecations_by_plugin.mdx | 9 +- api_docs/deprecations_by_team.mdx | 2 +- api_docs/dev_tools.mdx | 2 +- api_docs/discover.mdx | 2 +- api_docs/discover_enhanced.mdx | 2 +- api_docs/discover_shared.mdx | 2 +- api_docs/ecs_data_quality_dashboard.mdx | 2 +- api_docs/elastic_assistant.mdx | 2 +- api_docs/embeddable.mdx | 2 +- api_docs/embeddable_enhanced.mdx | 2 +- api_docs/encrypted_saved_objects.mdx | 2 +- api_docs/enterprise_search.mdx | 2 +- api_docs/entity_manager.mdx | 2 +- api_docs/es_ui_shared.mdx | 2 +- api_docs/esql_data_grid.mdx | 2 +- api_docs/event_annotation.mdx | 2 +- api_docs/event_annotation_listing.mdx | 2 +- api_docs/event_log.mdx | 2 +- api_docs/exploratory_view.mdx | 2 +- api_docs/expression_error.mdx | 2 +- api_docs/expression_gauge.mdx | 2 +- api_docs/expression_heatmap.mdx | 2 +- api_docs/expression_image.mdx | 2 +- api_docs/expression_legacy_metric_vis.mdx | 2 +- api_docs/expression_metric.mdx | 2 +- api_docs/expression_metric_vis.mdx | 2 +- api_docs/expression_partition_vis.mdx | 2 +- api_docs/expression_repeat_image.mdx | 2 +- api_docs/expression_reveal_image.mdx | 2 +- api_docs/expression_shape.mdx | 2 +- api_docs/expression_tagcloud.mdx | 2 +- api_docs/expression_x_y.mdx | 2 +- api_docs/expressions.mdx | 2 +- api_docs/features.mdx | 2 +- api_docs/field_formats.mdx | 2 +- api_docs/fields_metadata.mdx | 2 +- api_docs/file_upload.mdx | 2 +- api_docs/files.mdx | 2 +- api_docs/files_management.mdx | 2 +- api_docs/fleet.mdx | 2 +- api_docs/global_search.mdx | 2 +- api_docs/guided_onboarding.mdx | 2 +- api_docs/home.mdx | 2 +- api_docs/image_embeddable.mdx | 2 +- api_docs/index_lifecycle_management.mdx | 2 +- api_docs/index_management.mdx | 2 +- api_docs/infra.mdx | 2 +- api_docs/ingest_pipelines.mdx | 2 +- api_docs/inspector.mdx | 2 +- api_docs/integration_assistant.mdx | 2 +- api_docs/interactive_setup.mdx | 2 +- api_docs/investigate.mdx | 2 +- api_docs/kbn_ace.mdx | 2 +- api_docs/kbn_actions_types.mdx | 2 +- api_docs/kbn_aiops_components.mdx | 2 +- api_docs/kbn_aiops_log_pattern_analysis.mdx | 2 +- api_docs/kbn_aiops_log_rate_analysis.mdx | 2 +- .../kbn_alerting_api_integration_helpers.mdx | 2 +- api_docs/kbn_alerting_comparators.mdx | 2 +- api_docs/kbn_alerting_state_types.mdx | 2 +- api_docs/kbn_alerting_types.mdx | 2 +- api_docs/kbn_alerts_as_data_utils.mdx | 2 +- api_docs/kbn_alerts_ui_shared.mdx | 2 +- api_docs/kbn_analytics.mdx | 2 +- api_docs/kbn_analytics_collection_utils.mdx | 2 +- api_docs/kbn_apm_config_loader.mdx | 2 +- api_docs/kbn_apm_data_view.mdx | 2 +- api_docs/kbn_apm_synthtrace.mdx | 2 +- api_docs/kbn_apm_synthtrace_client.mdx | 2 +- api_docs/kbn_apm_utils.mdx | 2 +- api_docs/kbn_axe_config.mdx | 2 +- api_docs/kbn_bfetch_error.mdx | 2 +- api_docs/kbn_calculate_auto.mdx | 2 +- .../kbn_calculate_width_from_char_count.mdx | 2 +- api_docs/kbn_cases_components.mdx | 2 +- api_docs/kbn_cell_actions.mdx | 2 +- api_docs/kbn_chart_expressions_common.mdx | 2 +- api_docs/kbn_chart_icons.mdx | 2 +- api_docs/kbn_ci_stats_core.mdx | 2 +- api_docs/kbn_ci_stats_performance_metrics.mdx | 2 +- api_docs/kbn_ci_stats_reporter.mdx | 2 +- api_docs/kbn_cli_dev_mode.mdx | 2 +- api_docs/kbn_code_editor.mdx | 2 +- api_docs/kbn_code_editor_mock.mdx | 2 +- api_docs/kbn_code_owners.mdx | 2 +- api_docs/kbn_coloring.mdx | 2 +- api_docs/kbn_config.mdx | 2 +- api_docs/kbn_config_mocks.mdx | 2 +- api_docs/kbn_config_schema.mdx | 2 +- .../kbn_content_management_content_editor.mdx | 2 +- ...tent_management_tabbed_table_list_view.mdx | 2 +- ...kbn_content_management_table_list_view.mdx | 2 +- ...tent_management_table_list_view_common.mdx | 2 +- ...ntent_management_table_list_view_table.mdx | 2 +- .../kbn_content_management_user_profiles.mdx | 2 +- api_docs/kbn_content_management_utils.mdx | 2 +- .../kbn_core_analytics_browser.devdocs.json | 64 +++++++++ api_docs/kbn_core_analytics_browser.mdx | 2 +- .../kbn_core_analytics_browser_internal.mdx | 2 +- api_docs/kbn_core_analytics_browser_mocks.mdx | 2 +- .../kbn_core_analytics_server.devdocs.json | 64 +++++++++ api_docs/kbn_core_analytics_server.mdx | 2 +- .../kbn_core_analytics_server_internal.mdx | 2 +- api_docs/kbn_core_analytics_server_mocks.mdx | 2 +- api_docs/kbn_core_application_browser.mdx | 2 +- .../kbn_core_application_browser_internal.mdx | 2 +- .../kbn_core_application_browser_mocks.mdx | 2 +- api_docs/kbn_core_application_common.mdx | 2 +- api_docs/kbn_core_apps_browser_internal.mdx | 2 +- api_docs/kbn_core_apps_browser_mocks.mdx | 2 +- api_docs/kbn_core_apps_server_internal.mdx | 2 +- api_docs/kbn_core_base_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_common.mdx | 2 +- api_docs/kbn_core_base_server_internal.mdx | 2 +- api_docs/kbn_core_base_server_mocks.mdx | 2 +- .../kbn_core_capabilities_browser_mocks.mdx | 2 +- api_docs/kbn_core_capabilities_common.mdx | 2 +- api_docs/kbn_core_capabilities_server.mdx | 2 +- .../kbn_core_capabilities_server_mocks.mdx | 2 +- api_docs/kbn_core_chrome_browser.mdx | 2 +- api_docs/kbn_core_chrome_browser_mocks.mdx | 2 +- api_docs/kbn_core_config_server_internal.mdx | 2 +- api_docs/kbn_core_custom_branding_browser.mdx | 2 +- ..._core_custom_branding_browser_internal.mdx | 2 +- ...kbn_core_custom_branding_browser_mocks.mdx | 2 +- api_docs/kbn_core_custom_branding_common.mdx | 2 +- api_docs/kbn_core_custom_branding_server.mdx | 2 +- ...n_core_custom_branding_server_internal.mdx | 2 +- .../kbn_core_custom_branding_server_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_browser.mdx | 2 +- ...kbn_core_deprecations_browser_internal.mdx | 2 +- .../kbn_core_deprecations_browser_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_common.mdx | 2 +- api_docs/kbn_core_deprecations_server.mdx | 2 +- .../kbn_core_deprecations_server_internal.mdx | 2 +- .../kbn_core_deprecations_server_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_browser.mdx | 2 +- api_docs/kbn_core_doc_links_browser_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_server.mdx | 2 +- api_docs/kbn_core_doc_links_server_mocks.mdx | 2 +- ...e_elasticsearch_client_server_internal.mdx | 2 +- ...core_elasticsearch_client_server_mocks.mdx | 2 +- api_docs/kbn_core_elasticsearch_server.mdx | 2 +- ...kbn_core_elasticsearch_server_internal.mdx | 2 +- .../kbn_core_elasticsearch_server_mocks.mdx | 2 +- .../kbn_core_environment_server_internal.mdx | 2 +- .../kbn_core_environment_server_mocks.mdx | 2 +- .../kbn_core_execution_context_browser.mdx | 2 +- ...ore_execution_context_browser_internal.mdx | 2 +- ...n_core_execution_context_browser_mocks.mdx | 2 +- .../kbn_core_execution_context_common.mdx | 2 +- .../kbn_core_execution_context_server.mdx | 2 +- ...core_execution_context_server_internal.mdx | 2 +- ...bn_core_execution_context_server_mocks.mdx | 2 +- api_docs/kbn_core_fatal_errors_browser.mdx | 2 +- .../kbn_core_fatal_errors_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_browser.mdx | 2 +- api_docs/kbn_core_http_browser_internal.mdx | 2 +- api_docs/kbn_core_http_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_common.mdx | 2 +- .../kbn_core_http_context_server_mocks.mdx | 2 +- ...re_http_request_handler_context_server.mdx | 2 +- api_docs/kbn_core_http_resources_server.mdx | 2 +- ...bn_core_http_resources_server_internal.mdx | 2 +- .../kbn_core_http_resources_server_mocks.mdx | 2 +- .../kbn_core_http_router_server_internal.mdx | 2 +- .../kbn_core_http_router_server_mocks.mdx | 2 +- api_docs/kbn_core_http_server.devdocs.json | 8 ++ api_docs/kbn_core_http_server.mdx | 2 +- api_docs/kbn_core_http_server_internal.mdx | 2 +- api_docs/kbn_core_http_server_mocks.mdx | 2 +- api_docs/kbn_core_i18n_browser.mdx | 2 +- api_docs/kbn_core_i18n_browser_mocks.mdx | 2 +- api_docs/kbn_core_i18n_server.mdx | 2 +- api_docs/kbn_core_i18n_server_internal.mdx | 2 +- api_docs/kbn_core_i18n_server_mocks.mdx | 2 +- ...n_core_injected_metadata_browser_mocks.mdx | 2 +- ...kbn_core_integrations_browser_internal.mdx | 2 +- .../kbn_core_integrations_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_browser.mdx | 2 +- api_docs/kbn_core_lifecycle_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_server.mdx | 2 +- api_docs/kbn_core_lifecycle_server_mocks.mdx | 2 +- api_docs/kbn_core_logging_browser_mocks.mdx | 2 +- api_docs/kbn_core_logging_common_internal.mdx | 2 +- api_docs/kbn_core_logging_server.mdx | 2 +- api_docs/kbn_core_logging_server_internal.mdx | 2 +- api_docs/kbn_core_logging_server_mocks.mdx | 2 +- ...ore_metrics_collectors_server_internal.mdx | 2 +- ...n_core_metrics_collectors_server_mocks.mdx | 2 +- api_docs/kbn_core_metrics_server.mdx | 2 +- api_docs/kbn_core_metrics_server_internal.mdx | 2 +- api_docs/kbn_core_metrics_server_mocks.mdx | 2 +- api_docs/kbn_core_mount_utils_browser.mdx | 2 +- api_docs/kbn_core_node_server.mdx | 2 +- api_docs/kbn_core_node_server_internal.mdx | 2 +- api_docs/kbn_core_node_server_mocks.mdx | 2 +- api_docs/kbn_core_notifications_browser.mdx | 2 +- ...bn_core_notifications_browser_internal.mdx | 2 +- .../kbn_core_notifications_browser_mocks.mdx | 2 +- api_docs/kbn_core_overlays_browser.mdx | 2 +- .../kbn_core_overlays_browser_internal.mdx | 2 +- api_docs/kbn_core_overlays_browser_mocks.mdx | 2 +- api_docs/kbn_core_plugins_browser.mdx | 2 +- api_docs/kbn_core_plugins_browser_mocks.mdx | 2 +- .../kbn_core_plugins_contracts_browser.mdx | 2 +- .../kbn_core_plugins_contracts_server.mdx | 2 +- api_docs/kbn_core_plugins_server.mdx | 2 +- api_docs/kbn_core_plugins_server_mocks.mdx | 2 +- api_docs/kbn_core_preboot_server.mdx | 2 +- api_docs/kbn_core_preboot_server_mocks.mdx | 2 +- api_docs/kbn_core_rendering_browser_mocks.mdx | 2 +- .../kbn_core_rendering_server_internal.mdx | 2 +- api_docs/kbn_core_rendering_server_mocks.mdx | 2 +- api_docs/kbn_core_root_server_internal.mdx | 2 +- .../kbn_core_saved_objects_api_browser.mdx | 2 +- .../kbn_core_saved_objects_api_server.mdx | 2 +- ...bn_core_saved_objects_api_server_mocks.mdx | 2 +- ...ore_saved_objects_base_server_internal.mdx | 2 +- ...n_core_saved_objects_base_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_browser.mdx | 2 +- ...bn_core_saved_objects_browser_internal.mdx | 2 +- .../kbn_core_saved_objects_browser_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_common.mdx | 2 +- ..._objects_import_export_server_internal.mdx | 2 +- ...ved_objects_import_export_server_mocks.mdx | 2 +- ...aved_objects_migration_server_internal.mdx | 2 +- ...e_saved_objects_migration_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_server.mdx | 2 +- ...kbn_core_saved_objects_server_internal.mdx | 2 +- .../kbn_core_saved_objects_server_mocks.mdx | 2 +- .../kbn_core_saved_objects_utils_server.mdx | 2 +- api_docs/kbn_core_security_browser.mdx | 2 +- .../kbn_core_security_browser_internal.mdx | 2 +- ...n_core_security_browser_mocks.devdocs.json | 72 ++++++++++ api_docs/kbn_core_security_browser_mocks.mdx | 4 +- api_docs/kbn_core_security_common.mdx | 2 +- .../kbn_core_security_server.devdocs.json | 56 ++++++++ api_docs/kbn_core_security_server.mdx | 4 +- .../kbn_core_security_server_internal.mdx | 2 +- ...bn_core_security_server_mocks.devdocs.json | 72 ++++++++++ api_docs/kbn_core_security_server_mocks.mdx | 4 +- api_docs/kbn_core_status_common.mdx | 2 +- api_docs/kbn_core_status_common_internal.mdx | 2 +- api_docs/kbn_core_status_server.mdx | 2 +- api_docs/kbn_core_status_server_internal.mdx | 2 +- api_docs/kbn_core_status_server_mocks.mdx | 2 +- ...core_test_helpers_deprecations_getters.mdx | 2 +- ...n_core_test_helpers_http_setup_browser.mdx | 2 +- api_docs/kbn_core_test_helpers_kbn_server.mdx | 2 +- .../kbn_core_test_helpers_model_versions.mdx | 2 +- ...n_core_test_helpers_so_type_serializer.mdx | 2 +- api_docs/kbn_core_test_helpers_test_utils.mdx | 2 +- api_docs/kbn_core_theme_browser.mdx | 2 +- api_docs/kbn_core_theme_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_browser.mdx | 2 +- .../kbn_core_ui_settings_browser_internal.mdx | 2 +- .../kbn_core_ui_settings_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_common.mdx | 2 +- api_docs/kbn_core_ui_settings_server.mdx | 2 +- .../kbn_core_ui_settings_server_internal.mdx | 2 +- .../kbn_core_ui_settings_server_mocks.mdx | 2 +- api_docs/kbn_core_usage_data_server.mdx | 2 +- .../kbn_core_usage_data_server_internal.mdx | 2 +- api_docs/kbn_core_usage_data_server_mocks.mdx | 2 +- api_docs/kbn_core_user_profile_browser.mdx | 2 +- ...kbn_core_user_profile_browser_internal.mdx | 2 +- .../kbn_core_user_profile_browser_mocks.mdx | 2 +- api_docs/kbn_core_user_profile_common.mdx | 2 +- api_docs/kbn_core_user_profile_server.mdx | 2 +- .../kbn_core_user_profile_server_internal.mdx | 2 +- .../kbn_core_user_profile_server_mocks.mdx | 2 +- api_docs/kbn_core_user_settings_server.mdx | 2 +- .../kbn_core_user_settings_server_mocks.mdx | 2 +- api_docs/kbn_crypto.mdx | 2 +- api_docs/kbn_crypto_browser.mdx | 2 +- api_docs/kbn_custom_icons.mdx | 2 +- api_docs/kbn_custom_integrations.mdx | 2 +- api_docs/kbn_cypress_config.mdx | 2 +- api_docs/kbn_data_forge.mdx | 2 +- api_docs/kbn_data_service.mdx | 2 +- api_docs/kbn_data_stream_adapter.mdx | 2 +- api_docs/kbn_data_view_utils.mdx | 2 +- api_docs/kbn_datemath.mdx | 2 +- api_docs/kbn_deeplinks_analytics.mdx | 2 +- api_docs/kbn_deeplinks_devtools.mdx | 2 +- api_docs/kbn_deeplinks_fleet.mdx | 2 +- api_docs/kbn_deeplinks_management.mdx | 2 +- api_docs/kbn_deeplinks_ml.mdx | 2 +- api_docs/kbn_deeplinks_observability.mdx | 2 +- api_docs/kbn_deeplinks_search.mdx | 2 +- api_docs/kbn_deeplinks_security.mdx | 2 +- api_docs/kbn_deeplinks_shared.mdx | 2 +- api_docs/kbn_default_nav_analytics.mdx | 2 +- api_docs/kbn_default_nav_devtools.mdx | 2 +- api_docs/kbn_default_nav_management.mdx | 2 +- api_docs/kbn_default_nav_ml.mdx | 2 +- api_docs/kbn_dev_cli_errors.mdx | 2 +- api_docs/kbn_dev_cli_runner.mdx | 2 +- api_docs/kbn_dev_proc_runner.mdx | 2 +- api_docs/kbn_dev_utils.mdx | 2 +- api_docs/kbn_discover_utils.mdx | 2 +- api_docs/kbn_doc_links.devdocs.json | 2 +- api_docs/kbn_doc_links.mdx | 2 +- api_docs/kbn_docs_utils.mdx | 2 +- api_docs/kbn_dom_drag_drop.mdx | 2 +- api_docs/kbn_ebt.devdocs.json | 64 +++++++++ api_docs/kbn_ebt.mdx | 2 +- api_docs/kbn_ebt_tools.mdx | 2 +- api_docs/kbn_ecs_data_quality_dashboard.mdx | 2 +- api_docs/kbn_elastic_agent_utils.mdx | 2 +- api_docs/kbn_elastic_assistant.mdx | 2 +- api_docs/kbn_elastic_assistant_common.mdx | 2 +- api_docs/kbn_entities_schema.mdx | 2 +- api_docs/kbn_es.mdx | 2 +- api_docs/kbn_es_archiver.mdx | 2 +- api_docs/kbn_es_errors.mdx | 2 +- api_docs/kbn_es_query.mdx | 2 +- api_docs/kbn_es_types.mdx | 2 +- api_docs/kbn_eslint_plugin_imports.mdx | 2 +- api_docs/kbn_esql_ast.mdx | 2 +- api_docs/kbn_esql_utils.mdx | 2 +- ..._esql_validation_autocomplete.devdocs.json | 17 ++- api_docs/kbn_esql_validation_autocomplete.mdx | 4 +- api_docs/kbn_event_annotation_common.mdx | 2 +- api_docs/kbn_event_annotation_components.mdx | 2 +- api_docs/kbn_expandable_flyout.mdx | 2 +- api_docs/kbn_field_types.mdx | 2 +- api_docs/kbn_field_utils.mdx | 2 +- api_docs/kbn_find_used_node_modules.mdx | 2 +- api_docs/kbn_formatters.mdx | 2 +- .../kbn_ftr_common_functional_services.mdx | 2 +- .../kbn_ftr_common_functional_ui_services.mdx | 2 +- api_docs/kbn_generate.mdx | 2 +- api_docs/kbn_generate_console_definitions.mdx | 2 +- api_docs/kbn_generate_csv.mdx | 2 +- api_docs/kbn_grouping.mdx | 2 +- api_docs/kbn_guided_onboarding.mdx | 2 +- api_docs/kbn_handlebars.mdx | 2 +- api_docs/kbn_hapi_mocks.mdx | 2 +- api_docs/kbn_health_gateway_server.mdx | 2 +- api_docs/kbn_home_sample_data_card.mdx | 2 +- api_docs/kbn_home_sample_data_tab.mdx | 2 +- api_docs/kbn_i18n.mdx | 2 +- api_docs/kbn_i18n_react.mdx | 2 +- api_docs/kbn_import_resolver.mdx | 2 +- api_docs/kbn_index_management.mdx | 2 +- api_docs/kbn_inference_integration_flyout.mdx | 2 +- api_docs/kbn_infra_forge.mdx | 2 +- api_docs/kbn_interpreter.mdx | 2 +- api_docs/kbn_io_ts_utils.mdx | 2 +- api_docs/kbn_ipynb.mdx | 2 +- api_docs/kbn_jest_serializers.mdx | 2 +- api_docs/kbn_journeys.mdx | 2 +- api_docs/kbn_json_ast.mdx | 2 +- api_docs/kbn_json_schemas.mdx | 2 +- api_docs/kbn_kibana_manifest_schema.mdx | 2 +- .../kbn_language_documentation_popover.mdx | 2 +- api_docs/kbn_lens_embeddable_utils.mdx | 2 +- api_docs/kbn_lens_formula_docs.mdx | 2 +- api_docs/kbn_logging.mdx | 2 +- api_docs/kbn_logging_mocks.mdx | 2 +- api_docs/kbn_managed_content_badge.mdx | 2 +- api_docs/kbn_managed_vscode_config.mdx | 2 +- api_docs/kbn_management_cards_navigation.mdx | 2 +- .../kbn_management_settings_application.mdx | 2 +- ...ent_settings_components_field_category.mdx | 2 +- ...gement_settings_components_field_input.mdx | 2 +- ...nagement_settings_components_field_row.mdx | 2 +- ...bn_management_settings_components_form.mdx | 2 +- ...n_management_settings_field_definition.mdx | 2 +- .../kbn_management_settings_ids.devdocs.json | 15 ++ api_docs/kbn_management_settings_ids.mdx | 4 +- ...n_management_settings_section_registry.mdx | 2 +- api_docs/kbn_management_settings_types.mdx | 2 +- .../kbn_management_settings_utilities.mdx | 2 +- api_docs/kbn_management_storybook_config.mdx | 2 +- api_docs/kbn_mapbox_gl.mdx | 2 +- api_docs/kbn_maps_vector_tile_utils.mdx | 2 +- api_docs/kbn_ml_agg_utils.mdx | 2 +- api_docs/kbn_ml_anomaly_utils.mdx | 2 +- api_docs/kbn_ml_cancellable_search.mdx | 2 +- api_docs/kbn_ml_category_validator.mdx | 2 +- api_docs/kbn_ml_chi2test.mdx | 2 +- .../kbn_ml_data_frame_analytics_utils.mdx | 2 +- api_docs/kbn_ml_data_grid.mdx | 2 +- api_docs/kbn_ml_date_picker.mdx | 2 +- api_docs/kbn_ml_date_utils.mdx | 2 +- api_docs/kbn_ml_error_utils.mdx | 2 +- api_docs/kbn_ml_in_memory_table.mdx | 2 +- api_docs/kbn_ml_is_defined.mdx | 2 +- api_docs/kbn_ml_is_populated_object.mdx | 2 +- api_docs/kbn_ml_kibana_theme.mdx | 2 +- api_docs/kbn_ml_local_storage.mdx | 2 +- api_docs/kbn_ml_nested_property.mdx | 2 +- api_docs/kbn_ml_number_utils.mdx | 2 +- api_docs/kbn_ml_query_utils.devdocs.json | 25 +++- api_docs/kbn_ml_query_utils.mdx | 4 +- api_docs/kbn_ml_random_sampler_utils.mdx | 2 +- api_docs/kbn_ml_route_utils.mdx | 2 +- api_docs/kbn_ml_runtime_field_utils.mdx | 2 +- api_docs/kbn_ml_string_hash.mdx | 2 +- api_docs/kbn_ml_time_buckets.mdx | 2 +- api_docs/kbn_ml_trained_models_utils.mdx | 2 +- api_docs/kbn_ml_ui_actions.mdx | 2 +- api_docs/kbn_ml_url_state.mdx | 2 +- api_docs/kbn_mock_idp_utils.mdx | 2 +- api_docs/kbn_monaco.mdx | 2 +- api_docs/kbn_object_versioning.mdx | 2 +- api_docs/kbn_observability_alert_details.mdx | 2 +- .../kbn_observability_alerting_test_data.mdx | 2 +- ...ility_get_padded_alert_time_range_util.mdx | 2 +- api_docs/kbn_openapi_bundler.mdx | 2 +- api_docs/kbn_openapi_generator.mdx | 2 +- api_docs/kbn_optimizer.mdx | 2 +- api_docs/kbn_optimizer_webpack_helpers.mdx | 2 +- api_docs/kbn_osquery_io_ts_types.mdx | 2 +- api_docs/kbn_panel_loader.mdx | 2 +- ..._performance_testing_dataset_extractor.mdx | 2 +- api_docs/kbn_plugin_check.mdx | 2 +- api_docs/kbn_plugin_generator.mdx | 2 +- api_docs/kbn_plugin_helpers.mdx | 2 +- api_docs/kbn_presentation_containers.mdx | 2 +- api_docs/kbn_presentation_publishing.mdx | 2 +- api_docs/kbn_profiling_utils.mdx | 2 +- api_docs/kbn_random_sampling.mdx | 2 +- api_docs/kbn_react_field.mdx | 2 +- api_docs/kbn_react_hooks.mdx | 2 +- api_docs/kbn_react_kibana_context_common.mdx | 2 +- api_docs/kbn_react_kibana_context_render.mdx | 2 +- api_docs/kbn_react_kibana_context_root.mdx | 2 +- api_docs/kbn_react_kibana_context_styled.mdx | 2 +- api_docs/kbn_react_kibana_context_theme.mdx | 2 +- api_docs/kbn_react_kibana_mount.mdx | 2 +- api_docs/kbn_repo_file_maps.mdx | 2 +- api_docs/kbn_repo_linter.mdx | 2 +- api_docs/kbn_repo_path.mdx | 2 +- api_docs/kbn_repo_source_classifier.mdx | 2 +- api_docs/kbn_reporting_common.mdx | 2 +- api_docs/kbn_reporting_csv_share_panel.mdx | 2 +- api_docs/kbn_reporting_export_types_csv.mdx | 2 +- .../kbn_reporting_export_types_csv_common.mdx | 2 +- api_docs/kbn_reporting_export_types_pdf.mdx | 2 +- .../kbn_reporting_export_types_pdf_common.mdx | 2 +- api_docs/kbn_reporting_export_types_png.mdx | 2 +- .../kbn_reporting_export_types_png_common.mdx | 2 +- api_docs/kbn_reporting_mocks_server.mdx | 2 +- api_docs/kbn_reporting_public.mdx | 2 +- api_docs/kbn_reporting_server.mdx | 2 +- api_docs/kbn_resizable_layout.mdx | 2 +- .../kbn_response_ops_feature_flag_service.mdx | 2 +- api_docs/kbn_rison.mdx | 2 +- api_docs/kbn_rollup.mdx | 2 +- api_docs/kbn_router_to_openapispec.mdx | 2 +- api_docs/kbn_router_utils.mdx | 2 +- api_docs/kbn_rrule.mdx | 2 +- api_docs/kbn_rule_data_utils.mdx | 2 +- api_docs/kbn_saved_objects_settings.mdx | 2 +- api_docs/kbn_search_api_panels.devdocs.json | 4 +- api_docs/kbn_search_api_panels.mdx | 2 +- api_docs/kbn_search_connectors.mdx | 2 +- api_docs/kbn_search_errors.mdx | 2 +- api_docs/kbn_search_index_documents.mdx | 2 +- api_docs/kbn_search_response_warnings.mdx | 2 +- api_docs/kbn_search_types.mdx | 2 +- api_docs/kbn_security_api_key_management.mdx | 2 +- api_docs/kbn_security_form_components.mdx | 2 +- api_docs/kbn_security_hardening.mdx | 2 +- ..._security_plugin_types_common.devdocs.json | 29 ++++ api_docs/kbn_security_plugin_types_common.mdx | 4 +- api_docs/kbn_security_plugin_types_public.mdx | 2 +- ..._security_plugin_types_server.devdocs.json | 8 -- api_docs/kbn_security_plugin_types_server.mdx | 2 +- api_docs/kbn_security_solution_features.mdx | 2 +- api_docs/kbn_security_solution_navigation.mdx | 2 +- api_docs/kbn_security_solution_side_nav.mdx | 2 +- ...kbn_security_solution_storybook_config.mdx | 2 +- .../kbn_securitysolution_autocomplete.mdx | 2 +- api_docs/kbn_securitysolution_data_table.mdx | 2 +- api_docs/kbn_securitysolution_ecs.mdx | 2 +- api_docs/kbn_securitysolution_es_utils.mdx | 2 +- ...ritysolution_exception_list_components.mdx | 2 +- api_docs/kbn_securitysolution_hook_utils.mdx | 2 +- ..._securitysolution_io_ts_alerting_types.mdx | 2 +- .../kbn_securitysolution_io_ts_list_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_utils.mdx | 2 +- api_docs/kbn_securitysolution_list_api.mdx | 2 +- .../kbn_securitysolution_list_constants.mdx | 2 +- api_docs/kbn_securitysolution_list_hooks.mdx | 2 +- api_docs/kbn_securitysolution_list_utils.mdx | 2 +- api_docs/kbn_securitysolution_rules.mdx | 2 +- api_docs/kbn_securitysolution_t_grid.mdx | 2 +- api_docs/kbn_securitysolution_utils.mdx | 2 +- api_docs/kbn_server_http_tools.mdx | 2 +- api_docs/kbn_server_route_repository.mdx | 2 +- api_docs/kbn_serverless_common_settings.mdx | 2 +- .../kbn_serverless_observability_settings.mdx | 2 +- api_docs/kbn_serverless_project_switcher.mdx | 2 +- api_docs/kbn_serverless_search_settings.mdx | 2 +- api_docs/kbn_serverless_security_settings.mdx | 2 +- api_docs/kbn_serverless_storybook_config.mdx | 2 +- api_docs/kbn_shared_svg.mdx | 2 +- api_docs/kbn_shared_ux_avatar_solution.mdx | 2 +- .../kbn_shared_ux_button_exit_full_screen.mdx | 2 +- api_docs/kbn_shared_ux_button_toolbar.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_chrome_navigation.mdx | 2 +- api_docs/kbn_shared_ux_error_boundary.mdx | 2 +- api_docs/kbn_shared_ux_file_context.mdx | 2 +- api_docs/kbn_shared_ux_file_image.mdx | 2 +- api_docs/kbn_shared_ux_file_image_mocks.mdx | 2 +- api_docs/kbn_shared_ux_file_mocks.mdx | 2 +- api_docs/kbn_shared_ux_file_picker.mdx | 2 +- api_docs/kbn_shared_ux_file_types.mdx | 2 +- api_docs/kbn_shared_ux_file_upload.mdx | 2 +- api_docs/kbn_shared_ux_file_util.mdx | 2 +- api_docs/kbn_shared_ux_link_redirect_app.mdx | 2 +- .../kbn_shared_ux_link_redirect_app_mocks.mdx | 2 +- api_docs/kbn_shared_ux_markdown.mdx | 2 +- api_docs/kbn_shared_ux_markdown_mocks.mdx | 2 +- .../kbn_shared_ux_page_analytics_no_data.mdx | 2 +- ...shared_ux_page_analytics_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_no_data.mdx | 2 +- ...bn_shared_ux_page_kibana_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_template.mdx | 2 +- ...n_shared_ux_page_kibana_template_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data.mdx | 2 +- .../kbn_shared_ux_page_no_data_config.mdx | 2 +- ...bn_shared_ux_page_no_data_config_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_solution_nav.mdx | 2 +- .../kbn_shared_ux_prompt_no_data_views.mdx | 2 +- ...n_shared_ux_prompt_no_data_views_mocks.mdx | 2 +- api_docs/kbn_shared_ux_prompt_not_found.mdx | 2 +- api_docs/kbn_shared_ux_router.mdx | 2 +- api_docs/kbn_shared_ux_router_mocks.mdx | 2 +- api_docs/kbn_shared_ux_storybook_config.mdx | 2 +- api_docs/kbn_shared_ux_storybook_mock.mdx | 2 +- api_docs/kbn_shared_ux_tabbed_modal.mdx | 2 +- api_docs/kbn_shared_ux_utility.mdx | 2 +- api_docs/kbn_slo_schema.mdx | 2 +- api_docs/kbn_some_dev_log.mdx | 2 +- api_docs/kbn_sort_predicates.mdx | 2 +- api_docs/kbn_std.mdx | 2 +- api_docs/kbn_stdio_dev_helpers.mdx | 2 +- api_docs/kbn_storybook.mdx | 2 +- api_docs/kbn_telemetry_tools.mdx | 2 +- api_docs/kbn_test.mdx | 2 +- api_docs/kbn_test_eui_helpers.mdx | 2 +- api_docs/kbn_test_jest_helpers.mdx | 2 +- api_docs/kbn_test_subj_selector.mdx | 2 +- api_docs/kbn_text_based_editor.mdx | 2 +- api_docs/kbn_timerange.mdx | 2 +- api_docs/kbn_tooling_log.mdx | 2 +- api_docs/kbn_triggers_actions_ui_types.mdx | 2 +- api_docs/kbn_try_in_console.mdx | 2 +- api_docs/kbn_ts_projects.mdx | 2 +- api_docs/kbn_typed_react_router_config.mdx | 2 +- api_docs/kbn_ui_actions_browser.mdx | 2 +- api_docs/kbn_ui_shared_deps_src.mdx | 2 +- api_docs/kbn_ui_theme.mdx | 2 +- api_docs/kbn_unified_data_table.mdx | 2 +- api_docs/kbn_unified_doc_viewer.mdx | 2 +- api_docs/kbn_unified_field_list.mdx | 2 +- api_docs/kbn_unsaved_changes_badge.mdx | 2 +- api_docs/kbn_unsaved_changes_prompt.mdx | 2 +- api_docs/kbn_use_tracked_promise.mdx | 2 +- api_docs/kbn_user_profile_components.mdx | 2 +- api_docs/kbn_utility_types.mdx | 2 +- api_docs/kbn_utility_types_jest.mdx | 2 +- api_docs/kbn_utils.mdx | 2 +- api_docs/kbn_visualization_ui_components.mdx | 2 +- api_docs/kbn_visualization_utils.mdx | 2 +- api_docs/kbn_xstate_utils.mdx | 2 +- api_docs/kbn_yarn_lock_validator.mdx | 2 +- api_docs/kbn_zod_helpers.mdx | 2 +- api_docs/kibana_overview.mdx | 2 +- api_docs/kibana_react.mdx | 2 +- api_docs/kibana_utils.mdx | 2 +- api_docs/kubernetes_security.mdx | 2 +- api_docs/lens.mdx | 2 +- api_docs/license_api_guard.mdx | 2 +- api_docs/license_management.mdx | 2 +- api_docs/licensing.mdx | 2 +- api_docs/links.mdx | 2 +- api_docs/lists.mdx | 2 +- api_docs/logs_data_access.devdocs.json | 14 +- api_docs/logs_data_access.mdx | 4 +- api_docs/logs_explorer.mdx | 2 +- api_docs/logs_shared.mdx | 2 +- api_docs/management.mdx | 2 +- api_docs/maps.mdx | 2 +- api_docs/maps_ems.mdx | 2 +- api_docs/metrics_data_access.mdx | 2 +- api_docs/ml.mdx | 2 +- api_docs/mock_idp_plugin.mdx | 2 +- api_docs/monitoring.mdx | 2 +- api_docs/monitoring_collection.mdx | 2 +- api_docs/navigation.mdx | 2 +- api_docs/newsfeed.mdx | 2 +- api_docs/no_data_page.mdx | 2 +- api_docs/notifications.mdx | 2 +- api_docs/observability.mdx | 2 +- api_docs/observability_a_i_assistant.mdx | 2 +- api_docs/observability_a_i_assistant_app.mdx | 2 +- .../observability_ai_assistant_management.mdx | 2 +- api_docs/observability_logs_explorer.mdx | 2 +- .../observability_onboarding.devdocs.json | 96 +++++++++++++ api_docs/observability_onboarding.mdx | 4 +- api_docs/observability_shared.mdx | 2 +- api_docs/osquery.mdx | 2 +- api_docs/painless_lab.mdx | 2 +- api_docs/plugin_directory.mdx | 24 ++-- api_docs/presentation_panel.mdx | 2 +- api_docs/presentation_util.mdx | 2 +- api_docs/profiling.mdx | 2 +- api_docs/profiling_data_access.mdx | 2 +- api_docs/remote_clusters.mdx | 2 +- api_docs/reporting.mdx | 2 +- api_docs/rollup.mdx | 2 +- api_docs/rule_registry.mdx | 2 +- api_docs/runtime_fields.mdx | 2 +- api_docs/saved_objects.mdx | 2 +- api_docs/saved_objects_finder.mdx | 2 +- api_docs/saved_objects_management.mdx | 2 +- api_docs/saved_objects_tagging.mdx | 2 +- api_docs/saved_objects_tagging_oss.mdx | 2 +- api_docs/saved_search.mdx | 2 +- api_docs/screenshot_mode.mdx | 2 +- api_docs/screenshotting.mdx | 2 +- api_docs/search_connectors.mdx | 2 +- api_docs/search_homepage.mdx | 2 +- api_docs/search_inference_endpoints.mdx | 2 +- api_docs/search_notebooks.mdx | 2 +- api_docs/search_playground.mdx | 2 +- api_docs/security.devdocs.json | 70 ++++++++-- api_docs/security.mdx | 4 +- api_docs/security_solution.devdocs.json | 14 +- api_docs/security_solution.mdx | 2 +- api_docs/security_solution_ess.mdx | 2 +- api_docs/security_solution_serverless.mdx | 2 +- api_docs/serverless.mdx | 2 +- api_docs/serverless_observability.mdx | 2 +- api_docs/serverless_search.mdx | 2 +- api_docs/session_view.mdx | 2 +- api_docs/share.mdx | 2 +- api_docs/slo.mdx | 2 +- api_docs/snapshot_restore.mdx | 2 +- api_docs/spaces.mdx | 2 +- api_docs/stack_alerts.mdx | 2 +- api_docs/stack_connectors.mdx | 2 +- api_docs/task_manager.mdx | 2 +- api_docs/telemetry.mdx | 2 +- api_docs/telemetry_collection_manager.mdx | 2 +- api_docs/telemetry_collection_xpack.mdx | 2 +- api_docs/telemetry_management_section.mdx | 2 +- api_docs/text_based_languages.mdx | 2 +- api_docs/threat_intelligence.mdx | 2 +- api_docs/timelines.devdocs.json | 12 ++ api_docs/timelines.mdx | 2 +- api_docs/transform.mdx | 2 +- api_docs/triggers_actions_ui.mdx | 2 +- api_docs/ui_actions.mdx | 2 +- api_docs/ui_actions_enhanced.mdx | 2 +- api_docs/unified_doc_viewer.mdx | 2 +- api_docs/unified_histogram.mdx | 2 +- api_docs/unified_search.mdx | 2 +- api_docs/unified_search_autocomplete.mdx | 2 +- api_docs/uptime.mdx | 2 +- api_docs/url_forwarding.mdx | 2 +- api_docs/usage_collection.mdx | 2 +- api_docs/ux.mdx | 2 +- api_docs/vis_default_editor.mdx | 2 +- api_docs/vis_type_gauge.mdx | 2 +- api_docs/vis_type_heatmap.mdx | 2 +- api_docs/vis_type_pie.mdx | 2 +- api_docs/vis_type_table.mdx | 2 +- api_docs/vis_type_timelion.mdx | 2 +- api_docs/vis_type_timeseries.mdx | 2 +- api_docs/vis_type_vega.mdx | 2 +- api_docs/vis_type_vislib.mdx | 2 +- api_docs/vis_type_xy.mdx | 2 +- api_docs/visualizations.mdx | 2 +- 723 files changed, 1472 insertions(+), 835 deletions(-) diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 089bb205e7ff7..dc6b68ce36159 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index 4902e659d41cd..bdb13f3bd5dda 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/ai_assistant_management_selection.mdx b/api_docs/ai_assistant_management_selection.mdx index 23c046dfe2d2c..9387fe6325bc5 100644 --- a/api_docs/ai_assistant_management_selection.mdx +++ b/api_docs/ai_assistant_management_selection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiAssistantManagementSelection title: "aiAssistantManagementSelection" image: https://source.unsplash.com/400x175/?github description: API docs for the aiAssistantManagementSelection plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiAssistantManagementSelection'] --- import aiAssistantManagementSelectionObj from './ai_assistant_management_selection.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 30d8bb916e3ae..abc6c59ade9e7 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 8ef41508e6557..92077cdd9876b 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.devdocs.json b/api_docs/apm.devdocs.json index 6454cad9c995b..9890cb6043243 100644 --- a/api_docs/apm.devdocs.json +++ b/api_docs/apm.devdocs.json @@ -273,7 +273,7 @@ "APMPluginSetupDependencies", ") => { config$: ", "Observable", - "<Readonly<{} & { enabled: boolean; agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapTraceIdBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; serviceMapTerminateAfter: number; serviceMapMaxTraces: number; ui: Readonly<{} & { enabled: boolean; maxTraceItems: number; }>; searchAggregatedTransactions: ", + "<Readonly<{} & { enabled: boolean; agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapMaxAllowableBytes: number; serviceMapTraceIdBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; serviceMapTerminateAfter: number; serviceMapMaxTraces: number; ui: Readonly<{} & { enabled: boolean; maxTraceItems: number; }>; searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; telemetryCollectionEnabled: boolean; metricsInterval: number; forceSyntheticSource: boolean; latestAgentVersionsUrl: string; serverless: Readonly<{} & { enabled: true; }>; serverlessOnboarding: boolean; managedServiceUrl: string; featureFlags: Readonly<{} & { agentConfigurationAvailable: boolean; configurableIndicesAvailable: boolean; infrastructureTabAvailable: boolean; infraUiAvailable: boolean; migrationToFleetAvailable: boolean; sourcemapApiAvailable: boolean; storageExplorerAvailable: boolean; profilingIntegrationAvailable: boolean; ruleFormV2Enabled: boolean; }>; }>>; }" ], @@ -448,7 +448,7 @@ "label": "APMConfig", "description": [], "signature": [ - "{ readonly enabled: boolean; readonly agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; readonly autoCreateApmDataView: boolean; readonly serviceMapEnabled: boolean; readonly serviceMapFingerprintBucketSize: number; readonly serviceMapFingerprintGlobalBucketSize: number; readonly serviceMapTraceIdBucketSize: number; readonly serviceMapTraceIdGlobalBucketSize: number; readonly serviceMapMaxTracesPerRequest: number; readonly serviceMapTerminateAfter: number; readonly serviceMapMaxTraces: number; readonly ui: Readonly<{} & { enabled: boolean; maxTraceItems: number; }>; readonly searchAggregatedTransactions: ", + "{ readonly enabled: boolean; readonly agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; readonly autoCreateApmDataView: boolean; readonly serviceMapEnabled: boolean; readonly serviceMapFingerprintBucketSize: number; readonly serviceMapFingerprintGlobalBucketSize: number; readonly serviceMapMaxAllowableBytes: number; readonly serviceMapTraceIdBucketSize: number; readonly serviceMapTraceIdGlobalBucketSize: number; readonly serviceMapMaxTracesPerRequest: number; readonly serviceMapTerminateAfter: number; readonly serviceMapMaxTraces: number; readonly ui: Readonly<{} & { enabled: boolean; maxTraceItems: number; }>; readonly searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; readonly telemetryCollectionEnabled: boolean; readonly metricsInterval: number; readonly forceSyntheticSource: boolean; readonly latestAgentVersionsUrl: string; readonly serverless: Readonly<{} & { enabled: true; }>; readonly serverlessOnboarding: boolean; readonly managedServiceUrl: string; readonly featureFlags: Readonly<{} & { agentConfigurationAvailable: boolean; configurableIndicesAvailable: boolean; infrastructureTabAvailable: boolean; infraUiAvailable: boolean; migrationToFleetAvailable: boolean; sourcemapApiAvailable: boolean; storageExplorerAvailable: boolean; profilingIntegrationAvailable: boolean; ruleFormV2Enabled: boolean; }>; }" ], @@ -7984,7 +7984,7 @@ "description": [], "signature": [ "Observable", - "<Readonly<{} & { enabled: boolean; agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapTraceIdBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; serviceMapTerminateAfter: number; serviceMapMaxTraces: number; ui: Readonly<{} & { enabled: boolean; maxTraceItems: number; }>; searchAggregatedTransactions: ", + "<Readonly<{} & { enabled: boolean; agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapMaxAllowableBytes: number; serviceMapTraceIdBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; serviceMapTerminateAfter: number; serviceMapMaxTraces: number; ui: Readonly<{} & { enabled: boolean; maxTraceItems: number; }>; searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; telemetryCollectionEnabled: boolean; metricsInterval: number; forceSyntheticSource: boolean; latestAgentVersionsUrl: string; serverless: Readonly<{} & { enabled: true; }>; serverlessOnboarding: boolean; managedServiceUrl: string; featureFlags: Readonly<{} & { agentConfigurationAvailable: boolean; configurableIndicesAvailable: boolean; infrastructureTabAvailable: boolean; infraUiAvailable: boolean; migrationToFleetAvailable: boolean; sourcemapApiAvailable: boolean; storageExplorerAvailable: boolean; profilingIntegrationAvailable: boolean; ruleFormV2Enabled: boolean; }>; }>>" ], diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 161a9f7530a7b..15485cb19df8b 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/apm_data_access.mdx b/api_docs/apm_data_access.mdx index 116881f2451e1..74df82f1091fd 100644 --- a/api_docs/apm_data_access.mdx +++ b/api_docs/apm_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apmDataAccess title: "apmDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the apmDataAccess plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apmDataAccess'] --- import apmDataAccessObj from './apm_data_access.devdocs.json'; diff --git a/api_docs/assets_data_access.mdx b/api_docs/assets_data_access.mdx index 1e5fe28083a13..8be547ae4bb79 100644 --- a/api_docs/assets_data_access.mdx +++ b/api_docs/assets_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/assetsDataAccess title: "assetsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the assetsDataAccess plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'assetsDataAccess'] --- import assetsDataAccessObj from './assets_data_access.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 4a0898bb277bc..5a6803a3010f2 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index 5d184cd9f744c..ef1c107d45674 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index cf8b182150a40..d9654b4a1ffca 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.devdocs.json b/api_docs/cases.devdocs.json index c62a4c8d7c4c2..772e653c16240 100644 --- a/api_docs/cases.devdocs.json +++ b/api_docs/cases.devdocs.json @@ -501,15 +501,7 @@ "section": "def-common.CaseMetricsFeature", "text": "CaseMetricsFeature" }, - "[]; } & { from?: string | undefined; to?: string | undefined; owner?: string | string[] | undefined; }, signal?: AbortSignal | undefined) => Promise<{ mttr?: number | null | undefined; }>; bulkGet: (params: { ids: string[]; }, signal?: AbortSignal | undefined) => Promise<{ cases: ({ description: string; status: ", - { - "pluginId": "@kbn/cases-components", - "scope": "common", - "docId": "kibKbnCasesComponentsPluginApi", - "section": "def-common.CaseStatuses", - "text": "CaseStatuses" - }, - "; tags: string[]; title: string; connector: { id: string; } & (({ type: ", + "[]; } & { from?: string | undefined; to?: string | undefined; owner?: string | string[] | undefined; }, signal?: AbortSignal | undefined) => Promise<{ mttr?: number | null | undefined; }>; bulkGet: (params: { ids: string[]; }, signal?: AbortSignal | undefined) => Promise<{ cases: ({ description: string; tags: string[]; title: string; connector: { id: string; } & (({ type: ", { "pluginId": "cases", "scope": "common", @@ -565,7 +557,7 @@ "section": "def-common.ConnectorTypes", "text": "ConnectorTypes" }, - ".swimlane; fields: { caseId: string | null; } | null; } & { name: string; })); settings: { syncAlerts: boolean; }; owner: string; severity: ", + ".swimlane; fields: { caseId: string | null; } | null; } & { name: string; })); severity: ", { "pluginId": "cases", "scope": "common", @@ -577,7 +569,15 @@ "CustomFieldTypes", ".TEXT; value: string | null; } | { key: string; type: ", "CustomFieldTypes", - ".TOGGLE; value: boolean | null; })[]; } & { duration: number | null; closed_at: string | null; closed_by: ({ email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }) | null; created_at: string; created_by: { email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }; external_service: ({ connector_id: string; } & { connector_name: string; external_id: string; external_title: string; external_url: string; pushed_at: string; pushed_by: { email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }; }) | null; updated_at: string | null; updated_by: ({ email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }) | null; } & { id: string; totalComment: number; totalAlerts: number; version: string; } & { comments?: ((({ comment: string; type: ", + ".TOGGLE; value: boolean | null; })[]; settings: { syncAlerts: boolean; }; status: ", + { + "pluginId": "@kbn/cases-components", + "scope": "common", + "docId": "kibKbnCasesComponentsPluginApi", + "section": "def-common.CaseStatuses", + "text": "CaseStatuses" + }, + "; owner: string; } & { duration: number | null; closed_at: string | null; closed_by: ({ email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }) | null; created_at: string; created_by: { email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }; external_service: ({ connector_id: string; } & { connector_name: string; external_id: string; external_title: string; external_url: string; pushed_at: string; pushed_by: { email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }; }) | null; updated_at: string | null; updated_by: ({ email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }) | null; } & { id: string; totalComment: number; totalAlerts: number; version: string; } & { comments?: ((({ comment: string; type: ", { "pluginId": "cases", "scope": "common", @@ -1870,15 +1870,7 @@ "label": "Case", "description": [], "signature": [ - "{ description: string; status: ", - { - "pluginId": "@kbn/cases-components", - "scope": "common", - "docId": "kibKbnCasesComponentsPluginApi", - "section": "def-common.CaseStatuses", - "text": "CaseStatuses" - }, - "; tags: string[]; title: string; connector: { id: string; } & (({ type: ", + "{ description: string; tags: string[]; title: string; connector: { id: string; } & (({ type: ", { "pluginId": "cases", "scope": "common", @@ -1934,7 +1926,7 @@ "section": "def-common.ConnectorTypes", "text": "ConnectorTypes" }, - ".swimlane; fields: { caseId: string | null; } | null; } & { name: string; })); settings: { syncAlerts: boolean; }; owner: string; severity: ", + ".swimlane; fields: { caseId: string | null; } | null; } & { name: string; })); severity: ", { "pluginId": "cases", "scope": "common", @@ -1946,7 +1938,15 @@ "CustomFieldTypes", ".TEXT; value: string | null; } | { key: string; type: ", "CustomFieldTypes", - ".TOGGLE; value: boolean | null; })[]; } & { duration: number | null; closed_at: string | null; closed_by: ({ email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }) | null; created_at: string; created_by: { email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }; external_service: ({ connector_id: string; } & { connector_name: string; external_id: string; external_title: string; external_url: string; pushed_at: string; pushed_by: { email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }; }) | null; updated_at: string | null; updated_by: ({ email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }) | null; } & { id: string; totalComment: number; totalAlerts: number; version: string; } & { comments?: ((({ comment: string; type: ", + ".TOGGLE; value: boolean | null; })[]; settings: { syncAlerts: boolean; }; status: ", + { + "pluginId": "@kbn/cases-components", + "scope": "common", + "docId": "kibKbnCasesComponentsPluginApi", + "section": "def-common.CaseStatuses", + "text": "CaseStatuses" + }, + "; owner: string; } & { duration: number | null; closed_at: string | null; closed_by: ({ email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }) | null; created_at: string; created_by: { email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }; external_service: ({ connector_id: string; } & { connector_name: string; external_id: string; external_title: string; external_url: string; pushed_at: string; pushed_by: { email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }; }) | null; updated_at: string | null; updated_by: ({ email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }) | null; } & { id: string; totalComment: number; totalAlerts: number; version: string; } & { comments?: ((({ comment: string; type: ", { "pluginId": "cases", "scope": "common", @@ -2147,15 +2147,7 @@ "label": "Cases", "description": [], "signature": [ - "({ description: string; status: ", - { - "pluginId": "@kbn/cases-components", - "scope": "common", - "docId": "kibKbnCasesComponentsPluginApi", - "section": "def-common.CaseStatuses", - "text": "CaseStatuses" - }, - "; tags: string[]; title: string; connector: { id: string; } & (({ type: ", + "({ description: string; tags: string[]; title: string; connector: { id: string; } & (({ type: ", { "pluginId": "cases", "scope": "common", @@ -2211,7 +2203,7 @@ "section": "def-common.ConnectorTypes", "text": "ConnectorTypes" }, - ".swimlane; fields: { caseId: string | null; } | null; } & { name: string; })); settings: { syncAlerts: boolean; }; owner: string; severity: ", + ".swimlane; fields: { caseId: string | null; } | null; } & { name: string; })); severity: ", { "pluginId": "cases", "scope": "common", @@ -2223,7 +2215,15 @@ "CustomFieldTypes", ".TEXT; value: string | null; } | { key: string; type: ", "CustomFieldTypes", - ".TOGGLE; value: boolean | null; })[]; } & { duration: number | null; closed_at: string | null; closed_by: ({ email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }) | null; created_at: string; created_by: { email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }; external_service: ({ connector_id: string; } & { connector_name: string; external_id: string; external_title: string; external_url: string; pushed_at: string; pushed_by: { email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }; }) | null; updated_at: string | null; updated_by: ({ email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }) | null; } & { id: string; totalComment: number; totalAlerts: number; version: string; } & { comments?: ((({ comment: string; type: ", + ".TOGGLE; value: boolean | null; })[]; settings: { syncAlerts: boolean; }; status: ", + { + "pluginId": "@kbn/cases-components", + "scope": "common", + "docId": "kibKbnCasesComponentsPluginApi", + "section": "def-common.CaseStatuses", + "text": "CaseStatuses" + }, + "; owner: string; } & { duration: number | null; closed_at: string | null; closed_by: ({ email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }) | null; created_at: string; created_by: { email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }; external_service: ({ connector_id: string; } & { connector_name: string; external_id: string; external_title: string; external_url: string; pushed_at: string; pushed_by: { email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }; }) | null; updated_at: string | null; updated_by: ({ email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }) | null; } & { id: string; totalComment: number; totalAlerts: number; version: string; } & { comments?: ((({ comment: string; type: ", { "pluginId": "cases", "scope": "common", @@ -2373,15 +2373,7 @@ "label": "CasesBulkGetResponse", "description": [], "signature": [ - "{ cases: ({ description: string; status: ", - { - "pluginId": "@kbn/cases-components", - "scope": "common", - "docId": "kibKbnCasesComponentsPluginApi", - "section": "def-common.CaseStatuses", - "text": "CaseStatuses" - }, - "; tags: string[]; title: string; connector: { id: string; } & (({ type: ", + "{ cases: ({ description: string; tags: string[]; title: string; connector: { id: string; } & (({ type: ", { "pluginId": "cases", "scope": "common", @@ -2437,7 +2429,7 @@ "section": "def-common.ConnectorTypes", "text": "ConnectorTypes" }, - ".swimlane; fields: { caseId: string | null; } | null; } & { name: string; })); settings: { syncAlerts: boolean; }; owner: string; severity: ", + ".swimlane; fields: { caseId: string | null; } | null; } & { name: string; })); severity: ", { "pluginId": "cases", "scope": "common", @@ -2449,7 +2441,15 @@ "CustomFieldTypes", ".TEXT; value: string | null; } | { key: string; type: ", "CustomFieldTypes", - ".TOGGLE; value: boolean | null; })[]; } & { duration: number | null; closed_at: string | null; closed_by: ({ email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }) | null; created_at: string; created_by: { email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }; external_service: ({ connector_id: string; } & { connector_name: string; external_id: string; external_title: string; external_url: string; pushed_at: string; pushed_by: { email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }; }) | null; updated_at: string | null; updated_by: ({ email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }) | null; } & { id: string; totalComment: number; totalAlerts: number; version: string; } & { comments?: ((({ comment: string; type: ", + ".TOGGLE; value: boolean | null; })[]; settings: { syncAlerts: boolean; }; status: ", + { + "pluginId": "@kbn/cases-components", + "scope": "common", + "docId": "kibKbnCasesComponentsPluginApi", + "section": "def-common.CaseStatuses", + "text": "CaseStatuses" + }, + "; owner: string; } & { duration: number | null; closed_at: string | null; closed_by: ({ email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }) | null; created_at: string; created_by: { email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }; external_service: ({ connector_id: string; } & { connector_name: string; external_id: string; external_title: string; external_url: string; pushed_at: string; pushed_by: { email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }; }) | null; updated_at: string | null; updated_by: ({ email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }) | null; } & { id: string; totalComment: number; totalAlerts: number; version: string; } & { comments?: ((({ comment: string; type: ", { "pluginId": "cases", "scope": "common", @@ -2552,15 +2552,7 @@ "label": "CasesFindResponseUI", "description": [], "signature": [ - "Omit<{ cases: { description: string; status: ", - { - "pluginId": "@kbn/cases-components", - "scope": "common", - "docId": "kibKbnCasesComponentsPluginApi", - "section": "def-common.CaseStatuses", - "text": "CaseStatuses" - }, - "; tags: string[]; title: string; connector: { id: string; type: ", + "Omit<{ cases: { description: string; tags: string[]; title: string; connector: { id: string; type: ", { "pluginId": "cases", "scope": "common", @@ -2616,7 +2608,7 @@ "section": "def-common.ConnectorTypes", "text": "ConnectorTypes" }, - ".swimlane; fields: { caseId: string | null; } | null; name: string; }; settings: { syncAlerts: boolean; }; owner: string; severity: ", + ".swimlane; fields: { caseId: string | null; } | null; name: string; }; severity: ", { "pluginId": "cases", "scope": "common", @@ -2628,7 +2620,15 @@ "CustomFieldTypes", ".TEXT; value: string | null; } | { key: string; type: ", "CustomFieldTypes", - ".TOGGLE; value: boolean | null; })[]; duration: number | null; closedAt: string | null; closedBy: { email: string | null | undefined; fullName: string | null | undefined; username: string | null | undefined; profileUid?: string | undefined; } | null; createdAt: string; createdBy: { email: string | null | undefined; fullName: string | null | undefined; username: string | null | undefined; profileUid?: string | undefined; }; externalService: { connectorId: string; connectorName: string; externalId: string; externalTitle: string; externalUrl: string; pushedAt: string; pushedBy: { email: string | null | undefined; fullName: string | null | undefined; username: string | null | undefined; profileUid?: string | undefined; }; } | null; updatedAt: string | null; updatedBy: { email: string | null | undefined; fullName: string | null | undefined; username: string | null | undefined; profileUid?: string | undefined; } | null; id: string; totalComment: number; totalAlerts: number; version: string; comments?: ({ comment: string; type: ", + ".TOGGLE; value: boolean | null; })[]; settings: { syncAlerts: boolean; }; status: ", + { + "pluginId": "@kbn/cases-components", + "scope": "common", + "docId": "kibKbnCasesComponentsPluginApi", + "section": "def-common.CaseStatuses", + "text": "CaseStatuses" + }, + "; owner: string; duration: number | null; closedAt: string | null; closedBy: { email: string | null | undefined; fullName: string | null | undefined; username: string | null | undefined; profileUid?: string | undefined; } | null; createdAt: string; createdBy: { email: string | null | undefined; fullName: string | null | undefined; username: string | null | undefined; profileUid?: string | undefined; }; externalService: { connectorId: string; connectorName: string; externalId: string; externalTitle: string; externalUrl: string; pushedAt: string; pushedBy: { email: string | null | undefined; fullName: string | null | undefined; username: string | null | undefined; profileUid?: string | undefined; }; } | null; updatedAt: string | null; updatedBy: { email: string | null | undefined; fullName: string | null | undefined; username: string | null | undefined; profileUid?: string | undefined; } | null; id: string; totalComment: number; totalAlerts: number; version: string; comments?: ({ comment: string; type: ", { "pluginId": "cases", "scope": "common", @@ -2767,15 +2767,7 @@ "label": "CaseUI", "description": [], "signature": [ - "Omit<{ description: string; status: ", - { - "pluginId": "@kbn/cases-components", - "scope": "common", - "docId": "kibKbnCasesComponentsPluginApi", - "section": "def-common.CaseStatuses", - "text": "CaseStatuses" - }, - "; tags: string[]; title: string; connector: { id: string; type: ", + "Omit<{ description: string; tags: string[]; title: string; connector: { id: string; type: ", { "pluginId": "cases", "scope": "common", @@ -2831,7 +2823,7 @@ "section": "def-common.ConnectorTypes", "text": "ConnectorTypes" }, - ".swimlane; fields: { caseId: string | null; } | null; name: string; }; settings: { syncAlerts: boolean; }; owner: string; severity: ", + ".swimlane; fields: { caseId: string | null; } | null; name: string; }; severity: ", { "pluginId": "cases", "scope": "common", @@ -2843,7 +2835,15 @@ "CustomFieldTypes", ".TEXT; value: string | null; } | { key: string; type: ", "CustomFieldTypes", - ".TOGGLE; value: boolean | null; })[]; duration: number | null; closedAt: string | null; closedBy: { email: string | null | undefined; fullName: string | null | undefined; username: string | null | undefined; profileUid?: string | undefined; } | null; createdAt: string; createdBy: { email: string | null | undefined; fullName: string | null | undefined; username: string | null | undefined; profileUid?: string | undefined; }; externalService: { connectorId: string; connectorName: string; externalId: string; externalTitle: string; externalUrl: string; pushedAt: string; pushedBy: { email: string | null | undefined; fullName: string | null | undefined; username: string | null | undefined; profileUid?: string | undefined; }; } | null; updatedAt: string | null; updatedBy: { email: string | null | undefined; fullName: string | null | undefined; username: string | null | undefined; profileUid?: string | undefined; } | null; id: string; totalComment: number; totalAlerts: number; version: string; comments?: ({ comment: string; type: ", + ".TOGGLE; value: boolean | null; })[]; settings: { syncAlerts: boolean; }; status: ", + { + "pluginId": "@kbn/cases-components", + "scope": "common", + "docId": "kibKbnCasesComponentsPluginApi", + "section": "def-common.CaseStatuses", + "text": "CaseStatuses" + }, + "; owner: string; duration: number | null; closedAt: string | null; closedBy: { email: string | null | undefined; fullName: string | null | undefined; username: string | null | undefined; profileUid?: string | undefined; } | null; createdAt: string; createdBy: { email: string | null | undefined; fullName: string | null | undefined; username: string | null | undefined; profileUid?: string | undefined; }; externalService: { connectorId: string; connectorName: string; externalId: string; externalTitle: string; externalUrl: string; pushedAt: string; pushedBy: { email: string | null | undefined; fullName: string | null | undefined; username: string | null | undefined; profileUid?: string | undefined; }; } | null; updatedAt: string | null; updatedBy: { email: string | null | undefined; fullName: string | null | undefined; username: string | null | undefined; profileUid?: string | undefined; } | null; id: string; totalComment: number; totalAlerts: number; version: string; comments?: ({ comment: string; type: ", { "pluginId": "cases", "scope": "common", diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index dbc9601ae025f..8bd66d4b518dc 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index b1167ed25660e..87a019415b601 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index 5c8378a364d67..cde4a1383375f 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_data_migration.mdx b/api_docs/cloud_data_migration.mdx index be2e77e42670f..3289a8a5c31c7 100644 --- a/api_docs/cloud_data_migration.mdx +++ b/api_docs/cloud_data_migration.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDataMigration title: "cloudDataMigration" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDataMigration plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDataMigration'] --- import cloudDataMigrationObj from './cloud_data_migration.devdocs.json'; diff --git a/api_docs/cloud_defend.mdx b/api_docs/cloud_defend.mdx index 9aa22ff4bb54b..4c01a05536b3e 100644 --- a/api_docs/cloud_defend.mdx +++ b/api_docs/cloud_defend.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDefend title: "cloudDefend" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDefend plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDefend'] --- import cloudDefendObj from './cloud_defend.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index 60020659deec0..853d76c8a6302 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index 957f69231c9bd..2c9584aa65e4d 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index 0e682d7d30f8e..6b5c17fad5ec9 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/content_management.mdx b/api_docs/content_management.mdx index 5331b84b25b2c..b9c1fd3dc263d 100644 --- a/api_docs/content_management.mdx +++ b/api_docs/content_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/contentManagement title: "contentManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the contentManagement plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'contentManagement'] --- import contentManagementObj from './content_management.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 8d0daa844417f..d6ac2357d7cb6 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index 8f060547b4a7a..f76e6db18acb1 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index 7168a1f2c25f6..82e936a38ef76 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index b7c933abebdb1..63821e9da4788 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.devdocs.json b/api_docs/data.devdocs.json index 296fbccb6cc31..f90123373cdb4 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -11819,6 +11819,14 @@ "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx" }, + { + "plugin": "dataVisualizer", + "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_data_visualizer_esql_data.tsx" + }, + { + "plugin": "dataVisualizer", + "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_data_visualizer_esql_data.tsx" + }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/data_drift/charts/default_value_formatter.ts" diff --git a/api_docs/data.mdx b/api_docs/data.mdx index cae9f9c76653a..cc8a8cee337a3 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_quality.mdx b/api_docs/data_quality.mdx index a7ac7b4b90cf8..19b39f99558bf 100644 --- a/api_docs/data_quality.mdx +++ b/api_docs/data_quality.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataQuality title: "dataQuality" image: https://source.unsplash.com/400x175/?github description: API docs for the dataQuality plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataQuality'] --- import dataQualityObj from './data_quality.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 4a729deaa1445..f075bef36cfad 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index e5a49c1a9e868..a34095c5a5682 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index ca78f7fd66f7b..f49fd61ec7853 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index fe726b483fc0f..ffddbde759530 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index 872710dea197d..ff252838359ce 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index ddd63045decbf..9d7096f7079ab 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 7fe630d2c5413..be2f07df93816 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/dataset_quality.mdx b/api_docs/dataset_quality.mdx index a854d7facde33..104daea12e969 100644 --- a/api_docs/dataset_quality.mdx +++ b/api_docs/dataset_quality.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/datasetQuality title: "datasetQuality" image: https://source.unsplash.com/400x175/?github description: API docs for the datasetQuality plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'datasetQuality'] --- import datasetQualityObj from './dataset_quality.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index 8438260bdc43b..bbcfc14670f17 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -18,7 +18,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | ---------------|-----------|-----------| | <DocLink id="kibAlertingPluginApi" section="def-public.PluginSetupContract.registerNavigation" text="registerNavigation"/> | ml, stackAlerts | - | | <DocLink id="kibDataViewsPluginApi" section="def-common.AbstractDataView.title" text="title"/> | data, @kbn/search-errors, savedObjectsManagement, unifiedSearch, @kbn/unified-field-list, lens, controls, triggersActionsUi, dataVisualizer, canvas, presentationUtil, logsShared, fleet, ml, @kbn/lens-embeddable-utils, @kbn/ml-data-view-utils, enterpriseSearch, graph, visTypeTimeseries, exploratoryView, stackAlerts, infra, securitySolution, timelines, transform, upgradeAssistant, uptime, ux, maps, dataViewManagement, eventAnnotationListing, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega | - | -| <DocLink id="kibSecurityPluginApi" section="def-server.SecurityPluginSetup.authc" text="authc"/> | encryptedSavedObjects, actions, ml, logstash, securitySolution, cloudChat | - | +| <DocLink id="kibSecurityPluginApi" section="def-server.SecurityPluginSetup.authc" text="authc"/> | encryptedSavedObjects, ml, logstash, securitySolution, cloudChat | - | | <DocLink id="kibSecurityPluginApi" section="def-server.SecurityPluginSetup.authz" text="authz"/> | actions, savedObjectsTagging, ml, enterpriseSearch | - | | <DocLink id="kibKbnCoreSavedObjectsApiBrowserPluginApi" section="def-common.SimpleSavedObject" text="SimpleSavedObject"/> | @kbn/core-saved-objects-browser-internal, @kbn/core, savedObjects, visualizations, aiops, dataVisualizer, ml, dashboardEnhanced, graph, lens, securitySolution, eventAnnotation, @kbn/core-saved-objects-browser-mocks | - | | <DocLink id="kibKbnCoreSavedObjectsCommonPluginApi" section="def-common.SavedObjectAttributes" text="SavedObjectAttributes"/> | @kbn/core, savedObjects, embeddable, visualizations, canvas, graph, ml, @kbn/core-saved-objects-common, @kbn/core-saved-objects-server, actions, @kbn/alerting-types, alerting, savedSearch, enterpriseSearch, securitySolution, taskManager, @kbn/core-saved-objects-server-internal, @kbn/core-saved-objects-api-server | - | @@ -39,7 +39,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | <DocLink id="kibDataPluginApi" section="def-common.EqlSearchStrategyRequest.options" text="options"/> | securitySolution | - | | <DocLink id="kibFleetPluginApi" section="def-public.NewPackagePolicy.policy_id" text="policy_id"/> | cloudDefend, osquery, securitySolution, synthetics | - | | <DocLink id="kibFleetPluginApi" section="def-common.NewPackagePolicy.policy_id" text="policy_id"/> | cloudDefend, osquery, securitySolution, synthetics | - | -| <DocLink id="kibSecurityPluginApi" section="def-server.SecurityPluginStart.authc" text="authc"/> | actions, alerting, observabilityAIAssistant, fleet, cloudSecurityPosture, enterpriseSearch, lists, securitySolution, serverlessSearch, transform, upgradeAssistant, apm, entityManager, observabilityOnboarding, synthetics, security | - | +| <DocLink id="kibSecurityPluginApi" section="def-server.SecurityPluginStart.authc" text="authc"/> | alerting, observabilityAIAssistant, fleet, cloudSecurityPosture, enterpriseSearch, securitySolution, serverlessSearch, transform, upgradeAssistant, apm, entityManager, observabilityOnboarding, synthetics, security | - | | <DocLink id="kibSecurityPluginApi" section="def-server.SecurityPluginStart.userProfiles" text="userProfiles"/> | cases, securitySolution, security | - | | <DocLink id="kibTimelinesPluginApi" section="def-common.DeprecatedCellValueElementProps" text="DeprecatedCellValueElementProps"/> | @kbn/securitysolution-data-table, securitySolution | - | | <DocLink id="kibTimelinesPluginApi" section="def-common.DeprecatedRowRenderer" text="DeprecatedRowRenderer"/> | @kbn/securitysolution-data-table, securitySolution | - | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 5084c1bf7de81..08343c9117a7c 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -447,9 +447,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| | <DocLink id="kibLicensingPluginApi" section="def-server.LicensingPluginSetup.license$" text="license$"/> | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/plugin.ts#:~:text=license%24), [license_state.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/lib/license_state.test.ts#:~:text=license%24), [license_state.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/lib/license_state.test.ts#:~:text=license%24) | 8.8.0 | -| <DocLink id="kibSecurityPluginApi" section="def-server.SecurityPluginSetup.authc" text="authc"/> | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/plugin.ts#:~:text=authc) | - | | <DocLink id="kibSecurityPluginApi" section="def-server.SecurityPluginSetup.authz" text="authz"/> | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/plugin.ts#:~:text=authz) | - | -| <DocLink id="kibSecurityPluginApi" section="def-server.SecurityPluginStart.authc" text="authc"/> | [action_executor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/lib/action_executor.ts#:~:text=authc), [action_executor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/lib/action_executor.ts#:~:text=authc) | - | | <DocLink id="kibTaskManagerPluginApi" section="def-server.TaskManagerSetupContract.index" text="index"/> | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/plugin.ts#:~:text=index) | - | | <DocLink id="kibKbnCoreSavedObjectsApiServerPluginApi" section="def-common.SavedObjectAttributes" text="SavedObjectAttributes"/> | [actions_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/actions_client/actions_client.ts#:~:text=SavedObjectAttributes), [actions_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/actions_client/actions_client.ts#:~:text=SavedObjectAttributes), [actions_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/actions_client/actions_client.ts#:~:text=SavedObjectAttributes), [actions_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/actions_client/actions_client.ts#:~:text=SavedObjectAttributes), [actions_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/actions_client/actions_client.ts#:~:text=SavedObjectAttributes), [actions_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/actions_client/actions_client.ts#:~:text=SavedObjectAttributes), [actions_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/actions_client/actions_client.ts#:~:text=SavedObjectAttributes), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/types.ts#:~:text=SavedObjectAttributes), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/types.ts#:~:text=SavedObjectAttributes), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/types.ts#:~:text=SavedObjectAttributes)+ 10 more | - | | <DocLink id="kibKbnCoreSavedObjectsServerPluginApi" section="def-common.SavedObjectsType.migrations" text="migrations"/> | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/saved_objects/index.ts#:~:text=migrations), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/actions/server/saved_objects/index.ts#:~:text=migrations) | - | @@ -699,7 +697,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| <DocLink id="kibDataPluginApi" section="def-public.DataPublicPluginStart.fieldFormats" text="fieldFormats"/> | [document_stats.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/document_stats.tsx#:~:text=fieldFormats), [distinct_values.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/distinct_values.tsx#:~:text=fieldFormats), [top_values.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx#:~:text=fieldFormats), [choropleth_map.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx#:~:text=fieldFormats), [default_value_formatter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/data_drift/charts/default_value_formatter.ts#:~:text=fieldFormats) | - | +| <DocLink id="kibDataPluginApi" section="def-public.DataPublicPluginStart.fieldFormats" text="fieldFormats"/> | [document_stats.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/document_stats.tsx#:~:text=fieldFormats), [distinct_values.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/distinct_values.tsx#:~:text=fieldFormats), [top_values.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx#:~:text=fieldFormats), [choropleth_map.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx#:~:text=fieldFormats), [use_data_visualizer_esql_data.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_data_visualizer_esql_data.tsx#:~:text=fieldFormats), [use_data_visualizer_esql_data.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_data_visualizer_esql_data.tsx#:~:text=fieldFormats), [default_value_formatter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/data_drift/charts/default_value_formatter.ts#:~:text=fieldFormats) | - | | <DocLink id="kibDataViewsPluginApi" section="def-common.AbstractDataView.title" text="title"/> | [use_data_visualizer_grid_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts#:~:text=title) | - | | <DocLink id="kibKbnCoreLifecycleBrowserPluginApi" section="def-common.CoreStart.savedObjects" text="savedObjects"/> | [index_data_visualizer.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx#:~:text=savedObjects) | - | | <DocLink id="kibKbnCoreSavedObjectsApiBrowserPluginApi" section="def-common.SimpleSavedObject" text="SimpleSavedObject"/> | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/common/types/index.ts#:~:text=SimpleSavedObject), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/common/types/index.ts#:~:text=SimpleSavedObject) | - | @@ -1013,7 +1011,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | ---------------|-----------|-----------| | <DocLink id="kibDataPluginApi" section="def-public.SavedObject.migrationVersion" text="migrationVersion"/> | [exception_list_client.mock.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts#:~:text=migrationVersion) | - | | <DocLink id="kibDataPluginApi" section="def-common.SavedObject.migrationVersion" text="migrationVersion"/> | [exception_list_client.mock.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts#:~:text=migrationVersion), [exception_list_client.mock.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts#:~:text=migrationVersion), [exception_list_client.mock.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts#:~:text=migrationVersion), [exception_list_client.mock.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts#:~:text=migrationVersion) | - | -| <DocLink id="kibSecurityPluginApi" section="def-server.SecurityPluginStart.authc" text="authc"/> | [get_user.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lists/server/get_user.ts#:~:text=authc), [get_user.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lists/server/get_user.ts#:~:text=authc) | - | | <DocLink id="kibKbnCoreSavedObjectsCommonPluginApi" section="def-common.SavedObject" text="SavedObject"/> | [exception_list_client.mock.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts#:~:text=SavedObject), [exception_list_client.mock.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts#:~:text=SavedObject), [exception_list_client.mock.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts#:~:text=SavedObject), [exception_list_client.mock.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts#:~:text=SavedObject), [exception_list_client.mock.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts#:~:text=SavedObject) | - | | <DocLink id="kibKbnCoreSavedObjectsServerPluginApi" section="def-common.SavedObjectsType.migrations" text="migrations"/> | [exception_list.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lists/server/saved_objects/exception_list.ts#:~:text=migrations), [exception_list.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lists/server/saved_objects/exception_list.ts#:~:text=migrations) | - | | <DocLink id="kibKbnCoreSavedObjectsServerPluginApi" section="def-common.SavedObjectsType.convertToMultiNamespaceTypeVersion" text="convertToMultiNamespaceTypeVersion"/> | [exception_list.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lists/server/saved_objects/exception_list.ts#:~:text=convertToMultiNamespaceTypeVersion) | - | @@ -1349,7 +1346,7 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | <DocLink id="kibTimelinesPluginApi" section="def-common.DeprecatedCellValueElementProps" text="DeprecatedCellValueElementProps"/> | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx#:~:text=DeprecatedCellValueElementProps), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx#:~:text=DeprecatedCellValueElementProps) | - | | <DocLink id="kibTimelinesPluginApi" section="def-common.DeprecatedRowRenderer" text="DeprecatedRowRenderer"/> | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx#:~:text=DeprecatedRowRenderer), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx#:~:text=DeprecatedRowRenderer) | - | | <DocLink id="kibTimelinesPluginApi" section="def-common.BeatFields" text="BeatFields"/> | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts#:~:text=BeatFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=BeatFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=BeatFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=BeatFields) | - | -| <DocLink id="kibTimelinesPluginApi" section="def-common.BrowserField" text="BrowserField"/> | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts#:~:text=BrowserField), [helpers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts#:~:text=BrowserField), [helpers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts#:~:text=BrowserField), [helpers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts#:~:text=BrowserField), [helpers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts#:~:text=BrowserField), [columns.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx#:~:text=BrowserField), [columns.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx#:~:text=BrowserField), [columns.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx#:~:text=BrowserField), [enrichment_summary.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_summary.tsx#:~:text=BrowserField), [enrichment_summary.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_summary.tsx#:~:text=BrowserField)+ 32 more | - | +| <DocLink id="kibTimelinesPluginApi" section="def-common.BrowserField" text="BrowserField"/> | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts#:~:text=BrowserField), [helpers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts#:~:text=BrowserField), [helpers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts#:~:text=BrowserField), [helpers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts#:~:text=BrowserField), [helpers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts#:~:text=BrowserField), [columns.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx#:~:text=BrowserField), [columns.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx#:~:text=BrowserField), [columns.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx#:~:text=BrowserField), [enrichment_summary.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_summary.tsx#:~:text=BrowserField), [enrichment_summary.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_summary.tsx#:~:text=BrowserField)+ 35 more | - | | <DocLink id="kibTimelinesPluginApi" section="def-common.BrowserFields" text="BrowserFields"/> | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/timeline/cells/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/timeline/cells/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/header_actions/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/types/header_actions/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields)+ 105 more | - | | <DocLink id="kibTimelinesPluginApi" section="def-common.IndexFieldsStrategyRequest" text="IndexFieldsStrategyRequest"/> | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts#:~:text=IndexFieldsStrategyRequest), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=IndexFieldsStrategyRequest), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=IndexFieldsStrategyRequest), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=IndexFieldsStrategyRequest), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=IndexFieldsStrategyRequest), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=IndexFieldsStrategyRequest) | - | | <DocLink id="kibTimelinesPluginApi" section="def-common.IndexFieldsStrategyResponse" text="IndexFieldsStrategyResponse"/> | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts#:~:text=IndexFieldsStrategyResponse), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=IndexFieldsStrategyResponse), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=IndexFieldsStrategyResponse), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/search_strategy/endpoint_fields/index.ts#:~:text=IndexFieldsStrategyResponse), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=IndexFieldsStrategyResponse), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=IndexFieldsStrategyResponse) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 86813098ef475..7b30e5e3376d3 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 9bd9342e38d90..03d1852670831 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 86086518c5ec9..6f81e74775d26 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 6dfd758a353b4..8994c4c345eea 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/discover_shared.mdx b/api_docs/discover_shared.mdx index 09b4e32edeb64..f5436338bdbf3 100644 --- a/api_docs/discover_shared.mdx +++ b/api_docs/discover_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverShared title: "discoverShared" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverShared plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverShared'] --- import discoverSharedObj from './discover_shared.devdocs.json'; diff --git a/api_docs/ecs_data_quality_dashboard.mdx b/api_docs/ecs_data_quality_dashboard.mdx index 395931a0f30e7..f128e16198740 100644 --- a/api_docs/ecs_data_quality_dashboard.mdx +++ b/api_docs/ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ecsDataQualityDashboard title: "ecsDataQualityDashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the ecsDataQualityDashboard plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ecsDataQualityDashboard'] --- import ecsDataQualityDashboardObj from './ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/elastic_assistant.mdx b/api_docs/elastic_assistant.mdx index b2e6a52f803cc..7e06bb46c9186 100644 --- a/api_docs/elastic_assistant.mdx +++ b/api_docs/elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/elasticAssistant title: "elasticAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the elasticAssistant plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'elasticAssistant'] --- import elasticAssistantObj from './elastic_assistant.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index b15ad570394a6..44dae18c90ee7 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 2fdc2a508e6f6..0145dbadb370b 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index ed4b96eaa3d95..4f26ce7e130d0 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index a149c24ad3ee7..d5a703ea842d7 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/entity_manager.mdx b/api_docs/entity_manager.mdx index 726544e187c60..529faff211338 100644 --- a/api_docs/entity_manager.mdx +++ b/api_docs/entity_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/entityManager title: "entityManager" image: https://source.unsplash.com/400x175/?github description: API docs for the entityManager plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'entityManager'] --- import entityManagerObj from './entity_manager.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index bb2d047a2879a..5057e05b31d2d 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/esql_data_grid.mdx b/api_docs/esql_data_grid.mdx index de2755078e9fd..f3f92191d98d8 100644 --- a/api_docs/esql_data_grid.mdx +++ b/api_docs/esql_data_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esqlDataGrid title: "esqlDataGrid" image: https://source.unsplash.com/400x175/?github description: API docs for the esqlDataGrid plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esqlDataGrid'] --- import esqlDataGridObj from './esql_data_grid.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index e628071ee4934..ba87ac7296fa5 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_annotation_listing.mdx b/api_docs/event_annotation_listing.mdx index dc7887175e060..3fde94d1de6d8 100644 --- a/api_docs/event_annotation_listing.mdx +++ b/api_docs/event_annotation_listing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotationListing title: "eventAnnotationListing" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotationListing plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotationListing'] --- import eventAnnotationListingObj from './event_annotation_listing.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 07787d04cf659..20669f8feb9ae 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/exploratory_view.mdx b/api_docs/exploratory_view.mdx index 6f26375c0a326..1498927b97b40 100644 --- a/api_docs/exploratory_view.mdx +++ b/api_docs/exploratory_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/exploratoryView title: "exploratoryView" image: https://source.unsplash.com/400x175/?github description: API docs for the exploratoryView plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'exploratoryView'] --- import exploratoryViewObj from './exploratory_view.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index 217abb4a3da81..3bd6a5660f961 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index d34b47be8e4a0..32e8edb562f3d 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index ded7ab8f7a0ae..0d28d3b546657 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index 02c5a3a2384cc..679cd84fc6deb 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index 21207676d742f..badf9e80fdab7 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index 2fd54ba779ba1..85af108334a96 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index 6ed2fadfcb832..98fea84569010 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index a70ec46e402c2..4f7d5e94a4179 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index 76c3a2293568f..56eb3eaba9cf2 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index f4b9929d8d09d..609bb7a36065a 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index 347abfeba1577..ec4fd965fd811 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index 6462827c6d37e..a37e6e9682a4b 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index 6d240631c7861..a275906908948 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index 3762a30d9d937..09fbc3602215b 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 0ad7647b41c90..0a4f1eacbe548 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index 0e60c5a5f4229..090102c9171ba 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/fields_metadata.mdx b/api_docs/fields_metadata.mdx index d7e115d9ee8ef..87ffd7bea430b 100644 --- a/api_docs/fields_metadata.mdx +++ b/api_docs/fields_metadata.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldsMetadata title: "fieldsMetadata" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldsMetadata plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldsMetadata'] --- import fieldsMetadataObj from './fields_metadata.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index e93ba838dbd9a..ce11d343b4852 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.mdx b/api_docs/files.mdx index b4cc4b7aaa36f..800d6afef99af 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/files_management.mdx b/api_docs/files_management.mdx index 628ee241962c1..f0e4cc2cf1782 100644 --- a/api_docs/files_management.mdx +++ b/api_docs/files_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/filesManagement title: "filesManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the filesManagement plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'filesManagement'] --- import filesManagementObj from './files_management.devdocs.json'; diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 992b932e619f1..21ed516f421a5 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index bc01cb373e227..b3b9fae7d0533 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 2da43333622b8..06c5c30baf394 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 577f995206df3..0d1c2694a6dab 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/image_embeddable.mdx b/api_docs/image_embeddable.mdx index f8d0091546d1d..e0c40417ab9c2 100644 --- a/api_docs/image_embeddable.mdx +++ b/api_docs/image_embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/imageEmbeddable title: "imageEmbeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the imageEmbeddable plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'imageEmbeddable'] --- import imageEmbeddableObj from './image_embeddable.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index 6b4da28e9c759..e31e619532b7e 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 945fdbaffa53e..e7103b2b467da 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 0e04e24e16704..db7c162946a72 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/ingest_pipelines.mdx b/api_docs/ingest_pipelines.mdx index 54fbadf2c9874..135f45aac04df 100644 --- a/api_docs/ingest_pipelines.mdx +++ b/api_docs/ingest_pipelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ingestPipelines title: "ingestPipelines" image: https://source.unsplash.com/400x175/?github description: API docs for the ingestPipelines plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ingestPipelines'] --- import ingestPipelinesObj from './ingest_pipelines.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index b31ae884f9566..7e5538ec8b8d5 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/integration_assistant.mdx b/api_docs/integration_assistant.mdx index 9b70ac4420d2d..a9b4bcad2d866 100644 --- a/api_docs/integration_assistant.mdx +++ b/api_docs/integration_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/integrationAssistant title: "integrationAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the integrationAssistant plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'integrationAssistant'] --- import integrationAssistantObj from './integration_assistant.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 29af3ddff2afb..34317363c6470 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/investigate.mdx b/api_docs/investigate.mdx index ee8486b88a7e5..9c07563f7066c 100644 --- a/api_docs/investigate.mdx +++ b/api_docs/investigate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/investigate title: "investigate" image: https://source.unsplash.com/400x175/?github description: API docs for the investigate plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'investigate'] --- import investigateObj from './investigate.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index 23aee56bdac7b..6b01f115bca76 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_actions_types.mdx b/api_docs/kbn_actions_types.mdx index ff89c43715e85..d41c6036a0c50 100644 --- a/api_docs/kbn_actions_types.mdx +++ b/api_docs/kbn_actions_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-actions-types title: "@kbn/actions-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/actions-types plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/actions-types'] --- import kbnActionsTypesObj from './kbn_actions_types.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index 1a6385fb00a2c..55b7e8836d7de 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_log_pattern_analysis.mdx b/api_docs/kbn_aiops_log_pattern_analysis.mdx index 1fc6bd2ce64c4..d16d66e67f792 100644 --- a/api_docs/kbn_aiops_log_pattern_analysis.mdx +++ b/api_docs/kbn_aiops_log_pattern_analysis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-log-pattern-analysis title: "@kbn/aiops-log-pattern-analysis" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-log-pattern-analysis plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-log-pattern-analysis'] --- import kbnAiopsLogPatternAnalysisObj from './kbn_aiops_log_pattern_analysis.devdocs.json'; diff --git a/api_docs/kbn_aiops_log_rate_analysis.mdx b/api_docs/kbn_aiops_log_rate_analysis.mdx index 3aba3b3eb6001..15fbeabbd6f9c 100644 --- a/api_docs/kbn_aiops_log_rate_analysis.mdx +++ b/api_docs/kbn_aiops_log_rate_analysis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-log-rate-analysis title: "@kbn/aiops-log-rate-analysis" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-log-rate-analysis plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-log-rate-analysis'] --- import kbnAiopsLogRateAnalysisObj from './kbn_aiops_log_rate_analysis.devdocs.json'; diff --git a/api_docs/kbn_alerting_api_integration_helpers.mdx b/api_docs/kbn_alerting_api_integration_helpers.mdx index 13c04f60c0077..c39de91e2952f 100644 --- a/api_docs/kbn_alerting_api_integration_helpers.mdx +++ b/api_docs/kbn_alerting_api_integration_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-api-integration-helpers title: "@kbn/alerting-api-integration-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-api-integration-helpers plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-api-integration-helpers'] --- import kbnAlertingApiIntegrationHelpersObj from './kbn_alerting_api_integration_helpers.devdocs.json'; diff --git a/api_docs/kbn_alerting_comparators.mdx b/api_docs/kbn_alerting_comparators.mdx index d17df9a9516dc..76960053ddcda 100644 --- a/api_docs/kbn_alerting_comparators.mdx +++ b/api_docs/kbn_alerting_comparators.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-comparators title: "@kbn/alerting-comparators" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-comparators plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-comparators'] --- import kbnAlertingComparatorsObj from './kbn_alerting_comparators.devdocs.json'; diff --git a/api_docs/kbn_alerting_state_types.mdx b/api_docs/kbn_alerting_state_types.mdx index 72eacc2fc3ad7..65880e9357552 100644 --- a/api_docs/kbn_alerting_state_types.mdx +++ b/api_docs/kbn_alerting_state_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-state-types title: "@kbn/alerting-state-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-state-types plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-state-types'] --- import kbnAlertingStateTypesObj from './kbn_alerting_state_types.devdocs.json'; diff --git a/api_docs/kbn_alerting_types.mdx b/api_docs/kbn_alerting_types.mdx index 519d4c913868a..79ee704d94f19 100644 --- a/api_docs/kbn_alerting_types.mdx +++ b/api_docs/kbn_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-types title: "@kbn/alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-types plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-types'] --- import kbnAlertingTypesObj from './kbn_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx index 8cfdb0378d47e..f78730dc16fbb 100644 --- a/api_docs/kbn_alerts_as_data_utils.mdx +++ b/api_docs/kbn_alerts_as_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-as-data-utils title: "@kbn/alerts-as-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-as-data-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-as-data-utils'] --- import kbnAlertsAsDataUtilsObj from './kbn_alerts_as_data_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts_ui_shared.mdx b/api_docs/kbn_alerts_ui_shared.mdx index 9c3ce29931b64..d139fdae38b3d 100644 --- a/api_docs/kbn_alerts_ui_shared.mdx +++ b/api_docs/kbn_alerts_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-ui-shared title: "@kbn/alerts-ui-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-ui-shared plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-ui-shared'] --- import kbnAlertsUiSharedObj from './kbn_alerts_ui_shared.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index 5ae2193a5fd82..372c787326141 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_collection_utils.mdx b/api_docs/kbn_analytics_collection_utils.mdx index 09b6d825d3a07..c92af5dfa18e2 100644 --- a/api_docs/kbn_analytics_collection_utils.mdx +++ b/api_docs/kbn_analytics_collection_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-collection-utils title: "@kbn/analytics-collection-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-collection-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-collection-utils'] --- import kbnAnalyticsCollectionUtilsObj from './kbn_analytics_collection_utils.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 63a63a5bd3027..4df277626b033 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_data_view.mdx b/api_docs/kbn_apm_data_view.mdx index 819f6843014cb..436cf4575619d 100644 --- a/api_docs/kbn_apm_data_view.mdx +++ b/api_docs/kbn_apm_data_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-data-view title: "@kbn/apm-data-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-data-view plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-data-view'] --- import kbnApmDataViewObj from './kbn_apm_data_view.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 7e288744a97a5..a7a0c1dba8c61 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace_client.mdx b/api_docs/kbn_apm_synthtrace_client.mdx index 80b83bc500d2e..de4ec063441a3 100644 --- a/api_docs/kbn_apm_synthtrace_client.mdx +++ b/api_docs/kbn_apm_synthtrace_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace-client title: "@kbn/apm-synthtrace-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace-client plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace-client'] --- import kbnApmSynthtraceClientObj from './kbn_apm_synthtrace_client.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 74bdfa13a5682..9db8927805a0d 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index 1f9ab20cf8226..55ceca0a0eade 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_bfetch_error.mdx b/api_docs/kbn_bfetch_error.mdx index 2f5fd9ec6628b..1ccb67108fb62 100644 --- a/api_docs/kbn_bfetch_error.mdx +++ b/api_docs/kbn_bfetch_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-bfetch-error title: "@kbn/bfetch-error" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/bfetch-error plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/bfetch-error'] --- import kbnBfetchErrorObj from './kbn_bfetch_error.devdocs.json'; diff --git a/api_docs/kbn_calculate_auto.mdx b/api_docs/kbn_calculate_auto.mdx index b24b89d5be35f..ccdecc6bbdd5e 100644 --- a/api_docs/kbn_calculate_auto.mdx +++ b/api_docs/kbn_calculate_auto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-calculate-auto title: "@kbn/calculate-auto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/calculate-auto plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/calculate-auto'] --- import kbnCalculateAutoObj from './kbn_calculate_auto.devdocs.json'; diff --git a/api_docs/kbn_calculate_width_from_char_count.mdx b/api_docs/kbn_calculate_width_from_char_count.mdx index ffe7f7f4263c0..e24e808fd2bbe 100644 --- a/api_docs/kbn_calculate_width_from_char_count.mdx +++ b/api_docs/kbn_calculate_width_from_char_count.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-calculate-width-from-char-count title: "@kbn/calculate-width-from-char-count" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/calculate-width-from-char-count plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/calculate-width-from-char-count'] --- import kbnCalculateWidthFromCharCountObj from './kbn_calculate_width_from_char_count.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index 070381d998cb2..b1598ff97e616 100644 --- a/api_docs/kbn_cases_components.mdx +++ b/api_docs/kbn_cases_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cases-components title: "@kbn/cases-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cases-components plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cases-components'] --- import kbnCasesComponentsObj from './kbn_cases_components.devdocs.json'; diff --git a/api_docs/kbn_cell_actions.mdx b/api_docs/kbn_cell_actions.mdx index 5e516217151f0..fdc685fb9231f 100644 --- a/api_docs/kbn_cell_actions.mdx +++ b/api_docs/kbn_cell_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cell-actions title: "@kbn/cell-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cell-actions plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cell-actions'] --- import kbnCellActionsObj from './kbn_cell_actions.devdocs.json'; diff --git a/api_docs/kbn_chart_expressions_common.mdx b/api_docs/kbn_chart_expressions_common.mdx index 0fb0c476893f6..72122b5c9e511 100644 --- a/api_docs/kbn_chart_expressions_common.mdx +++ b/api_docs/kbn_chart_expressions_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-expressions-common title: "@kbn/chart-expressions-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-expressions-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-expressions-common'] --- import kbnChartExpressionsCommonObj from './kbn_chart_expressions_common.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index e6dbef80fb6da..e768edc933618 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index 41a535f805c72..fd608d74ee8bc 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index b709dc243a81e..4fd05957dde77 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index 92b94fe7e7fc8..6526802dfde8a 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index 4fe7a06da17f0..108b32c01af03 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_code_editor.mdx b/api_docs/kbn_code_editor.mdx index ab37a405c4439..2baeca00663c1 100644 --- a/api_docs/kbn_code_editor.mdx +++ b/api_docs/kbn_code_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor title: "@kbn/code-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor'] --- import kbnCodeEditorObj from './kbn_code_editor.devdocs.json'; diff --git a/api_docs/kbn_code_editor_mock.mdx b/api_docs/kbn_code_editor_mock.mdx index f23d63eaf87b9..df29cba4449b2 100644 --- a/api_docs/kbn_code_editor_mock.mdx +++ b/api_docs/kbn_code_editor_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor-mock title: "@kbn/code-editor-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor-mock plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor-mock'] --- import kbnCodeEditorMockObj from './kbn_code_editor_mock.devdocs.json'; diff --git a/api_docs/kbn_code_owners.mdx b/api_docs/kbn_code_owners.mdx index 56009b2104a5c..ac13d1a8ac50f 100644 --- a/api_docs/kbn_code_owners.mdx +++ b/api_docs/kbn_code_owners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-owners title: "@kbn/code-owners" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-owners plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-owners'] --- import kbnCodeOwnersObj from './kbn_code_owners.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index d6b40e956feb5..c78e81fb389dd 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index ced3a27657dc3..9ce9e3bbda86d 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index 0632c4237e1cf..1b89dabb8f3e5 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index 737a3f575f51b..541a1472dce83 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_content_editor.mdx b/api_docs/kbn_content_management_content_editor.mdx index 59eede6a225e7..6b8f4845611cf 100644 --- a/api_docs/kbn_content_management_content_editor.mdx +++ b/api_docs/kbn_content_management_content_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-content-editor title: "@kbn/content-management-content-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-content-editor plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-content-editor'] --- import kbnContentManagementContentEditorObj from './kbn_content_management_content_editor.devdocs.json'; diff --git a/api_docs/kbn_content_management_tabbed_table_list_view.mdx b/api_docs/kbn_content_management_tabbed_table_list_view.mdx index e3f154bf7e2e5..cc120deb3beeb 100644 --- a/api_docs/kbn_content_management_tabbed_table_list_view.mdx +++ b/api_docs/kbn_content_management_tabbed_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-tabbed-table-list-view title: "@kbn/content-management-tabbed-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-tabbed-table-list-view plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-tabbed-table-list-view'] --- import kbnContentManagementTabbedTableListViewObj from './kbn_content_management_tabbed_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view.mdx b/api_docs/kbn_content_management_table_list_view.mdx index 341978fd43d6d..59a2c82d98c43 100644 --- a/api_docs/kbn_content_management_table_list_view.mdx +++ b/api_docs/kbn_content_management_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view title: "@kbn/content-management-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view'] --- import kbnContentManagementTableListViewObj from './kbn_content_management_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view_common.mdx b/api_docs/kbn_content_management_table_list_view_common.mdx index 9eddd10872dc5..9eb3eb60c0f8f 100644 --- a/api_docs/kbn_content_management_table_list_view_common.mdx +++ b/api_docs/kbn_content_management_table_list_view_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-common title: "@kbn/content-management-table-list-view-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-common'] --- import kbnContentManagementTableListViewCommonObj from './kbn_content_management_table_list_view_common.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view_table.mdx b/api_docs/kbn_content_management_table_list_view_table.mdx index 5cdfc5b506f03..60f31a1f9cba9 100644 --- a/api_docs/kbn_content_management_table_list_view_table.mdx +++ b/api_docs/kbn_content_management_table_list_view_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-table title: "@kbn/content-management-table-list-view-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-table plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-table'] --- import kbnContentManagementTableListViewTableObj from './kbn_content_management_table_list_view_table.devdocs.json'; diff --git a/api_docs/kbn_content_management_user_profiles.mdx b/api_docs/kbn_content_management_user_profiles.mdx index e9f46fcf553d5..0d1fc5f12270b 100644 --- a/api_docs/kbn_content_management_user_profiles.mdx +++ b/api_docs/kbn_content_management_user_profiles.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-user-profiles title: "@kbn/content-management-user-profiles" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-user-profiles plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-user-profiles'] --- import kbnContentManagementUserProfilesObj from './kbn_content_management_user_profiles.devdocs.json'; diff --git a/api_docs/kbn_content_management_utils.mdx b/api_docs/kbn_content_management_utils.mdx index ce03e7966d783..34fa9dc1b9b9f 100644 --- a/api_docs/kbn_content_management_utils.mdx +++ b/api_docs/kbn_content_management_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-utils title: "@kbn/content-management-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-utils'] --- import kbnContentManagementUtilsObj from './kbn_content_management_utils.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.devdocs.json b/api_docs/kbn_core_analytics_browser.devdocs.json index 492043475802f..dfaa391fa2f9b 100644 --- a/api_docs/kbn_core_analytics_browser.devdocs.json +++ b/api_docs/kbn_core_analytics_browser.devdocs.json @@ -731,6 +731,18 @@ "plugin": "fleet", "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_client.ts" + }, { "plugin": "elasticAssistant", "path": "x-pack/plugins/elastic_assistant/server/lib/langchain/elasticsearch_store/elasticsearch_store.ts" @@ -915,6 +927,26 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, { "plugin": "osquery", "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" @@ -1255,6 +1287,38 @@ "plugin": "apm", "path": "x-pack/plugins/observability_solution/apm/public/services/telemetry/telemetry_service.test.ts" }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, { "plugin": "infra", "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 03ee16fedc58a..ceec4b4e33078 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 34185a9cc2cb4..3e3c07067ec5a 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index 306ea6315aa11..e3c0a6d47e463 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.devdocs.json b/api_docs/kbn_core_analytics_server.devdocs.json index f0fb04c0b6745..6675d7501f672 100644 --- a/api_docs/kbn_core_analytics_server.devdocs.json +++ b/api_docs/kbn_core_analytics_server.devdocs.json @@ -731,6 +731,18 @@ "plugin": "fleet", "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_client.ts" + }, { "plugin": "elasticAssistant", "path": "x-pack/plugins/elastic_assistant/server/lib/langchain/elasticsearch_store/elasticsearch_store.ts" @@ -915,6 +927,26 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, { "plugin": "osquery", "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" @@ -1255,6 +1287,38 @@ "plugin": "apm", "path": "x-pack/plugins/observability_solution/apm/public/services/telemetry/telemetry_service.test.ts" }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, { "plugin": "infra", "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index dc02ae2d568f1..e797434f2d6b9 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index f5fd5b00e6c49..62572df3ad3c1 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index 434b2a84a2e5f..594607e81b093 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index da86c2ab178f6..2610a7f0ff012 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index c23446e906462..0bda9757c8f9a 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index de33e071f48a3..bf3bc99f34abe 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index fcd85266e96a3..44f45586f4fb8 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index 626b1565c76f6..b28ecc232fadd 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index 8990f7cabd04e..73ab4db437c1d 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_apps_server_internal.mdx b/api_docs/kbn_core_apps_server_internal.mdx index 5af9e5d636075..ad1a08dfee996 100644 --- a/api_docs/kbn_core_apps_server_internal.mdx +++ b/api_docs/kbn_core_apps_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-server-internal title: "@kbn/core-apps-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-server-internal'] --- import kbnCoreAppsServerInternalObj from './kbn_core_apps_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index 77c77f578ca73..3ce0110995a3f 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 326ccacf05452..51fc7668a96c0 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index 1f8ffec2eb5c5..2bcf3cb79c7da 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index 492721237e747..ff53c8dccf352 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index 30ea1dcb4e469..2ad35b890c181 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index dfe1284977f16..42b1524399769 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index 4d4ee98cd4cb1..fd511dbbf29b1 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index c1a01ee841a6c..5652bc5f23d0a 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index d7bde9c05efc9..c852726988a8d 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index a9da9c467d88b..a9c10a2e94a52 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index 021494d595e56..f60cf806bf76d 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser.mdx b/api_docs/kbn_core_custom_branding_browser.mdx index 8e659c83b60da..548c59469ba1c 100644 --- a/api_docs/kbn_core_custom_branding_browser.mdx +++ b/api_docs/kbn_core_custom_branding_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser title: "@kbn/core-custom-branding-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser'] --- import kbnCoreCustomBrandingBrowserObj from './kbn_core_custom_branding_browser.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_internal.mdx b/api_docs/kbn_core_custom_branding_browser_internal.mdx index b5bac2de8c813..8b8675596c4b5 100644 --- a/api_docs/kbn_core_custom_branding_browser_internal.mdx +++ b/api_docs/kbn_core_custom_branding_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-internal title: "@kbn/core-custom-branding-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-internal'] --- import kbnCoreCustomBrandingBrowserInternalObj from './kbn_core_custom_branding_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_mocks.mdx b/api_docs/kbn_core_custom_branding_browser_mocks.mdx index 6e9897235f13c..692306ccbd0cc 100644 --- a/api_docs/kbn_core_custom_branding_browser_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-mocks title: "@kbn/core-custom-branding-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-mocks'] --- import kbnCoreCustomBrandingBrowserMocksObj from './kbn_core_custom_branding_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_common.mdx b/api_docs/kbn_core_custom_branding_common.mdx index 978ba2c42a6d5..be26184d780b5 100644 --- a/api_docs/kbn_core_custom_branding_common.mdx +++ b/api_docs/kbn_core_custom_branding_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-common title: "@kbn/core-custom-branding-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-common'] --- import kbnCoreCustomBrandingCommonObj from './kbn_core_custom_branding_common.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server.mdx b/api_docs/kbn_core_custom_branding_server.mdx index 77e51df212644..459c71e178ee8 100644 --- a/api_docs/kbn_core_custom_branding_server.mdx +++ b/api_docs/kbn_core_custom_branding_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server title: "@kbn/core-custom-branding-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server'] --- import kbnCoreCustomBrandingServerObj from './kbn_core_custom_branding_server.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_internal.mdx b/api_docs/kbn_core_custom_branding_server_internal.mdx index 6fd209b592891..90c2244b64c39 100644 --- a/api_docs/kbn_core_custom_branding_server_internal.mdx +++ b/api_docs/kbn_core_custom_branding_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-internal title: "@kbn/core-custom-branding-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-internal'] --- import kbnCoreCustomBrandingServerInternalObj from './kbn_core_custom_branding_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_mocks.mdx b/api_docs/kbn_core_custom_branding_server_mocks.mdx index e40d0a7e2c0f3..f8f6810666b14 100644 --- a/api_docs/kbn_core_custom_branding_server_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-mocks title: "@kbn/core-custom-branding-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-mocks'] --- import kbnCoreCustomBrandingServerMocksObj from './kbn_core_custom_branding_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index 0f2c523863eff..84aaef0fcca3a 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index 7cd4345becf38..f5c8f85e0c2be 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index b46e0b894a3be..6f5cb75b57720 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index 01b20c36876a3..a7d7bcb3e2a50 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index 363adaa5a3d7a..61bd4256f7e4c 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index 5c23a887b50f5..760998fc7997c 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index 4fc3a83d19864..beadd29d8e7f9 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index fbcd18b0a90d6..38998a00cd30a 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index 6d465fad94575..9efd142ca976d 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index 4407e37301ff9..e9ba29b927c52 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index df79f07d92367..d64a418894c1b 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index 96ec115c3bc82..2e24832d969b4 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index 2020232176410..a407aa47dbd69 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index 4866d8375cd2c..d64ec1c6a8eef 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index 965239b029808..72f0d8605121d 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index 43e30bab675bf..b251eee63ad6e 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index 36c5bcb2ff13c..9460e4326a055 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index 1e25d6531888a..85c9a3a92c220 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index 385ad4bad9653..2200a3f8ad9aa 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index ed5796f04023b..14fe14dbdad8b 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index 460954788f0fb..f278552ad568a 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index a81cda5291e45..4882c7bd76b45 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index f5812eab86fa2..bf70df92639f8 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index 7b540d93e9e56..44499d96d010f 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index 788d274af073a..c62dec50cdea2 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index 0869dc22449d0..6643a08ca65c7 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index b94ce1eb2ea1a..8e0a49b2db953 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index 3867e3217d542..39acb51bc343d 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index 97a363856a981..277104e40ad87 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index b41fc8727261a..a5b8f7aa1538f 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index c9ffdc50e99cf..73bb3c9b78d5b 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index 9911f139cef74..aaa958a4a4bd1 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_request_handler_context_server.mdx b/api_docs/kbn_core_http_request_handler_context_server.mdx index 11880a41a5d36..4de22b339c6cf 100644 --- a/api_docs/kbn_core_http_request_handler_context_server.mdx +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server title: "@kbn/core-http-request-handler-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-request-handler-context-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-request-handler-context-server'] --- import kbnCoreHttpRequestHandlerContextServerObj from './kbn_core_http_request_handler_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server.mdx b/api_docs/kbn_core_http_resources_server.mdx index c7f389e8ff4fe..60ccd1c74b590 100644 --- a/api_docs/kbn_core_http_resources_server.mdx +++ b/api_docs/kbn_core_http_resources_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server title: "@kbn/core-http-resources-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server'] --- import kbnCoreHttpResourcesServerObj from './kbn_core_http_resources_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_internal.mdx b/api_docs/kbn_core_http_resources_server_internal.mdx index 02aa37a1d7449..2d2c7df9e7a3b 100644 --- a/api_docs/kbn_core_http_resources_server_internal.mdx +++ b/api_docs/kbn_core_http_resources_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-internal title: "@kbn/core-http-resources-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-internal'] --- import kbnCoreHttpResourcesServerInternalObj from './kbn_core_http_resources_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_mocks.mdx b/api_docs/kbn_core_http_resources_server_mocks.mdx index b0b3694cd51a3..1cea9ff37db60 100644 --- a/api_docs/kbn_core_http_resources_server_mocks.mdx +++ b/api_docs/kbn_core_http_resources_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-mocks title: "@kbn/core-http-resources-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-mocks'] --- import kbnCoreHttpResourcesServerMocksObj from './kbn_core_http_resources_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index 148aa4d688fa9..335ac7888b1be 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index 6900fea4b72fc..d3d46e9eec4b2 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.devdocs.json b/api_docs/kbn_core_http_server.devdocs.json index 87a354908cae5..ec187c7f4458f 100644 --- a/api_docs/kbn_core_http_server.devdocs.json +++ b/api_docs/kbn_core_http_server.devdocs.json @@ -4198,6 +4198,10 @@ "plugin": "enterpriseSearch", "path": "x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts" }, + { + "plugin": "enterpriseSearch", + "path": "x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts" + }, { "plugin": "enterpriseSearch", "path": "x-pack/plugins/enterprise_search/server/routes/enterprise_search/config_data.ts" @@ -6828,6 +6832,10 @@ "plugin": "enterpriseSearch", "path": "x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts" }, + { + "plugin": "enterpriseSearch", + "path": "x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts" + }, { "plugin": "enterpriseSearch", "path": "x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler_crawl_rules.ts" diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 2bdd0d7bc5fc2..26f6199a11262 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index 88b367ebdbf0b..263528b3d81b0 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index f472ee37c8efb..35225ec82b2a0 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index 80a9adfc5e0ea..9bce687f3435a 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index dd6147798af32..411c7a970ba8b 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index b1d4de3280f10..1a720c91a475b 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index 0d31538978c98..8add534188460 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index 8584e2a95d93b..855eacd854b96 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index 94c18053976cf..0259f1ddc68a8 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index 3a4bf0b5fbe75..9cc8a78c739f7 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index 32566769d2f76..f7cce01f08387 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index ed6dce5e83ee1..65e9ec3f08938 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index c0681954c4e0b..c1151bcdd62c6 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server.mdx b/api_docs/kbn_core_lifecycle_server.mdx index 2552de14914ba..59333ab5b224b 100644 --- a/api_docs/kbn_core_lifecycle_server.mdx +++ b/api_docs/kbn_core_lifecycle_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server title: "@kbn/core-lifecycle-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server'] --- import kbnCoreLifecycleServerObj from './kbn_core_lifecycle_server.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server_mocks.mdx b/api_docs/kbn_core_lifecycle_server_mocks.mdx index a6881c247fc36..670cccaa29936 100644 --- a/api_docs/kbn_core_lifecycle_server_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server-mocks title: "@kbn/core-lifecycle-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server-mocks'] --- import kbnCoreLifecycleServerMocksObj from './kbn_core_lifecycle_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_browser_mocks.mdx b/api_docs/kbn_core_logging_browser_mocks.mdx index 43e26e804a442..e8a5362583195 100644 --- a/api_docs/kbn_core_logging_browser_mocks.mdx +++ b/api_docs/kbn_core_logging_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-browser-mocks title: "@kbn/core-logging-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-browser-mocks'] --- import kbnCoreLoggingBrowserMocksObj from './kbn_core_logging_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_common_internal.mdx b/api_docs/kbn_core_logging_common_internal.mdx index f38b1b3d8e95e..784d7b2f430fd 100644 --- a/api_docs/kbn_core_logging_common_internal.mdx +++ b/api_docs/kbn_core_logging_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-common-internal title: "@kbn/core-logging-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-common-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-common-internal'] --- import kbnCoreLoggingCommonInternalObj from './kbn_core_logging_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 04e504cf2fb13..17511a7cbf3fd 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index 93994b09d5f5a..d566d09a70499 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index 639b06bdd6a09..1b4342792eea7 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index 0386850d25dc2..6571b12bb1d3f 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index a66864ef65e14..9bd19c0770952 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index cba8c8ab6b62f..0ee98af4edc7b 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_server.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index 6f9b20081da9b..60e82404acb8e 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index 5e54846e4b3c8..a658eecd89764 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index fc8651fc901d7..f7ff4fe8a1fc3 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index 0d6458897d020..8c98a543f6ad8 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index 54e3df0fd1702..545a5efd82049 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index 0daaa6a2e3ebe..9516a05a1a372 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index ebd7346e2db50..fa7b7bfdeaa76 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index fec623e8cf016..347e50c455817 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index f3346fb04da1b..4999be9a671e8 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index 7d59c37e3dda9..405189ee88fe0 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index e78eff678db60..ae238868b2392 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index 9ac7df2e312db..76a3128fb8b59 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index 454a63fdd8bcd..3c22a8dd2bb39 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index 95d7700b82fc2..8b8a6a9223ab6 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_contracts_browser.mdx b/api_docs/kbn_core_plugins_contracts_browser.mdx index 049ca26dd1975..3b66747f2d4ed 100644 --- a/api_docs/kbn_core_plugins_contracts_browser.mdx +++ b/api_docs/kbn_core_plugins_contracts_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-contracts-browser title: "@kbn/core-plugins-contracts-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-contracts-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-contracts-browser'] --- import kbnCorePluginsContractsBrowserObj from './kbn_core_plugins_contracts_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_contracts_server.mdx b/api_docs/kbn_core_plugins_contracts_server.mdx index b8b0f967f1650..e41edea21a8c5 100644 --- a/api_docs/kbn_core_plugins_contracts_server.mdx +++ b/api_docs/kbn_core_plugins_contracts_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-contracts-server title: "@kbn/core-plugins-contracts-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-contracts-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-contracts-server'] --- import kbnCorePluginsContractsServerObj from './kbn_core_plugins_contracts_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server.mdx b/api_docs/kbn_core_plugins_server.mdx index b40bd3812bc25..354b9a4f990c4 100644 --- a/api_docs/kbn_core_plugins_server.mdx +++ b/api_docs/kbn_core_plugins_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server title: "@kbn/core-plugins-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server'] --- import kbnCorePluginsServerObj from './kbn_core_plugins_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server_mocks.mdx b/api_docs/kbn_core_plugins_server_mocks.mdx index 45a1050700436..d5eebf4af034c 100644 --- a/api_docs/kbn_core_plugins_server_mocks.mdx +++ b/api_docs/kbn_core_plugins_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server-mocks title: "@kbn/core-plugins-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server-mocks'] --- import kbnCorePluginsServerMocksObj from './kbn_core_plugins_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index 46dc60c4f4e5a..ca57796f0f971 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index 49e8293655418..337642e6ea16b 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index b46cdc42362be..ec133d646146b 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_internal.mdx b/api_docs/kbn_core_rendering_server_internal.mdx index 99fb29cfdb435..57bda099ab792 100644 --- a/api_docs/kbn_core_rendering_server_internal.mdx +++ b/api_docs/kbn_core_rendering_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-internal title: "@kbn/core-rendering-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-internal'] --- import kbnCoreRenderingServerInternalObj from './kbn_core_rendering_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_mocks.mdx b/api_docs/kbn_core_rendering_server_mocks.mdx index 8ffb428e2dc79..b731992c54bed 100644 --- a/api_docs/kbn_core_rendering_server_mocks.mdx +++ b/api_docs/kbn_core_rendering_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-mocks title: "@kbn/core-rendering-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] --- import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_root_server_internal.mdx b/api_docs/kbn_core_root_server_internal.mdx index 45205f9146268..29c66f66f9ec3 100644 --- a/api_docs/kbn_core_root_server_internal.mdx +++ b/api_docs/kbn_core_root_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-root-server-internal title: "@kbn/core-root-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-root-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-root-server-internal'] --- import kbnCoreRootServerInternalObj from './kbn_core_root_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index bb33e6cd2d80a..ccefd672d5fa4 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index 757b619a41b2a..ea53a0fd3f792 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index ef1eb6fbcd115..886d65f570e37 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index e2a233ada34ac..67b431fa96b19 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index e0e385416ffef..e7bcf82a9681a 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index dd02401a10728..c821f124291d4 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index f338540b7f073..6a36b98acb27c 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index 050c72c726fb1..fbae58d3403f6 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 7a01b43774217..4b13be23b6f28 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index dbb8270e39021..46d9030b50217 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index a81d4a947b5a5..3e34d3c084aed 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index 5e538565c87b3..e4f046ea968fe 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index 96097521f4ada..e5bf1efb104a0 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 4048bd7ee1372..87e0add15f522 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index 063816510db3f..7a57a6b24933b 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index c97b9ad38c21b..678267c4750e7 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index d1b4591bcffb7..1f26d50974bf0 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser.mdx b/api_docs/kbn_core_security_browser.mdx index 4c9b434baf3eb..566e8dc743432 100644 --- a/api_docs/kbn_core_security_browser.mdx +++ b/api_docs/kbn_core_security_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser title: "@kbn/core-security-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser'] --- import kbnCoreSecurityBrowserObj from './kbn_core_security_browser.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser_internal.mdx b/api_docs/kbn_core_security_browser_internal.mdx index 4c7da33fd03f2..ee1e88710e631 100644 --- a/api_docs/kbn_core_security_browser_internal.mdx +++ b/api_docs/kbn_core_security_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser-internal title: "@kbn/core-security-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser-internal'] --- import kbnCoreSecurityBrowserInternalObj from './kbn_core_security_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser_mocks.devdocs.json b/api_docs/kbn_core_security_browser_mocks.devdocs.json index 9d1edf7048bca..57254746eb34f 100644 --- a/api_docs/kbn_core_security_browser_mocks.devdocs.json +++ b/api_docs/kbn_core_security_browser_mocks.devdocs.json @@ -145,6 +145,78 @@ "trackAdoption": false, "returnComment": [], "children": [] + }, + { + "parentPluginId": "@kbn/core-security-browser-mocks", + "id": "def-common.securityServiceMock.createMockAuthenticatedUser", + "type": "Function", + "tags": [], + "label": "createMockAuthenticatedUser", + "description": [], + "signature": [ + "(props?: Partial<Omit<", + { + "pluginId": "@kbn/core-security-common", + "scope": "common", + "docId": "kibKbnCoreSecurityCommonPluginApi", + "section": "def-common.AuthenticatedUser", + "text": "AuthenticatedUser" + }, + ", \"roles\"> & { roles: string[]; }>) => { username: string; enabled: boolean; email: string; full_name: string; profile_uid: string; metadata: { _reserved: boolean; _deprecated?: boolean | undefined; _deprecated_reason?: string | undefined; }; authentication_provider: ", + { + "pluginId": "@kbn/core-security-common", + "scope": "common", + "docId": "kibKbnCoreSecurityCommonPluginApi", + "section": "def-common.AuthenticationProvider", + "text": "AuthenticationProvider" + }, + "; authentication_realm: ", + { + "pluginId": "@kbn/core-security-common", + "scope": "common", + "docId": "kibKbnCoreSecurityCommonPluginApi", + "section": "def-common.UserRealm", + "text": "UserRealm" + }, + "; lookup_realm: ", + { + "pluginId": "@kbn/core-security-common", + "scope": "common", + "docId": "kibKbnCoreSecurityCommonPluginApi", + "section": "def-common.UserRealm", + "text": "UserRealm" + }, + "; authentication_type: string; elastic_cloud_user: boolean; roles: string[]; }" + ], + "path": "packages/core/security/core-security-browser-mocks/src/security_service.mock.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-security-browser-mocks", + "id": "def-common.securityServiceMock.createMockAuthenticatedUser.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "Partial<Omit<", + { + "pluginId": "@kbn/core-security-common", + "scope": "common", + "docId": "kibKbnCoreSecurityCommonPluginApi", + "section": "def-common.AuthenticatedUser", + "text": "AuthenticatedUser" + }, + ", \"roles\"> & { roles: string[]; }>" + ], + "path": "packages/core/security/core-security-browser-mocks/src/security_service.mock.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ], "initialIsOpen": false diff --git a/api_docs/kbn_core_security_browser_mocks.mdx b/api_docs/kbn_core_security_browser_mocks.mdx index 24ec8e9e1421e..9e6fa6f294a91 100644 --- a/api_docs/kbn_core_security_browser_mocks.mdx +++ b/api_docs/kbn_core_security_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser-mocks title: "@kbn/core-security-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser-mocks'] --- import kbnCoreSecurityBrowserMocksObj from './kbn_core_security_browser_mocks.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 6 | 0 | 6 | 0 | +| 8 | 0 | 8 | 0 | ## Common diff --git a/api_docs/kbn_core_security_common.mdx b/api_docs/kbn_core_security_common.mdx index 31310d27ec273..769fc9e886e2c 100644 --- a/api_docs/kbn_core_security_common.mdx +++ b/api_docs/kbn_core_security_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-common title: "@kbn/core-security-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-common'] --- import kbnCoreSecurityCommonObj from './kbn_core_security_common.devdocs.json'; diff --git a/api_docs/kbn_core_security_server.devdocs.json b/api_docs/kbn_core_security_server.devdocs.json index 849c687d7a972..fb144f1f8e2d8 100644 --- a/api_docs/kbn_core_security_server.devdocs.json +++ b/api_docs/kbn_core_security_server.devdocs.json @@ -711,6 +711,40 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/core-security-server", + "id": "def-common.CoreFipsService", + "type": "Interface", + "tags": [], + "label": "CoreFipsService", + "description": [ + "\nCore's FIPS service\n" + ], + "path": "packages/core/security/core-security-server/src/fips.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-security-server", + "id": "def-common.CoreFipsService.isEnabled", + "type": "Function", + "tags": [], + "label": "isEnabled", + "description": [ + "\nCheck if Kibana is configured to run in FIPS mode" + ], + "signature": [ + "() => boolean" + ], + "path": "packages/core/security/core-security-server/src/fips.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-security-server", "id": "def-common.CoreSecurityDelegateContract", @@ -881,6 +915,28 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-security-server", + "id": "def-common.SecurityServiceSetup.fips", + "type": "Object", + "tags": [], + "label": "fips", + "description": [ + "\nThe {@link CoreFipsService | FIPS service}" + ], + "signature": [ + { + "pluginId": "@kbn/core-security-server", + "scope": "common", + "docId": "kibKbnCoreSecurityServerPluginApi", + "section": "def-common.CoreFipsService", + "text": "CoreFipsService" + } + ], + "path": "packages/core/security/core-security-server/src/contracts.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_core_security_server.mdx b/api_docs/kbn_core_security_server.mdx index 1502df6476a25..a2933316b6a1a 100644 --- a/api_docs/kbn_core_security_server.mdx +++ b/api_docs/kbn_core_security_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server title: "@kbn/core-security-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server'] --- import kbnCoreSecurityServerObj from './kbn_core_security_server.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 49 | 0 | 16 | 0 | +| 52 | 0 | 16 | 0 | ## Common diff --git a/api_docs/kbn_core_security_server_internal.mdx b/api_docs/kbn_core_security_server_internal.mdx index 1b4b7e7182b62..7a267ca323159 100644 --- a/api_docs/kbn_core_security_server_internal.mdx +++ b/api_docs/kbn_core_security_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server-internal title: "@kbn/core-security-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server-internal'] --- import kbnCoreSecurityServerInternalObj from './kbn_core_security_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_security_server_mocks.devdocs.json b/api_docs/kbn_core_security_server_mocks.devdocs.json index c7c7f3a2d3c8a..b9910d935968b 100644 --- a/api_docs/kbn_core_security_server_mocks.devdocs.json +++ b/api_docs/kbn_core_security_server_mocks.devdocs.json @@ -265,6 +265,78 @@ "trackAdoption": false, "returnComment": [], "children": [] + }, + { + "parentPluginId": "@kbn/core-security-server-mocks", + "id": "def-common.securityServiceMock.createMockAuthenticatedUser", + "type": "Function", + "tags": [], + "label": "createMockAuthenticatedUser", + "description": [], + "signature": [ + "(props?: Partial<Omit<", + { + "pluginId": "@kbn/core-security-common", + "scope": "common", + "docId": "kibKbnCoreSecurityCommonPluginApi", + "section": "def-common.AuthenticatedUser", + "text": "AuthenticatedUser" + }, + ", \"roles\"> & { roles: string[]; }>) => { username: string; enabled: boolean; email: string; full_name: string; profile_uid: string; metadata: { _reserved: boolean; _deprecated?: boolean | undefined; _deprecated_reason?: string | undefined; }; authentication_provider: ", + { + "pluginId": "@kbn/core-security-common", + "scope": "common", + "docId": "kibKbnCoreSecurityCommonPluginApi", + "section": "def-common.AuthenticationProvider", + "text": "AuthenticationProvider" + }, + "; authentication_realm: ", + { + "pluginId": "@kbn/core-security-common", + "scope": "common", + "docId": "kibKbnCoreSecurityCommonPluginApi", + "section": "def-common.UserRealm", + "text": "UserRealm" + }, + "; lookup_realm: ", + { + "pluginId": "@kbn/core-security-common", + "scope": "common", + "docId": "kibKbnCoreSecurityCommonPluginApi", + "section": "def-common.UserRealm", + "text": "UserRealm" + }, + "; authentication_type: string; elastic_cloud_user: boolean; roles: string[]; }" + ], + "path": "packages/core/security/core-security-server-mocks/src/security_service.mock.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-security-server-mocks", + "id": "def-common.securityServiceMock.createMockAuthenticatedUser.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "Partial<Omit<", + { + "pluginId": "@kbn/core-security-common", + "scope": "common", + "docId": "kibKbnCoreSecurityCommonPluginApi", + "section": "def-common.AuthenticatedUser", + "text": "AuthenticatedUser" + }, + ", \"roles\"> & { roles: string[]; }>" + ], + "path": "packages/core/security/core-security-server-mocks/src/security_service.mock.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ], "initialIsOpen": false diff --git a/api_docs/kbn_core_security_server_mocks.mdx b/api_docs/kbn_core_security_server_mocks.mdx index 421aa38ab4d56..83337cae135db 100644 --- a/api_docs/kbn_core_security_server_mocks.mdx +++ b/api_docs/kbn_core_security_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server-mocks title: "@kbn/core-security-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server-mocks'] --- import kbnCoreSecurityServerMocksObj from './kbn_core_security_server_mocks.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 11 | 0 | 11 | 2 | +| 13 | 0 | 13 | 2 | ## Common diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 5701cd4ad73db..685a33854b781 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index 7848e60b1a93d..5a56b3e0f1a98 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common-internal'] --- import kbnCoreStatusCommonInternalObj from './kbn_core_status_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index a12fdeccd2e23..aecfa1fa13a97 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index 7d7c5c4e92326..770f48d7af00b 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index 5ec2badcdf42c..393342f415a3b 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index db4db0bc9dc6c..414a13ce96e8e 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index 7a32af6927598..9789f08a23bfe 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_kbn_server.mdx b/api_docs/kbn_core_test_helpers_kbn_server.mdx index 3e14fc547b7de..20a37bf89d604 100644 --- a/api_docs/kbn_core_test_helpers_kbn_server.mdx +++ b/api_docs/kbn_core_test_helpers_kbn_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-kbn-server title: "@kbn/core-test-helpers-kbn-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-kbn-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-kbn-server'] --- import kbnCoreTestHelpersKbnServerObj from './kbn_core_test_helpers_kbn_server.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_model_versions.mdx b/api_docs/kbn_core_test_helpers_model_versions.mdx index d0a79ba4052f0..c907092b8c664 100644 --- a/api_docs/kbn_core_test_helpers_model_versions.mdx +++ b/api_docs/kbn_core_test_helpers_model_versions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-model-versions title: "@kbn/core-test-helpers-model-versions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-model-versions plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-model-versions'] --- import kbnCoreTestHelpersModelVersionsObj from './kbn_core_test_helpers_model_versions.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx index a9bff8bc4471f..46a6ff64cb6a9 100644 --- a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx +++ b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-so-type-serializer title: "@kbn/core-test-helpers-so-type-serializer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-so-type-serializer plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-so-type-serializer'] --- import kbnCoreTestHelpersSoTypeSerializerObj from './kbn_core_test_helpers_so_type_serializer.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_test_utils.mdx b/api_docs/kbn_core_test_helpers_test_utils.mdx index 695937598f37b..d6453e81bedf6 100644 --- a/api_docs/kbn_core_test_helpers_test_utils.mdx +++ b/api_docs/kbn_core_test_helpers_test_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-test-utils title: "@kbn/core-test-helpers-test-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-test-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-test-utils'] --- import kbnCoreTestHelpersTestUtilsObj from './kbn_core_test_helpers_test_utils.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index 632f7303ad781..f12620a80eb10 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 197c645165dd0..1dac0e63f8b8e 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index 926b0430461f2..eac38de1168e0 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index 1bcb7adf0ab21..f7c5d79308e66 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index 4a7cea0edd1dc..4fc6d3591f84d 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index d3808cc8cc13f..16a7a61e9fa56 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index 1e609efe7530f..21048023efc72 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index 07c0c0aed7f81..20e494b7ffdd0 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index 021027fdff6dc..1a118ee314917 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index 0c1f2fd1e90ba..346c1ce91bc27 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index 7d3ff0274ea2b..38fd27b89466f 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index 0552b4dfaeced..c657dfa0aa014 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser.mdx b/api_docs/kbn_core_user_profile_browser.mdx index 2c9ca40490b7c..964393aad85a7 100644 --- a/api_docs/kbn_core_user_profile_browser.mdx +++ b/api_docs/kbn_core_user_profile_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser title: "@kbn/core-user-profile-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser'] --- import kbnCoreUserProfileBrowserObj from './kbn_core_user_profile_browser.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser_internal.mdx b/api_docs/kbn_core_user_profile_browser_internal.mdx index b83d1668cf9cc..794cdc788884d 100644 --- a/api_docs/kbn_core_user_profile_browser_internal.mdx +++ b/api_docs/kbn_core_user_profile_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser-internal title: "@kbn/core-user-profile-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser-internal'] --- import kbnCoreUserProfileBrowserInternalObj from './kbn_core_user_profile_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser_mocks.mdx b/api_docs/kbn_core_user_profile_browser_mocks.mdx index 75a581cf9f779..56c163157f136 100644 --- a/api_docs/kbn_core_user_profile_browser_mocks.mdx +++ b/api_docs/kbn_core_user_profile_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser-mocks title: "@kbn/core-user-profile-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser-mocks'] --- import kbnCoreUserProfileBrowserMocksObj from './kbn_core_user_profile_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_common.mdx b/api_docs/kbn_core_user_profile_common.mdx index 5f33dc71663c9..760eaa31586fa 100644 --- a/api_docs/kbn_core_user_profile_common.mdx +++ b/api_docs/kbn_core_user_profile_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-common title: "@kbn/core-user-profile-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-common'] --- import kbnCoreUserProfileCommonObj from './kbn_core_user_profile_common.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server.mdx b/api_docs/kbn_core_user_profile_server.mdx index 63a10c6a6de6f..eb36914f925b2 100644 --- a/api_docs/kbn_core_user_profile_server.mdx +++ b/api_docs/kbn_core_user_profile_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server title: "@kbn/core-user-profile-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server'] --- import kbnCoreUserProfileServerObj from './kbn_core_user_profile_server.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server_internal.mdx b/api_docs/kbn_core_user_profile_server_internal.mdx index bebfef766b07b..ad1370aa170e1 100644 --- a/api_docs/kbn_core_user_profile_server_internal.mdx +++ b/api_docs/kbn_core_user_profile_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server-internal title: "@kbn/core-user-profile-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server-internal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server-internal'] --- import kbnCoreUserProfileServerInternalObj from './kbn_core_user_profile_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server_mocks.mdx b/api_docs/kbn_core_user_profile_server_mocks.mdx index 261a00774f6da..1ac4bac549f3b 100644 --- a/api_docs/kbn_core_user_profile_server_mocks.mdx +++ b/api_docs/kbn_core_user_profile_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server-mocks title: "@kbn/core-user-profile-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server-mocks'] --- import kbnCoreUserProfileServerMocksObj from './kbn_core_user_profile_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server.mdx b/api_docs/kbn_core_user_settings_server.mdx index b4ae070885c6a..169a0647aabf6 100644 --- a/api_docs/kbn_core_user_settings_server.mdx +++ b/api_docs/kbn_core_user_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server title: "@kbn/core-user-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server'] --- import kbnCoreUserSettingsServerObj from './kbn_core_user_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server_mocks.mdx b/api_docs/kbn_core_user_settings_server_mocks.mdx index fd793595b44ab..73cba1186e524 100644 --- a/api_docs/kbn_core_user_settings_server_mocks.mdx +++ b/api_docs/kbn_core_user_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server-mocks title: "@kbn/core-user-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server-mocks'] --- import kbnCoreUserSettingsServerMocksObj from './kbn_core_user_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index 9b3da182ff894..749ad97179a01 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index e3d4ec803aaad..ae2560cc1aad5 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_custom_icons.mdx b/api_docs/kbn_custom_icons.mdx index 8c853c6a88d68..50393973868c6 100644 --- a/api_docs/kbn_custom_icons.mdx +++ b/api_docs/kbn_custom_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-custom-icons title: "@kbn/custom-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/custom-icons plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-icons'] --- import kbnCustomIconsObj from './kbn_custom_icons.devdocs.json'; diff --git a/api_docs/kbn_custom_integrations.mdx b/api_docs/kbn_custom_integrations.mdx index 0e1ae4a187848..a39f3936dbebf 100644 --- a/api_docs/kbn_custom_integrations.mdx +++ b/api_docs/kbn_custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-custom-integrations title: "@kbn/custom-integrations" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/custom-integrations plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-integrations'] --- import kbnCustomIntegrationsObj from './kbn_custom_integrations.devdocs.json'; diff --git a/api_docs/kbn_cypress_config.mdx b/api_docs/kbn_cypress_config.mdx index d35ddb7d386c6..8fb374a42b8bd 100644 --- a/api_docs/kbn_cypress_config.mdx +++ b/api_docs/kbn_cypress_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cypress-config title: "@kbn/cypress-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cypress-config plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_data_forge.mdx b/api_docs/kbn_data_forge.mdx index 01c196a135a8c..dc789949ff824 100644 --- a/api_docs/kbn_data_forge.mdx +++ b/api_docs/kbn_data_forge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-forge title: "@kbn/data-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-forge plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-forge'] --- import kbnDataForgeObj from './kbn_data_forge.devdocs.json'; diff --git a/api_docs/kbn_data_service.mdx b/api_docs/kbn_data_service.mdx index c9ecea76a09e9..4afdbb036f94f 100644 --- a/api_docs/kbn_data_service.mdx +++ b/api_docs/kbn_data_service.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-service title: "@kbn/data-service" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-service plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-service'] --- import kbnDataServiceObj from './kbn_data_service.devdocs.json'; diff --git a/api_docs/kbn_data_stream_adapter.mdx b/api_docs/kbn_data_stream_adapter.mdx index efa1e09acc72b..cab13249e5d06 100644 --- a/api_docs/kbn_data_stream_adapter.mdx +++ b/api_docs/kbn_data_stream_adapter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-stream-adapter title: "@kbn/data-stream-adapter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-stream-adapter plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-stream-adapter'] --- import kbnDataStreamAdapterObj from './kbn_data_stream_adapter.devdocs.json'; diff --git a/api_docs/kbn_data_view_utils.mdx b/api_docs/kbn_data_view_utils.mdx index b17495952f3a2..5a570e7e22266 100644 --- a/api_docs/kbn_data_view_utils.mdx +++ b/api_docs/kbn_data_view_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-view-utils title: "@kbn/data-view-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-view-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-view-utils'] --- import kbnDataViewUtilsObj from './kbn_data_view_utils.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 1f7090e39b0c5..f6b52d2c676d4 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_analytics.mdx b/api_docs/kbn_deeplinks_analytics.mdx index 7c8abba79d1a0..fd5bbf1975035 100644 --- a/api_docs/kbn_deeplinks_analytics.mdx +++ b/api_docs/kbn_deeplinks_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-analytics title: "@kbn/deeplinks-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-analytics plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-analytics'] --- import kbnDeeplinksAnalyticsObj from './kbn_deeplinks_analytics.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_devtools.mdx b/api_docs/kbn_deeplinks_devtools.mdx index c53b7b4b52246..520eca67c3143 100644 --- a/api_docs/kbn_deeplinks_devtools.mdx +++ b/api_docs/kbn_deeplinks_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-devtools title: "@kbn/deeplinks-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-devtools plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-devtools'] --- import kbnDeeplinksDevtoolsObj from './kbn_deeplinks_devtools.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_fleet.mdx b/api_docs/kbn_deeplinks_fleet.mdx index 49804a270ed55..35e68d2beaf6d 100644 --- a/api_docs/kbn_deeplinks_fleet.mdx +++ b/api_docs/kbn_deeplinks_fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-fleet title: "@kbn/deeplinks-fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-fleet plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-fleet'] --- import kbnDeeplinksFleetObj from './kbn_deeplinks_fleet.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_management.mdx b/api_docs/kbn_deeplinks_management.mdx index 5b0eaccabc916..2cde0cf4ec525 100644 --- a/api_docs/kbn_deeplinks_management.mdx +++ b/api_docs/kbn_deeplinks_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-management title: "@kbn/deeplinks-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-management plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-management'] --- import kbnDeeplinksManagementObj from './kbn_deeplinks_management.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_ml.mdx b/api_docs/kbn_deeplinks_ml.mdx index 2e30cd3b1989f..04b0505c72361 100644 --- a/api_docs/kbn_deeplinks_ml.mdx +++ b/api_docs/kbn_deeplinks_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-ml title: "@kbn/deeplinks-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-ml plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-ml'] --- import kbnDeeplinksMlObj from './kbn_deeplinks_ml.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_observability.mdx b/api_docs/kbn_deeplinks_observability.mdx index a0fe11fcdc764..e029241ae3871 100644 --- a/api_docs/kbn_deeplinks_observability.mdx +++ b/api_docs/kbn_deeplinks_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-observability title: "@kbn/deeplinks-observability" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-observability plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-observability'] --- import kbnDeeplinksObservabilityObj from './kbn_deeplinks_observability.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_search.mdx b/api_docs/kbn_deeplinks_search.mdx index 55dd85df61cc3..19b0ea9a92fa9 100644 --- a/api_docs/kbn_deeplinks_search.mdx +++ b/api_docs/kbn_deeplinks_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-search title: "@kbn/deeplinks-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-search plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-search'] --- import kbnDeeplinksSearchObj from './kbn_deeplinks_search.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_security.mdx b/api_docs/kbn_deeplinks_security.mdx index e5464429dd223..23988382e35e8 100644 --- a/api_docs/kbn_deeplinks_security.mdx +++ b/api_docs/kbn_deeplinks_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-security title: "@kbn/deeplinks-security" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-security plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-security'] --- import kbnDeeplinksSecurityObj from './kbn_deeplinks_security.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_shared.mdx b/api_docs/kbn_deeplinks_shared.mdx index 476e131ac68ef..d95beaa445827 100644 --- a/api_docs/kbn_deeplinks_shared.mdx +++ b/api_docs/kbn_deeplinks_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-shared title: "@kbn/deeplinks-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-shared plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-shared'] --- import kbnDeeplinksSharedObj from './kbn_deeplinks_shared.devdocs.json'; diff --git a/api_docs/kbn_default_nav_analytics.mdx b/api_docs/kbn_default_nav_analytics.mdx index 917357f58a7ca..63584422338de 100644 --- a/api_docs/kbn_default_nav_analytics.mdx +++ b/api_docs/kbn_default_nav_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-analytics title: "@kbn/default-nav-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-analytics plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-analytics'] --- import kbnDefaultNavAnalyticsObj from './kbn_default_nav_analytics.devdocs.json'; diff --git a/api_docs/kbn_default_nav_devtools.mdx b/api_docs/kbn_default_nav_devtools.mdx index b736232e39c5d..513e240f8c922 100644 --- a/api_docs/kbn_default_nav_devtools.mdx +++ b/api_docs/kbn_default_nav_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-devtools title: "@kbn/default-nav-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-devtools plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-devtools'] --- import kbnDefaultNavDevtoolsObj from './kbn_default_nav_devtools.devdocs.json'; diff --git a/api_docs/kbn_default_nav_management.mdx b/api_docs/kbn_default_nav_management.mdx index 2a768bf4ccf9b..504871fe7679a 100644 --- a/api_docs/kbn_default_nav_management.mdx +++ b/api_docs/kbn_default_nav_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-management title: "@kbn/default-nav-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-management plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-management'] --- import kbnDefaultNavManagementObj from './kbn_default_nav_management.devdocs.json'; diff --git a/api_docs/kbn_default_nav_ml.mdx b/api_docs/kbn_default_nav_ml.mdx index f33f42d6c0acb..d5c56e4e01df2 100644 --- a/api_docs/kbn_default_nav_ml.mdx +++ b/api_docs/kbn_default_nav_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-ml title: "@kbn/default-nav-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-ml plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-ml'] --- import kbnDefaultNavMlObj from './kbn_default_nav_ml.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index 2f61c8101ca54..b20c88d68c120 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 84504d1b9f669..0464df03f6760 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index 7dada1d331cb9..07eab992ee2d4 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index 092fe46b425bc..80755157e6039 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_discover_utils.mdx b/api_docs/kbn_discover_utils.mdx index 65eaa6d7c85f4..45ca8030b216f 100644 --- a/api_docs/kbn_discover_utils.mdx +++ b/api_docs/kbn_discover_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-discover-utils title: "@kbn/discover-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/discover-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/discover-utils'] --- import kbnDiscoverUtilsObj from './kbn_discover_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.devdocs.json b/api_docs/kbn_doc_links.devdocs.json index c2893cf2b4973..ab331022baa0b 100644 --- a/api_docs/kbn_doc_links.devdocs.json +++ b/api_docs/kbn_doc_links.devdocs.json @@ -840,7 +840,7 @@ "label": "fleet", "description": [], "signature": [ - "{ readonly beatsAgentComparison: string; readonly guide: string; readonly fleetServer: string; readonly fleetServerAddFleetServer: string; readonly esSettings: string; readonly settings: string; readonly logstashSettings: string; readonly kafkaSettings: string; readonly settingsFleetServerHostSettings: string; readonly settingsFleetServerProxySettings: string; readonly troubleshooting: string; readonly elasticAgent: string; readonly datastreams: string; readonly datastreamsILM: string; readonly datastreamsNamingScheme: string; readonly datastreamsManualRollover: string; readonly datastreamsTSDS: string; readonly datastreamsTSDSMetrics: string; readonly datastreamsDownsampling: string; readonly installElasticAgent: string; readonly installElasticAgentStandalone: string; readonly packageSignatures: string; readonly upgradeElasticAgent: string; readonly learnMoreBlog: string; readonly apiKeysLearnMore: string; readonly onPremRegistry: string; readonly secureLogstash: string; readonly agentPolicy: string; readonly api: string; readonly uninstallAgent: string; readonly installAndUninstallIntegrationAssets: string; readonly elasticAgentInputConfiguration: string; readonly policySecrets: string; readonly remoteESOoutput: string; readonly performancePresets: string; readonly scalingKubernetesResourcesAndLimits: string; readonly roleAndPrivileges: string; readonly proxiesSettings: string; readonly unprivilegedMode: string; }" + "{ readonly beatsAgentComparison: string; readonly guide: string; readonly fleetServer: string; readonly fleetServerAddFleetServer: string; readonly esSettings: string; readonly settings: string; readonly logstashSettings: string; readonly kafkaSettings: string; readonly settingsFleetServerHostSettings: string; readonly settingsFleetServerProxySettings: string; readonly troubleshooting: string; readonly elasticAgent: string; readonly datastreams: string; readonly datastreamsILM: string; readonly datastreamsNamingScheme: string; readonly datastreamsManualRollover: string; readonly datastreamsTSDS: string; readonly datastreamsTSDSMetrics: string; readonly datastreamsDownsampling: string; readonly installElasticAgent: string; readonly installElasticAgentStandalone: string; readonly grantESAccessToStandaloneAgents: string; readonly packageSignatures: string; readonly upgradeElasticAgent: string; readonly learnMoreBlog: string; readonly apiKeysLearnMore: string; readonly onPremRegistry: string; readonly secureLogstash: string; readonly agentPolicy: string; readonly api: string; readonly uninstallAgent: string; readonly installAndUninstallIntegrationAssets: string; readonly elasticAgentInputConfiguration: string; readonly policySecrets: string; readonly remoteESOoutput: string; readonly performancePresets: string; readonly scalingKubernetesResourcesAndLimits: string; readonly roleAndPrivileges: string; readonly proxiesSettings: string; readonly unprivilegedMode: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 083f16be9db7b..9ae5873cc323d 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index 73ee0f3d4ec83..f7dc1d187d423 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_dom_drag_drop.mdx b/api_docs/kbn_dom_drag_drop.mdx index 3dbef41ef957d..bbccdb23667eb 100644 --- a/api_docs/kbn_dom_drag_drop.mdx +++ b/api_docs/kbn_dom_drag_drop.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dom-drag-drop title: "@kbn/dom-drag-drop" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dom-drag-drop plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dom-drag-drop'] --- import kbnDomDragDropObj from './kbn_dom_drag_drop.devdocs.json'; diff --git a/api_docs/kbn_ebt.devdocs.json b/api_docs/kbn_ebt.devdocs.json index 0822efe4727eb..050372089dddb 100644 --- a/api_docs/kbn_ebt.devdocs.json +++ b/api_docs/kbn_ebt.devdocs.json @@ -1866,6 +1866,18 @@ "plugin": "fleet", "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_client.ts" + }, { "plugin": "elasticAssistant", "path": "x-pack/plugins/elastic_assistant/server/lib/langchain/elasticsearch_store/elasticsearch_store.ts" @@ -2050,6 +2062,26 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, { "plugin": "osquery", "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" @@ -2246,6 +2278,38 @@ "plugin": "apm", "path": "x-pack/plugins/observability_solution/apm/public/services/telemetry/telemetry_service.test.ts" }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "datasetQuality", + "path": "x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_service.test.ts" + }, { "plugin": "infra", "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" diff --git a/api_docs/kbn_ebt.mdx b/api_docs/kbn_ebt.mdx index c825c07983c0d..65ee6792ef5c3 100644 --- a/api_docs/kbn_ebt.mdx +++ b/api_docs/kbn_ebt.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt title: "@kbn/ebt" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt'] --- import kbnEbtObj from './kbn_ebt.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index 5504b223532df..b5ceb04934cbd 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_ecs_data_quality_dashboard.mdx b/api_docs/kbn_ecs_data_quality_dashboard.mdx index de4baa7563208..0d1739142ab58 100644 --- a/api_docs/kbn_ecs_data_quality_dashboard.mdx +++ b/api_docs/kbn_ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs-data-quality-dashboard title: "@kbn/ecs-data-quality-dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs-data-quality-dashboard plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs-data-quality-dashboard'] --- import kbnEcsDataQualityDashboardObj from './kbn_ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/kbn_elastic_agent_utils.mdx b/api_docs/kbn_elastic_agent_utils.mdx index 2b1e0961d56b1..c2a4481fef5bc 100644 --- a/api_docs/kbn_elastic_agent_utils.mdx +++ b/api_docs/kbn_elastic_agent_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-agent-utils title: "@kbn/elastic-agent-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-agent-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-agent-utils'] --- import kbnElasticAgentUtilsObj from './kbn_elastic_agent_utils.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant.mdx b/api_docs/kbn_elastic_assistant.mdx index 1a6ee5cce6bf5..db7825770e15f 100644 --- a/api_docs/kbn_elastic_assistant.mdx +++ b/api_docs/kbn_elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant title: "@kbn/elastic-assistant" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant'] --- import kbnElasticAssistantObj from './kbn_elastic_assistant.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant_common.mdx b/api_docs/kbn_elastic_assistant_common.mdx index 1aac5995bd622..d83bd3ed62b83 100644 --- a/api_docs/kbn_elastic_assistant_common.mdx +++ b/api_docs/kbn_elastic_assistant_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant-common title: "@kbn/elastic-assistant-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant-common'] --- import kbnElasticAssistantCommonObj from './kbn_elastic_assistant_common.devdocs.json'; diff --git a/api_docs/kbn_entities_schema.mdx b/api_docs/kbn_entities_schema.mdx index 1aef01fbd070e..0ca71f98a16ad 100644 --- a/api_docs/kbn_entities_schema.mdx +++ b/api_docs/kbn_entities_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-entities-schema title: "@kbn/entities-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/entities-schema plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/entities-schema'] --- import kbnEntitiesSchemaObj from './kbn_entities_schema.devdocs.json'; diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index d32dbbea262be..438c47828220c 100644 --- a/api_docs/kbn_es.mdx +++ b/api_docs/kbn_es.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es title: "@kbn/es" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es'] --- import kbnEsObj from './kbn_es.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index cdb919e0b9a1d..72cb9c99d0a29 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index e510651bef95a..df08983167765 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index 8d66d7c0288a0..153c2c3858508 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index 1a6d0a8a522d5..aee6c4615b633 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index e28a43619b47b..6f1e3299d5b36 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_esql_ast.mdx b/api_docs/kbn_esql_ast.mdx index 413246f2fc803..57fea63d403a8 100644 --- a/api_docs/kbn_esql_ast.mdx +++ b/api_docs/kbn_esql_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-ast title: "@kbn/esql-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-ast plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-ast'] --- import kbnEsqlAstObj from './kbn_esql_ast.devdocs.json'; diff --git a/api_docs/kbn_esql_utils.mdx b/api_docs/kbn_esql_utils.mdx index e3448bd511852..5e316273dbeb0 100644 --- a/api_docs/kbn_esql_utils.mdx +++ b/api_docs/kbn_esql_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-utils title: "@kbn/esql-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-utils'] --- import kbnEsqlUtilsObj from './kbn_esql_utils.devdocs.json'; diff --git a/api_docs/kbn_esql_validation_autocomplete.devdocs.json b/api_docs/kbn_esql_validation_autocomplete.devdocs.json index 7f6aa1393fd19..79100a05ac285 100644 --- a/api_docs/kbn_esql_validation_autocomplete.devdocs.json +++ b/api_docs/kbn_esql_validation_autocomplete.devdocs.json @@ -2075,7 +2075,7 @@ "section": "def-common.ESQLFunction", "text": "ESQLFunction" }, - ") => string" + ", useCaps: boolean) => string" ], "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", "deprecated": false, @@ -2101,6 +2101,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "@kbn/esql-validation-autocomplete", + "id": "def-common.printFunctionSignature.$2", + "type": "boolean", + "tags": [], + "label": "useCaps", + "description": [], + "signature": [ + "boolean" + ], + "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true } ], "returnComment": [], diff --git a/api_docs/kbn_esql_validation_autocomplete.mdx b/api_docs/kbn_esql_validation_autocomplete.mdx index 175ac7c66c0b8..b77450ebf4258 100644 --- a/api_docs/kbn_esql_validation_autocomplete.mdx +++ b/api_docs/kbn_esql_validation_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-validation-autocomplete title: "@kbn/esql-validation-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-validation-autocomplete plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-validation-autocomplete'] --- import kbnEsqlValidationAutocompleteObj from './kbn_esql_validation_autocomplete.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 192 | 0 | 181 | 10 | +| 193 | 0 | 182 | 10 | ## Common diff --git a/api_docs/kbn_event_annotation_common.mdx b/api_docs/kbn_event_annotation_common.mdx index e9b4d444de5e4..ffc7518189371 100644 --- a/api_docs/kbn_event_annotation_common.mdx +++ b/api_docs/kbn_event_annotation_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-common title: "@kbn/event-annotation-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/event-annotation-common'] --- import kbnEventAnnotationCommonObj from './kbn_event_annotation_common.devdocs.json'; diff --git a/api_docs/kbn_event_annotation_components.mdx b/api_docs/kbn_event_annotation_components.mdx index f60f605f36b1b..528c024c8aa0a 100644 --- a/api_docs/kbn_event_annotation_components.mdx +++ b/api_docs/kbn_event_annotation_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-components title: "@kbn/event-annotation-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-components plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/event-annotation-components'] --- import kbnEventAnnotationComponentsObj from './kbn_event_annotation_components.devdocs.json'; diff --git a/api_docs/kbn_expandable_flyout.mdx b/api_docs/kbn_expandable_flyout.mdx index da5d8766bb8f7..5f117b1966c3e 100644 --- a/api_docs/kbn_expandable_flyout.mdx +++ b/api_docs/kbn_expandable_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-expandable-flyout title: "@kbn/expandable-flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/expandable-flyout plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/expandable-flyout'] --- import kbnExpandableFlyoutObj from './kbn_expandable_flyout.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index e0440c6219bc0..f953df0a44a90 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_field_utils.mdx b/api_docs/kbn_field_utils.mdx index 130fa58f548d2..d81686dbc0573 100644 --- a/api_docs/kbn_field_utils.mdx +++ b/api_docs/kbn_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-utils title: "@kbn/field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-utils'] --- import kbnFieldUtilsObj from './kbn_field_utils.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index 2ffd0d4cd2b54..59fbf7b567355 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_formatters.mdx b/api_docs/kbn_formatters.mdx index 7e9482ff7e888..7d95880cb8654 100644 --- a/api_docs/kbn_formatters.mdx +++ b/api_docs/kbn_formatters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-formatters title: "@kbn/formatters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/formatters plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/formatters'] --- import kbnFormattersObj from './kbn_formatters.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index 30042c2823130..b4d422e907b6e 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_ui_services.mdx b/api_docs/kbn_ftr_common_functional_ui_services.mdx index 27f9ac1db1de1..5b18b97c23366 100644 --- a/api_docs/kbn_ftr_common_functional_ui_services.mdx +++ b/api_docs/kbn_ftr_common_functional_ui_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-ui-services title: "@kbn/ftr-common-functional-ui-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-ui-services plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-ui-services'] --- import kbnFtrCommonFunctionalUiServicesObj from './kbn_ftr_common_functional_ui_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index a4f5a818cc6ed..0892007482be3 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_generate_console_definitions.mdx b/api_docs/kbn_generate_console_definitions.mdx index f80a1ff1dde10..6500ff0465b73 100644 --- a/api_docs/kbn_generate_console_definitions.mdx +++ b/api_docs/kbn_generate_console_definitions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-console-definitions title: "@kbn/generate-console-definitions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-console-definitions plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-console-definitions'] --- import kbnGenerateConsoleDefinitionsObj from './kbn_generate_console_definitions.devdocs.json'; diff --git a/api_docs/kbn_generate_csv.mdx b/api_docs/kbn_generate_csv.mdx index 0bf31cb696b56..3d50747543f64 100644 --- a/api_docs/kbn_generate_csv.mdx +++ b/api_docs/kbn_generate_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv title: "@kbn/generate-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv'] --- import kbnGenerateCsvObj from './kbn_generate_csv.devdocs.json'; diff --git a/api_docs/kbn_grouping.mdx b/api_docs/kbn_grouping.mdx index 9080467cb3f6b..e8d2386f5d2fd 100644 --- a/api_docs/kbn_grouping.mdx +++ b/api_docs/kbn_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-grouping title: "@kbn/grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/grouping plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/grouping'] --- import kbnGroupingObj from './kbn_grouping.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index 3cf9467ccf87a..649beee0a2889 100644 --- a/api_docs/kbn_guided_onboarding.mdx +++ b/api_docs/kbn_guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-guided-onboarding title: "@kbn/guided-onboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/guided-onboarding plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/guided-onboarding'] --- import kbnGuidedOnboardingObj from './kbn_guided_onboarding.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index 2d92ab85fcf0e..b519e2e4281cc 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index 6c488f9190c15..8c374d967a095 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_health_gateway_server.mdx b/api_docs/kbn_health_gateway_server.mdx index 9806f4b584a84..04e1d56022171 100644 --- a/api_docs/kbn_health_gateway_server.mdx +++ b/api_docs/kbn_health_gateway_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-health-gateway-server title: "@kbn/health-gateway-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/health-gateway-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/health-gateway-server'] --- import kbnHealthGatewayServerObj from './kbn_health_gateway_server.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index 5610a4f35abd1..784601444350f 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index 3e0deaf4be6da..48d73fbf5af89 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index d2da516a8357d..a07796bf712df 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_i18n_react.mdx b/api_docs/kbn_i18n_react.mdx index a79d98659ec50..efd3c4842c2b1 100644 --- a/api_docs/kbn_i18n_react.mdx +++ b/api_docs/kbn_i18n_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n-react title: "@kbn/i18n-react" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n-react plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n-react'] --- import kbnI18nReactObj from './kbn_i18n_react.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 75d976070a87e..d5eb541a7c2fc 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_index_management.mdx b/api_docs/kbn_index_management.mdx index 5b31ba8815113..27b035d27d53f 100644 --- a/api_docs/kbn_index_management.mdx +++ b/api_docs/kbn_index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-index-management title: "@kbn/index-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/index-management plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/index-management'] --- import kbnIndexManagementObj from './kbn_index_management.devdocs.json'; diff --git a/api_docs/kbn_inference_integration_flyout.mdx b/api_docs/kbn_inference_integration_flyout.mdx index cc8763aa93920..77cf7cf089829 100644 --- a/api_docs/kbn_inference_integration_flyout.mdx +++ b/api_docs/kbn_inference_integration_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-inference_integration_flyout title: "@kbn/inference_integration_flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/inference_integration_flyout plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/inference_integration_flyout'] --- import kbnInferenceIntegrationFlyoutObj from './kbn_inference_integration_flyout.devdocs.json'; diff --git a/api_docs/kbn_infra_forge.mdx b/api_docs/kbn_infra_forge.mdx index a5172f634ed86..313d068505054 100644 --- a/api_docs/kbn_infra_forge.mdx +++ b/api_docs/kbn_infra_forge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-infra-forge title: "@kbn/infra-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/infra-forge plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/infra-forge'] --- import kbnInfraForgeObj from './kbn_infra_forge.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index d9a77cdcb4c54..27f972cb59819 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index 17e18c9d4824b..52f0d5c38e62a 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_ipynb.mdx b/api_docs/kbn_ipynb.mdx index dbe001af179d9..c5085927e80b7 100644 --- a/api_docs/kbn_ipynb.mdx +++ b/api_docs/kbn_ipynb.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ipynb title: "@kbn/ipynb" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ipynb plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ipynb'] --- import kbnIpynbObj from './kbn_ipynb.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index d68639bcbf0fe..65eb66347e9ee 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index 1dda8eb0ac23a..bb99cf93676f1 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_json_ast.mdx b/api_docs/kbn_json_ast.mdx index f9e0a883fc286..9324462fd20b0 100644 --- a/api_docs/kbn_json_ast.mdx +++ b/api_docs/kbn_json_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-ast title: "@kbn/json-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-ast plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-ast'] --- import kbnJsonAstObj from './kbn_json_ast.devdocs.json'; diff --git a/api_docs/kbn_json_schemas.mdx b/api_docs/kbn_json_schemas.mdx index 3f65818580acf..6d81142f53579 100644 --- a/api_docs/kbn_json_schemas.mdx +++ b/api_docs/kbn_json_schemas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-schemas title: "@kbn/json-schemas" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-schemas plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-schemas'] --- import kbnJsonSchemasObj from './kbn_json_schemas.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 57f00cc72aa20..1f2f3f8ef4236 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_language_documentation_popover.mdx b/api_docs/kbn_language_documentation_popover.mdx index eaede007ea7d6..35d29c0a2801a 100644 --- a/api_docs/kbn_language_documentation_popover.mdx +++ b/api_docs/kbn_language_documentation_popover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-language-documentation-popover title: "@kbn/language-documentation-popover" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/language-documentation-popover plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/language-documentation-popover'] --- import kbnLanguageDocumentationPopoverObj from './kbn_language_documentation_popover.devdocs.json'; diff --git a/api_docs/kbn_lens_embeddable_utils.mdx b/api_docs/kbn_lens_embeddable_utils.mdx index c5839e6b551c0..4a9bdd0801189 100644 --- a/api_docs/kbn_lens_embeddable_utils.mdx +++ b/api_docs/kbn_lens_embeddable_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-lens-embeddable-utils title: "@kbn/lens-embeddable-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/lens-embeddable-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-embeddable-utils'] --- import kbnLensEmbeddableUtilsObj from './kbn_lens_embeddable_utils.devdocs.json'; diff --git a/api_docs/kbn_lens_formula_docs.mdx b/api_docs/kbn_lens_formula_docs.mdx index 5d4aa9f2cafa2..a3e0f9ec87e4c 100644 --- a/api_docs/kbn_lens_formula_docs.mdx +++ b/api_docs/kbn_lens_formula_docs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-lens-formula-docs title: "@kbn/lens-formula-docs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/lens-formula-docs plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-formula-docs'] --- import kbnLensFormulaDocsObj from './kbn_lens_formula_docs.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 634387ebe7287..c0b5032a1edf4 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index 1afc4a7c21596..a7fe373bd023f 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_content_badge.mdx b/api_docs/kbn_managed_content_badge.mdx index 0d8ca5b1db1a1..96cac73b5a0d5 100644 --- a/api_docs/kbn_managed_content_badge.mdx +++ b/api_docs/kbn_managed_content_badge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-content-badge title: "@kbn/managed-content-badge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-content-badge plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-content-badge'] --- import kbnManagedContentBadgeObj from './kbn_managed_content_badge.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 786832ad85a12..f6d9d3c18e716 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_management_cards_navigation.mdx b/api_docs/kbn_management_cards_navigation.mdx index 2ec607afdf49f..d47f102babfa4 100644 --- a/api_docs/kbn_management_cards_navigation.mdx +++ b/api_docs/kbn_management_cards_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-cards-navigation title: "@kbn/management-cards-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-cards-navigation plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-cards-navigation'] --- import kbnManagementCardsNavigationObj from './kbn_management_cards_navigation.devdocs.json'; diff --git a/api_docs/kbn_management_settings_application.mdx b/api_docs/kbn_management_settings_application.mdx index 90f12142a0b1f..47065fbbed7ef 100644 --- a/api_docs/kbn_management_settings_application.mdx +++ b/api_docs/kbn_management_settings_application.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-application title: "@kbn/management-settings-application" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-application plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-application'] --- import kbnManagementSettingsApplicationObj from './kbn_management_settings_application.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_category.mdx b/api_docs/kbn_management_settings_components_field_category.mdx index 3ec5c7e8b450c..033dfccb67973 100644 --- a/api_docs/kbn_management_settings_components_field_category.mdx +++ b/api_docs/kbn_management_settings_components_field_category.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-category title: "@kbn/management-settings-components-field-category" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-category plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-category'] --- import kbnManagementSettingsComponentsFieldCategoryObj from './kbn_management_settings_components_field_category.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_input.mdx b/api_docs/kbn_management_settings_components_field_input.mdx index f86bc492e4335..d71ab4b82d177 100644 --- a/api_docs/kbn_management_settings_components_field_input.mdx +++ b/api_docs/kbn_management_settings_components_field_input.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-input title: "@kbn/management-settings-components-field-input" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-input plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-input'] --- import kbnManagementSettingsComponentsFieldInputObj from './kbn_management_settings_components_field_input.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_row.mdx b/api_docs/kbn_management_settings_components_field_row.mdx index f8cca3b521c56..ac6446ae4ff96 100644 --- a/api_docs/kbn_management_settings_components_field_row.mdx +++ b/api_docs/kbn_management_settings_components_field_row.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-row title: "@kbn/management-settings-components-field-row" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-row plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-row'] --- import kbnManagementSettingsComponentsFieldRowObj from './kbn_management_settings_components_field_row.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_form.mdx b/api_docs/kbn_management_settings_components_form.mdx index 9dbe965a3f1ff..58641d8b46c05 100644 --- a/api_docs/kbn_management_settings_components_form.mdx +++ b/api_docs/kbn_management_settings_components_form.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-form title: "@kbn/management-settings-components-form" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-form plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-form'] --- import kbnManagementSettingsComponentsFormObj from './kbn_management_settings_components_form.devdocs.json'; diff --git a/api_docs/kbn_management_settings_field_definition.mdx b/api_docs/kbn_management_settings_field_definition.mdx index 0ded710c70ec9..0db8ea19abbc7 100644 --- a/api_docs/kbn_management_settings_field_definition.mdx +++ b/api_docs/kbn_management_settings_field_definition.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-field-definition title: "@kbn/management-settings-field-definition" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-field-definition plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-field-definition'] --- import kbnManagementSettingsFieldDefinitionObj from './kbn_management_settings_field_definition.devdocs.json'; diff --git a/api_docs/kbn_management_settings_ids.devdocs.json b/api_docs/kbn_management_settings_ids.devdocs.json index d3906a812c56c..5ba648240dcd5 100644 --- a/api_docs/kbn_management_settings_ids.devdocs.json +++ b/api_docs/kbn_management_settings_ids.devdocs.json @@ -1402,6 +1402,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID", + "description": [], + "signature": [ + "\"observability:logSources\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/management-settings-ids", "id": "def-common.OBSERVABILITY_LOGS_EXPLORER_ALLOWED_DATA_VIEWS_ID", diff --git a/api_docs/kbn_management_settings_ids.mdx b/api_docs/kbn_management_settings_ids.mdx index 9d7a8baf693dc..f3f355d81bb37 100644 --- a/api_docs/kbn_management_settings_ids.mdx +++ b/api_docs/kbn_management_settings_ids.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-ids title: "@kbn/management-settings-ids" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-ids plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-ids'] --- import kbnManagementSettingsIdsObj from './kbn_management_settings_ids.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux @elastic/kibana-management](https://github.com/ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 138 | 0 | 136 | 0 | +| 139 | 0 | 137 | 0 | ## Common diff --git a/api_docs/kbn_management_settings_section_registry.mdx b/api_docs/kbn_management_settings_section_registry.mdx index e6abf44546f05..a29ae78a8cf5a 100644 --- a/api_docs/kbn_management_settings_section_registry.mdx +++ b/api_docs/kbn_management_settings_section_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-section-registry title: "@kbn/management-settings-section-registry" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-section-registry plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-section-registry'] --- import kbnManagementSettingsSectionRegistryObj from './kbn_management_settings_section_registry.devdocs.json'; diff --git a/api_docs/kbn_management_settings_types.mdx b/api_docs/kbn_management_settings_types.mdx index 4c990f8212268..bd964c3ea7246 100644 --- a/api_docs/kbn_management_settings_types.mdx +++ b/api_docs/kbn_management_settings_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-types title: "@kbn/management-settings-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-types plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-types'] --- import kbnManagementSettingsTypesObj from './kbn_management_settings_types.devdocs.json'; diff --git a/api_docs/kbn_management_settings_utilities.mdx b/api_docs/kbn_management_settings_utilities.mdx index 91d6071b83de3..4c11d0a905e5d 100644 --- a/api_docs/kbn_management_settings_utilities.mdx +++ b/api_docs/kbn_management_settings_utilities.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-utilities title: "@kbn/management-settings-utilities" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-utilities plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-utilities'] --- import kbnManagementSettingsUtilitiesObj from './kbn_management_settings_utilities.devdocs.json'; diff --git a/api_docs/kbn_management_storybook_config.mdx b/api_docs/kbn_management_storybook_config.mdx index dad4f0ec9ce32..d953811d76cf4 100644 --- a/api_docs/kbn_management_storybook_config.mdx +++ b/api_docs/kbn_management_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-storybook-config title: "@kbn/management-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-storybook-config plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-storybook-config'] --- import kbnManagementStorybookConfigObj from './kbn_management_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 6675a72e44489..3e5300df5bcd6 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_maps_vector_tile_utils.mdx b/api_docs/kbn_maps_vector_tile_utils.mdx index e2da8390c3b47..9e870d518a722 100644 --- a/api_docs/kbn_maps_vector_tile_utils.mdx +++ b/api_docs/kbn_maps_vector_tile_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-maps-vector-tile-utils title: "@kbn/maps-vector-tile-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/maps-vector-tile-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/maps-vector-tile-utils'] --- import kbnMapsVectorTileUtilsObj from './kbn_maps_vector_tile_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index a6848336371dc..8bfc053d82e7e 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_anomaly_utils.mdx b/api_docs/kbn_ml_anomaly_utils.mdx index d45ed4e2673b8..1bb6c5328ca30 100644 --- a/api_docs/kbn_ml_anomaly_utils.mdx +++ b/api_docs/kbn_ml_anomaly_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-anomaly-utils title: "@kbn/ml-anomaly-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-anomaly-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-anomaly-utils'] --- import kbnMlAnomalyUtilsObj from './kbn_ml_anomaly_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_cancellable_search.mdx b/api_docs/kbn_ml_cancellable_search.mdx index adc7f65b4813f..6f31af0bd08d4 100644 --- a/api_docs/kbn_ml_cancellable_search.mdx +++ b/api_docs/kbn_ml_cancellable_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-cancellable-search title: "@kbn/ml-cancellable-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-cancellable-search plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-cancellable-search'] --- import kbnMlCancellableSearchObj from './kbn_ml_cancellable_search.devdocs.json'; diff --git a/api_docs/kbn_ml_category_validator.mdx b/api_docs/kbn_ml_category_validator.mdx index e1a745510ebda..e8c6a57456fbc 100644 --- a/api_docs/kbn_ml_category_validator.mdx +++ b/api_docs/kbn_ml_category_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-category-validator title: "@kbn/ml-category-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-category-validator plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-category-validator'] --- import kbnMlCategoryValidatorObj from './kbn_ml_category_validator.devdocs.json'; diff --git a/api_docs/kbn_ml_chi2test.mdx b/api_docs/kbn_ml_chi2test.mdx index 0d8b7c7c15dfa..5c5586f99e6ce 100644 --- a/api_docs/kbn_ml_chi2test.mdx +++ b/api_docs/kbn_ml_chi2test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-chi2test title: "@kbn/ml-chi2test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-chi2test plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-chi2test'] --- import kbnMlChi2testObj from './kbn_ml_chi2test.devdocs.json'; diff --git a/api_docs/kbn_ml_data_frame_analytics_utils.mdx b/api_docs/kbn_ml_data_frame_analytics_utils.mdx index cd76f123b0064..8a736e6d7a5c9 100644 --- a/api_docs/kbn_ml_data_frame_analytics_utils.mdx +++ b/api_docs/kbn_ml_data_frame_analytics_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-frame-analytics-utils title: "@kbn/ml-data-frame-analytics-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-frame-analytics-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-data-frame-analytics-utils'] --- import kbnMlDataFrameAnalyticsUtilsObj from './kbn_ml_data_frame_analytics_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_data_grid.mdx b/api_docs/kbn_ml_data_grid.mdx index 98db095869699..05960891aa447 100644 --- a/api_docs/kbn_ml_data_grid.mdx +++ b/api_docs/kbn_ml_data_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-grid title: "@kbn/ml-data-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-grid plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-data-grid'] --- import kbnMlDataGridObj from './kbn_ml_data_grid.devdocs.json'; diff --git a/api_docs/kbn_ml_date_picker.mdx b/api_docs/kbn_ml_date_picker.mdx index 81e1b69e62da4..a216f6911c1a2 100644 --- a/api_docs/kbn_ml_date_picker.mdx +++ b/api_docs/kbn_ml_date_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-picker title: "@kbn/ml-date-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-picker plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-picker'] --- import kbnMlDatePickerObj from './kbn_ml_date_picker.devdocs.json'; diff --git a/api_docs/kbn_ml_date_utils.mdx b/api_docs/kbn_ml_date_utils.mdx index b236b7d6c7ff1..63a383b47542f 100644 --- a/api_docs/kbn_ml_date_utils.mdx +++ b/api_docs/kbn_ml_date_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-utils title: "@kbn/ml-date-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-utils'] --- import kbnMlDateUtilsObj from './kbn_ml_date_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_error_utils.mdx b/api_docs/kbn_ml_error_utils.mdx index acdcf1800b08d..b608650920b0d 100644 --- a/api_docs/kbn_ml_error_utils.mdx +++ b/api_docs/kbn_ml_error_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-error-utils title: "@kbn/ml-error-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-error-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-error-utils'] --- import kbnMlErrorUtilsObj from './kbn_ml_error_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_in_memory_table.mdx b/api_docs/kbn_ml_in_memory_table.mdx index a064a6cb2ffe9..05b79835f6bdb 100644 --- a/api_docs/kbn_ml_in_memory_table.mdx +++ b/api_docs/kbn_ml_in_memory_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-in-memory-table title: "@kbn/ml-in-memory-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-in-memory-table plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-in-memory-table'] --- import kbnMlInMemoryTableObj from './kbn_ml_in_memory_table.devdocs.json'; diff --git a/api_docs/kbn_ml_is_defined.mdx b/api_docs/kbn_ml_is_defined.mdx index 7c01871684839..b2a927da81261 100644 --- a/api_docs/kbn_ml_is_defined.mdx +++ b/api_docs/kbn_ml_is_defined.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-defined title: "@kbn/ml-is-defined" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-defined plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-defined'] --- import kbnMlIsDefinedObj from './kbn_ml_is_defined.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index e0be61dff3c4c..65dbafb582207 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_kibana_theme.mdx b/api_docs/kbn_ml_kibana_theme.mdx index 3e1829835be6b..9984c1e1034e0 100644 --- a/api_docs/kbn_ml_kibana_theme.mdx +++ b/api_docs/kbn_ml_kibana_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-kibana-theme title: "@kbn/ml-kibana-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-kibana-theme plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-kibana-theme'] --- import kbnMlKibanaThemeObj from './kbn_ml_kibana_theme.devdocs.json'; diff --git a/api_docs/kbn_ml_local_storage.mdx b/api_docs/kbn_ml_local_storage.mdx index d54023ab4ca2b..c8d146c813972 100644 --- a/api_docs/kbn_ml_local_storage.mdx +++ b/api_docs/kbn_ml_local_storage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-local-storage title: "@kbn/ml-local-storage" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-local-storage plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-local-storage'] --- import kbnMlLocalStorageObj from './kbn_ml_local_storage.devdocs.json'; diff --git a/api_docs/kbn_ml_nested_property.mdx b/api_docs/kbn_ml_nested_property.mdx index 6215af6a958bf..2630d91e18be8 100644 --- a/api_docs/kbn_ml_nested_property.mdx +++ b/api_docs/kbn_ml_nested_property.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-nested-property title: "@kbn/ml-nested-property" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-nested-property plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-nested-property'] --- import kbnMlNestedPropertyObj from './kbn_ml_nested_property.devdocs.json'; diff --git a/api_docs/kbn_ml_number_utils.mdx b/api_docs/kbn_ml_number_utils.mdx index 8613864038acd..2c1d004856525 100644 --- a/api_docs/kbn_ml_number_utils.mdx +++ b/api_docs/kbn_ml_number_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-number-utils title: "@kbn/ml-number-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-number-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-number-utils'] --- import kbnMlNumberUtilsObj from './kbn_ml_number_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_query_utils.devdocs.json b/api_docs/kbn_ml_query_utils.devdocs.json index b9520a8965d44..a6c6032165c6f 100644 --- a/api_docs/kbn_ml_query_utils.devdocs.json +++ b/api_docs/kbn_ml_query_utils.devdocs.json @@ -73,7 +73,7 @@ "\nBuilds the base filter criteria used in queries,\nadding criteria for the time range and an optional query.\n" ], "signature": [ - "(timeFieldName: string | undefined, earliestMs: number | undefined, latestMs: number | undefined, query: string | { [key: string]: any; } | undefined) => ", + "(timeFieldName: string | undefined, earliestMs: string | number | undefined, latestMs: string | number | undefined, query: string | { [key: string]: any; } | undefined, timeFormat: string) => ", "QueryDslQueryContainer", "[]" ], @@ -101,14 +101,14 @@ { "parentPluginId": "@kbn/ml-query-utils", "id": "def-common.buildBaseFilterCriteria.$2", - "type": "number", + "type": "CompoundType", "tags": [], "label": "earliestMs", "description": [ "- optional earliest timestamp of the selected time range" ], "signature": [ - "number | undefined" + "string | number | undefined" ], "path": "x-pack/packages/ml/query_utils/src/build_base_filter_criteria.ts", "deprecated": false, @@ -118,14 +118,14 @@ { "parentPluginId": "@kbn/ml-query-utils", "id": "def-common.buildBaseFilterCriteria.$3", - "type": "number", + "type": "CompoundType", "tags": [], "label": "latestMs", "description": [ "- optional latest timestamp of the selected time range" ], "signature": [ - "number | undefined" + "string | number | undefined" ], "path": "x-pack/packages/ml/query_utils/src/build_base_filter_criteria.ts", "deprecated": false, @@ -148,6 +148,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": false + }, + { + "parentPluginId": "@kbn/ml-query-utils", + "id": "def-common.buildBaseFilterCriteria.$5", + "type": "string", + "tags": [], + "label": "timeFormat", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/packages/ml/query_utils/src/build_base_filter_criteria.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true } ], "returnComment": [ diff --git a/api_docs/kbn_ml_query_utils.mdx b/api_docs/kbn_ml_query_utils.mdx index 695aa09af2bfb..461eeef73bd1a 100644 --- a/api_docs/kbn_ml_query_utils.mdx +++ b/api_docs/kbn_ml_query_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-query-utils title: "@kbn/ml-query-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-query-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-query-utils'] --- import kbnMlQueryUtilsObj from './kbn_ml_query_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) for questi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 28 | 0 | 0 | 0 | +| 29 | 0 | 1 | 0 | ## Common diff --git a/api_docs/kbn_ml_random_sampler_utils.mdx b/api_docs/kbn_ml_random_sampler_utils.mdx index 911b9481f6d05..a5ea521372eb1 100644 --- a/api_docs/kbn_ml_random_sampler_utils.mdx +++ b/api_docs/kbn_ml_random_sampler_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-random-sampler-utils title: "@kbn/ml-random-sampler-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-random-sampler-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-random-sampler-utils'] --- import kbnMlRandomSamplerUtilsObj from './kbn_ml_random_sampler_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_route_utils.mdx b/api_docs/kbn_ml_route_utils.mdx index 2a06d20c38a64..d01af58a86197 100644 --- a/api_docs/kbn_ml_route_utils.mdx +++ b/api_docs/kbn_ml_route_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-route-utils title: "@kbn/ml-route-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-route-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-route-utils'] --- import kbnMlRouteUtilsObj from './kbn_ml_route_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_runtime_field_utils.mdx b/api_docs/kbn_ml_runtime_field_utils.mdx index 58266a587504a..45a06e7ce9cab 100644 --- a/api_docs/kbn_ml_runtime_field_utils.mdx +++ b/api_docs/kbn_ml_runtime_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-runtime-field-utils title: "@kbn/ml-runtime-field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-runtime-field-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-runtime-field-utils'] --- import kbnMlRuntimeFieldUtilsObj from './kbn_ml_runtime_field_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 41ea694815055..a8f440d8902eb 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_ml_time_buckets.mdx b/api_docs/kbn_ml_time_buckets.mdx index 5342fc18f8440..fc2b823da6133 100644 --- a/api_docs/kbn_ml_time_buckets.mdx +++ b/api_docs/kbn_ml_time_buckets.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-time-buckets title: "@kbn/ml-time-buckets" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-time-buckets plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-time-buckets'] --- import kbnMlTimeBucketsObj from './kbn_ml_time_buckets.devdocs.json'; diff --git a/api_docs/kbn_ml_trained_models_utils.mdx b/api_docs/kbn_ml_trained_models_utils.mdx index bebd392e6322e..6baec8dcf83f6 100644 --- a/api_docs/kbn_ml_trained_models_utils.mdx +++ b/api_docs/kbn_ml_trained_models_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-trained-models-utils title: "@kbn/ml-trained-models-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-trained-models-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-trained-models-utils'] --- import kbnMlTrainedModelsUtilsObj from './kbn_ml_trained_models_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_ui_actions.mdx b/api_docs/kbn_ml_ui_actions.mdx index a4a65c5d900b4..3db8b45f6b537 100644 --- a/api_docs/kbn_ml_ui_actions.mdx +++ b/api_docs/kbn_ml_ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-ui-actions title: "@kbn/ml-ui-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-ui-actions plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-ui-actions'] --- import kbnMlUiActionsObj from './kbn_ml_ui_actions.devdocs.json'; diff --git a/api_docs/kbn_ml_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index e081864aa7d7d..e009dbce2ce36 100644 --- a/api_docs/kbn_ml_url_state.mdx +++ b/api_docs/kbn_ml_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-url-state title: "@kbn/ml-url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-url-state plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-url-state'] --- import kbnMlUrlStateObj from './kbn_ml_url_state.devdocs.json'; diff --git a/api_docs/kbn_mock_idp_utils.mdx b/api_docs/kbn_mock_idp_utils.mdx index e65f99cbbd927..478dbfb9f44ca 100644 --- a/api_docs/kbn_mock_idp_utils.mdx +++ b/api_docs/kbn_mock_idp_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mock-idp-utils title: "@kbn/mock-idp-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mock-idp-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mock-idp-utils'] --- import kbnMockIdpUtilsObj from './kbn_mock_idp_utils.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 411cfb6ad8030..aef8e252735ff 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_object_versioning.mdx b/api_docs/kbn_object_versioning.mdx index 3c6d6fe84c463..ecd3d60efb993 100644 --- a/api_docs/kbn_object_versioning.mdx +++ b/api_docs/kbn_object_versioning.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-object-versioning title: "@kbn/object-versioning" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/object-versioning plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/object-versioning'] --- import kbnObjectVersioningObj from './kbn_object_versioning.devdocs.json'; diff --git a/api_docs/kbn_observability_alert_details.mdx b/api_docs/kbn_observability_alert_details.mdx index eb731fa78dba1..851cf812d37e8 100644 --- a/api_docs/kbn_observability_alert_details.mdx +++ b/api_docs/kbn_observability_alert_details.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alert-details title: "@kbn/observability-alert-details" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alert-details plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alert-details'] --- import kbnObservabilityAlertDetailsObj from './kbn_observability_alert_details.devdocs.json'; diff --git a/api_docs/kbn_observability_alerting_test_data.mdx b/api_docs/kbn_observability_alerting_test_data.mdx index f21d5559fb680..45ba3f9c1c443 100644 --- a/api_docs/kbn_observability_alerting_test_data.mdx +++ b/api_docs/kbn_observability_alerting_test_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alerting-test-data title: "@kbn/observability-alerting-test-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alerting-test-data plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alerting-test-data'] --- import kbnObservabilityAlertingTestDataObj from './kbn_observability_alerting_test_data.devdocs.json'; diff --git a/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx b/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx index 69010ae222d23..66e300f10e800 100644 --- a/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx +++ b/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-get-padded-alert-time-range-util title: "@kbn/observability-get-padded-alert-time-range-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-get-padded-alert-time-range-util plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-get-padded-alert-time-range-util'] --- import kbnObservabilityGetPaddedAlertTimeRangeUtilObj from './kbn_observability_get_padded_alert_time_range_util.devdocs.json'; diff --git a/api_docs/kbn_openapi_bundler.mdx b/api_docs/kbn_openapi_bundler.mdx index 4fca7d45fa0ed..e408421b986f9 100644 --- a/api_docs/kbn_openapi_bundler.mdx +++ b/api_docs/kbn_openapi_bundler.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-openapi-bundler title: "@kbn/openapi-bundler" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/openapi-bundler plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/openapi-bundler'] --- import kbnOpenapiBundlerObj from './kbn_openapi_bundler.devdocs.json'; diff --git a/api_docs/kbn_openapi_generator.mdx b/api_docs/kbn_openapi_generator.mdx index 3d72748bc4f6a..884730ee4a3cb 100644 --- a/api_docs/kbn_openapi_generator.mdx +++ b/api_docs/kbn_openapi_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-openapi-generator title: "@kbn/openapi-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/openapi-generator plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/openapi-generator'] --- import kbnOpenapiGeneratorObj from './kbn_openapi_generator.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index db8c8ea35e19d..56b7e4dc7cc91 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index 3d9735b536e4b..4b1b55cd9f186 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index 04a8610638c87..0e36bbcd167c8 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_panel_loader.mdx b/api_docs/kbn_panel_loader.mdx index 41830fcc2457a..03087980bd358 100644 --- a/api_docs/kbn_panel_loader.mdx +++ b/api_docs/kbn_panel_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-panel-loader title: "@kbn/panel-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/panel-loader plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/panel-loader'] --- import kbnPanelLoaderObj from './kbn_panel_loader.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index ca830f34dc03c..c3074f7eaa102 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_check.mdx b/api_docs/kbn_plugin_check.mdx index 1d9dfa5661bc1..43789ae15f214 100644 --- a/api_docs/kbn_plugin_check.mdx +++ b/api_docs/kbn_plugin_check.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-check title: "@kbn/plugin-check" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-check plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-check'] --- import kbnPluginCheckObj from './kbn_plugin_check.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 7cf78bbea75d3..edb75d1e1e82a 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index 8b36f05605295..6f31064b762f1 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_presentation_containers.mdx b/api_docs/kbn_presentation_containers.mdx index 8cec2143e40f6..8b10dc525f5a8 100644 --- a/api_docs/kbn_presentation_containers.mdx +++ b/api_docs/kbn_presentation_containers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-presentation-containers title: "@kbn/presentation-containers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/presentation-containers plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-containers'] --- import kbnPresentationContainersObj from './kbn_presentation_containers.devdocs.json'; diff --git a/api_docs/kbn_presentation_publishing.mdx b/api_docs/kbn_presentation_publishing.mdx index 1c1af3d6c54bd..ad12beab78883 100644 --- a/api_docs/kbn_presentation_publishing.mdx +++ b/api_docs/kbn_presentation_publishing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-presentation-publishing title: "@kbn/presentation-publishing" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/presentation-publishing plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-publishing'] --- import kbnPresentationPublishingObj from './kbn_presentation_publishing.devdocs.json'; diff --git a/api_docs/kbn_profiling_utils.mdx b/api_docs/kbn_profiling_utils.mdx index f9261e8192f5c..2ed405a06d33c 100644 --- a/api_docs/kbn_profiling_utils.mdx +++ b/api_docs/kbn_profiling_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-profiling-utils title: "@kbn/profiling-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/profiling-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/profiling-utils'] --- import kbnProfilingUtilsObj from './kbn_profiling_utils.devdocs.json'; diff --git a/api_docs/kbn_random_sampling.mdx b/api_docs/kbn_random_sampling.mdx index 34aadc7bb5f62..5192cdabb4071 100644 --- a/api_docs/kbn_random_sampling.mdx +++ b/api_docs/kbn_random_sampling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-random-sampling title: "@kbn/random-sampling" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/random-sampling plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/random-sampling'] --- import kbnRandomSamplingObj from './kbn_random_sampling.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index 8d9c4c1c67a01..bd228eda5c725 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_react_hooks.mdx b/api_docs/kbn_react_hooks.mdx index 9e0d267563e7c..26ff523da5260 100644 --- a/api_docs/kbn_react_hooks.mdx +++ b/api_docs/kbn_react_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-hooks title: "@kbn/react-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-hooks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-hooks'] --- import kbnReactHooksObj from './kbn_react_hooks.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_common.mdx b/api_docs/kbn_react_kibana_context_common.mdx index f522a514efaec..567b280bd6126 100644 --- a/api_docs/kbn_react_kibana_context_common.mdx +++ b/api_docs/kbn_react_kibana_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-common title: "@kbn/react-kibana-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-common'] --- import kbnReactKibanaContextCommonObj from './kbn_react_kibana_context_common.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_render.mdx b/api_docs/kbn_react_kibana_context_render.mdx index d4ce9e5890055..4de620ca7126b 100644 --- a/api_docs/kbn_react_kibana_context_render.mdx +++ b/api_docs/kbn_react_kibana_context_render.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-render title: "@kbn/react-kibana-context-render" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-render plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-render'] --- import kbnReactKibanaContextRenderObj from './kbn_react_kibana_context_render.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_root.mdx b/api_docs/kbn_react_kibana_context_root.mdx index b8ef18f7f1668..4bbb9a7d767e7 100644 --- a/api_docs/kbn_react_kibana_context_root.mdx +++ b/api_docs/kbn_react_kibana_context_root.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-root title: "@kbn/react-kibana-context-root" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-root plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-root'] --- import kbnReactKibanaContextRootObj from './kbn_react_kibana_context_root.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_styled.mdx b/api_docs/kbn_react_kibana_context_styled.mdx index 571d292c4bc01..3c19e998928b1 100644 --- a/api_docs/kbn_react_kibana_context_styled.mdx +++ b/api_docs/kbn_react_kibana_context_styled.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-styled title: "@kbn/react-kibana-context-styled" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-styled plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-styled'] --- import kbnReactKibanaContextStyledObj from './kbn_react_kibana_context_styled.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_theme.mdx b/api_docs/kbn_react_kibana_context_theme.mdx index e2679bcdff842..be9cb70479267 100644 --- a/api_docs/kbn_react_kibana_context_theme.mdx +++ b/api_docs/kbn_react_kibana_context_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-theme title: "@kbn/react-kibana-context-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-theme plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-theme'] --- import kbnReactKibanaContextThemeObj from './kbn_react_kibana_context_theme.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_mount.mdx b/api_docs/kbn_react_kibana_mount.mdx index ceb70a466aaeb..729542daa919a 100644 --- a/api_docs/kbn_react_kibana_mount.mdx +++ b/api_docs/kbn_react_kibana_mount.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-mount title: "@kbn/react-kibana-mount" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-mount plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-mount'] --- import kbnReactKibanaMountObj from './kbn_react_kibana_mount.devdocs.json'; diff --git a/api_docs/kbn_repo_file_maps.mdx b/api_docs/kbn_repo_file_maps.mdx index 6eba78e7e2946..0c20156994099 100644 --- a/api_docs/kbn_repo_file_maps.mdx +++ b/api_docs/kbn_repo_file_maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-file-maps title: "@kbn/repo-file-maps" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-file-maps plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-file-maps'] --- import kbnRepoFileMapsObj from './kbn_repo_file_maps.devdocs.json'; diff --git a/api_docs/kbn_repo_linter.mdx b/api_docs/kbn_repo_linter.mdx index 310c814093ea2..36a26cda8981b 100644 --- a/api_docs/kbn_repo_linter.mdx +++ b/api_docs/kbn_repo_linter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-linter title: "@kbn/repo-linter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-linter plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-linter'] --- import kbnRepoLinterObj from './kbn_repo_linter.devdocs.json'; diff --git a/api_docs/kbn_repo_path.mdx b/api_docs/kbn_repo_path.mdx index b215725392c20..0987bf58a3840 100644 --- a/api_docs/kbn_repo_path.mdx +++ b/api_docs/kbn_repo_path.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-path title: "@kbn/repo-path" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-path plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-path'] --- import kbnRepoPathObj from './kbn_repo_path.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index 05fcee61ee529..b75e41ed1da64 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_reporting_common.mdx b/api_docs/kbn_reporting_common.mdx index c7bf84cfcabe2..0493a0654047f 100644 --- a/api_docs/kbn_reporting_common.mdx +++ b/api_docs/kbn_reporting_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-common title: "@kbn/reporting-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-common'] --- import kbnReportingCommonObj from './kbn_reporting_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_csv_share_panel.mdx b/api_docs/kbn_reporting_csv_share_panel.mdx index cc7b5862185eb..33ec3ad3b5041 100644 --- a/api_docs/kbn_reporting_csv_share_panel.mdx +++ b/api_docs/kbn_reporting_csv_share_panel.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-csv-share-panel title: "@kbn/reporting-csv-share-panel" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-csv-share-panel plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-csv-share-panel'] --- import kbnReportingCsvSharePanelObj from './kbn_reporting_csv_share_panel.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_csv.mdx b/api_docs/kbn_reporting_export_types_csv.mdx index c8d800083c9f4..f682fc7b3c594 100644 --- a/api_docs/kbn_reporting_export_types_csv.mdx +++ b/api_docs/kbn_reporting_export_types_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-csv title: "@kbn/reporting-export-types-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-csv plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-csv'] --- import kbnReportingExportTypesCsvObj from './kbn_reporting_export_types_csv.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_csv_common.mdx b/api_docs/kbn_reporting_export_types_csv_common.mdx index a1b3a76a954bf..1bef19942311d 100644 --- a/api_docs/kbn_reporting_export_types_csv_common.mdx +++ b/api_docs/kbn_reporting_export_types_csv_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-csv-common title: "@kbn/reporting-export-types-csv-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-csv-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-csv-common'] --- import kbnReportingExportTypesCsvCommonObj from './kbn_reporting_export_types_csv_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_pdf.mdx b/api_docs/kbn_reporting_export_types_pdf.mdx index e99dcb198d898..502518454da6f 100644 --- a/api_docs/kbn_reporting_export_types_pdf.mdx +++ b/api_docs/kbn_reporting_export_types_pdf.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-pdf title: "@kbn/reporting-export-types-pdf" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-pdf plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-pdf'] --- import kbnReportingExportTypesPdfObj from './kbn_reporting_export_types_pdf.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_pdf_common.mdx b/api_docs/kbn_reporting_export_types_pdf_common.mdx index 01668db194698..80be734322907 100644 --- a/api_docs/kbn_reporting_export_types_pdf_common.mdx +++ b/api_docs/kbn_reporting_export_types_pdf_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-pdf-common title: "@kbn/reporting-export-types-pdf-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-pdf-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-pdf-common'] --- import kbnReportingExportTypesPdfCommonObj from './kbn_reporting_export_types_pdf_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_png.mdx b/api_docs/kbn_reporting_export_types_png.mdx index 7f6442264e231..c4dcd47eaa823 100644 --- a/api_docs/kbn_reporting_export_types_png.mdx +++ b/api_docs/kbn_reporting_export_types_png.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-png title: "@kbn/reporting-export-types-png" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-png plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-png'] --- import kbnReportingExportTypesPngObj from './kbn_reporting_export_types_png.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_png_common.mdx b/api_docs/kbn_reporting_export_types_png_common.mdx index a27cedc9dc97c..2f39a6818e8db 100644 --- a/api_docs/kbn_reporting_export_types_png_common.mdx +++ b/api_docs/kbn_reporting_export_types_png_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-png-common title: "@kbn/reporting-export-types-png-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-png-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-png-common'] --- import kbnReportingExportTypesPngCommonObj from './kbn_reporting_export_types_png_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_mocks_server.mdx b/api_docs/kbn_reporting_mocks_server.mdx index b41b3b3fceefd..f2509878b9978 100644 --- a/api_docs/kbn_reporting_mocks_server.mdx +++ b/api_docs/kbn_reporting_mocks_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-mocks-server title: "@kbn/reporting-mocks-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-mocks-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-mocks-server'] --- import kbnReportingMocksServerObj from './kbn_reporting_mocks_server.devdocs.json'; diff --git a/api_docs/kbn_reporting_public.mdx b/api_docs/kbn_reporting_public.mdx index 331c3bdc59355..83e9bad0064d7 100644 --- a/api_docs/kbn_reporting_public.mdx +++ b/api_docs/kbn_reporting_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-public title: "@kbn/reporting-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-public plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-public'] --- import kbnReportingPublicObj from './kbn_reporting_public.devdocs.json'; diff --git a/api_docs/kbn_reporting_server.mdx b/api_docs/kbn_reporting_server.mdx index bf71961fd72bd..99b14dca48327 100644 --- a/api_docs/kbn_reporting_server.mdx +++ b/api_docs/kbn_reporting_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-server title: "@kbn/reporting-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-server'] --- import kbnReportingServerObj from './kbn_reporting_server.devdocs.json'; diff --git a/api_docs/kbn_resizable_layout.mdx b/api_docs/kbn_resizable_layout.mdx index 8c62673244bfd..ff1c04bc11037 100644 --- a/api_docs/kbn_resizable_layout.mdx +++ b/api_docs/kbn_resizable_layout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-resizable-layout title: "@kbn/resizable-layout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/resizable-layout plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/resizable-layout'] --- import kbnResizableLayoutObj from './kbn_resizable_layout.devdocs.json'; diff --git a/api_docs/kbn_response_ops_feature_flag_service.mdx b/api_docs/kbn_response_ops_feature_flag_service.mdx index 6a3cf64a706f0..459809dd7a9f8 100644 --- a/api_docs/kbn_response_ops_feature_flag_service.mdx +++ b/api_docs/kbn_response_ops_feature_flag_service.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-response-ops-feature-flag-service title: "@kbn/response-ops-feature-flag-service" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/response-ops-feature-flag-service plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/response-ops-feature-flag-service'] --- import kbnResponseOpsFeatureFlagServiceObj from './kbn_response_ops_feature_flag_service.devdocs.json'; diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index 598261b79f837..d34c783b0bc9b 100644 --- a/api_docs/kbn_rison.mdx +++ b/api_docs/kbn_rison.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rison title: "@kbn/rison" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rison plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rollup.mdx b/api_docs/kbn_rollup.mdx index b2df9fd449dfa..b8a97d4d8ab80 100644 --- a/api_docs/kbn_rollup.mdx +++ b/api_docs/kbn_rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rollup title: "@kbn/rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rollup plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rollup'] --- import kbnRollupObj from './kbn_rollup.devdocs.json'; diff --git a/api_docs/kbn_router_to_openapispec.mdx b/api_docs/kbn_router_to_openapispec.mdx index 2d2165346c3df..8d51b904496ff 100644 --- a/api_docs/kbn_router_to_openapispec.mdx +++ b/api_docs/kbn_router_to_openapispec.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-router-to-openapispec title: "@kbn/router-to-openapispec" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/router-to-openapispec plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/router-to-openapispec'] --- import kbnRouterToOpenapispecObj from './kbn_router_to_openapispec.devdocs.json'; diff --git a/api_docs/kbn_router_utils.mdx b/api_docs/kbn_router_utils.mdx index 7a3e7b56f5f91..5d05471d8ae00 100644 --- a/api_docs/kbn_router_utils.mdx +++ b/api_docs/kbn_router_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-router-utils title: "@kbn/router-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/router-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/router-utils'] --- import kbnRouterUtilsObj from './kbn_router_utils.devdocs.json'; diff --git a/api_docs/kbn_rrule.mdx b/api_docs/kbn_rrule.mdx index c014f687e5888..031fae15c6e94 100644 --- a/api_docs/kbn_rrule.mdx +++ b/api_docs/kbn_rrule.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rrule title: "@kbn/rrule" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rrule plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rrule'] --- import kbnRruleObj from './kbn_rrule.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 71b55e87407f8..8842a1cd368eb 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_saved_objects_settings.mdx b/api_docs/kbn_saved_objects_settings.mdx index f11343d16b399..e4c2ec48edeb5 100644 --- a/api_docs/kbn_saved_objects_settings.mdx +++ b/api_docs/kbn_saved_objects_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-saved-objects-settings title: "@kbn/saved-objects-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/saved-objects-settings plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/saved-objects-settings'] --- import kbnSavedObjectsSettingsObj from './kbn_saved_objects_settings.devdocs.json'; diff --git a/api_docs/kbn_search_api_panels.devdocs.json b/api_docs/kbn_search_api_panels.devdocs.json index 3e17ad34f9985..60f8ab7069ab2 100644 --- a/api_docs/kbn_search_api_panels.devdocs.json +++ b/api_docs/kbn_search_api_panels.devdocs.json @@ -74,7 +74,7 @@ "label": "CodeBox", "description": [], "signature": [ - "({ application, codeSnippet, consolePlugin, languageType, languages, assetBasePath, selectedLanguage, setSelectedLanguage, sharePlugin, consoleRequest, }: React.PropsWithChildren<CodeBoxProps>) => JSX.Element" + "({ application, codeSnippet, consolePlugin, languageType, languages, assetBasePath, selectedLanguage, setSelectedLanguage, sharePlugin, consoleRequest, showTopBar, }: React.PropsWithChildren<CodeBoxProps>) => JSX.Element" ], "path": "packages/kbn-search-api-panels/components/code_box.tsx", "deprecated": false, @@ -85,7 +85,7 @@ "id": "def-common.CodeBox.$1", "type": "CompoundType", "tags": [], - "label": "{\n application,\n codeSnippet,\n consolePlugin,\n languageType,\n languages,\n assetBasePath,\n selectedLanguage,\n setSelectedLanguage,\n sharePlugin,\n consoleRequest,\n}", + "label": "{\n application,\n codeSnippet,\n consolePlugin,\n languageType,\n languages,\n assetBasePath,\n selectedLanguage,\n setSelectedLanguage,\n sharePlugin,\n consoleRequest,\n showTopBar = true,\n}", "description": [], "signature": [ "React.PropsWithChildren<CodeBoxProps>" diff --git a/api_docs/kbn_search_api_panels.mdx b/api_docs/kbn_search_api_panels.mdx index b1161981b1c69..5062a76e103ba 100644 --- a/api_docs/kbn_search_api_panels.mdx +++ b/api_docs/kbn_search_api_panels.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-api-panels title: "@kbn/search-api-panels" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-api-panels plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-api-panels'] --- import kbnSearchApiPanelsObj from './kbn_search_api_panels.devdocs.json'; diff --git a/api_docs/kbn_search_connectors.mdx b/api_docs/kbn_search_connectors.mdx index 600b8201eb06f..91299fe5ae53c 100644 --- a/api_docs/kbn_search_connectors.mdx +++ b/api_docs/kbn_search_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-connectors title: "@kbn/search-connectors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-connectors plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-connectors'] --- import kbnSearchConnectorsObj from './kbn_search_connectors.devdocs.json'; diff --git a/api_docs/kbn_search_errors.mdx b/api_docs/kbn_search_errors.mdx index 41c4dff6928f4..0461c1520e843 100644 --- a/api_docs/kbn_search_errors.mdx +++ b/api_docs/kbn_search_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-errors title: "@kbn/search-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-errors plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-errors'] --- import kbnSearchErrorsObj from './kbn_search_errors.devdocs.json'; diff --git a/api_docs/kbn_search_index_documents.mdx b/api_docs/kbn_search_index_documents.mdx index 3440d6365f165..a5a0b1f9b4ef7 100644 --- a/api_docs/kbn_search_index_documents.mdx +++ b/api_docs/kbn_search_index_documents.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-index-documents title: "@kbn/search-index-documents" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-index-documents plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-index-documents'] --- import kbnSearchIndexDocumentsObj from './kbn_search_index_documents.devdocs.json'; diff --git a/api_docs/kbn_search_response_warnings.mdx b/api_docs/kbn_search_response_warnings.mdx index 827729ecde166..900835b0f36ad 100644 --- a/api_docs/kbn_search_response_warnings.mdx +++ b/api_docs/kbn_search_response_warnings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-response-warnings title: "@kbn/search-response-warnings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-response-warnings plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-response-warnings'] --- import kbnSearchResponseWarningsObj from './kbn_search_response_warnings.devdocs.json'; diff --git a/api_docs/kbn_search_types.mdx b/api_docs/kbn_search_types.mdx index fe310c8364e78..1f9a6c71a1d2a 100644 --- a/api_docs/kbn_search_types.mdx +++ b/api_docs/kbn_search_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-types title: "@kbn/search-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-types plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-types'] --- import kbnSearchTypesObj from './kbn_search_types.devdocs.json'; diff --git a/api_docs/kbn_security_api_key_management.mdx b/api_docs/kbn_security_api_key_management.mdx index 3464c269a5d7e..1b224c3371e68 100644 --- a/api_docs/kbn_security_api_key_management.mdx +++ b/api_docs/kbn_security_api_key_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-api-key-management title: "@kbn/security-api-key-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-api-key-management plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-api-key-management'] --- import kbnSecurityApiKeyManagementObj from './kbn_security_api_key_management.devdocs.json'; diff --git a/api_docs/kbn_security_form_components.mdx b/api_docs/kbn_security_form_components.mdx index e987fc5ad75eb..298ac8a70b040 100644 --- a/api_docs/kbn_security_form_components.mdx +++ b/api_docs/kbn_security_form_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-form-components title: "@kbn/security-form-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-form-components plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-form-components'] --- import kbnSecurityFormComponentsObj from './kbn_security_form_components.devdocs.json'; diff --git a/api_docs/kbn_security_hardening.mdx b/api_docs/kbn_security_hardening.mdx index 1db9d3199e3c0..dc9fe920f2087 100644 --- a/api_docs/kbn_security_hardening.mdx +++ b/api_docs/kbn_security_hardening.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-hardening title: "@kbn/security-hardening" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-hardening plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-hardening'] --- import kbnSecurityHardeningObj from './kbn_security_hardening.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_common.devdocs.json b/api_docs/kbn_security_plugin_types_common.devdocs.json index 4bb4d0397c70f..c19a34122d799 100644 --- a/api_docs/kbn_security_plugin_types_common.devdocs.json +++ b/api_docs/kbn_security_plugin_types_common.devdocs.json @@ -1057,6 +1057,22 @@ "children": [], "returnComment": [] }, + { + "parentPluginId": "@kbn/security-plugin-types-common", + "id": "def-common.SecurityLicense.getLicenseType", + "type": "Function", + "tags": [], + "label": "getLicenseType", + "description": [], + "signature": [ + "() => string | undefined" + ], + "path": "x-pack/packages/security/plugin_types_common/src/licensing/license.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "@kbn/security-plugin-types-common", "id": "def-common.SecurityLicense.getUnavailableReason", @@ -1374,6 +1390,19 @@ "path": "x-pack/packages/security/plugin_types_common/src/licensing/license_features.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/security-plugin-types-common", + "id": "def-common.SecurityLicenseFeatures.allowFips", + "type": "boolean", + "tags": [], + "label": "allowFips", + "description": [ + "\nIndicates whether we allow FIPS mode" + ], + "path": "x-pack/packages/security/plugin_types_common/src/licensing/license_features.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_security_plugin_types_common.mdx b/api_docs/kbn_security_plugin_types_common.mdx index 65bba991c5486..546a2f59a373b 100644 --- a/api_docs/kbn_security_plugin_types_common.mdx +++ b/api_docs/kbn_security_plugin_types_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-common title: "@kbn/security-plugin-types-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-common plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-common'] --- import kbnSecurityPluginTypesCommonObj from './kbn_security_plugin_types_common.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 116 | 0 | 58 | 0 | +| 118 | 0 | 59 | 0 | ## Common diff --git a/api_docs/kbn_security_plugin_types_public.mdx b/api_docs/kbn_security_plugin_types_public.mdx index 80416ce94c0b1..da6569217c17e 100644 --- a/api_docs/kbn_security_plugin_types_public.mdx +++ b/api_docs/kbn_security_plugin_types_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-public title: "@kbn/security-plugin-types-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-public plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-public'] --- import kbnSecurityPluginTypesPublicObj from './kbn_security_plugin_types_public.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_server.devdocs.json b/api_docs/kbn_security_plugin_types_server.devdocs.json index 724814846da59..6f72da16b03a8 100644 --- a/api_docs/kbn_security_plugin_types_server.devdocs.json +++ b/api_docs/kbn_security_plugin_types_server.devdocs.json @@ -3232,10 +3232,6 @@ "plugin": "security", "path": "x-pack/plugins/security/server/plugin.ts" }, - { - "plugin": "actions", - "path": "x-pack/plugins/actions/server/lib/action_executor.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/rules_client_factory.ts" @@ -3284,10 +3280,6 @@ "plugin": "enterpriseSearch", "path": "x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts" }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/get_user.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts" diff --git a/api_docs/kbn_security_plugin_types_server.mdx b/api_docs/kbn_security_plugin_types_server.mdx index f05bcb67cd468..a185786c79fed 100644 --- a/api_docs/kbn_security_plugin_types_server.mdx +++ b/api_docs/kbn_security_plugin_types_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-server title: "@kbn/security-plugin-types-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-server plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-server'] --- import kbnSecurityPluginTypesServerObj from './kbn_security_plugin_types_server.devdocs.json'; diff --git a/api_docs/kbn_security_solution_features.mdx b/api_docs/kbn_security_solution_features.mdx index a1d800cef4edc..c7f563d0cca76 100644 --- a/api_docs/kbn_security_solution_features.mdx +++ b/api_docs/kbn_security_solution_features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-features title: "@kbn/security-solution-features" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-features plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-features'] --- import kbnSecuritySolutionFeaturesObj from './kbn_security_solution_features.devdocs.json'; diff --git a/api_docs/kbn_security_solution_navigation.mdx b/api_docs/kbn_security_solution_navigation.mdx index 1b1c587ee2cf1..023086a20654d 100644 --- a/api_docs/kbn_security_solution_navigation.mdx +++ b/api_docs/kbn_security_solution_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-navigation title: "@kbn/security-solution-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-navigation plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-navigation'] --- import kbnSecuritySolutionNavigationObj from './kbn_security_solution_navigation.devdocs.json'; diff --git a/api_docs/kbn_security_solution_side_nav.mdx b/api_docs/kbn_security_solution_side_nav.mdx index cdaecdd194ff5..187bae5484852 100644 --- a/api_docs/kbn_security_solution_side_nav.mdx +++ b/api_docs/kbn_security_solution_side_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-side-nav title: "@kbn/security-solution-side-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-side-nav plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-side-nav'] --- import kbnSecuritySolutionSideNavObj from './kbn_security_solution_side_nav.devdocs.json'; diff --git a/api_docs/kbn_security_solution_storybook_config.mdx b/api_docs/kbn_security_solution_storybook_config.mdx index 18ee9c6da6431..ce84e647f99a2 100644 --- a/api_docs/kbn_security_solution_storybook_config.mdx +++ b/api_docs/kbn_security_solution_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-storybook-config title: "@kbn/security-solution-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-storybook-config plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-storybook-config'] --- import kbnSecuritySolutionStorybookConfigObj from './kbn_security_solution_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index d8fd466416d3f..9eaafc81de915 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_data_table.mdx b/api_docs/kbn_securitysolution_data_table.mdx index 17715c970c71c..5d4411ca65892 100644 --- a/api_docs/kbn_securitysolution_data_table.mdx +++ b/api_docs/kbn_securitysolution_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-data-table title: "@kbn/securitysolution-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-data-table plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-data-table'] --- import kbnSecuritysolutionDataTableObj from './kbn_securitysolution_data_table.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_ecs.mdx b/api_docs/kbn_securitysolution_ecs.mdx index 056f5e5c86802..e183dc0842109 100644 --- a/api_docs/kbn_securitysolution_ecs.mdx +++ b/api_docs/kbn_securitysolution_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-ecs title: "@kbn/securitysolution-ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-ecs plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-ecs'] --- import kbnSecuritysolutionEcsObj from './kbn_securitysolution_ecs.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index e5bc0d0869a3a..d1b1ccfffb010 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index 919547a1e8421..e5e0aa4dd0940 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.mdx +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components title: "@kbn/securitysolution-exception-list-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-exception-list-components plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index abb89f2c33f94..b9d501c2caed5 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index 758a3ef4d5be8..decddf42c6bf9 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 7aff2873fb994..e0c62d30a62ce 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index ad6ed32459978..9763ddde3f712 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index ec08d5c21eb34..20fd2d45a4742 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index fab456836306e..c8a273313ad9c 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index 3c88afc07a82b..68255d78ad849 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index 478bee62116c4..fb26309cd0bce 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index 354168a7829ea..ae6721015cd00 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index cac01d08bc5f7..0f2a1d7b1718f 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index b48e0338fc24d..6ff2a8cca68a0 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index 5b0b418157eaa..4d6cbec4736ec 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index bbf0204f4b049..f8a78bc9995a3 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index 92b1e3d21dece..4735d2cfb3644 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_serverless_common_settings.mdx b/api_docs/kbn_serverless_common_settings.mdx index d4aca879adf56..d71630c8b1aee 100644 --- a/api_docs/kbn_serverless_common_settings.mdx +++ b/api_docs/kbn_serverless_common_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-common-settings title: "@kbn/serverless-common-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-common-settings plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-common-settings'] --- import kbnServerlessCommonSettingsObj from './kbn_serverless_common_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_observability_settings.mdx b/api_docs/kbn_serverless_observability_settings.mdx index 810d5623c1b63..024160d40bd2a 100644 --- a/api_docs/kbn_serverless_observability_settings.mdx +++ b/api_docs/kbn_serverless_observability_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-observability-settings title: "@kbn/serverless-observability-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-observability-settings plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-observability-settings'] --- import kbnServerlessObservabilitySettingsObj from './kbn_serverless_observability_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_project_switcher.mdx b/api_docs/kbn_serverless_project_switcher.mdx index 2b4c7d69f0e36..46ac7d9eaff33 100644 --- a/api_docs/kbn_serverless_project_switcher.mdx +++ b/api_docs/kbn_serverless_project_switcher.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-project-switcher title: "@kbn/serverless-project-switcher" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-project-switcher plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-project-switcher'] --- import kbnServerlessProjectSwitcherObj from './kbn_serverless_project_switcher.devdocs.json'; diff --git a/api_docs/kbn_serverless_search_settings.mdx b/api_docs/kbn_serverless_search_settings.mdx index be11453ea43f1..a592e4dd4c115 100644 --- a/api_docs/kbn_serverless_search_settings.mdx +++ b/api_docs/kbn_serverless_search_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-search-settings title: "@kbn/serverless-search-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-search-settings plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-search-settings'] --- import kbnServerlessSearchSettingsObj from './kbn_serverless_search_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_security_settings.mdx b/api_docs/kbn_serverless_security_settings.mdx index 365a36814770a..41cfd4295cff4 100644 --- a/api_docs/kbn_serverless_security_settings.mdx +++ b/api_docs/kbn_serverless_security_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-security-settings title: "@kbn/serverless-security-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-security-settings plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-security-settings'] --- import kbnServerlessSecuritySettingsObj from './kbn_serverless_security_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_storybook_config.mdx b/api_docs/kbn_serverless_storybook_config.mdx index 04a3b1a9bdf91..97c457192ece9 100644 --- a/api_docs/kbn_serverless_storybook_config.mdx +++ b/api_docs/kbn_serverless_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-storybook-config title: "@kbn/serverless-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-storybook-config plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-storybook-config'] --- import kbnServerlessStorybookConfigObj from './kbn_serverless_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index b4135a065c1ae..04dd5b1cc4505 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_solution.mdx b/api_docs/kbn_shared_ux_avatar_solution.mdx index 2166bb29b5f22..d98839c0d9c58 100644 --- a/api_docs/kbn_shared_ux_avatar_solution.mdx +++ b/api_docs/kbn_shared_ux_avatar_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-solution title: "@kbn/shared-ux-avatar-solution" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-solution plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-solution'] --- import kbnSharedUxAvatarSolutionObj from './kbn_shared_ux_avatar_solution.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx index bebb4766635c9..39e5c8490b918 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen title: "@kbn/shared-ux-button-exit-full-screen" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen'] --- import kbnSharedUxButtonExitFullScreenObj from './kbn_shared_ux_button_exit_full_screen.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 9e5e5ab07701d..909f32b4c9166 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index f79843c558f11..5b066bf34ce13 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index 36a16afc5954e..9992ca5d5fc24 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_chrome_navigation.mdx b/api_docs/kbn_shared_ux_chrome_navigation.mdx index f12f1e82638bd..5ddbbb5b6eb7e 100644 --- a/api_docs/kbn_shared_ux_chrome_navigation.mdx +++ b/api_docs/kbn_shared_ux_chrome_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-chrome-navigation title: "@kbn/shared-ux-chrome-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-chrome-navigation plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-chrome-navigation'] --- import kbnSharedUxChromeNavigationObj from './kbn_shared_ux_chrome_navigation.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_error_boundary.mdx b/api_docs/kbn_shared_ux_error_boundary.mdx index 2dc08494e8c79..b7f25c9d9c4a9 100644 --- a/api_docs/kbn_shared_ux_error_boundary.mdx +++ b/api_docs/kbn_shared_ux_error_boundary.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-error-boundary title: "@kbn/shared-ux-error-boundary" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-error-boundary plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-error-boundary'] --- import kbnSharedUxErrorBoundaryObj from './kbn_shared_ux_error_boundary.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_context.mdx b/api_docs/kbn_shared_ux_file_context.mdx index bb70deb67014b..3b01fb8778555 100644 --- a/api_docs/kbn_shared_ux_file_context.mdx +++ b/api_docs/kbn_shared_ux_file_context.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-context title: "@kbn/shared-ux-file-context" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-context plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-context'] --- import kbnSharedUxFileContextObj from './kbn_shared_ux_file_context.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image.mdx b/api_docs/kbn_shared_ux_file_image.mdx index aa6dd14ac8c8b..a46ca1c0e34ff 100644 --- a/api_docs/kbn_shared_ux_file_image.mdx +++ b/api_docs/kbn_shared_ux_file_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image title: "@kbn/shared-ux-file-image" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image'] --- import kbnSharedUxFileImageObj from './kbn_shared_ux_file_image.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image_mocks.mdx b/api_docs/kbn_shared_ux_file_image_mocks.mdx index e57de7336fe84..5d681df763439 100644 --- a/api_docs/kbn_shared_ux_file_image_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_image_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image-mocks title: "@kbn/shared-ux-file-image-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image-mocks'] --- import kbnSharedUxFileImageMocksObj from './kbn_shared_ux_file_image_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_mocks.mdx b/api_docs/kbn_shared_ux_file_mocks.mdx index 0c098cb8a8b96..0fdaac33bf821 100644 --- a/api_docs/kbn_shared_ux_file_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-mocks title: "@kbn/shared-ux-file-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-mocks'] --- import kbnSharedUxFileMocksObj from './kbn_shared_ux_file_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_picker.mdx b/api_docs/kbn_shared_ux_file_picker.mdx index a8849a571deb1..90a2aace4b3ed 100644 --- a/api_docs/kbn_shared_ux_file_picker.mdx +++ b/api_docs/kbn_shared_ux_file_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-picker title: "@kbn/shared-ux-file-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-picker plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-picker'] --- import kbnSharedUxFilePickerObj from './kbn_shared_ux_file_picker.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_types.mdx b/api_docs/kbn_shared_ux_file_types.mdx index 119b6b33331ad..a04948b6d36bc 100644 --- a/api_docs/kbn_shared_ux_file_types.mdx +++ b/api_docs/kbn_shared_ux_file_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-types title: "@kbn/shared-ux-file-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-types plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-types'] --- import kbnSharedUxFileTypesObj from './kbn_shared_ux_file_types.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_upload.mdx b/api_docs/kbn_shared_ux_file_upload.mdx index 48faf7f54088d..a0a3f296d09dc 100644 --- a/api_docs/kbn_shared_ux_file_upload.mdx +++ b/api_docs/kbn_shared_ux_file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-upload title: "@kbn/shared-ux-file-upload" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-upload plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-upload'] --- import kbnSharedUxFileUploadObj from './kbn_shared_ux_file_upload.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_util.mdx b/api_docs/kbn_shared_ux_file_util.mdx index 77f99ae11af77..15d28db30b8c0 100644 --- a/api_docs/kbn_shared_ux_file_util.mdx +++ b/api_docs/kbn_shared_ux_file_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-util title: "@kbn/shared-ux-file-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-util plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-util'] --- import kbnSharedUxFileUtilObj from './kbn_shared_ux_file_util.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app.mdx b/api_docs/kbn_shared_ux_link_redirect_app.mdx index 1cff93697fa9e..ab16dc362c178 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app title: "@kbn/shared-ux-link-redirect-app" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app'] --- import kbnSharedUxLinkRedirectAppObj from './kbn_shared_ux_link_redirect_app.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 6ba399fa3223e..72ee477f9b943 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown.mdx b/api_docs/kbn_shared_ux_markdown.mdx index 479e3a6359d8f..86e20431dce6c 100644 --- a/api_docs/kbn_shared_ux_markdown.mdx +++ b/api_docs/kbn_shared_ux_markdown.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown title: "@kbn/shared-ux-markdown" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown'] --- import kbnSharedUxMarkdownObj from './kbn_shared_ux_markdown.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown_mocks.mdx b/api_docs/kbn_shared_ux_markdown_mocks.mdx index bc577f62e1f87..863e0ed64dc70 100644 --- a/api_docs/kbn_shared_ux_markdown_mocks.mdx +++ b/api_docs/kbn_shared_ux_markdown_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown-mocks title: "@kbn/shared-ux-markdown-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown-mocks'] --- import kbnSharedUxMarkdownMocksObj from './kbn_shared_ux_markdown_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index a4f2327db9240..7981948e69486 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index b3df36452f3b5..337087dbd0bf4 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index 7dc99348dc028..8c9cf98b3577a 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index ac83e8dedf20e..c8b9269a8d8ac 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index 90bb4c4474e81..6f98c45a1963e 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index f7f6efaac9437..6e19744fc39be 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index 3c3f1ca3b3dad..a58f8e50bbe55 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index 1c726ef14781d..3d7c0792ff209 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index 89d469eaf08ab..4957346ff16da 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index 2912378d0a9bd..2867f28d2066d 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index 009684e736646..84acbee872cc4 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index 0c32294a781a5..109ac87d18192 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index 4ef537cac3ada..3541aed49122c 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_not_found.mdx b/api_docs/kbn_shared_ux_prompt_not_found.mdx index 9f8b2bf46816a..aac6b7af887de 100644 --- a/api_docs/kbn_shared_ux_prompt_not_found.mdx +++ b/api_docs/kbn_shared_ux_prompt_not_found.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-not-found title: "@kbn/shared-ux-prompt-not-found" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-not-found plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-not-found'] --- import kbnSharedUxPromptNotFoundObj from './kbn_shared_ux_prompt_not_found.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index 1b50cab3cca41..9f54866eba5f2 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index 7c755cb640f59..a9a2fd9ccbdb2 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index 7fab8aa2e6064..08223441cc785 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index 6e1619408dad3..d6433972243ef 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_tabbed_modal.mdx b/api_docs/kbn_shared_ux_tabbed_modal.mdx index 2987d9406865f..b7a1634399ad8 100644 --- a/api_docs/kbn_shared_ux_tabbed_modal.mdx +++ b/api_docs/kbn_shared_ux_tabbed_modal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-tabbed-modal title: "@kbn/shared-ux-tabbed-modal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-tabbed-modal plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-tabbed-modal'] --- import kbnSharedUxTabbedModalObj from './kbn_shared_ux_tabbed_modal.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index e777b94073328..c69ce88f1507a 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_slo_schema.mdx b/api_docs/kbn_slo_schema.mdx index 3c980d324b5ac..6aff179f85079 100644 --- a/api_docs/kbn_slo_schema.mdx +++ b/api_docs/kbn_slo_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-slo-schema title: "@kbn/slo-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/slo-schema plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/slo-schema'] --- import kbnSloSchemaObj from './kbn_slo_schema.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index e485a10146a5e..adf48918a6b6c 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_predicates.mdx b/api_docs/kbn_sort_predicates.mdx index 0f45ffb67da44..b8e206d06ca10 100644 --- a/api_docs/kbn_sort_predicates.mdx +++ b/api_docs/kbn_sort_predicates.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-predicates title: "@kbn/sort-predicates" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-predicates plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-predicates'] --- import kbnSortPredicatesObj from './kbn_sort_predicates.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index b1a0a81838f61..ffe366ca06795 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index 16d85d7b597ac..32ad4f3240e4b 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index 4a2704f9ce58d..6000da3fe1742 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 0617f321c4d75..2c444b3aa60cf 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 1c7879d687ec9..e18118625085b 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_eui_helpers.mdx b/api_docs/kbn_test_eui_helpers.mdx index ee8f509a15512..43922e8e1e1a9 100644 --- a/api_docs/kbn_test_eui_helpers.mdx +++ b/api_docs/kbn_test_eui_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-eui-helpers title: "@kbn/test-eui-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-eui-helpers plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-eui-helpers'] --- import kbnTestEuiHelpersObj from './kbn_test_eui_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index 4d2b4a7aa3bcc..435de325bb5b9 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index 065f375399218..edc3b52274b36 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_text_based_editor.mdx b/api_docs/kbn_text_based_editor.mdx index a72b775e0521f..c07cdf9251b1a 100644 --- a/api_docs/kbn_text_based_editor.mdx +++ b/api_docs/kbn_text_based_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-text-based-editor title: "@kbn/text-based-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/text-based-editor plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/text-based-editor'] --- import kbnTextBasedEditorObj from './kbn_text_based_editor.devdocs.json'; diff --git a/api_docs/kbn_timerange.mdx b/api_docs/kbn_timerange.mdx index 658f724d84dbf..5be45f62001f6 100644 --- a/api_docs/kbn_timerange.mdx +++ b/api_docs/kbn_timerange.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-timerange title: "@kbn/timerange" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/timerange plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/timerange'] --- import kbnTimerangeObj from './kbn_timerange.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 58e0db94145fa..51426167fe362 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_triggers_actions_ui_types.mdx b/api_docs/kbn_triggers_actions_ui_types.mdx index bbb0b8f2a2024..cbe235180133f 100644 --- a/api_docs/kbn_triggers_actions_ui_types.mdx +++ b/api_docs/kbn_triggers_actions_ui_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-triggers-actions-ui-types title: "@kbn/triggers-actions-ui-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/triggers-actions-ui-types plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/triggers-actions-ui-types'] --- import kbnTriggersActionsUiTypesObj from './kbn_triggers_actions_ui_types.devdocs.json'; diff --git a/api_docs/kbn_try_in_console.mdx b/api_docs/kbn_try_in_console.mdx index fb0c895730ed8..100d851052e52 100644 --- a/api_docs/kbn_try_in_console.mdx +++ b/api_docs/kbn_try_in_console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-try-in-console title: "@kbn/try-in-console" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/try-in-console plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/try-in-console'] --- import kbnTryInConsoleObj from './kbn_try_in_console.devdocs.json'; diff --git a/api_docs/kbn_ts_projects.mdx b/api_docs/kbn_ts_projects.mdx index be3b688d70733..a4b2847019c3a 100644 --- a/api_docs/kbn_ts_projects.mdx +++ b/api_docs/kbn_ts_projects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ts-projects title: "@kbn/ts-projects" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ts-projects plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ts-projects'] --- import kbnTsProjectsObj from './kbn_ts_projects.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index 9164dea77561d..cdd22ede8c0b6 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_actions_browser.mdx b/api_docs/kbn_ui_actions_browser.mdx index eb03d2bc62386..6160d108f290b 100644 --- a/api_docs/kbn_ui_actions_browser.mdx +++ b/api_docs/kbn_ui_actions_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-actions-browser title: "@kbn/ui-actions-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-actions-browser plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-actions-browser'] --- import kbnUiActionsBrowserObj from './kbn_ui_actions_browser.devdocs.json'; diff --git a/api_docs/kbn_ui_shared_deps_src.mdx b/api_docs/kbn_ui_shared_deps_src.mdx index eb1bef6033ab4..e5eaf40c7b1a8 100644 --- a/api_docs/kbn_ui_shared_deps_src.mdx +++ b/api_docs/kbn_ui_shared_deps_src.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-shared-deps-src title: "@kbn/ui-shared-deps-src" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-shared-deps-src plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-shared-deps-src'] --- import kbnUiSharedDepsSrcObj from './kbn_ui_shared_deps_src.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index c704533a9df98..37b19c43ea4e3 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_unified_data_table.mdx b/api_docs/kbn_unified_data_table.mdx index c6e46db320d70..443ae0572dcbb 100644 --- a/api_docs/kbn_unified_data_table.mdx +++ b/api_docs/kbn_unified_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-data-table title: "@kbn/unified-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-data-table plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-data-table'] --- import kbnUnifiedDataTableObj from './kbn_unified_data_table.devdocs.json'; diff --git a/api_docs/kbn_unified_doc_viewer.mdx b/api_docs/kbn_unified_doc_viewer.mdx index 59cda687f8cbb..424b5b3425c1a 100644 --- a/api_docs/kbn_unified_doc_viewer.mdx +++ b/api_docs/kbn_unified_doc_viewer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-doc-viewer title: "@kbn/unified-doc-viewer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-doc-viewer plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-doc-viewer'] --- import kbnUnifiedDocViewerObj from './kbn_unified_doc_viewer.devdocs.json'; diff --git a/api_docs/kbn_unified_field_list.mdx b/api_docs/kbn_unified_field_list.mdx index c55282399b272..57a3d5ee70e90 100644 --- a/api_docs/kbn_unified_field_list.mdx +++ b/api_docs/kbn_unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-field-list title: "@kbn/unified-field-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-field-list plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-field-list'] --- import kbnUnifiedFieldListObj from './kbn_unified_field_list.devdocs.json'; diff --git a/api_docs/kbn_unsaved_changes_badge.mdx b/api_docs/kbn_unsaved_changes_badge.mdx index 4dfd80d408092..e7c12014ab15f 100644 --- a/api_docs/kbn_unsaved_changes_badge.mdx +++ b/api_docs/kbn_unsaved_changes_badge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unsaved-changes-badge title: "@kbn/unsaved-changes-badge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unsaved-changes-badge plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unsaved-changes-badge'] --- import kbnUnsavedChangesBadgeObj from './kbn_unsaved_changes_badge.devdocs.json'; diff --git a/api_docs/kbn_unsaved_changes_prompt.mdx b/api_docs/kbn_unsaved_changes_prompt.mdx index 9450be4f2595e..46f2147e0c7f0 100644 --- a/api_docs/kbn_unsaved_changes_prompt.mdx +++ b/api_docs/kbn_unsaved_changes_prompt.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unsaved-changes-prompt title: "@kbn/unsaved-changes-prompt" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unsaved-changes-prompt plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unsaved-changes-prompt'] --- import kbnUnsavedChangesPromptObj from './kbn_unsaved_changes_prompt.devdocs.json'; diff --git a/api_docs/kbn_use_tracked_promise.mdx b/api_docs/kbn_use_tracked_promise.mdx index b95523811f1ca..a27ecf5cb2112 100644 --- a/api_docs/kbn_use_tracked_promise.mdx +++ b/api_docs/kbn_use_tracked_promise.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-use-tracked-promise title: "@kbn/use-tracked-promise" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/use-tracked-promise plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/use-tracked-promise'] --- import kbnUseTrackedPromiseObj from './kbn_use_tracked_promise.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index e0292a4700f38..b601326f715bb 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index b236c38cd2090..f9846bb3f0a6f 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index 3198415f11a50..de672491acfb6 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index 805c4d2b13fdd..0774b41071c95 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_visualization_ui_components.mdx b/api_docs/kbn_visualization_ui_components.mdx index be730b28d1f2e..5d471f7a0e639 100644 --- a/api_docs/kbn_visualization_ui_components.mdx +++ b/api_docs/kbn_visualization_ui_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-visualization-ui-components title: "@kbn/visualization-ui-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/visualization-ui-components plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-ui-components'] --- import kbnVisualizationUiComponentsObj from './kbn_visualization_ui_components.devdocs.json'; diff --git a/api_docs/kbn_visualization_utils.mdx b/api_docs/kbn_visualization_utils.mdx index 214ea1f50d745..fd903e0ff2219 100644 --- a/api_docs/kbn_visualization_utils.mdx +++ b/api_docs/kbn_visualization_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-visualization-utils title: "@kbn/visualization-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/visualization-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-utils'] --- import kbnVisualizationUtilsObj from './kbn_visualization_utils.devdocs.json'; diff --git a/api_docs/kbn_xstate_utils.mdx b/api_docs/kbn_xstate_utils.mdx index d2f03ffd1d84e..9ac905bb8fa35 100644 --- a/api_docs/kbn_xstate_utils.mdx +++ b/api_docs/kbn_xstate_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-xstate-utils title: "@kbn/xstate-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/xstate-utils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/xstate-utils'] --- import kbnXstateUtilsObj from './kbn_xstate_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index 8bb01ba87a572..f7ffe97c177a2 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kbn_zod_helpers.mdx b/api_docs/kbn_zod_helpers.mdx index 73e100a798b6c..0b239f678ae90 100644 --- a/api_docs/kbn_zod_helpers.mdx +++ b/api_docs/kbn_zod_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-zod-helpers title: "@kbn/zod-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/zod-helpers plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/zod-helpers'] --- import kbnZodHelpersObj from './kbn_zod_helpers.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 34389774728a9..97ec2b6b04a68 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 4a58da6924bd5..e16240712bdf8 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index bbe1d21dc369e..2478fea13d956 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index ca65ac8aee662..d209f94184f32 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index ebf11366e609e..ab261a2828fd8 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index b177637d2c842..2d9cea7503c00 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index ab5f0bd64b8e2..d13d9a54b3696 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index 9b1d2bb043d25..9407518bd57d0 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/links.mdx b/api_docs/links.mdx index 8c1d3e9a37006..0dd6288eb7c07 100644 --- a/api_docs/links.mdx +++ b/api_docs/links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/links title: "links" image: https://source.unsplash.com/400x175/?github description: API docs for the links plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'links'] --- import linksObj from './links.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index e0d216280fdeb..d320418a931d2 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/logs_data_access.devdocs.json b/api_docs/logs_data_access.devdocs.json index 89d5df7f6c938..c4f3b466d53a4 100644 --- a/api_docs/logs_data_access.devdocs.json +++ b/api_docs/logs_data_access.devdocs.json @@ -124,7 +124,19 @@ "section": "def-server.LogsRatesServiceReturnType", "text": "LogsRatesServiceReturnType" }, - ">; }; }" + ">; getLogSourcesService: (request: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" + }, + "<unknown, unknown, unknown, any>) => Promise<{ getLogSources: () => Promise<", + "LogSource", + "[]>; setLogSources: (sources: ", + "LogSource", + "[]) => Promise<void>; }>; }; }" ], "path": "x-pack/plugins/observability_solution/logs_data_access/server/plugin.ts", "deprecated": false, diff --git a/api_docs/logs_data_access.mdx b/api_docs/logs_data_access.mdx index f39c8cab562ac..0cedb6e589b5f 100644 --- a/api_docs/logs_data_access.mdx +++ b/api_docs/logs_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsDataAccess title: "logsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the logsDataAccess plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsDataAccess'] --- import logsDataAccessObj from './logs_data_access.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 7 | 0 | 7 | 1 | +| 7 | 0 | 7 | 2 | ## Server diff --git a/api_docs/logs_explorer.mdx b/api_docs/logs_explorer.mdx index c5e7fe9b11024..e588a541605c1 100644 --- a/api_docs/logs_explorer.mdx +++ b/api_docs/logs_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsExplorer title: "logsExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the logsExplorer plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsExplorer'] --- import logsExplorerObj from './logs_explorer.devdocs.json'; diff --git a/api_docs/logs_shared.mdx b/api_docs/logs_shared.mdx index e97c57754dcaa..ac239a4c43146 100644 --- a/api_docs/logs_shared.mdx +++ b/api_docs/logs_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsShared title: "logsShared" image: https://source.unsplash.com/400x175/?github description: API docs for the logsShared plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsShared'] --- import logsSharedObj from './logs_shared.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index e1cf3a9b77f33..b34dd50652217 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index bbd1be2717c12..65349362fb8e8 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index ba7751aec9780..bd42728ddc2ce 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/metrics_data_access.mdx b/api_docs/metrics_data_access.mdx index ccf4dae284259..36508eb572d0d 100644 --- a/api_docs/metrics_data_access.mdx +++ b/api_docs/metrics_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/metricsDataAccess title: "metricsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the metricsDataAccess plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'metricsDataAccess'] --- import metricsDataAccessObj from './metrics_data_access.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index d91dedaf0c778..9f10b0f0a2458 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/mock_idp_plugin.mdx b/api_docs/mock_idp_plugin.mdx index 9db7f79b30787..70b72928527a7 100644 --- a/api_docs/mock_idp_plugin.mdx +++ b/api_docs/mock_idp_plugin.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mockIdpPlugin title: "mockIdpPlugin" image: https://source.unsplash.com/400x175/?github description: API docs for the mockIdpPlugin plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mockIdpPlugin'] --- import mockIdpPluginObj from './mock_idp_plugin.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 6e42e5733af3c..a622803d33476 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index 39d1c1e4cd9b5..1c8aa617945d5 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index 93a6fa9227c36..7a064f7d41c00 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index ecff332279934..6f07a1515cdcc 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/no_data_page.mdx b/api_docs/no_data_page.mdx index 7cb8f468df8d4..339962a194411 100644 --- a/api_docs/no_data_page.mdx +++ b/api_docs/no_data_page.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/noDataPage title: "noDataPage" image: https://source.unsplash.com/400x175/?github description: API docs for the noDataPage plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'noDataPage'] --- import noDataPageObj from './no_data_page.devdocs.json'; diff --git a/api_docs/notifications.mdx b/api_docs/notifications.mdx index d08801c997816..28ef4474329ad 100644 --- a/api_docs/notifications.mdx +++ b/api_docs/notifications.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/notifications title: "notifications" image: https://source.unsplash.com/400x175/?github description: API docs for the notifications plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'notifications'] --- import notificationsObj from './notifications.devdocs.json'; diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index f7348d8a14f9a..b6be9ec30008d 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/observability_a_i_assistant.mdx b/api_docs/observability_a_i_assistant.mdx index c217894f55ed1..df7de17e9105f 100644 --- a/api_docs/observability_a_i_assistant.mdx +++ b/api_docs/observability_a_i_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAIAssistant title: "observabilityAIAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAIAssistant plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistant'] --- import observabilityAIAssistantObj from './observability_a_i_assistant.devdocs.json'; diff --git a/api_docs/observability_a_i_assistant_app.mdx b/api_docs/observability_a_i_assistant_app.mdx index 61b34dccc57f6..9e6cf395951c0 100644 --- a/api_docs/observability_a_i_assistant_app.mdx +++ b/api_docs/observability_a_i_assistant_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAIAssistantApp title: "observabilityAIAssistantApp" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAIAssistantApp plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistantApp'] --- import observabilityAIAssistantAppObj from './observability_a_i_assistant_app.devdocs.json'; diff --git a/api_docs/observability_ai_assistant_management.mdx b/api_docs/observability_ai_assistant_management.mdx index 81baaa1f3c786..5bdddc0826b93 100644 --- a/api_docs/observability_ai_assistant_management.mdx +++ b/api_docs/observability_ai_assistant_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAiAssistantManagement title: "observabilityAiAssistantManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAiAssistantManagement plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAiAssistantManagement'] --- import observabilityAiAssistantManagementObj from './observability_ai_assistant_management.devdocs.json'; diff --git a/api_docs/observability_logs_explorer.mdx b/api_docs/observability_logs_explorer.mdx index ff7748288ce12..eddf5ffca2f7a 100644 --- a/api_docs/observability_logs_explorer.mdx +++ b/api_docs/observability_logs_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityLogsExplorer title: "observabilityLogsExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityLogsExplorer plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityLogsExplorer'] --- import observabilityLogsExplorerObj from './observability_logs_explorer.devdocs.json'; diff --git a/api_docs/observability_onboarding.devdocs.json b/api_docs/observability_onboarding.devdocs.json index b5d8d41f255cf..c3edfe2d3c38f 100644 --- a/api_docs/observability_onboarding.devdocs.json +++ b/api_docs/observability_onboarding.devdocs.json @@ -4,6 +4,42 @@ "classes": [], "functions": [], "interfaces": [ + { + "parentPluginId": "observabilityOnboarding", + "id": "def-public.AppContext", + "type": "Interface", + "tags": [], + "label": "AppContext", + "description": [], + "path": "x-pack/plugins/observability_solution/observability_onboarding/public/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityOnboarding", + "id": "def-public.AppContext.isServerless", + "type": "boolean", + "tags": [], + "label": "isServerless", + "description": [], + "path": "x-pack/plugins/observability_solution/observability_onboarding/public/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observabilityOnboarding", + "id": "def-public.AppContext.stackVersion", + "type": "string", + "tags": [], + "label": "stackVersion", + "description": [], + "path": "x-pack/plugins/observability_solution/observability_onboarding/public/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "observabilityOnboarding", "id": "def-public.ConfigSchema", @@ -97,6 +133,66 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "observabilityOnboarding", + "id": "def-public.ObservabilityOnboardingAppServices.share", + "type": "CompoundType", + "tags": [], + "label": "share", + "description": [], + "signature": [ + "{ toggleShareContextMenu: (options: ", + { + "pluginId": "share", + "scope": "public", + "docId": "kibSharePluginApi", + "section": "def-public.ShowShareMenuOptions", + "text": "ShowShareMenuOptions" + }, + ") => void; } & { url: ", + { + "pluginId": "share", + "scope": "public", + "docId": "kibSharePluginApi", + "section": "def-public.BrowserUrlService", + "text": "BrowserUrlService" + }, + "; navigate(options: ", + "RedirectOptions", + "<", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.SerializableRecord", + "text": "SerializableRecord" + }, + ">): void; }" + ], + "path": "x-pack/plugins/observability_solution/observability_onboarding/public/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observabilityOnboarding", + "id": "def-public.ObservabilityOnboardingAppServices.context", + "type": "Object", + "tags": [], + "label": "context", + "description": [], + "signature": [ + { + "pluginId": "observabilityOnboarding", + "scope": "public", + "docId": "kibObservabilityOnboardingPluginApi", + "section": "def-public.AppContext", + "text": "AppContext" + } + ], + "path": "x-pack/plugins/observability_solution/observability_onboarding/public/index.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "observabilityOnboarding", "id": "def-public.ObservabilityOnboardingAppServices.config", diff --git a/api_docs/observability_onboarding.mdx b/api_docs/observability_onboarding.mdx index 6b70fb1fc1a6b..e5ea3e7669e7c 100644 --- a/api_docs/observability_onboarding.mdx +++ b/api_docs/observability_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityOnboarding title: "observabilityOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityOnboarding plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityOnboarding'] --- import observabilityOnboardingObj from './observability_onboarding.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 16 | 0 | 16 | 0 | +| 21 | 0 | 21 | 0 | ## Client diff --git a/api_docs/observability_shared.mdx b/api_docs/observability_shared.mdx index 3661b3f8f07d0..a4f8eeb9945f8 100644 --- a/api_docs/observability_shared.mdx +++ b/api_docs/observability_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityShared title: "observabilityShared" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityShared plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityShared'] --- import observabilitySharedObj from './observability_shared.devdocs.json'; diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index d5293d70426ac..1009f21ce1d5a 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/painless_lab.mdx b/api_docs/painless_lab.mdx index a1ba7b8a32f35..26006036e38ae 100644 --- a/api_docs/painless_lab.mdx +++ b/api_docs/painless_lab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/painlessLab title: "painlessLab" image: https://source.unsplash.com/400x175/?github description: API docs for the painlessLab plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'painlessLab'] --- import painlessLabObj from './painless_lab.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index b326bfc17c2e4..322c1bc81373d 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -21,7 +21,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 49625 | 238 | 37857 | 1888 | +| 49646 | 238 | 37872 | 1889 | ## Plugin Directory @@ -131,7 +131,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | <DocLink id="kibLicensingPluginApi" text="licensing"/> | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 117 | 0 | 42 | 10 | | <DocLink id="kibLinksPluginApi" text="links"/> | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | A dashboard panel for creating links to dashboards or external links. | 58 | 0 | 58 | 6 | | <DocLink id="kibListsPluginApi" text="lists"/> | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 226 | 0 | 97 | 52 | -| <DocLink id="kibLogsDataAccessPluginApi" text="logsDataAccess"/> | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 7 | 0 | 7 | 1 | +| <DocLink id="kibLogsDataAccessPluginApi" text="logsDataAccess"/> | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 7 | 0 | 7 | 2 | | <DocLink id="kibLogsExplorerPluginApi" text="logsExplorer"/> | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | This plugin provides a LogsExplorer component using the Discover customization framework, offering several affordances specifically designed for log consumption. | 117 | 4 | 117 | 22 | | <DocLink id="kibLogsSharedPluginApi" text="logsShared"/> | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | Exposes the shared components and APIs to access and visualize logs. | 296 | 0 | 268 | 32 | | logstash | [@elastic/logstash](https://github.com/orgs/elastic/teams/logstash) | - | 0 | 0 | 0 | 0 | @@ -152,7 +152,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | <DocLink id="kibObservabilityAIAssistantAppPluginApi" text="observabilityAIAssistantApp"/> | [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai-assistant) | - | 4 | 0 | 4 | 0 | | <DocLink id="kibObservabilityAiAssistantManagementPluginApi" text="observabilityAiAssistantManagement"/> | [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai-assistant) | - | 2 | 0 | 2 | 0 | | <DocLink id="kibObservabilityLogsExplorerPluginApi" text="observabilityLogsExplorer"/> | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | This plugin exposes and registers observability log consumption features. | 19 | 0 | 19 | 1 | -| <DocLink id="kibObservabilityOnboardingPluginApi" text="observabilityOnboarding"/> | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 16 | 0 | 16 | 0 | +| <DocLink id="kibObservabilityOnboardingPluginApi" text="observabilityOnboarding"/> | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 21 | 0 | 21 | 0 | | <DocLink id="kibObservabilitySharedPluginApi" text="observabilityShared"/> | [@elastic/observability-ui](https://github.com/orgs/elastic/teams/observability-ui) | - | 355 | 1 | 350 | 22 | | <DocLink id="kibOsqueryPluginApi" text="osquery"/> | [@elastic/security-defend-workflows](https://github.com/orgs/elastic/teams/security-defend-workflows) | - | 23 | 0 | 23 | 7 | | <DocLink id="kibPainlessLabPluginApi" text="painlessLab"/> | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 2 | 0 | 2 | 0 | @@ -179,7 +179,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | <DocLink id="kibSearchNotebooksPluginApi" text="searchNotebooks"/> | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | Plugin to provide access to and rendering of python notebooks for use in the persistent developer console. | 8 | 0 | 8 | 1 | | <DocLink id="kibSearchPlaygroundPluginApi" text="searchPlayground"/> | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 18 | 0 | 10 | 1 | | searchprofiler | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 0 | 0 | 0 | 0 | -| <DocLink id="kibSecurityPluginApi" text="security"/> | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user. | 411 | 0 | 204 | 1 | +| <DocLink id="kibSecurityPluginApi" text="security"/> | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user. | 415 | 0 | 206 | 1 | | <DocLink id="kibSecuritySolutionPluginApi" text="securitySolution"/> | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 191 | 0 | 121 | 37 | | <DocLink id="kibSecuritySolutionEssPluginApi" text="securitySolutionEss"/> | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | ESS customizations for Security Solution. | 6 | 0 | 6 | 0 | | <DocLink id="kibSecuritySolutionServerlessPluginApi" text="securitySolutionServerless"/> | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | Serverless customizations for security. | 7 | 0 | 7 | 0 | @@ -415,11 +415,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | <DocLink id="kibKbnCoreSavedObjectsUtilsServerPluginApi" text="@kbn/core-saved-objects-utils-server"/> | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 36 | 0 | 6 | 0 | | <DocLink id="kibKbnCoreSecurityBrowserPluginApi" text="@kbn/core-security-browser"/> | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 10 | 0 | 3 | 0 | | <DocLink id="kibKbnCoreSecurityBrowserInternalPluginApi" text="@kbn/core-security-browser-internal"/> | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 8 | 0 | 8 | 0 | -| <DocLink id="kibKbnCoreSecurityBrowserMocksPluginApi" text="@kbn/core-security-browser-mocks"/> | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 6 | 0 | 6 | 0 | +| <DocLink id="kibKbnCoreSecurityBrowserMocksPluginApi" text="@kbn/core-security-browser-mocks"/> | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 8 | 0 | 8 | 0 | | <DocLink id="kibKbnCoreSecurityCommonPluginApi" text="@kbn/core-security-common"/> | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 20 | 0 | 6 | 0 | -| <DocLink id="kibKbnCoreSecurityServerPluginApi" text="@kbn/core-security-server"/> | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 49 | 0 | 16 | 0 | +| <DocLink id="kibKbnCoreSecurityServerPluginApi" text="@kbn/core-security-server"/> | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 52 | 0 | 16 | 0 | | <DocLink id="kibKbnCoreSecurityServerInternalPluginApi" text="@kbn/core-security-server-internal"/> | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 16 | 0 | 16 | 0 | -| <DocLink id="kibKbnCoreSecurityServerMocksPluginApi" text="@kbn/core-security-server-mocks"/> | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 11 | 0 | 11 | 2 | +| <DocLink id="kibKbnCoreSecurityServerMocksPluginApi" text="@kbn/core-security-server-mocks"/> | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 13 | 0 | 13 | 2 | | <DocLink id="kibKbnCoreStatusCommonPluginApi" text="@kbn/core-status-common"/> | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 12 | 0 | 2 | 0 | | <DocLink id="kibKbnCoreStatusCommonInternalPluginApi" text="@kbn/core-status-common-internal"/> | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 21 | 0 | 20 | 0 | | <DocLink id="kibKbnCoreStatusServerPluginApi" text="@kbn/core-status-server"/> | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 20 | 0 | 3 | 0 | @@ -498,7 +498,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | <DocLink id="kibKbnEslintPluginImportsPluginApi" text="@kbn/eslint-plugin-imports"/> | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 1 | 0 | | <DocLink id="kibKbnEsqlAstPluginApi" text="@kbn/esql-ast"/> | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 99 | 1 | 96 | 11 | | <DocLink id="kibKbnEsqlUtilsPluginApi" text="@kbn/esql-utils"/> | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 53 | 0 | 51 | 0 | -| <DocLink id="kibKbnEsqlValidationAutocompletePluginApi" text="@kbn/esql-validation-autocomplete"/> | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 192 | 0 | 181 | 10 | +| <DocLink id="kibKbnEsqlValidationAutocompletePluginApi" text="@kbn/esql-validation-autocomplete"/> | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 193 | 0 | 182 | 10 | | <DocLink id="kibKbnEventAnnotationCommonPluginApi" text="@kbn/event-annotation-common"/> | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 39 | 0 | 39 | 0 | | <DocLink id="kibKbnEventAnnotationComponentsPluginApi" text="@kbn/event-annotation-components"/> | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 52 | 0 | 52 | 1 | | <DocLink id="kibKbnExpandableFlyoutPluginApi" text="@kbn/expandable-flyout"/> | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 39 | 0 | 14 | 1 | @@ -546,7 +546,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | <DocLink id="kibKbnManagementSettingsComponentsFieldRowPluginApi" text="@kbn/management-settings-components-field-row"/> | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 23 | 0 | 7 | 0 | | <DocLink id="kibKbnManagementSettingsComponentsFormPluginApi" text="@kbn/management-settings-components-form"/> | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 8 | 0 | 2 | 3 | | <DocLink id="kibKbnManagementSettingsFieldDefinitionPluginApi" text="@kbn/management-settings-field-definition"/> | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 45 | 0 | 0 | 0 | -| <DocLink id="kibKbnManagementSettingsIdsPluginApi" text="@kbn/management-settings-ids"/> | [@elastic/appex-sharedux @elastic/kibana-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 138 | 0 | 136 | 0 | +| <DocLink id="kibKbnManagementSettingsIdsPluginApi" text="@kbn/management-settings-ids"/> | [@elastic/appex-sharedux @elastic/kibana-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 139 | 0 | 137 | 0 | | <DocLink id="kibKbnManagementSettingsSectionRegistryPluginApi" text="@kbn/management-settings-section-registry"/> | [@elastic/appex-sharedux @elastic/kibana-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 20 | 0 | 11 | 0 | | <DocLink id="kibKbnManagementSettingsTypesPluginApi" text="@kbn/management-settings-types"/> | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 88 | 0 | 10 | 0 | | <DocLink id="kibKbnManagementSettingsUtilitiesPluginApi" text="@kbn/management-settings-utilities"/> | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 56 | 0 | 6 | 0 | @@ -570,7 +570,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | <DocLink id="kibKbnMlLocalStoragePluginApi" text="@kbn/ml-local-storage"/> | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 5 | 0 | 3 | 0 | | <DocLink id="kibKbnMlNestedPropertyPluginApi" text="@kbn/ml-nested-property"/> | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 8 | 2 | 8 | 0 | | <DocLink id="kibKbnMlNumberUtilsPluginApi" text="@kbn/ml-number-utils"/> | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 3 | 0 | 0 | 0 | -| <DocLink id="kibKbnMlQueryUtilsPluginApi" text="@kbn/ml-query-utils"/> | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 28 | 0 | 0 | 0 | +| <DocLink id="kibKbnMlQueryUtilsPluginApi" text="@kbn/ml-query-utils"/> | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 29 | 0 | 1 | 0 | | <DocLink id="kibKbnMlRandomSamplerUtilsPluginApi" text="@kbn/ml-random-sampler-utils"/> | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 30 | 0 | 0 | 0 | | <DocLink id="kibKbnMlRouteUtilsPluginApi" text="@kbn/ml-route-utils"/> | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 5 | 0 | 0 | 0 | | <DocLink id="kibKbnMlRuntimeFieldUtilsPluginApi" text="@kbn/ml-runtime-field-utils"/> | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 8 | 0 | 0 | 0 | @@ -640,7 +640,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | <DocLink id="kibKbnSecurityApiKeyManagementPluginApi" text="@kbn/security-api-key-management"/> | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 66 | 0 | 63 | 0 | | <DocLink id="kibKbnSecurityFormComponentsPluginApi" text="@kbn/security-form-components"/> | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 35 | 0 | 25 | 0 | | <DocLink id="kibKbnSecurityHardeningPluginApi" text="@kbn/security-hardening"/> | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 7 | 0 | 7 | 0 | -| <DocLink id="kibKbnSecurityPluginTypesCommonPluginApi" text="@kbn/security-plugin-types-common"/> | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 116 | 0 | 58 | 0 | +| <DocLink id="kibKbnSecurityPluginTypesCommonPluginApi" text="@kbn/security-plugin-types-common"/> | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 118 | 0 | 59 | 0 | | <DocLink id="kibKbnSecurityPluginTypesPublicPluginApi" text="@kbn/security-plugin-types-public"/> | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 51 | 0 | 25 | 0 | | <DocLink id="kibKbnSecurityPluginTypesServerPluginApi" text="@kbn/security-plugin-types-server"/> | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 216 | 0 | 121 | 0 | | <DocLink id="kibKbnSecuritySolutionFeaturesPluginApi" text="@kbn/security-solution-features"/> | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 14 | 0 | 14 | 6 | diff --git a/api_docs/presentation_panel.mdx b/api_docs/presentation_panel.mdx index c565b4a9f1b0c..11c2be4c851f4 100644 --- a/api_docs/presentation_panel.mdx +++ b/api_docs/presentation_panel.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationPanel title: "presentationPanel" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationPanel plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationPanel'] --- import presentationPanelObj from './presentation_panel.devdocs.json'; diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index b5d96c83c4709..b54536890a11f 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index 64683be34d421..bc56022876734 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/profiling_data_access.mdx b/api_docs/profiling_data_access.mdx index b0c17a2b5b3ff..620b39508dffe 100644 --- a/api_docs/profiling_data_access.mdx +++ b/api_docs/profiling_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profilingDataAccess title: "profilingDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the profilingDataAccess plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profilingDataAccess'] --- import profilingDataAccessObj from './profiling_data_access.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 991dba717c59e..772d180cec59a 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index 29873d1c17699..d3c1a06dff1fb 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index 544426846e7a7..ee495407fff49 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 4915b2c0318a6..20e1f6a0fa83d 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index 5c6cc2e90f977..9168c08ea6334 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 924252b2f4348..687ea14fdf622 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index 463865509d373..8f1971f381224 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index af36d0979c3d7..0593b5fb971db 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index 344732767ff88..547e2acf35343 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index 100c3f6a0e797..8752a5816a939 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index bf731b7962377..b568d4d5f3358 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index 51a9bda5eadae..7f35e63dabd04 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index 9b8dc824ad355..4ce1c48689f9f 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/search_connectors.mdx b/api_docs/search_connectors.mdx index 542c330989c12..59a0e44141e8d 100644 --- a/api_docs/search_connectors.mdx +++ b/api_docs/search_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchConnectors title: "searchConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the searchConnectors plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchConnectors'] --- import searchConnectorsObj from './search_connectors.devdocs.json'; diff --git a/api_docs/search_homepage.mdx b/api_docs/search_homepage.mdx index a1a70cd36de5d..66e09b012255f 100644 --- a/api_docs/search_homepage.mdx +++ b/api_docs/search_homepage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchHomepage title: "searchHomepage" image: https://source.unsplash.com/400x175/?github description: API docs for the searchHomepage plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchHomepage'] --- import searchHomepageObj from './search_homepage.devdocs.json'; diff --git a/api_docs/search_inference_endpoints.mdx b/api_docs/search_inference_endpoints.mdx index 3bf7dbc0ee318..706d4462d7e37 100644 --- a/api_docs/search_inference_endpoints.mdx +++ b/api_docs/search_inference_endpoints.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchInferenceEndpoints title: "searchInferenceEndpoints" image: https://source.unsplash.com/400x175/?github description: API docs for the searchInferenceEndpoints plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchInferenceEndpoints'] --- import searchInferenceEndpointsObj from './search_inference_endpoints.devdocs.json'; diff --git a/api_docs/search_notebooks.mdx b/api_docs/search_notebooks.mdx index 90da558e7ee5e..9ab9dc47bb750 100644 --- a/api_docs/search_notebooks.mdx +++ b/api_docs/search_notebooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchNotebooks title: "searchNotebooks" image: https://source.unsplash.com/400x175/?github description: API docs for the searchNotebooks plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchNotebooks'] --- import searchNotebooksObj from './search_notebooks.devdocs.json'; diff --git a/api_docs/search_playground.mdx b/api_docs/search_playground.mdx index a4218a7a013ab..d4747ce2312cd 100644 --- a/api_docs/search_playground.mdx +++ b/api_docs/search_playground.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchPlayground title: "searchPlayground" image: https://source.unsplash.com/400x175/?github description: API docs for the searchPlayground plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchPlayground'] --- import searchPlaygroundObj from './search_playground.devdocs.json'; diff --git a/api_docs/security.devdocs.json b/api_docs/security.devdocs.json index e5a0c7debea31..6778f16c4683e 100644 --- a/api_docs/security.devdocs.json +++ b/api_docs/security.devdocs.json @@ -352,6 +352,22 @@ "children": [], "returnComment": [] }, + { + "parentPluginId": "security", + "id": "def-public.SecurityLicense.getLicenseType", + "type": "Function", + "tags": [], + "label": "getLicenseType", + "description": [], + "signature": [ + "() => string | undefined" + ], + "path": "x-pack/packages/security/plugin_types_common/src/licensing/license.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "security", "id": "def-public.SecurityLicense.getUnavailableReason", @@ -669,6 +685,19 @@ "path": "x-pack/packages/security/plugin_types_common/src/licensing/license_features.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "security", + "id": "def-public.SecurityLicenseFeatures.allowFips", + "type": "boolean", + "tags": [], + "label": "allowFips", + "description": [ + "\nIndicates whether we allow FIPS mode" + ], + "path": "x-pack/packages/security/plugin_types_common/src/licensing/license_features.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -5291,10 +5320,6 @@ "plugin": "encryptedSavedObjects", "path": "x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts" }, - { - "plugin": "actions", - "path": "x-pack/plugins/actions/server/plugin.ts" - }, { "plugin": "ml", "path": "x-pack/plugins/ml/server/routes/annotations.ts" @@ -5434,10 +5459,6 @@ "deprecated": true, "trackAdoption": false, "references": [ - { - "plugin": "actions", - "path": "x-pack/plugins/actions/server/lib/action_executor.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/rules_client_factory.ts" @@ -5486,10 +5507,6 @@ "plugin": "enterpriseSearch", "path": "x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts" }, - { - "plugin": "lists", - "path": "x-pack/plugins/lists/server/get_user.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts" @@ -6999,6 +7016,22 @@ "children": [], "returnComment": [] }, + { + "parentPluginId": "security", + "id": "def-common.SecurityLicense.getLicenseType", + "type": "Function", + "tags": [], + "label": "getLicenseType", + "description": [], + "signature": [ + "() => string | undefined" + ], + "path": "x-pack/packages/security/plugin_types_common/src/licensing/license.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "security", "id": "def-common.SecurityLicense.getUnavailableReason", @@ -7316,6 +7349,19 @@ "path": "x-pack/packages/security/plugin_types_common/src/licensing/license_features.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "security", + "id": "def-common.SecurityLicenseFeatures.allowFips", + "type": "boolean", + "tags": [], + "label": "allowFips", + "description": [ + "\nIndicates whether we allow FIPS mode" + ], + "path": "x-pack/packages/security/plugin_types_common/src/licensing/license_features.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 2745c705d0d6e..66359047091dc 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 411 | 0 | 204 | 1 | +| 415 | 0 | 206 | 1 | ## Client diff --git a/api_docs/security_solution.devdocs.json b/api_docs/security_solution.devdocs.json index 292b7a1d793bc..e07a045ba7722 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -390,7 +390,7 @@ "label": "data", "description": [], "signature": [ - "({ id: string; type: \"eql\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; description: string; risk_score: number; from: string; to: string; language: \"eql\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; data_view_id?: string | undefined; filters?: unknown[] | undefined; event_category_override?: string | undefined; tiebreaker_field?: string | undefined; timestamp_field?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { id: string; type: \"query\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; description: string; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; saved_id?: string | undefined; response_actions?: ({ params: { query?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional<Zod.ZodString>; value: Zod.ZodOptional<Zod.ZodUnion<[Zod.ZodString, Zod.ZodArray<Zod.ZodString, \"many\">]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; queries?: { id: string; query: string; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional<Zod.ZodString>; value: Zod.ZodOptional<Zod.ZodUnion<[Zod.ZodString, Zod.ZodArray<Zod.ZodString, \"many\">]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; version?: string | undefined; platform?: string | undefined; removed?: boolean | undefined; snapshot?: boolean | undefined; }[] | undefined; pack_id?: string | undefined; saved_query_id?: string | undefined; timeout?: number | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; } | { config: { field: string; overwrite: boolean; }; command: \"kill-process\" | \"suspend-process\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; } | { id: string; type: \"saved_query\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; description: string; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; saved_id: string; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; query?: string | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; response_actions?: ({ params: { query?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional<Zod.ZodString>; value: Zod.ZodOptional<Zod.ZodUnion<[Zod.ZodString, Zod.ZodArray<Zod.ZodString, \"many\">]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; queries?: { id: string; query: string; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional<Zod.ZodString>; value: Zod.ZodOptional<Zod.ZodUnion<[Zod.ZodString, Zod.ZodArray<Zod.ZodString, \"many\">]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; version?: string | undefined; platform?: string | undefined; removed?: boolean | undefined; snapshot?: boolean | undefined; }[] | undefined; pack_id?: string | undefined; saved_query_id?: string | undefined; timeout?: number | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; } | { config: { field: string; overwrite: boolean; }; command: \"kill-process\" | \"suspend-process\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; } | { id: string; type: \"threshold\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; description: string; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; threshold: { value: number; field: (string | string[]) & (string | string[] | undefined); cardinality?: { value: number; field: string; }[] | undefined; }; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; alert_suppression?: { duration: { value: number; unit: \"m\" | \"h\" | \"s\"; }; } | undefined; saved_id?: string | undefined; } | { id: string; type: \"threat_match\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; description: string; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; threat_query: string; threat_mapping: { entries: { value: string; type: \"mapping\"; field: string; }[]; }[]; threat_index: string[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; saved_id?: string | undefined; threat_filters?: unknown[] | undefined; threat_indicator_path?: string | undefined; threat_language?: \"lucene\" | \"kuery\" | undefined; concurrent_searches?: number | undefined; items_per_search?: number | undefined; } | { id: string; type: \"machine_learning\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; description: string; risk_score: number; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; anomaly_threshold: number; machine_learning_job_id: (string | string[]) & (string | string[] | undefined); meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; } | { id: string; type: \"new_terms\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; description: string; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; new_terms_fields: string[]; history_window_start: string; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { id: string; type: \"esql\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; description: string; risk_score: number; from: string; to: string; language: \"esql\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; })[]" + "({ id: string; type: \"eql\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; description: string; risk_score: number; from: string; to: string; language: \"eql\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; data_view_id?: string | undefined; filters?: unknown[] | undefined; event_category_override?: string | undefined; tiebreaker_field?: string | undefined; timestamp_field?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { id: string; type: \"query\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; description: string; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; saved_id?: string | undefined; response_actions?: ({ params: { query?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional<Zod.ZodString>; value: Zod.ZodOptional<Zod.ZodUnion<[Zod.ZodString, Zod.ZodArray<Zod.ZodString, \"many\">]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; queries?: { id: string; query: string; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional<Zod.ZodString>; value: Zod.ZodOptional<Zod.ZodUnion<[Zod.ZodString, Zod.ZodArray<Zod.ZodString, \"many\">]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; version?: string | undefined; platform?: string | undefined; removed?: boolean | undefined; snapshot?: boolean | undefined; }[] | undefined; pack_id?: string | undefined; saved_query_id?: string | undefined; timeout?: number | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; } | { config: { field: string; overwrite: boolean; }; command: \"kill-process\" | \"suspend-process\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; } | { id: string; type: \"saved_query\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; description: string; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; saved_id: string; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; query?: string | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; response_actions?: ({ params: { query?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional<Zod.ZodString>; value: Zod.ZodOptional<Zod.ZodUnion<[Zod.ZodString, Zod.ZodArray<Zod.ZodString, \"many\">]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; queries?: { id: string; query: string; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional<Zod.ZodString>; value: Zod.ZodOptional<Zod.ZodUnion<[Zod.ZodString, Zod.ZodArray<Zod.ZodString, \"many\">]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; version?: string | undefined; platform?: string | undefined; removed?: boolean | undefined; snapshot?: boolean | undefined; }[] | undefined; pack_id?: string | undefined; saved_query_id?: string | undefined; timeout?: number | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; } | { config: { field: string; overwrite: boolean; }; command: \"kill-process\" | \"suspend-process\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; } | { id: string; type: \"threshold\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; description: string; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; threshold: { value: number; field: (string | string[]) & (string | string[] | undefined); cardinality?: { value: number; field: string; }[] | undefined; }; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; alert_suppression?: { duration: { value: number; unit: \"m\" | \"h\" | \"s\"; }; } | undefined; saved_id?: string | undefined; } | { id: string; type: \"threat_match\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; description: string; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; threat_query: string; threat_mapping: { entries: { value: string; type: \"mapping\"; field: string; }[]; }[]; threat_index: string[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; saved_id?: string | undefined; threat_filters?: unknown[] | undefined; threat_indicator_path?: string | undefined; threat_language?: \"lucene\" | \"kuery\" | undefined; concurrent_searches?: number | undefined; items_per_search?: number | undefined; } | { id: string; type: \"machine_learning\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; description: string; risk_score: number; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; anomaly_threshold: number; machine_learning_job_id: (string | string[]) & (string | string[] | undefined); meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { id: string; type: \"new_terms\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; description: string; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; new_terms_fields: string[]; history_window_start: string; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { id: string; type: \"esql\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; description: string; risk_score: number; from: string; to: string; language: \"esql\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; })[]" ], "path": "x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts", "deprecated": false, @@ -485,7 +485,7 @@ "\nExperimental flag needed to enable the link" ], "signature": [ - "\"assistantKnowledgeBaseByDefault\" | \"assistantModelEvaluation\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"responseActionsSentinelOneV2Enabled\" | \"responseActionsSentinelOneGetFileEnabled\" | \"responseActionsCrowdstrikeManualHostIsolationEnabled\" | \"responseActionScanEnabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutDisabled\" | \"securitySolutionNotesEnabled\" | \"entityAlertPreviewEnabled\" | \"newUserDetailsFlyoutManagedUser\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"AIAssistantOnRuleCreationFormEnabled\" | \"disableTimelineSaveTour\" | \"alertSuppressionForEsqlRuleEnabled\" | \"riskEnginePrivilegesRouteEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"crowdstrikeDataInAnalyzerEnabled\" | \"jamfDataInAnalyzerEnabled\" | \"timelineEsqlTabDisabled\" | \"unifiedComponentsInTimelineEnabled\" | \"analyzerDatePickersAndSourcererDisabled\" | \"prebuiltRulesCustomizationEnabled\" | \"malwareOnWriteScanOptionAvailable\" | \"unifiedManifestEnabled\" | \"aiAssistantFlyoutMode\" | \"valueListItemsModalEnabled\" | \"bulkCustomHighlightedFieldsEnabled\" | \"manualRuleRunEnabled\" | \"filterProcessDescendantsForEventFiltersEnabled\" | undefined" + "\"assistantKnowledgeBaseByDefault\" | \"assistantModelEvaluation\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"responseActionsSentinelOneV2Enabled\" | \"responseActionsSentinelOneGetFileEnabled\" | \"responseActionsCrowdstrikeManualHostIsolationEnabled\" | \"responseActionScanEnabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutDisabled\" | \"securitySolutionNotesEnabled\" | \"entityAlertPreviewEnabled\" | \"newUserDetailsFlyoutManagedUser\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"AIAssistantOnRuleCreationFormEnabled\" | \"disableTimelineSaveTour\" | \"alertSuppressionForEsqlRuleEnabled\" | \"riskEnginePrivilegesRouteEnabled\" | \"alertSuppressionForMachineLearningRuleEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"crowdstrikeDataInAnalyzerEnabled\" | \"jamfDataInAnalyzerEnabled\" | \"timelineEsqlTabDisabled\" | \"unifiedComponentsInTimelineEnabled\" | \"analyzerDatePickersAndSourcererDisabled\" | \"prebuiltRulesCustomizationEnabled\" | \"malwareOnWriteScanOptionAvailable\" | \"unifiedManifestEnabled\" | \"aiAssistantFlyoutMode\" | \"valueListItemsModalEnabled\" | \"bulkCustomHighlightedFieldsEnabled\" | \"manualRuleRunEnabled\" | \"filterProcessDescendantsForEventFiltersEnabled\" | undefined" ], "path": "x-pack/plugins/security_solution/public/common/links/types.ts", "deprecated": false, @@ -565,7 +565,7 @@ "\nExperimental flag needed to disable the link. Opposite of experimentalKey" ], "signature": [ - "\"assistantKnowledgeBaseByDefault\" | \"assistantModelEvaluation\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"responseActionsSentinelOneV2Enabled\" | \"responseActionsSentinelOneGetFileEnabled\" | \"responseActionsCrowdstrikeManualHostIsolationEnabled\" | \"responseActionScanEnabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutDisabled\" | \"securitySolutionNotesEnabled\" | \"entityAlertPreviewEnabled\" | \"newUserDetailsFlyoutManagedUser\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"AIAssistantOnRuleCreationFormEnabled\" | \"disableTimelineSaveTour\" | \"alertSuppressionForEsqlRuleEnabled\" | \"riskEnginePrivilegesRouteEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"crowdstrikeDataInAnalyzerEnabled\" | \"jamfDataInAnalyzerEnabled\" | \"timelineEsqlTabDisabled\" | \"unifiedComponentsInTimelineEnabled\" | \"analyzerDatePickersAndSourcererDisabled\" | \"prebuiltRulesCustomizationEnabled\" | \"malwareOnWriteScanOptionAvailable\" | \"unifiedManifestEnabled\" | \"aiAssistantFlyoutMode\" | \"valueListItemsModalEnabled\" | \"bulkCustomHighlightedFieldsEnabled\" | \"manualRuleRunEnabled\" | \"filterProcessDescendantsForEventFiltersEnabled\" | undefined" + "\"assistantKnowledgeBaseByDefault\" | \"assistantModelEvaluation\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"responseActionsSentinelOneV2Enabled\" | \"responseActionsSentinelOneGetFileEnabled\" | \"responseActionsCrowdstrikeManualHostIsolationEnabled\" | \"responseActionScanEnabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutDisabled\" | \"securitySolutionNotesEnabled\" | \"entityAlertPreviewEnabled\" | \"newUserDetailsFlyoutManagedUser\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"AIAssistantOnRuleCreationFormEnabled\" | \"disableTimelineSaveTour\" | \"alertSuppressionForEsqlRuleEnabled\" | \"riskEnginePrivilegesRouteEnabled\" | \"alertSuppressionForMachineLearningRuleEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"crowdstrikeDataInAnalyzerEnabled\" | \"jamfDataInAnalyzerEnabled\" | \"timelineEsqlTabDisabled\" | \"unifiedComponentsInTimelineEnabled\" | \"analyzerDatePickersAndSourcererDisabled\" | \"prebuiltRulesCustomizationEnabled\" | \"malwareOnWriteScanOptionAvailable\" | \"unifiedManifestEnabled\" | \"aiAssistantFlyoutMode\" | \"valueListItemsModalEnabled\" | \"bulkCustomHighlightedFieldsEnabled\" | \"manualRuleRunEnabled\" | \"filterProcessDescendantsForEventFiltersEnabled\" | undefined" ], "path": "x-pack/plugins/security_solution/public/common/links/types.ts", "deprecated": false, @@ -1964,7 +1964,7 @@ "label": "experimentalFeatures", "description": [], "signature": [ - "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutDisabled: boolean; readonly securitySolutionNotesEnabled: boolean; readonly entityAlertPreviewEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly AIAssistantOnRuleCreationFormEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly alertSuppressionForEsqlRuleEnabled: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineEnabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly prebuiltRulesCustomizationEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly aiAssistantFlyoutMode: boolean; readonly valueListItemsModalEnabled: boolean; readonly bulkCustomHighlightedFieldsEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; }" + "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutDisabled: boolean; readonly securitySolutionNotesEnabled: boolean; readonly entityAlertPreviewEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly AIAssistantOnRuleCreationFormEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly alertSuppressionForEsqlRuleEnabled: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly alertSuppressionForMachineLearningRuleEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineEnabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly prebuiltRulesCustomizationEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly aiAssistantFlyoutMode: boolean; readonly valueListItemsModalEnabled: boolean; readonly bulkCustomHighlightedFieldsEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/public/types.ts", "deprecated": false, @@ -3071,7 +3071,7 @@ "\nThe security solution generic experimental features" ], "signature": [ - "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutDisabled: boolean; readonly securitySolutionNotesEnabled: boolean; readonly entityAlertPreviewEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly AIAssistantOnRuleCreationFormEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly alertSuppressionForEsqlRuleEnabled: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineEnabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly prebuiltRulesCustomizationEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly aiAssistantFlyoutMode: boolean; readonly valueListItemsModalEnabled: boolean; readonly bulkCustomHighlightedFieldsEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; }" + "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutDisabled: boolean; readonly securitySolutionNotesEnabled: boolean; readonly entityAlertPreviewEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly AIAssistantOnRuleCreationFormEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly alertSuppressionForEsqlRuleEnabled: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly alertSuppressionForMachineLearningRuleEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineEnabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly prebuiltRulesCustomizationEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly aiAssistantFlyoutMode: boolean; readonly valueListItemsModalEnabled: boolean; readonly bulkCustomHighlightedFieldsEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/server/plugin_contract.ts", "deprecated": false, @@ -3247,7 +3247,7 @@ "label": "ExperimentalFeatures", "description": [], "signature": [ - "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutDisabled: boolean; readonly securitySolutionNotesEnabled: boolean; readonly entityAlertPreviewEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly AIAssistantOnRuleCreationFormEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly alertSuppressionForEsqlRuleEnabled: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineEnabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly prebuiltRulesCustomizationEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly aiAssistantFlyoutMode: boolean; readonly valueListItemsModalEnabled: boolean; readonly bulkCustomHighlightedFieldsEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; }" + "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutDisabled: boolean; readonly securitySolutionNotesEnabled: boolean; readonly entityAlertPreviewEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly AIAssistantOnRuleCreationFormEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly alertSuppressionForEsqlRuleEnabled: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly alertSuppressionForMachineLearningRuleEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineEnabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly prebuiltRulesCustomizationEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly aiAssistantFlyoutMode: boolean; readonly valueListItemsModalEnabled: boolean; readonly bulkCustomHighlightedFieldsEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/common/experimental_features.ts", "deprecated": false, @@ -3313,7 +3313,7 @@ "\nA list of allowed values that can be used in `xpack.securitySolution.enableExperimental`.\nThis object is then used to validate and parse the value entered." ], "signature": [ - "{ readonly excludePoliciesInFilterEnabled: false; readonly kubernetesEnabled: true; readonly donutChartEmbeddablesEnabled: false; readonly previewTelemetryUrlEnabled: false; readonly extendedRuleExecutionLoggingEnabled: false; readonly socTrendsEnabled: false; readonly responseActionsEnabled: true; readonly endpointResponseActionsEnabled: true; readonly responseActionUploadEnabled: true; readonly automatedProcessActionsEnabled: true; readonly responseActionsSentinelOneV1Enabled: true; readonly responseActionsSentinelOneV2Enabled: true; readonly responseActionsSentinelOneGetFileEnabled: true; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: true; readonly responseActionScanEnabled: false; readonly alertsPageChartsEnabled: true; readonly alertTypeEnabled: false; readonly expandableFlyoutDisabled: false; readonly securitySolutionNotesEnabled: false; readonly entityAlertPreviewEnabled: false; readonly assistantModelEvaluation: false; readonly assistantKnowledgeBaseByDefault: false; readonly newUserDetailsFlyoutManagedUser: false; readonly riskScoringPersistence: true; readonly riskScoringRoutesEnabled: true; readonly esqlRulesDisabled: false; readonly protectionUpdatesEnabled: true; readonly AIAssistantOnRuleCreationFormEnabled: false; readonly disableTimelineSaveTour: false; readonly alertSuppressionForEsqlRuleEnabled: false; readonly riskEnginePrivilegesRouteEnabled: true; readonly sentinelOneDataInAnalyzerEnabled: true; readonly sentinelOneManualHostActionsEnabled: true; readonly crowdstrikeDataInAnalyzerEnabled: true; readonly jamfDataInAnalyzerEnabled: false; readonly timelineEsqlTabDisabled: false; readonly unifiedComponentsInTimelineEnabled: false; readonly analyzerDatePickersAndSourcererDisabled: false; readonly prebuiltRulesCustomizationEnabled: false; readonly malwareOnWriteScanOptionAvailable: true; readonly unifiedManifestEnabled: true; readonly aiAssistantFlyoutMode: true; readonly valueListItemsModalEnabled: true; readonly bulkCustomHighlightedFieldsEnabled: false; readonly manualRuleRunEnabled: false; readonly filterProcessDescendantsForEventFiltersEnabled: false; }" + "{ readonly excludePoliciesInFilterEnabled: false; readonly kubernetesEnabled: true; readonly donutChartEmbeddablesEnabled: false; readonly previewTelemetryUrlEnabled: false; readonly extendedRuleExecutionLoggingEnabled: false; readonly socTrendsEnabled: false; readonly responseActionsEnabled: true; readonly endpointResponseActionsEnabled: true; readonly responseActionUploadEnabled: true; readonly automatedProcessActionsEnabled: true; readonly responseActionsSentinelOneV1Enabled: true; readonly responseActionsSentinelOneV2Enabled: true; readonly responseActionsSentinelOneGetFileEnabled: true; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: true; readonly responseActionScanEnabled: false; readonly alertsPageChartsEnabled: true; readonly alertTypeEnabled: false; readonly expandableFlyoutDisabled: false; readonly securitySolutionNotesEnabled: false; readonly entityAlertPreviewEnabled: false; readonly assistantModelEvaluation: false; readonly assistantKnowledgeBaseByDefault: false; readonly newUserDetailsFlyoutManagedUser: false; readonly riskScoringPersistence: true; readonly riskScoringRoutesEnabled: true; readonly esqlRulesDisabled: false; readonly protectionUpdatesEnabled: true; readonly AIAssistantOnRuleCreationFormEnabled: false; readonly disableTimelineSaveTour: false; readonly alertSuppressionForEsqlRuleEnabled: false; readonly riskEnginePrivilegesRouteEnabled: true; readonly alertSuppressionForMachineLearningRuleEnabled: false; readonly sentinelOneDataInAnalyzerEnabled: true; readonly sentinelOneManualHostActionsEnabled: true; readonly crowdstrikeDataInAnalyzerEnabled: true; readonly jamfDataInAnalyzerEnabled: false; readonly timelineEsqlTabDisabled: false; readonly unifiedComponentsInTimelineEnabled: false; readonly analyzerDatePickersAndSourcererDisabled: false; readonly prebuiltRulesCustomizationEnabled: false; readonly malwareOnWriteScanOptionAvailable: true; readonly unifiedManifestEnabled: true; readonly aiAssistantFlyoutMode: true; readonly valueListItemsModalEnabled: true; readonly bulkCustomHighlightedFieldsEnabled: false; readonly manualRuleRunEnabled: false; readonly filterProcessDescendantsForEventFiltersEnabled: false; }" ], "path": "x-pack/plugins/security_solution/common/experimental_features.ts", "deprecated": false, diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index c787d3dd3ae1a..c5b7688b40dda 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/security_solution_ess.mdx b/api_docs/security_solution_ess.mdx index 939fb812406ad..bde426c96f48d 100644 --- a/api_docs/security_solution_ess.mdx +++ b/api_docs/security_solution_ess.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionEss title: "securitySolutionEss" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionEss plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolutionEss'] --- import securitySolutionEssObj from './security_solution_ess.devdocs.json'; diff --git a/api_docs/security_solution_serverless.mdx b/api_docs/security_solution_serverless.mdx index b36e0aaedb6d8..d43ad2088a426 100644 --- a/api_docs/security_solution_serverless.mdx +++ b/api_docs/security_solution_serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionServerless title: "securitySolutionServerless" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionServerless plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolutionServerless'] --- import securitySolutionServerlessObj from './security_solution_serverless.devdocs.json'; diff --git a/api_docs/serverless.mdx b/api_docs/serverless.mdx index 59451b72aad34..b18d9e13f0e29 100644 --- a/api_docs/serverless.mdx +++ b/api_docs/serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverless title: "serverless" image: https://source.unsplash.com/400x175/?github description: API docs for the serverless plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverless'] --- import serverlessObj from './serverless.devdocs.json'; diff --git a/api_docs/serverless_observability.mdx b/api_docs/serverless_observability.mdx index 8720cbb138cb0..c89a73156780f 100644 --- a/api_docs/serverless_observability.mdx +++ b/api_docs/serverless_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessObservability title: "serverlessObservability" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessObservability plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessObservability'] --- import serverlessObservabilityObj from './serverless_observability.devdocs.json'; diff --git a/api_docs/serverless_search.mdx b/api_docs/serverless_search.mdx index 603ceb886e750..7fe245b5eef17 100644 --- a/api_docs/serverless_search.mdx +++ b/api_docs/serverless_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessSearch title: "serverlessSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessSearch plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessSearch'] --- import serverlessSearchObj from './serverless_search.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index 7ab8f0d7331a0..b872ddfcbd7ef 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index 96d9aead69b30..5efb83eda2eed 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/slo.mdx b/api_docs/slo.mdx index 1ee5c0b32a77a..f88595de917a8 100644 --- a/api_docs/slo.mdx +++ b/api_docs/slo.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/slo title: "slo" image: https://source.unsplash.com/400x175/?github description: API docs for the slo plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'slo'] --- import sloObj from './slo.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 95ae6e9fbabd1..a4dd6d967e103 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index 9c465c1c8c376..b11b515d81b4c 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index 1573ee8d57e73..22cadf5e3bd93 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index dae0e36e80fec..30d867e6537bc 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index 0c579cb1362ca..71e2a6c4aa363 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 4a5f59c70a638..0a7024331eead 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index a32e11943a810..12c2fca9a6fad 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index 42c2dd2aef7ce..7e83d82956376 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] --- import telemetryCollectionXpackObj from './telemetry_collection_xpack.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index b9ecf1338bf48..3f8a0f8e7030c 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/text_based_languages.mdx b/api_docs/text_based_languages.mdx index ed28d15be5534..649d488386060 100644 --- a/api_docs/text_based_languages.mdx +++ b/api_docs/text_based_languages.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/textBasedLanguages title: "textBasedLanguages" image: https://source.unsplash.com/400x175/?github description: API docs for the textBasedLanguages plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'textBasedLanguages'] --- import textBasedLanguagesObj from './text_based_languages.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index d8c1651b89472..70ffb2a2f29df 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.devdocs.json b/api_docs/timelines.devdocs.json index 49dc7a9ed7b5a..1dbc6c1702f89 100644 --- a/api_docs/timelines.devdocs.json +++ b/api_docs/timelines.devdocs.json @@ -1644,6 +1644,18 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/threatmatch_input/index.tsx" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx" diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 7156045c0436e..a626d21d4d91f 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index e933c13c2622b..931f49b4d2549 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index b8ec0273fd277..2718fe6ad9a64 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index dc85949f758df..d205d4a6a6b47 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index 1712adee943c4..1e219825899d7 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_doc_viewer.mdx b/api_docs/unified_doc_viewer.mdx index ff7731e38354b..e75531b5a08b5 100644 --- a/api_docs/unified_doc_viewer.mdx +++ b/api_docs/unified_doc_viewer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedDocViewer title: "unifiedDocViewer" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedDocViewer plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedDocViewer'] --- import unifiedDocViewerObj from './unified_doc_viewer.devdocs.json'; diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index 71725a9feac0c..7971b588823c4 100644 --- a/api_docs/unified_histogram.mdx +++ b/api_docs/unified_histogram.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedHistogram title: "unifiedHistogram" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedHistogram plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedHistogram'] --- import unifiedHistogramObj from './unified_histogram.devdocs.json'; diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 791f2544a4ff4..01f3cb26b1283 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index abfaeced9aff6..0d196da450fb8 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/uptime.mdx b/api_docs/uptime.mdx index 1f2730c092539..98ae9800e33e9 100644 --- a/api_docs/uptime.mdx +++ b/api_docs/uptime.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uptime title: "uptime" image: https://source.unsplash.com/400x175/?github description: API docs for the uptime plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uptime'] --- import uptimeObj from './uptime.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index b541f5df3c3ba..b7a52c67051ad 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index 3dec965bd703f..c87cffe2a5204 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index 66f09ca821f94..c6b23229e9a9f 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index 87fc77f3cd8fd..ccb746bc4f629 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index 8bb52f28b89c3..996d6169acaee 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index d473e7e86c68e..4368d698226a5 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index 829f998543e44..2346e3f40f5cf 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index 2054d85d13760..80a0b0fde21b7 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index 31eb23a29d730..fdeeefc3ed663 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index b12148b9cadb9..9c7a592c9ddda 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index 07cad724acd81..74038a3a81b30 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index b9115d3caab61..8c14dbd90d8f4 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 5985d38baab23..6336eb414e724 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 8c822b962314e..a019b4c4840b9 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2024-07-02 +date: 2024-07-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; From 1089f57b51904d20737ea49bfd1505b63e373c41 Mon Sep 17 00:00:00 2001 From: James Gowdy <jgowdy@elastic.co> Date: Wed, 3 Jul 2024 07:56:43 +0100 Subject: [PATCH 120/128] [ML] Fixing upgrade warning (#187387) The index migration checks were broken, so we were not showing our warnings when upgrade mode is set to `true` <img width="1139" alt="image" src="https://github.com/elastic/kibana/assets/22172091/8ede466b-c9e9-4ad9-a21a-d7e955b82247"> Also adds the warning to the ML stack management page so it is correctly displayed rather than an access denied page. <img width="1140" alt="image" src="https://github.com/elastic/kibana/assets/22172091/b78e4a60-109b-4ea7-8c7f-021ad26a3313"> To test, set `POST _ml/set_upgrade_mode?enabled=true` Fixes https://github.com/elastic/kibana/issues/176773 --- .../capabilities/check_capabilities.ts | 28 +++++++++++++++++++ .../capabilities/get_capabilities.ts | 14 +--------- .../components/upgrade/upgrade_warning.tsx | 6 ++-- .../jobs_list_page/jobs_list_page.tsx | 26 +++++++++++++++++ .../application/services/upgrade_service.ts | 16 ----------- 5 files changed, 59 insertions(+), 31 deletions(-) delete mode 100644 x-pack/plugins/ml/public/application/services/upgrade_service.ts diff --git a/x-pack/plugins/ml/public/application/capabilities/check_capabilities.ts b/x-pack/plugins/ml/public/application/capabilities/check_capabilities.ts index f974ace6f4d06..e7102560e0e02 100644 --- a/x-pack/plugins/ml/public/application/capabilities/check_capabilities.ts +++ b/x-pack/plugins/ml/public/application/capabilities/check_capabilities.ts @@ -48,6 +48,7 @@ export class MlCapabilitiesService { private _isPlatinumOrTrialLicense$ = new BehaviorSubject<boolean | null>(null); private _mlFeatureEnabledInSpace$ = new BehaviorSubject<boolean | null>(null); + private _isUpgradeInProgress$ = new BehaviorSubject<boolean | null>(null); public capabilities$ = this._capabilities$.pipe(distinctUntilChanged(isEqual)); @@ -73,6 +74,7 @@ export class MlCapabilitiesService { this._capabilities$.next(results.capabilities); this._isPlatinumOrTrialLicense$.next(results.isPlatinumOrTrialLicense); this._mlFeatureEnabledInSpace$.next(results.mlFeatureEnabledInSpace); + this._isUpgradeInProgress$.next(results.upgradeInProgress); this._isLoading$.next(false); /** @@ -94,6 +96,14 @@ export class MlCapabilitiesService { return this._mlFeatureEnabledInSpace$.getValue(); } + public isUpgradeInProgress$() { + return this._isUpgradeInProgress$; + } + + public isUpgradeInProgress(): boolean | null { + return this._isUpgradeInProgress$.getValue(); + } + public getCapabilities$() { return this._capabilitiesObs$; } @@ -137,6 +147,23 @@ export function usePermissionCheck<T extends MlCapabilitiesKey | MlCapabilitiesK }, [capabilities]); } +/** + * Check whether upgrade mode has been set. + */ +export function useUpgradeCheck(): boolean { + const { + services: { + mlServices: { mlCapabilities: mlCapabilitiesService }, + }, + } = useMlKibana(); + + const isUpgradeInProgress = useObservable( + mlCapabilitiesService.isUpgradeInProgress$(), + mlCapabilitiesService.isUpgradeInProgress() + ); + return isUpgradeInProgress ?? false; +} + export function checkGetManagementMlJobsResolver({ mlCapabilities }: MlGlobalServices) { return new Promise<void>(async (resolve, reject) => { try { @@ -160,6 +187,7 @@ export function checkGetManagementMlJobsResolver({ mlCapabilities }: MlGlobalSer capabilities, isPlatinumOrTrialLicense: mlCapabilities.isPlatinumOrTrialLicense(), mlFeatureEnabledInSpace: mlCapabilities.mlFeatureEnabledInSpace(), + isUpgradeInProgress: mlCapabilities.isUpgradeInProgress(), }); } } catch (error) { diff --git a/x-pack/plugins/ml/public/application/capabilities/get_capabilities.ts b/x-pack/plugins/ml/public/application/capabilities/get_capabilities.ts index 395dce9312dcd..ed4725b9ffde3 100644 --- a/x-pack/plugins/ml/public/application/capabilities/get_capabilities.ts +++ b/x-pack/plugins/ml/public/application/capabilities/get_capabilities.ts @@ -7,20 +7,8 @@ import { ml } from '../services/ml_api_service'; -import { setUpgradeInProgress } from '../services/upgrade_service'; import type { MlCapabilitiesResponse } from '../../../common/types/capabilities'; export function getCapabilities(): Promise<MlCapabilitiesResponse> { - return new Promise((resolve, reject) => { - ml.checkMlCapabilities() - .then((resp: MlCapabilitiesResponse) => { - if (resp.upgradeInProgress === true) { - setUpgradeInProgress(true); - } - resolve(resp); - }) - .catch(() => { - reject(); - }); - }); + return ml.checkMlCapabilities(); } diff --git a/x-pack/plugins/ml/public/application/components/upgrade/upgrade_warning.tsx b/x-pack/plugins/ml/public/application/components/upgrade/upgrade_warning.tsx index 6ffe1dea26768..7863b542f618c 100644 --- a/x-pack/plugins/ml/public/application/components/upgrade/upgrade_warning.tsx +++ b/x-pack/plugins/ml/public/application/components/upgrade/upgrade_warning.tsx @@ -11,10 +11,12 @@ import React from 'react'; import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { isUpgradeInProgress } from '../../services/upgrade_service'; +import { useUpgradeCheck } from '../../capabilities/check_capabilities'; export const UpgradeWarning: FC = () => { - if (isUpgradeInProgress() === true) { + const isUpgradeInProgress = useUpgradeCheck(); + + if (isUpgradeInProgress === true) { return ( <React.Fragment> <EuiCallOut diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx index ca0617bedc1c7..6c31aa8d043bf 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx +++ b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx @@ -29,6 +29,7 @@ import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import type { SharePluginStart } from '@kbn/share-plugin/public'; import type { SpacesContextProps, SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; +import { UpgradeWarning } from '../../../../components/upgrade/upgrade_warning'; import { getMlGlobalServices } from '../../../../util/get_services'; import { EnabledFeaturesContextProvider } from '../../../../contexts/ml'; import { type MlFeatures, PLUGIN_ID } from '../../../../../../common/constants/app'; @@ -71,6 +72,7 @@ export const JobsListPage: FC<Props> = ({ }) => { const [initialized, setInitialized] = useState(false); const [accessDenied, setAccessDenied] = useState(false); + const [isUpgradeInProgress, setIsUpgradeInProgress] = useState(false); const [isPlatinumOrTrialLicense, setIsPlatinumOrTrialLicense] = useState(true); const [showSyncFlyout, setShowSyncFlyout] = useState(false); const [currentTabId, setCurrentTabId] = useState<MlSavedObjectType>('anomaly-detector'); @@ -88,6 +90,8 @@ export const JobsListPage: FC<Props> = ({ } catch (e) { if (e.mlFeatureEnabledInSpace && e.isPlatinumOrTrialLicense === false) { setIsPlatinumOrTrialLicense(false); + } else if (e.isUpgradeInProgress) { + setIsUpgradeInProgress(true); } else { setAccessDenied(true); } @@ -117,6 +121,28 @@ export const JobsListPage: FC<Props> = ({ setShowSyncFlyout(false); } + if (isUpgradeInProgress) { + return ( + <I18nProvider> + <KibanaRenderContextProvider {...coreStart}> + <KibanaContextProvider + services={{ + ...coreStart, + share, + data, + usageCollection, + fieldFormats, + spacesApi, + mlServices, + }} + > + <UpgradeWarning /> + </KibanaContextProvider> + </KibanaRenderContextProvider> + </I18nProvider> + ); + } + if (accessDenied) { return ( <I18nProvider> diff --git a/x-pack/plugins/ml/public/application/services/upgrade_service.ts b/x-pack/plugins/ml/public/application/services/upgrade_service.ts deleted file mode 100644 index d72a9adddfcaa..0000000000000 --- a/x-pack/plugins/ml/public/application/services/upgrade_service.ts +++ /dev/null @@ -1,16 +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. - */ - -let upgradeInProgress: boolean = false; - -export function setUpgradeInProgress(show: boolean) { - upgradeInProgress = show; -} - -export function isUpgradeInProgress(): boolean { - return upgradeInProgress; -} From 28046696a22f6c32cfcbb244a29e3394b72b6115 Mon Sep 17 00:00:00 2001 From: Pablo Machado <pablo.nevesmachado@elastic.co> Date: Wed, 3 Jul 2024 09:16:37 +0200 Subject: [PATCH 121/128] [Security Solution] Fix risk score should not 404 when alerts index doesn't exist (#187158) ## Summary * It adds a parameter to the risk score calculation that prevents it from falling when the alert's index doesn't exist. ### How to reproduce it? Please take a look at the original issue https://github.com/elastic/kibana/issues/187052 --- .../risk_score/calculate_risk_scores.ts | 1 + .../trial_license_complete_tier/risk_score_preview.ts | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.ts index 27ef27b80070b..a26b1eb4b4f15 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.ts @@ -244,6 +244,7 @@ export const calculateRiskScores = async ({ size: 0, _source: false, index, + ignore_unavailable: true, runtime_mappings: runtimeMappings, query: { function_score: { diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_preview.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_preview.ts index dfd7efe8d6583..b2b72cc5a4b37 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_preview.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_preview.ts @@ -566,5 +566,16 @@ export default ({ getService }: FtrProviderContext): void => { }); }); }); + + it('does not return an 404 when the data_view_id is an non existent index', async () => { + const { scores } = await previewRiskScores({ + body: { data_view_id: 'invalid-index' }, + }); + + expect(scores).to.eql({ + host: [], + user: [], + }); + }); }); }; From faada573c4aa056d120108aa30f42b1dd62a4d30 Mon Sep 17 00:00:00 2001 From: Philippe Oberti <philippe.oberti@elastic.co> Date: Wed, 3 Jul 2024 09:25:05 +0200 Subject: [PATCH 122/128] [Security Solution][Notes] - prevent user from adding note if the markdown is invalid (#187346) --- .../left/components/add_note.test.tsx | 14 ++++++++++++++ .../left/components/add_note.tsx | 17 +++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/add_note.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/add_note.test.tsx index 9c3319aa9e9d2..980cb97d6edae 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/add_note.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/add_note.test.tsx @@ -18,6 +18,7 @@ import { import { ReqStatus } from '../../../../notes/store/notes.slice'; import { useIsTimelineFlyoutOpen } from '../../shared/hooks/use_is_timeline_flyout_open'; import { TimelineId } from '../../../../../common/types'; +import userEvent from '@testing-library/user-event'; jest.mock('../../shared/hooks/use_is_timeline_flyout_open'); @@ -56,11 +57,24 @@ describe('AddNote', () => { it('should create note', () => { const { getByTestId } = renderAddNote(); + userEvent.type(getByTestId('euiMarkdownEditorTextArea'), 'new note'); getByTestId(ADD_NOTE_BUTTON_TEST_ID).click(); expect(mockDispatch).toHaveBeenCalled(); }); + it('should disable add button markdown editor if invalid', () => { + const { getByTestId } = renderAddNote(); + + const addButton = getByTestId(ADD_NOTE_BUTTON_TEST_ID); + + expect(addButton).toHaveAttribute('disabled'); + + userEvent.type(getByTestId('euiMarkdownEditorTextArea'), 'new note'); + + expect(addButton).not.toHaveAttribute('disabled'); + }); + it('should render the add note button in loading state while creating a new note', () => { const store = createMockStore({ ...mockGlobalState, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/add_note.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/add_note.tsx index d89bcfb23a97c..6d66193f30efa 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/add_note.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/add_note.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useCallback, useEffect, useState } from 'react'; +import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { EuiButton, EuiCheckbox, @@ -83,6 +83,7 @@ export const AddNote = memo(({ eventId }: AddNewNoteProps) => { const dispatch = useDispatch(); const { addError: addErrorToast } = useAppToasts(); const [editorValue, setEditorValue] = useState(''); + const [isMarkdownInvalid, setIsMarkdownInvalid] = useState(false); const activeTimeline = useSelector((state: State) => timelineSelectors.selectTimelineById(state, TimelineId.active) @@ -121,8 +122,15 @@ export const AddNote = memo(({ eventId }: AddNewNoteProps) => { } }, [addErrorToast, createError, createStatus]); - const checkBoxDisabled = - !isTimelineFlyout || (isTimelineFlyout && activeTimeline.savedObjectId == null); + const buttonDisabled = useMemo( + () => editorValue.trim().length === 0 || isMarkdownInvalid, + [editorValue, isMarkdownInvalid] + ); + + const checkBoxDisabled = useMemo( + () => !isTimelineFlyout || (isTimelineFlyout && activeTimeline?.savedObjectId == null), + [activeTimeline?.savedObjectId, isTimelineFlyout] + ); return ( <> @@ -133,7 +141,7 @@ export const AddNote = memo(({ eventId }: AddNewNoteProps) => { value={editorValue} onChange={setEditorValue} ariaLabel={MARKDOWN_ARIA_LABEL} - setIsMarkdownInvalid={() => {}} + setIsMarkdownInvalid={setIsMarkdownInvalid} /> </EuiComment> </EuiCommentList> @@ -167,6 +175,7 @@ export const AddNote = memo(({ eventId }: AddNewNoteProps) => { <EuiButton onClick={addNote} isLoading={createStatus === ReqStatus.Loading} + disabled={buttonDisabled} data-test-subj={ADD_NOTE_BUTTON_TEST_ID} > {ADD_NOTE_BUTTON} From 81367bba9a8f502a36eb736533962b2c8a782fe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= <alejandro.haro@elastic.co> Date: Wed, 3 Jul 2024 10:19:26 +0200 Subject: [PATCH 123/128] [Security-in-core] Cloud Chat (#187306) --- .../cloud_chat/kibana.jsonc | 1 - .../cloud_chat/public/plugin.test.ts | 32 +-- .../cloud_chat/public/plugin.tsx | 20 +- .../cloud_chat/server/plugin.ts | 5 +- .../cloud_chat/server/routes/chat.test.ts | 217 ++++++++++++------ .../cloud_chat/server/routes/chat.ts | 27 ++- .../cloud_chat/tsconfig.json | 3 +- 7 files changed, 176 insertions(+), 129 deletions(-) diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/kibana.jsonc b/x-pack/plugins/cloud_integrations/cloud_chat/kibana.jsonc index 62f97a69e22f5..293d5f0baf3d7 100644 --- a/x-pack/plugins/cloud_integrations/cloud_chat/kibana.jsonc +++ b/x-pack/plugins/cloud_integrations/cloud_chat/kibana.jsonc @@ -18,7 +18,6 @@ "requiredBundles": [ ], "optionalPlugins": [ - "security", "cloudExperiments" ] } diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.test.ts b/x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.test.ts index f44c7cd5112e3..f2142d431c634 100644 --- a/x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.test.ts +++ b/x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.test.ts @@ -6,43 +6,35 @@ */ import { coreMock } from '@kbn/core/public/mocks'; -import { securityMock } from '@kbn/security-plugin/public/mocks'; import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; import type { CloudChatConfigType } from '../server/config'; import { CloudChatPlugin } from './plugin'; +import { type MockedLogger } from '@kbn/logging-mocks'; describe('Cloud Chat Plugin', () => { describe('#setup', () => { describe('setupChat', () => { - let consoleMock: jest.SpyInstance<void, [message?: any, ...optionalParams: any[]]>; let newTrialEndDate: Date; + let logger: MockedLogger; beforeEach(() => { - consoleMock = jest.spyOn(console, 'debug').mockImplementation(() => {}); newTrialEndDate = new Date(); newTrialEndDate.setDate(new Date().getDate() + 14); }); - afterEach(() => { - consoleMock.mockRestore(); - }); - const setupPlugin = async ({ config = {}, - securityEnabled = true, - currentUserProps = {}, isCloudEnabled = true, failHttp = false, trialEndDate = newTrialEndDate, }: { config?: Partial<CloudChatConfigType>; - securityEnabled?: boolean; - currentUserProps?: Record<string, any>; isCloudEnabled?: boolean; failHttp?: boolean; trialEndDate?: Date; }) => { const initContext = coreMock.createPluginInitializerContext(config); + logger = initContext.logger as MockedLogger; const plugin = new CloudChatPlugin(initContext); @@ -50,25 +42,22 @@ describe('Cloud Chat Plugin', () => { const coreStart = coreMock.createStart(); if (failHttp) { - coreSetup.http.get.mockImplementation(() => { + coreSetup.http.get.mockImplementation(async () => { throw new Error('HTTP request failed'); }); } coreSetup.getStartServices.mockResolvedValue([coreStart, {}, undefined]); - const securitySetup = securityMock.createSetup(); - securitySetup.authc.getCurrentUser.mockResolvedValue( - securityMock.createMockAuthenticatedUser(currentUserProps) - ); - const cloud = cloudMock.createSetup(); plugin.setup(coreSetup, { cloud: { ...cloud, isCloudEnabled, trialEndDate }, - ...(securityEnabled ? { security: securitySetup } : {}), }); + // Wait for the async processes to complete + await new Promise((resolve) => process.nextTick(resolve)); + return { initContext, plugin, coreSetup }; }; @@ -77,11 +66,6 @@ describe('Cloud Chat Plugin', () => { expect(coreSetup.http.get).not.toHaveBeenCalled(); }); - it('chatConfig is not retrieved if security is not enabled', async () => { - const { coreSetup } = await setupPlugin({ securityEnabled: false }); - expect(coreSetup.http.get).not.toHaveBeenCalled(); - }); - it('chatConfig is not retrieved if chat is enabled but url is not provided', async () => { // @ts-expect-error 2741 const { coreSetup } = await setupPlugin({ config: { chat: { enabled: true } } }); @@ -94,7 +78,7 @@ describe('Cloud Chat Plugin', () => { failHttp: true, }); expect(coreSetup.http.get).toHaveBeenCalled(); - expect(consoleMock).toHaveBeenCalled(); + expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining(`Error setting up Chat`)); }); it('chatConfig is not retrieved if chat is enabled and url is provided but trial has expired', async () => { diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.tsx b/x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.tsx index d7dcb4763b67d..c6d527808549b 100755 --- a/x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.tsx +++ b/x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.tsx @@ -8,11 +8,11 @@ import React, { type FC, type PropsWithChildren } from 'react'; import ReactDOM from 'react-dom'; import useObservable from 'react-use/lib/useObservable'; +import { ReplaySubject, first } from 'rxjs'; +import type { Logger } from '@kbn/logging'; import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; import type { HttpSetup } from '@kbn/core-http-browser'; -import type { SecurityPluginSetup } from '@kbn/security-plugin/public'; import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; -import { ReplaySubject, first } from 'rxjs'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import type { ChatVariant, GetChatUserDataResponseBody } from '../common/types'; import { GET_CHAT_USER_DATA_ROUTE_PATH } from '../common/constants'; @@ -22,7 +22,6 @@ import { ChatExperimentSwitcher } from './components/chat_experiment_switcher'; interface CloudChatSetupDeps { cloud: CloudSetup; - security?: SecurityPluginSetup; } interface CloudChatStartDeps { @@ -40,6 +39,7 @@ interface CloudChatConfig { export class CloudChatPlugin implements Plugin<void, void, CloudChatSetupDeps, CloudChatStartDeps> { private readonly config: CloudChatConfig; + private readonly logger: Logger; private chatConfig$ = new ReplaySubject<ChatConfig>(1); private kbnVersion: string; private kbnBuildNum: number; @@ -48,12 +48,12 @@ export class CloudChatPlugin implements Plugin<void, void, CloudChatSetupDeps, C this.kbnVersion = initializerContext.env.packageInfo.version; this.kbnBuildNum = initializerContext.env.packageInfo.buildNum; this.config = initializerContext.config.get(); + this.logger = initializerContext.logger.get(); } - public setup(core: CoreSetup, { cloud, security }: CloudChatSetupDeps) { - this.setupChat({ http: core.http, cloud, security }).catch((e) => - // eslint-disable-next-line no-console - console.debug(`Error setting up Chat: ${e.toString()}`) + public setup(core: CoreSetup, { cloud }: CloudChatSetupDeps) { + this.setupChat({ http: core.http, cloud }).catch((e) => + this.logger.debug(`Error setting up Chat: ${e.toString()}`) ); } @@ -92,12 +92,11 @@ export class CloudChatPlugin implements Plugin<void, void, CloudChatSetupDeps, C public stop() {} - private async setupChat({ cloud, http, security }: SetupChatDeps) { + private async setupChat({ cloud, http }: SetupChatDeps) { const { isCloudEnabled, trialEndDate } = cloud; const { chatURL, trialBuffer } = this.config; if ( - !security || !isCloudEnabled || !chatURL || !trialEndDate || @@ -131,8 +130,7 @@ export class CloudChatPlugin implements Plugin<void, void, CloudChatSetupDeps, C }, }); } catch (e) { - // eslint-disable-next-line no-console - console.debug( + this.logger.debug( `[cloud.chat] Could not retrieve chat config: ${e.response.status} ${e.message}`, e ); diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/server/plugin.ts b/x-pack/plugins/cloud_integrations/cloud_chat/server/plugin.ts index 633a8009522a1..a708dd81cf532 100755 --- a/x-pack/plugins/cloud_integrations/cloud_chat/server/plugin.ts +++ b/x-pack/plugins/cloud_integrations/cloud_chat/server/plugin.ts @@ -7,7 +7,6 @@ import { PluginInitializerContext, CoreSetup, Plugin } from '@kbn/core/server'; -import type { SecurityPluginSetup } from '@kbn/security-plugin/server'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { CloudExperimentsPluginStart } from '@kbn/cloud-experiments-plugin/common'; import { registerChatRoute } from './routes'; @@ -16,7 +15,6 @@ import type { ChatVariant } from '../common/types'; interface CloudChatSetupDeps { cloud: CloudSetup; - security?: SecurityPluginSetup; } interface CloudChatStartDeps { @@ -32,7 +30,7 @@ export class CloudChatPlugin implements Plugin<void, void, CloudChatSetupDeps, C this.isDev = initializerContext.env.mode.dev; } - public setup(core: CoreSetup<CloudChatStartDeps>, { cloud, security }: CloudChatSetupDeps) { + public setup(core: CoreSetup<CloudChatStartDeps>, { cloud }: CloudChatSetupDeps) { const { chatIdentitySecret, trialBuffer } = this.config; const { isCloudEnabled, trialEndDate } = cloud; @@ -42,7 +40,6 @@ export class CloudChatPlugin implements Plugin<void, void, CloudChatSetupDeps, C chatIdentitySecret, trialEndDate, trialBuffer, - security, isDev: this.isDev, getChatVariant: () => core.getStartServices().then(([_, { cloudExperiments }]) => { diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/server/routes/chat.test.ts b/x-pack/plugins/cloud_integrations/cloud_chat/server/routes/chat.test.ts index 7204d248fa762..94a55b2274a99 100644 --- a/x-pack/plugins/cloud_integrations/cloud_chat/server/routes/chat.test.ts +++ b/x-pack/plugins/cloud_integrations/cloud_chat/server/routes/chat.test.ts @@ -11,37 +11,69 @@ jest.mock('jsonwebtoken', () => ({ }, })); -import { httpServiceMock, httpServerMock } from '@kbn/core/server/mocks'; -import { securityMock } from '@kbn/security-plugin/server/mocks'; +import { + httpServiceMock, + httpServerMock, + coreMock, + securityServiceMock, +} from '@kbn/core/server/mocks'; import { kibanaResponseFactory } from '@kbn/core/server'; -import { registerChatRoute } from './chat'; +import { type MetaWithSaml, registerChatRoute } from './chat'; import { ChatVariant } from '../../common/types'; describe('chat route', () => { const getChatVariant = async (): Promise<ChatVariant> => 'header'; const getChatDisabledThroughExperiments = async (): Promise<boolean> => false; + let security: ReturnType<typeof securityServiceMock.createRequestHandlerContext>; + let requestHandlerContextMock: ReturnType<typeof coreMock.createCustomRequestHandlerContext>; + + beforeEach(() => { + const core = coreMock.createRequestHandlerContext(); + security = core.security; + requestHandlerContextMock = coreMock.createCustomRequestHandlerContext({ core }); + }); + + test('error if no user', async () => { + security.authc.getCurrentUser.mockReturnValueOnce(null); - test('do not add the route if security is not enabled', async () => { const router = httpServiceMock.createRouter(); registerChatRoute({ router, isDev: false, chatIdentitySecret: 'secret', trialBuffer: 60, + trialEndDate: new Date(), getChatVariant, getChatDisabledThroughExperiments, }); - expect(router.get.mock.calls).toEqual([]); + + const [_config, handler] = router.get.mock.calls[0]; + + await expect( + handler( + requestHandlerContextMock, + httpServerMock.createKibanaRequest(), + kibanaResponseFactory + ) + ).resolves.toMatchInlineSnapshot(` + KibanaResponse { + "options": Object {}, + "payload": "Not Found", + "status": 404, + } + `); }); - test('error if no user', async () => { - const security = securityMock.createSetup(); - security.authc.getCurrentUser.mockReturnValueOnce(null); + test('error if no user is missing any details', async () => { + security.authc.getCurrentUser.mockReturnValueOnce( + securityServiceMock.createMockAuthenticatedUser({ + username: undefined, + }) + ); const router = httpServiceMock.createRouter(); registerChatRoute({ router, - security, isDev: false, chatIdentitySecret: 'secret', trialBuffer: 60, @@ -52,34 +84,39 @@ describe('chat route', () => { const [_config, handler] = router.get.mock.calls[0]; - await expect(handler({}, httpServerMock.createKibanaRequest(), kibanaResponseFactory)).resolves - .toMatchInlineSnapshot(` - KibanaResponse { - "options": Object { - "body": "User has no email or username", - }, - "payload": "User has no email or username", - "status": 400, - } - `); + await expect( + handler( + requestHandlerContextMock, + httpServerMock.createKibanaRequest(), + kibanaResponseFactory + ) + ).resolves.toMatchInlineSnapshot(` + KibanaResponse { + "options": Object { + "body": "User has no email or username", + }, + "payload": "User has no email or username", + "status": 400, + } + `); }); test('error if no trial end date specified', async () => { - const security = securityMock.createSetup(); const username = 'user.name'; const email = 'user@elastic.co'; - security.authc.getCurrentUser.mockReturnValueOnce({ - username, - metadata: { - saml_email: [email], - }, - }); + security.authc.getCurrentUser.mockReturnValueOnce( + securityServiceMock.createMockAuthenticatedUser({ + username, + metadata: { + saml_email: [email], + } as MetaWithSaml, + }) + ); const router = httpServiceMock.createRouter(); registerChatRoute({ router, - security, isDev: false, chatIdentitySecret: 'secret', trialBuffer: 2, @@ -89,8 +126,13 @@ describe('chat route', () => { const [_config, handler] = router.get.mock.calls[0]; - await expect(handler({}, httpServerMock.createKibanaRequest(), kibanaResponseFactory)).resolves - .toMatchInlineSnapshot(` + await expect( + handler( + requestHandlerContextMock, + httpServerMock.createKibanaRequest(), + kibanaResponseFactory + ) + ).resolves.toMatchInlineSnapshot(` KibanaResponse { "options": Object { "body": "Chat can only be started if a trial end date is specified", @@ -102,23 +144,23 @@ describe('chat route', () => { }); test('error if not in trial window', async () => { - const security = securityMock.createSetup(); const username = 'user.name'; const email = 'user@elastic.co'; - security.authc.getCurrentUser.mockReturnValueOnce({ - username, - metadata: { - saml_email: [email], - }, - }); + security.authc.getCurrentUser.mockReturnValueOnce( + securityServiceMock.createMockAuthenticatedUser({ + username, + metadata: { + saml_email: [email], + } as MetaWithSaml, + }) + ); const router = httpServiceMock.createRouter(); const trialEndDate = new Date(); trialEndDate.setDate(trialEndDate.getDate() - 30); registerChatRoute({ router, - security, isDev: false, chatIdentitySecret: 'secret', trialBuffer: 2, @@ -129,8 +171,13 @@ describe('chat route', () => { const [_config, handler] = router.get.mock.calls[0]; - await expect(handler({}, httpServerMock.createKibanaRequest(), kibanaResponseFactory)).resolves - .toMatchInlineSnapshot(` + await expect( + handler( + requestHandlerContextMock, + httpServerMock.createKibanaRequest(), + kibanaResponseFactory + ) + ).resolves.toMatchInlineSnapshot(` KibanaResponse { "options": Object { "body": "Chat can only be started during trial and trial chat buffer", @@ -142,21 +189,21 @@ describe('chat route', () => { }); test('error if disabled in experiments', async () => { - const security = securityMock.createSetup(); const username = 'user.name'; const email = 'user@elastic.co'; - security.authc.getCurrentUser.mockReturnValueOnce({ - username, - metadata: { - saml_email: [email], - }, - }); + security.authc.getCurrentUser.mockReturnValueOnce( + securityServiceMock.createMockAuthenticatedUser({ + username, + metadata: { + saml_email: [email], + } as MetaWithSaml, + }) + ); const router = httpServiceMock.createRouter(); registerChatRoute({ router, - security, isDev: false, chatIdentitySecret: 'secret', trialBuffer: 60, @@ -165,8 +212,13 @@ describe('chat route', () => { getChatDisabledThroughExperiments: async () => true, }); const [_config, handler] = router.get.mock.calls[0]; - await expect(handler({}, httpServerMock.createKibanaRequest(), kibanaResponseFactory)).resolves - .toMatchInlineSnapshot(` + await expect( + handler( + requestHandlerContextMock, + httpServerMock.createKibanaRequest(), + kibanaResponseFactory + ) + ).resolves.toMatchInlineSnapshot(` KibanaResponse { "options": Object { "body": "Chat is disabled through experiments", @@ -178,21 +230,21 @@ describe('chat route', () => { }); test('returns user information taken from saml metadata and a token', async () => { - const security = securityMock.createSetup(); const username = 'user.name'; const email = 'user@elastic.co'; - security.authc.getCurrentUser.mockReturnValueOnce({ - username, - metadata: { - saml_email: [email], - }, - }); + security.authc.getCurrentUser.mockReturnValueOnce( + securityServiceMock.createMockAuthenticatedUser({ + username, + metadata: { + saml_email: [email], + } as MetaWithSaml, + }) + ); const router = httpServiceMock.createRouter(); registerChatRoute({ router, - security, isDev: false, chatIdentitySecret: 'secret', trialBuffer: 60, @@ -201,8 +253,13 @@ describe('chat route', () => { getChatDisabledThroughExperiments, }); const [_config, handler] = router.get.mock.calls[0]; - await expect(handler({}, httpServerMock.createKibanaRequest(), kibanaResponseFactory)).resolves - .toMatchInlineSnapshot(` + await expect( + handler( + requestHandlerContextMock, + httpServerMock.createKibanaRequest(), + kibanaResponseFactory + ) + ).resolves.toMatchInlineSnapshot(` KibanaResponse { "options": Object { "body": Object { @@ -224,16 +281,18 @@ describe('chat route', () => { }); test('returns placeholder user information and a token in dev mode', async () => { - const security = securityMock.createSetup(); const username = 'first.last'; const email = 'test+first.last@elasticsearch.com'; - security.authc.getCurrentUser.mockReturnValueOnce({}); + security.authc.getCurrentUser.mockReturnValueOnce( + securityServiceMock.createMockAuthenticatedUser({ + username: undefined, + }) + ); const router = httpServiceMock.createRouter(); registerChatRoute({ router, - security, isDev: true, chatIdentitySecret: 'secret', trialBuffer: 60, @@ -242,8 +301,13 @@ describe('chat route', () => { getChatDisabledThroughExperiments, }); const [_config, handler] = router.get.mock.calls[0]; - await expect(handler({}, httpServerMock.createKibanaRequest(), kibanaResponseFactory)).resolves - .toMatchInlineSnapshot(` + await expect( + handler( + requestHandlerContextMock, + httpServerMock.createKibanaRequest(), + kibanaResponseFactory + ) + ).resolves.toMatchInlineSnapshot(` KibanaResponse { "options": Object { "body": Object { @@ -265,21 +329,21 @@ describe('chat route', () => { }); test('returns chat variant', async () => { - const security = securityMock.createSetup(); const username = 'user.name'; const email = 'user@elastic.co'; - security.authc.getCurrentUser.mockReturnValueOnce({ - username, - metadata: { - saml_email: [email], - }, - }); + security.authc.getCurrentUser.mockReturnValueOnce( + securityServiceMock.createMockAuthenticatedUser({ + username, + metadata: { + saml_email: [email], + } as MetaWithSaml, + }) + ); const router = httpServiceMock.createRouter(); registerChatRoute({ router, - security, isDev: false, chatIdentitySecret: 'secret', trialBuffer: 60, @@ -288,8 +352,13 @@ describe('chat route', () => { getChatDisabledThroughExperiments, }); const [_config, handler] = router.get.mock.calls[0]; - await expect(handler({}, httpServerMock.createKibanaRequest(), kibanaResponseFactory)).resolves - .toMatchInlineSnapshot(` + await expect( + handler( + requestHandlerContextMock, + httpServerMock.createKibanaRequest(), + kibanaResponseFactory + ) + ).resolves.toMatchInlineSnapshot(` KibanaResponse { "options": Object { "body": Object { diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/server/routes/chat.ts b/x-pack/plugins/cloud_integrations/cloud_chat/server/routes/chat.ts index d14500c18372e..735a5db9298c4 100644 --- a/x-pack/plugins/cloud_integrations/cloud_chat/server/routes/chat.ts +++ b/x-pack/plugins/cloud_integrations/cloud_chat/server/routes/chat.ts @@ -5,14 +5,13 @@ * 2.0. */ -import { IRouter } from '@kbn/core/server'; -import type { SecurityPluginSetup, AuthenticatedUser } from '@kbn/security-plugin/server'; +import type { AuthenticatedUser, IRouter } from '@kbn/core/server'; import { GET_CHAT_USER_DATA_ROUTE_PATH } from '../../common/constants'; import type { GetChatUserDataResponseBody, ChatVariant } from '../../common/types'; import { generateSignedJwt } from '../util/generate_jwt'; import { isTodayInDateWindow } from '../../common/util'; -type MetaWithSaml = AuthenticatedUser['metadata'] & { +export type MetaWithSaml = AuthenticatedUser['metadata'] & { saml_name: [string]; saml_email: [string]; saml_roles: [string]; @@ -24,7 +23,6 @@ export const registerChatRoute = ({ chatIdentitySecret, trialEndDate, trialBuffer, - security, isDev, getChatVariant, getChatDisabledThroughExperiments, @@ -33,7 +31,6 @@ export const registerChatRoute = ({ chatIdentitySecret: string; trialEndDate?: Date; trialBuffer: number; - security?: SecurityPluginSetup; isDev: boolean; getChatVariant: () => Promise<ChatVariant>; /** @@ -42,20 +39,22 @@ export const registerChatRoute = ({ */ getChatDisabledThroughExperiments: () => Promise<boolean>; }) => { - if (!security) { - return; - } - router.get( { path: GET_CHAT_USER_DATA_ROUTE_PATH, validate: {}, }, - async (_context, request, response) => { - const user = security.authc.getCurrentUser(request); - const { metadata, username } = user || {}; - let userId = username; - let [userEmail] = (metadata as MetaWithSaml)?.saml_email || []; + async (context, request, response) => { + const { security } = await context.core; + const user = security.authc.getCurrentUser(); + + if (!user) { + // Hide the API from unauthenticated users + return response.notFound(); + } + + let userId = user.username; + let [userEmail] = (user.metadata as MetaWithSaml)?.saml_email || []; // In local development, these values are not populated. This is a workaround // to allow for local testing. diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/tsconfig.json b/x-pack/plugins/cloud_integrations/cloud_chat/tsconfig.json index 0e062b06b5667..ffa21f10a6b44 100644 --- a/x-pack/plugins/cloud_integrations/cloud_chat/tsconfig.json +++ b/x-pack/plugins/cloud_integrations/cloud_chat/tsconfig.json @@ -13,7 +13,6 @@ "kbn_references": [ "@kbn/core", "@kbn/cloud-plugin", - "@kbn/security-plugin", "@kbn/storybook", "@kbn/core-http-browser", "@kbn/i18n", @@ -21,6 +20,8 @@ "@kbn/ui-theme", "@kbn/cloud-experiments-plugin", "@kbn/react-kibana-context-render", + "@kbn/logging", + "@kbn/logging-mocks", ], "exclude": [ "target/**/*", From d816fdf53855a8c4021a41a81e9cd3fe677d7cbe Mon Sep 17 00:00:00 2001 From: Carlos Crespo <crespocarlos@users.noreply.github.com> Date: Wed, 3 Jul 2024 10:21:26 +0200 Subject: [PATCH 124/128] [APM][Serverless] Remove universal profiling setting from Advanced Settings (#187371) ## Summary Remove the "Enable Universal Profiling integration in APM" setting from Advanced Settings Tab not displayed <img width="1466" alt="image" src="https://github.com/elastic/kibana/assets/2767137/48c80f8a-5805-4740-9a58-602eddad4576"> Setting not available <img width="1724" alt="image" src="https://github.com/elastic/kibana/assets/2767137/1871c6c9-0a6b-454c-b3a5-bbb067eb27d9"> # How to test - `yarn es serverless --projectType=oblt --clean --no-ssl` - `yarn start --serverless=oblt --no-ssl` - Navigate to Advanced Settings --- packages/serverless/settings/observability_project/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/serverless/settings/observability_project/index.ts b/packages/serverless/settings/observability_project/index.ts index 58be247f0ff8a..0374fc9a6b6e8 100644 --- a/packages/serverless/settings/observability_project/index.ts +++ b/packages/serverless/settings/observability_project/index.ts @@ -21,7 +21,6 @@ export const OBSERVABILITY_PROJECT_SETTINGS = [ settings.OBSERVABILITY_APM_ENABLE_SERVICE_METRICS_ID, settings.OBSERVABILITY_APM_ENABLE_CONTINUOUS_ROLLUPS_ID, settings.OBSERVABILITY_APM_AGENT_EXPLORER_VIEW_ID, - settings.OBSERVABILITY_APM_ENABLE_PROFILING_INTEGRATION_ID, settings.OBSERVABILITY_APM_PROGRESSIVE_LOADING_ID, settings.OBSERVABILITY_APM_SERVICE_INVENTORY_OPTIMIZED_SORTING_ID, settings.OBSERVABILITY_APM_TRACE_EXPLORER_TAB_ID, From ac3ceba51f5ce6eb837c4f72b6eda64f4c11429f Mon Sep 17 00:00:00 2001 From: Antonio <antonio.coelho@elastic.co> Date: Wed, 3 Jul 2024 10:41:43 +0200 Subject: [PATCH 125/128] [ResponseOps][Cases] Change login role in serverless UI tests. (#187332) ## Summary Updated UI FTR tests to not run with operator privileges. Fixes #184743 --- .../test_suites/observability/cases/attachment_framework.ts | 2 +- .../functional/test_suites/observability/cases/configure.ts | 2 +- .../test_suites/observability/cases/create_case_form.ts | 2 +- .../functional/test_suites/observability/cases/list_view.ts | 2 +- .../functional/test_suites/observability/cases/view_case.ts | 2 +- .../functional/test_suites/search/cases/attachment_framework.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts index f7374c5f03e7e..72fb345fe012c 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts @@ -26,7 +26,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { describe('Cases persistable attachments', function () { describe('lens visualization', () => { before(async () => { - await svlCommonPage.loginWithRole('admin'); + await svlCommonPage.loginWithPrivilegedRole(); await kibanaServer.savedObjects.cleanStandardList(); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await kibanaServer.importExport.load( diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts index d7d0f30da7502..1909408d05332 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts @@ -24,7 +24,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { describe('Configure Case', function () { before(async () => { - await svlCommonPage.loginWithRole('admin'); + await svlCommonPage.loginWithPrivilegedRole(); await svlObltNavigation.navigateToLandingPage(); await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'observability-overview:cases' }); await header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts index 9377238535b40..7f393c4457d79 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts @@ -25,7 +25,7 @@ export default ({ getService, getPageObject }: FtrProviderContext) => { const header = getPageObject('header'); before(async () => { - await svlCommonPage.loginWithRole('admin'); + await svlCommonPage.loginWithPrivilegedRole(); }); beforeEach(async () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/list_view.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/list_view.ts index 71ce65ba59eb1..0605df54825e2 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/list_view.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/list_view.ts @@ -21,7 +21,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { describe('Cases list', function () { before(async () => { - await svlCommonPage.loginWithRole('admin'); + await svlCommonPage.loginWithPrivilegedRole(); await svlObltNavigation.navigateToLandingPage(); await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'observability-overview:cases' }); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts index 033af9d21848b..4446638ff0809 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts @@ -36,7 +36,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { describe('Case View', function () { before(async () => { - await svlCommonPage.loginWithRole('admin'); + await svlCommonPage.loginWithPrivilegedRole(); }); after(async () => { diff --git a/x-pack/test_serverless/functional/test_suites/search/cases/attachment_framework.ts b/x-pack/test_serverless/functional/test_suites/search/cases/attachment_framework.ts index 831369311f942..566e145a4fd87 100644 --- a/x-pack/test_serverless/functional/test_suites/search/cases/attachment_framework.ts +++ b/x-pack/test_serverless/functional/test_suites/search/cases/attachment_framework.ts @@ -20,7 +20,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { describe('persistable attachment', () => { before(async () => { - await svlCommonPage.loginWithRole('developer'); + await svlCommonPage.loginWithPrivilegedRole(); }); after(async () => { From ce4f4693f4c990482cf1e782480f6736cd4e7660 Mon Sep 17 00:00:00 2001 From: Alexey Antonov <alexwizp@gmail.com> Date: Wed, 3 Jul 2024 11:56:45 +0300 Subject: [PATCH 126/128] fix: [Obs Synthetics > Monitor test run][KEYBOARD]: Date (check) dropdown needs a semantic label for the current check (#187353) Closes: https://github.com/elastic/observability-dev/issues/3689 ## Description The synthetics monitors include thumbnail screenshots that open a larger preview window. These thumbnails must take keyboard focus, manage the `Enter` and `Space` keypresses to open the modal, and return focus to the originating thumbnail when the modal is closed. Screenshots attached below. ### Steps to recreate 1. Open the [Synthetics](https://keep-serverless-fyzdg-f07c50.kb.eu-west-1.aws.qa.elastic.cloud/app/synthetics) view 2. Create a monitor if none exist 3. Click on that monitor and navigate to the full monitor detail 4. Click the ["View test run link" ](https://keep-serverless-fyzdg-f07c50.kb.eu-west-1.aws.qa.elastic.cloud/app/synthetics/monitor/8b88e937-f917-4f12-9325-8ab005cffea5/test-run/90a1abac-3579-11ef-b9d4-4e0e2c056918-1?locationId=us_central_qa)in the Last test run module (about 2/3 of the way down the page) 5. Open the "Date" dropdown in the top right 6. Either listen to the dropdown nodes with a screen reader or evaluate with Dev Tools Accessibility Tree. This will show you a standard, non-semantic div for the current check. ### What was changed?: 1. `aria-label` attribute was added to address that issue 2. similar changes were applied to 3 places: - `x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_page_nav.tsx` - `x-pack/plugins/observability_solution/uptime/public/legacy_uptime/components/monitor/synthetics/step_detail/step_page_nav.tsx` - `x-pack/plugins/observability_solution/uptime/public/legacy_uptime/pages/synthetics/checks_navigation.tsx` ### Screen <img width="1269" alt="image" src="https://github.com/elastic/kibana/assets/20072247/dc1f4711-a834-4d06-825f-8fb6d49891d0"> --- .../step_details_page/step_page_nav.tsx | 27 ++++++++++++------- .../synthetics/step_detail/step_page_nav.tsx | 14 +++++++++- .../pages/synthetics/checks_navigation.tsx | 15 +++++++++-- 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_page_nav.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_page_nav.tsx index 9bc91a52df4a4..9dc4475778dc6 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_page_nav.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_page_nav.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { ReactElement, useState } from 'react'; +import React, { useState } from 'react'; import { EuiButtonEmpty, EuiDescriptionList, @@ -43,8 +43,7 @@ export const StepPageNavigation = ({ testRunPage }: { testRunPage?: boolean }) = const formatter = useDateFormat(); const { basePath } = useSyntheticsSettingsContext(); const selectedLocation = useSelectedLocation(); - - let startedAt: string | ReactElement = formatter(data?.details?.timestamp); + const startedAt = formatter(data?.details?.timestamp); const { stepIndex, monitorId } = useParams<{ stepIndex: string; monitorId: string }>(); @@ -77,9 +76,7 @@ export const StepPageNavigation = ({ testRunPage }: { testRunPage?: boolean }) = } } - if (!startedAt) { - startedAt = <EuiSkeletonText lines={1} />; - } + const startedAtWrapped = startedAt || <EuiSkeletonText lines={1} />; return ( <EuiPopover @@ -94,7 +91,7 @@ export const StepPageNavigation = ({ testRunPage }: { testRunPage?: boolean }) = iconSide="right" flush="left" > - {startedAt} + {startedAtWrapped} </EuiButtonEmpty> } > @@ -111,8 +108,12 @@ export const StepPageNavigation = ({ testRunPage }: { testRunPage?: boolean }) = </EuiButtonEmpty> </EuiFlexItem> <EuiFlexItem grow={false}> - <EuiText size="s" className="eui-textNoWrap"> - {startedAt} + <EuiText + size="s" + className="eui-textNoWrap" + aria-label={CURRENT_CHECK_ARIA_LABEL(startedAt)} + > + {startedAtWrapped} </EuiText> </EuiFlexItem> <EuiFlexItem grow={false}> @@ -145,3 +146,11 @@ export const NEXT_CHECK_BUTTON_TEXT = i18n.translate( defaultMessage: 'Next check', } ); + +export const CURRENT_CHECK_ARIA_LABEL = (timestamp: string) => + i18n.translate('xpack.synthetics.synthetics.stepDetail.currentCheckAriaLabel', { + defaultMessage: 'Current check: {timestamp}', + values: { + timestamp, + }, + }); diff --git a/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/components/monitor/synthetics/step_detail/step_page_nav.tsx b/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/components/monitor/synthetics/step_detail/step_page_nav.tsx index 694347ac621f0..5f7541abbc8b9 100644 --- a/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/components/monitor/synthetics/step_detail/step_page_nav.tsx +++ b/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/components/monitor/synthetics/step_detail/step_page_nav.tsx @@ -24,6 +24,14 @@ export const NEXT_CHECK_BUTTON_TEXT = i18n.translate( } ); +export const CURRENT_CHECK_ARIA_LABEL = (timestamp: string) => + i18n.translate('xpack.uptime.synthetics.stepDetail.currentCheckAriaLabel', { + defaultMessage: 'Current check: {timestamp}', + values: { + timestamp, + }, + }); + interface Props { previousCheckGroup?: string; dateFormat: string; @@ -40,6 +48,8 @@ export const StepPageNavigation = ({ checkTimestamp, nextCheckGroup, }: Props) => { + const formattedTimestamp = moment(checkTimestamp).format(dateFormat); + return ( <EuiFlexGroup alignItems="center" justifyContent="flexEnd" responsive={false}> <EuiFlexItem grow={false}> @@ -54,7 +64,9 @@ export const StepPageNavigation = ({ </EuiButtonEmpty> </EuiFlexItem> <EuiFlexItem grow={false}> - <EuiText size="s">{moment(checkTimestamp).format(dateFormat)}</EuiText> + <EuiText size="s" aria-label={CURRENT_CHECK_ARIA_LABEL(formattedTimestamp)}> + {formattedTimestamp} + </EuiText> </EuiFlexItem> <EuiFlexItem grow={false}> <EuiButtonEmpty diff --git a/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/pages/synthetics/checks_navigation.tsx b/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/pages/synthetics/checks_navigation.tsx index fce9055b9f82f..92cb498a0d2a8 100644 --- a/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/pages/synthetics/checks_navigation.tsx +++ b/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/pages/synthetics/checks_navigation.tsx @@ -16,6 +16,7 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { useHistory } from 'react-router-dom'; import moment from 'moment'; +import { i18n } from '@kbn/i18n'; import { SyntheticsJourneyApiResponse } from '../../../../common/runtime_types/ping'; import { getShortTimeStamp } from '../../components/overview/monitor_list/columns/monitor_status_column'; @@ -27,6 +28,7 @@ interface Props { export const ChecksNavigation = ({ timestamp, details }: Props) => { const history = useHistory(); const isMobile = useIsWithinMaxBreakpoint('s'); + const shortTimestamp = getShortTimeStamp(moment(timestamp)); return ( <EuiFlexGroup alignItems="center" responsive={false} gutterSize="none"> @@ -47,8 +49,17 @@ export const ChecksNavigation = ({ timestamp, details }: Props) => { </EuiButtonEmpty> </EuiFlexItem> <EuiFlexItem grow={false}> - <EuiText size={isMobile ? 'xs' : 'm'} className="eui-textNoWrap"> - {getShortTimeStamp(moment(timestamp))} + <EuiText + size={isMobile ? 'xs' : 'm'} + className="eui-textNoWrap" + aria-label={i18n.translate('xpack.uptime.synthetics.stepList.currentCheckAriaLabel', { + defaultMessage: 'Current check: {shortTimestamp}', + values: { + shortTimestamp, + }, + })} + > + {shortTimestamp} </EuiText> </EuiFlexItem> <EuiFlexItem grow={false}> From 7e042490549014f0abcff372036d3dff873f5e96 Mon Sep 17 00:00:00 2001 From: Vadim Kibana <82822460+vadimkibana@users.noreply.github.com> Date: Wed, 3 Jul 2024 11:04:12 +0200 Subject: [PATCH 127/128] [ES|QL] Add `weighted_avg` ES|QL function support (#187439) ## Summary - Adds `weighted_avg` function definition - Adds basic smoke tests ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../src/definitions/aggs.ts | 35 +++ .../esql_validation_meta_tests.json | 208 ++++++++++++++++++ .../src/validation/validation.test.ts | 158 +++++++++++++ 3 files changed, 401 insertions(+) diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts index 00f39c2659368..c1b7b5b00b58f 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts @@ -264,4 +264,39 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ `from employees | stats date = top(hire_date, 2, "asc"), double = top(salary_change, 2, "asc"),`, ], }, + { + name: 'weighted_avg', + type: 'agg', + description: i18n.translate( + 'kbn-esql-validation-autocomplete.esql.definitions.weightedAvgDoc', + { + defaultMessage: + 'An aggregation that computes the weighted average of numeric values that are extracted from the aggregated documents.', + } + ), + supportedCommands: ['stats', 'metrics'], + signatures: [ + { + params: [ + { + name: 'number', + type: 'number', + noNestingFunctions: true, + optional: false, + }, + { + name: 'weight', + type: 'number', + noNestingFunctions: true, + optional: false, + }, + ], + returnType: 'number', + }, + ], + examples: [ + `from employees | stats w_avg = weighted_avg(salary, height) by languages | eval w_avg = round(w_avg)`, + `from employees | stats w_avg_1 = weighted_avg(salary, 1), avg = avg(salary), w_avg_2 = weighted_avg(salary, height)`, + ], + }, ]); diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json index b75a9506cb766..daf8d7c6de862 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json +++ b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json @@ -26061,6 +26061,36 @@ ], "warning": [] }, + { + "query": "from a_index | stats var = top(stringField, 5, \"asc\")", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats top(stringField, 5, \"asc\")", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats top(stringField, numberField, \"asc\")", + "error": [ + "Argument of [top] must be a constant, received [numberField]" + ], + "warning": [] + }, + { + "query": "from a_index | stats top(null, null, null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | stats top(nullVar, nullVar, nullVar)", + "error": [ + "Argument of [top] must be a constant, received [nullVar]", + "Argument of [top] must be a constant, received [nullVar]" + ], + "warning": [] + }, { "query": "row var = st_distance(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], @@ -26159,6 +26189,184 @@ "error": [], "warning": [] }, + { + "query": "from a_index | stats var = weighted_avg(numberField, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats weighted_avg(numberField, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(weighted_avg(numberField, numberField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(weighted_avg(numberField, numberField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(weighted_avg(numberField, numberField)) + weighted_avg(numberField, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(weighted_avg(numberField, numberField)) + weighted_avg(numberField, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats weighted_avg(numberField / 2, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = weighted_avg(numberField / 2, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(numberField), weighted_avg(numberField / 2, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(numberField), var0 = weighted_avg(numberField / 2, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = weighted_avg(numberField, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(numberField), weighted_avg(numberField, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(numberField), var0 = weighted_avg(numberField, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats weighted_avg(numberField, numberField) by round(numberField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = weighted_avg(numberField, numberField) by var1 = round(numberField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(numberField), weighted_avg(numberField, numberField) by round(numberField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(numberField), var0 = weighted_avg(numberField, numberField) by var1 = round(numberField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(numberField), weighted_avg(numberField, numberField) by round(numberField / 2), numberField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(numberField), var0 = weighted_avg(numberField, numberField) by var1 = round(numberField / 2), numberField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = weighted_avg(avg(numberField), avg(numberField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" + ], + "warning": [] + }, + { + "query": "from a_index | stats weighted_avg(avg(numberField), avg(numberField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" + ], + "warning": [] + }, + { + "query": "from a_index | stats weighted_avg(booleanField, booleanField)", + "error": [ + "Argument of [weighted_avg] must be [number], found value [booleanField] type [boolean]", + "Argument of [weighted_avg] must be [number], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | sort weighted_avg(numberField, numberField)", + "error": [ + "SORT does not support function weighted_avg" + ], + "warning": [] + }, + { + "query": "from a_index | where weighted_avg(numberField, numberField)", + "error": [ + "WHERE does not support function weighted_avg" + ], + "warning": [] + }, + { + "query": "from a_index | where weighted_avg(numberField, numberField) > 0", + "error": [ + "WHERE does not support function weighted_avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = weighted_avg(numberField, numberField)", + "error": [ + "EVAL does not support function weighted_avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = weighted_avg(numberField, numberField) > 0", + "error": [ + "EVAL does not support function weighted_avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval weighted_avg(numberField, numberField)", + "error": [ + "EVAL does not support function weighted_avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval weighted_avg(numberField, numberField) > 0", + "error": [ + "EVAL does not support function weighted_avg" + ], + "warning": [] + }, + { + "query": "from a_index | stats weighted_avg(null, null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | stats weighted_avg(nullVar, nullVar)", + "error": [], + "warning": [] + }, { "query": "f", "error": [ diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts index 591efd1e413c1..6c1b66574862c 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts @@ -10253,6 +10253,18 @@ describe('validation logic', () => { testErrorsAndWarnings('from a_index | eval top(stringField, 5, "asc") > 0', [ 'EVAL does not support function top', ]); + testErrorsAndWarnings('from a_index | stats var = top(stringField, 5, "asc")', []); + testErrorsAndWarnings('from a_index | stats top(stringField, 5, "asc")', []); + + testErrorsAndWarnings('from a_index | stats top(stringField, numberField, "asc")', [ + 'Argument of [top] must be a constant, received [numberField]', + ]); + + testErrorsAndWarnings('from a_index | stats top(null, null, null)', []); + testErrorsAndWarnings('row nullVar = null | stats top(nullVar, nullVar, nullVar)', [ + 'Argument of [top] must be a constant, received [nullVar]', + 'Argument of [top] must be a constant, received [nullVar]', + ]); }); describe('st_distance', () => { @@ -10335,6 +10347,152 @@ describe('validation logic', () => { testErrorsAndWarnings('from a_index | eval st_distance(null, null)', []); testErrorsAndWarnings('row nullVar = null | eval st_distance(nullVar, nullVar)', []); }); + + describe('weighted_avg', () => { + testErrorsAndWarnings( + 'from a_index | stats var = weighted_avg(numberField, numberField)', + [] + ); + testErrorsAndWarnings('from a_index | stats weighted_avg(numberField, numberField)', []); + + testErrorsAndWarnings( + 'from a_index | stats var = round(weighted_avg(numberField, numberField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats round(weighted_avg(numberField, numberField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats var = round(weighted_avg(numberField, numberField)) + weighted_avg(numberField, numberField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats round(weighted_avg(numberField, numberField)) + weighted_avg(numberField, numberField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats weighted_avg(numberField / 2, numberField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats var0 = weighted_avg(numberField / 2, numberField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats avg(numberField), weighted_avg(numberField / 2, numberField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats avg(numberField), var0 = weighted_avg(numberField / 2, numberField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats var0 = weighted_avg(numberField, numberField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats avg(numberField), weighted_avg(numberField, numberField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats avg(numberField), var0 = weighted_avg(numberField, numberField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats weighted_avg(numberField, numberField) by round(numberField / 2)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats var0 = weighted_avg(numberField, numberField) by var1 = round(numberField / 2)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats avg(numberField), weighted_avg(numberField, numberField) by round(numberField / 2), ipField', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats avg(numberField), var0 = weighted_avg(numberField, numberField) by var1 = round(numberField / 2), ipField', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats avg(numberField), weighted_avg(numberField, numberField) by round(numberField / 2), numberField / 2', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats avg(numberField), var0 = weighted_avg(numberField, numberField) by var1 = round(numberField / 2), numberField / 2', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats var = weighted_avg(avg(numberField), avg(numberField))', + [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", + ] + ); + + testErrorsAndWarnings( + 'from a_index | stats weighted_avg(avg(numberField), avg(numberField))', + [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", + ] + ); + + testErrorsAndWarnings('from a_index | stats weighted_avg(booleanField, booleanField)', [ + 'Argument of [weighted_avg] must be [number], found value [booleanField] type [boolean]', + 'Argument of [weighted_avg] must be [number], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | sort weighted_avg(numberField, numberField)', [ + 'SORT does not support function weighted_avg', + ]); + + testErrorsAndWarnings('from a_index | where weighted_avg(numberField, numberField)', [ + 'WHERE does not support function weighted_avg', + ]); + + testErrorsAndWarnings('from a_index | where weighted_avg(numberField, numberField) > 0', [ + 'WHERE does not support function weighted_avg', + ]); + + testErrorsAndWarnings('from a_index | eval var = weighted_avg(numberField, numberField)', [ + 'EVAL does not support function weighted_avg', + ]); + + testErrorsAndWarnings( + 'from a_index | eval var = weighted_avg(numberField, numberField) > 0', + ['EVAL does not support function weighted_avg'] + ); + + testErrorsAndWarnings('from a_index | eval weighted_avg(numberField, numberField)', [ + 'EVAL does not support function weighted_avg', + ]); + + testErrorsAndWarnings('from a_index | eval weighted_avg(numberField, numberField) > 0', [ + 'EVAL does not support function weighted_avg', + ]); + + testErrorsAndWarnings('from a_index | stats weighted_avg(null, null)', []); + testErrorsAndWarnings('row nullVar = null | stats weighted_avg(nullVar, nullVar)', []); + }); }); }); From bfe8cf9cc1679f5ef2720a4c0cc257b39604c4fe Mon Sep 17 00:00:00 2001 From: Tom Myers <106530686+tommyers-elastic@users.noreply.github.com> Date: Wed, 3 Jul 2024 10:12:31 +0100 Subject: [PATCH 128/128] include entity type in EEM output docs (#187355) include entity type in EEM output docs --- .../__snapshots__/generate_history_processors.test.ts.snap | 6 ++++++ .../__snapshots__/generate_latest_processors.test.ts.snap | 6 ++++++ .../entities/ingest_pipeline/generate_history_processors.ts | 6 ++++++ .../entities/ingest_pipeline/generate_latest_processors.ts | 6 ++++++ .../entity_manager/server/templates/components/entity.ts | 4 ++++ 5 files changed, 28 insertions(+) diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_history_processors.test.ts.snap b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_history_processors.test.ts.snap index f5d7c7e3683b4..361807a707713 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_history_processors.test.ts.snap +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_history_processors.test.ts.snap @@ -8,6 +8,12 @@ Array [ "value": "{{{_ingest.timestamp}}}", }, }, + Object { + "set": Object { + "field": "entity.type", + "value": "service", + }, + }, Object { "set": Object { "field": "entity.spaceId", diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_latest_processors.test.ts.snap b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_latest_processors.test.ts.snap index bd31c8563be44..afd85802323e9 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_latest_processors.test.ts.snap +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_latest_processors.test.ts.snap @@ -8,6 +8,12 @@ Array [ "value": "{{{_ingest.timestamp}}}", }, }, + Object { + "set": Object { + "field": "entity.type", + "value": "service", + }, + }, Object { "set": Object { "field": "entity.spaceId", diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_history_processors.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_history_processors.ts index dcfa23d398813..a915f7f161ecc 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_history_processors.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_history_processors.ts @@ -51,6 +51,12 @@ export function generateHistoryProcessors(definition: EntityDefinition, spaceId: value: '{{{_ingest.timestamp}}}', }, }, + { + set: { + field: 'entity.type', + value: definition.type, + }, + }, { set: { field: 'entity.spaceId', diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts index 992f4e14c8d16..b4a78a9cd17dd 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts @@ -45,6 +45,12 @@ export function generateLatestProcessors(definition: EntityDefinition, spaceId: value: '{{{_ingest.timestamp}}}', }, }, + { + set: { + field: 'entity.type', + value: definition.type, + }, + }, { set: { field: 'entity.spaceId', diff --git a/x-pack/plugins/observability_solution/entity_manager/server/templates/components/entity.ts b/x-pack/plugins/observability_solution/entity_manager/server/templates/components/entity.ts index 56a0ea3333309..fd8781dc64e9c 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/templates/components/entity.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/templates/components/entity.ts @@ -21,6 +21,10 @@ export const entitiesEntityComponentTemplateConfig: ClusterPutComponentTemplateR ignore_above: 1024, type: 'keyword', }, + type: { + ignore_above: 1024, + type: 'keyword', + }, displayName: { type: 'text', fields: {